Fork me on GitHub

装箱与拆箱

java有八种基本类型,每种基本类型都有一个对应的包装类。

基本类型 包装类
boolean Boolean
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character

包装类的定义:它是一个类,内部有一个实例变量,保存对应的基本类型的值。这个类一般还有一些静态方法、静态变量和实例方法,以方便对数据进行操作。

各个基本类型和包装类之间的转换

Boolean

1
2
3
boolean bool1 = false;
Boolean boolObj = Boolean.valueOf(bool1);
boolean bool2 = boolObj.booleanValue();

Byte

1
2
3
byte byte1 = 123;
Byte byteObj = Byte.valueOf(byte1);
byte byte2 = byteObj.byteValue();

Short

1
2
3
short short1 = 12345;
Short shortObj = Short.valueOf(short1);
short short2 = shortObj.shortValue();

Integer

1
2
3
int int1 = 12345;
Integer integerObj = Integer.valueOf(int1);
int int2 = integerObj.intValue();

Long

1
2
3
long long1 = 12345;
Long longObj = Long.valueOf(long1);
long long2 = longObj.longValue();

Float

1
2
3
float float1 = 123.45f;
Float floatObj = Float.valueOf(float1);
float float2 = floatObj.floatValue();

Double

1
2
3
double double1 = 123.45;
Double doubleObj = Double.valueOf(double1);
double double2 = doubleObj.doubleValue();

Character

1
2
3
char char1 = 'A';
Character characterObj = Character.valueOf(char1);
char char2 = characterObj.charValue();

每种包装类都有一个静态方法valueOf(),接受基本类型,返回引用类型;也都有一个实例方法xxxValue()返回对应的基本类型。

装箱:基本类型 –> 包装类

拆箱:包装类 –> 基本类型

java1.5以后引入自动装箱和拆箱,可以直接将基本类型赋值给引用类型,反之亦可。

1
2
3
//自动装箱和拆箱
Integer a = 100;
int b = a;

关键字throws

throws关键字,用于声明一个方法可能抛出的异常

1
2
3
public void test() throws AppException,SQLException,NumberFormatException {
//....
}

throws跟在方法的括号后面,可以声明多个异常,以逗号分隔。

test()方法内可能抛出如上三种一场,但开发者没有处理完,调用者必须进行处理。如果处理完则不需要声明。

foreach循环输出二维数组

声明一个二维数组:

1
int[][] nums = new int[3][]; //可以只声明行数,不声明列数,反之不行;

初始化一个二维数组,并foreach循环遍历输出该数组的每一个元素:

1
2
3
4
5
6
7
int[][] num = {{78,98},{65,75,63},{98}};
for(int[] i:num) { //先定位到行
for(int j:i) { //再定位到列
System.out.print(j+" ");
}
System.out.println();
}

最后输出:

1
2
3
78 98 
65 75 63
98

链表的增删

单向链表的每一个结点包含两部分,一部分是存放数据的变量data,另一部分是指向下一个结点的指针next。

1
2
3
4
private static class Node {
int data;
Node next;
}

链表的第1个结点被称为头结点,最后1个结点被称为尾结点,尾结点的next指针指向空。

双向链表除了有data和next指针,还有指向前置结点的prev指针。

链表在内存中的存储方式则是随机存储。

链表的查询

从头部结点开始,向后一个一个结点逐一查找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//查询链表
public Node search(int index) throws IndexOutOfBoundsException{
if (index<0 || index>=size) {
throw new IndexOutOfBoundsException("search:index超出链表范围!");
}

Node searchNode;
if (index == 0) { //头部查询
searchNode = head;
} else if (index == size - 1) { //尾部查询
searchNode = tail;
} else { //中间查询
searchNode = head;
for (int i = 0;i<index;i++) {
searchNode =searchNode.next;
}
}

return searchNode;
}

链表的插入

链表插入结点时,分为3种情况:

  • 头部插入
  • 尾部插入
  • 中间插入

头部插入,将新结点的next指针指向head结点,再让head结点指向新结点。
尾部插入,将tail结点的next指针指向新结点,再让tail结点指向新结点。

中间插入,找到新结点要插入的index的前置结点,新结点的next指向前置结点的next,前置结点的next指向新结点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//插入链表
public void insert(int data,int index) throws IndexOutOfBoundsException {
if (index<0 || index>size) {
throw new IndexOutOfBoundsException("insert:index超出链表范围!");
}

Node insertNode = new Node(data);
//第一个结点
if (size==0) {
head = insertNode;
tail = insertNode;
size ++;
return;
}

if (index == 0) { //插入头部
insertNode.next = head;
head = insertNode;
}else if (index == size) { //插入尾部
tail.next = insertNode;
tail = insertNode;
}else { //插入中间
Node prev = search(index-1);
Node next = prev.next;
prev.next = insertNode;
insertNode.next = next;
}

size++;
}

链表的删除

链表删除结点时,分为3种情况:

  • 头部删除
  • 尾部删除
  • 中间删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//链表的删除
public void delete(int index) throws IndexOutOfBoundsException {
if (index<0 || index>=size) {
throw new IndexOutOfBoundsException("delete:index超出链表范围!");
}

//只有一个结点
if (size==1) {
head = null;
tail = null;
size--;
return;
}

if (index == 0) { //删除头部结点
head = head.next;
} else if (index == size-1) { //删除尾部结点
Node prev = search(index-1);
prev.next = null;
tail = prev;
} else { //删除中间结点
Node prev = search(index-1);
Node next = prev.next.next;
prev.next = next;
}

size--;
}

数组的增删

数组的插入

插入数组元素的操作存在3种情况:

  • 尾部插入

  • 中间插入(包含头部插入)

  • 超范围插入

尾部插入:直接把元素放在数组尾部的空闲位置,等同于更新元素。

中间插入:首先把插入位置以及后面的元素向后移动,腾出地方,再把要插入的元素放到对应的数组位置上。

超范围插入:创建一个新数组,长度是旧数组的2倍,再把旧数组中的元素统统复制过去,实现数组的扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class MyArray {
private int[] array;
private int size; //size:实际的元素个数

//构造一个数组,初始是空,容量为capacity
public MyArray(int capacity) {
this.array = new int[capacity];
size = 0;
}

//插入元素
public void insert(int element,int index) throws IndexOutOfBoundsException {
//越界判断
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("超出数组实际元素范围");
}
//超容量插入,需扩容
if (size >= array.length) {
resize();
}
//index及之后元素统一向右移动一位,从最后一位元素开始右移
for (int i=size-1;i>=index;i--) {
array[i+1] = array[i];
}
array[index] = element;
size++;
}

//扩容数组
public void resize() {
int[] arrayNew = new int[array.length*2];
//旧数组复制到新数组
System.arraycopy(array,0,arrayNew,0,array.length);
array = arrayNew;
}

//输出数组
public void outPut() {
System.out.println("当前的数组为:");
for (int x=0;x<size;x++) {
System.out.print(array[x]+" ");
}
System.out.println();
}

public static void main(String[] args) {
MyArray myArray = new MyArray(5);
myArray.insert(9,0);
myArray.insert(5,1);
myArray.insert(8,2);
myArray.insert(3,3);
myArray.insert(1,1);
myArray.insert(4,2);
myArray.outPut(); //9 1 4 5 8 3
}
}

数组的删除

数组的删除操作与插入操作的过程相反,假设被删除的元素的索引是x,x之后的元素都要向前挪动1位

删除操作不涉及扩容,比起插入要更简单。

1
2
3
4
5
6
7
8
9
10
11
//删除数组元素
public void delete(int index) throws ArrayIndexOutOfBoundsException {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("index超出数组实际元素范围");
}
for (int i = index;i<size-1;i++) { //size-1,否则array[i+1]=array[size],报错
array[i] = array[i+1];
}

size--;
}

冒泡排序

冒泡排序是从头开始,第1个和第2个比较,前大于后则互换位置;然后第2个和第3个比较,前大于后则互换位置……如此找到最大值放到最后位置。
再找到倒数第二大的值放到倒数第二的位置,依此类推……

假设有一个数组arr{867,143,99,452,301,992,53},冒泡排序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class BubbleSort {

public static void main(String[] args) {
int[] arr = {867,143,99,452,301,992,53};

System.out.println("数组的初始排序:");
for(int index:arr) {
System.out.print(index+" ");
}
System.out.println();

int temp; //临时寄存数字
for (int i=0;i<arr.length-1;i++) { //因为最后一位无需比较,进行length-1次对比置换
for (int j=0;j<arr.length-1-i;j++) { //length-1-i,i代表已经比较完成的数量
if(arr[j] > arr[j+1]) {
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}

System.out.println("数组的现今排序:");
for(int index:arr) {
System.out.print(index+" ");
}
System.out.println();
}
}

结果如下:

1
2
3
4
数组的初始排序:
867 143 99 452 301 992 53
数组的现今排序:
53 99 143 301 452 867 992

异常中的finally

catch后面可以跟finally语句,语法如下:

1
2
3
4
5
6
7
try {
//可能抛出异常
} catch (Exception e) {
//捕获异常
} finally {
//不管有无异常都执行
}

finally内的代码不管有无异常发生,都会执行:

  • 如果没有异常发生,在try内的代码执行结束后执行。

  • 如果有异常发生且被catch捕获,在catch内的代码执行结束后执行。

  • 如果有异常发生但没被捕获,则在异常被抛给上层之前执行。

由于finally的这个特点,它一般用于释放资源,如数据库连接、文件流等。

try/catch/finall语法中,catch不是必须的,可以只有try/finally。这表示不捕获异常,异常自动向上传递,但finally中的代码在异常发生后也执行。

如果在try或者catch语句内有return语句,则return语句在finally语句执行结束后才执行,但finally并不能改变返回值,我们来看下代码:

1
2
3
4
5
6
7
8
public static int test() {
int ret = 0;
try {
return ret;
}finally {
ret = 2;
}
}

函数的返回值是0,不是2。在执行到return ret;语句之前,会先将返回值ret保存在一个临时变量中,然后才执行finally语句,最后try再返回那个临时变量,finally中对ret的修改不会被返回。

如果在finally中也有return语句,try和catch内的return会丢失,实际会返回finally中的返回值。finally中有return,不仅会覆盖try和catch内的返回值,还会掩盖try和catch内的异常,就像一场没有发生一样。

同理,finally中抛出异常时,也会覆盖try和catch的异常。

因此,要尽量避免在finally中使用return语句或抛出异常。

自定义异常

自定义异常类,一般通过继承Exception或者它的某个子类。
如果父类是RuntimeException或它的某个子类,则自定义异常也是unchecked exception。如果是Exception或Exception的其他子类,则自定义异常是checked exception。

继承Exception的栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class AppException extends Exception{

public AppException() {
super();
}

public AppException(String message, Throwable cause) {
super(message,cause);
}

public AppException(String message) {
super(message);
}

public AppException(Throwable cause) {
super(cause);
}
}

AppException没有定义额外的属性和代码,只是继承了Exception,定义了构造方法并调用了父类的构造方法。

RuntimeException的常见子类

异常 说明
NullPointerException 空指针异常
IllegalStateException 非法状态
ClassCastException 非法强制类型转换
IllegalArgumentException 参数错误
NumberFormatException 数字格式错误
ArrayIndexOutOfBoundsException 数组索引越界
StringIndexOutOfBoundsException 字符串索引越界

这些异常类其实并没有比Throwable这个基类多多少属性和方法,大部分类在继承父类后只是定义了几个构造方法,这些构造方法也只是强调了父类的构造方法。

定义那么多类,主要是为了名字不同。异常类的名字本身就代表了异常的关键信息。

异常类

NullPointerException和NumberFormatException都是异常类,所有异常类都有一个共同的父类Throwable

Throwable有4个public构造方法:

1.public Throwable()

2.public Throwable(String message)

3.public Throwable(String message,Throwable cause)

4.public Throwable(Throwable cause)

message表示异常信息,cause表示触发该异常的其他异常。

异常可以形成一个异常链,上层的异常由底层异常触发,cause表示底层异常。

异常类体系

以Throwable为根,Java API中定义了非常多的异常类,表示各种类型的异常,部分类示意如下:

异常类体系.jpg

Throwable是所有异常的基类,它有两个子类Error和Excepetion。

Error表示系统错误或资源耗尽,由java系统自己使用,应用程序不应抛出和处理。比如图中列出的虚拟器错误(VirtualMachineError)、子类内存溢出错误(OutOfMemoryError)、栈溢出错误(StackOverflowError)。

Exception表示应用程序错误,它有很多子类,应用程序也可以通过继承Exception或其子类创建自定义异常,比如图中列出的IOException(输入输出I/O异常),SQLException(数据库SQL异常),RuntimeException(运行时异常)。

RuntimeException(运行时异常)比较特殊,它的名字有点误导,因为其他异常也是运行时产生的,它表示的实际含义是unchecked exception (未受检异常),相对而言,Exception的其他子类和Exception自身则是checked exception (受检异常),Error及其子类也是unchecked exception。

checked还是unchecked,区别在于java如何处理这两种异常。对于checked异常,java会强制要求开发者进行处理,否则会有编译错误,而对于unchecked异常则没有这个要求

  • Copyrights © 2021 Silver Shaded
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信