异常
Throwable
Throwable具有 Exception和 Error 两个子类
Error 类代表严重错误,往往不建议我们在程序中用 catch捕获的,我们只能通过优化代码来优化 Error。当出现 Error 时,JVM 一般会选择终止线程。
Exception 类具有Checked Exception和 Unchecked Exception 这两个子类。
Checked Exception 表明异常是在编译时就会被检查,如果该异常没有被 catch 捕获,或者没有被 throws, 就通不过编译。 包括 IOException, ClassNotFoundException.
Unchecked Exception表明在编译时不会被检查。它包括 RuntimeException 及其子类。比如NullPointerException, ArrayIndexOutOfBoundsException, 以及强制类型转换时可能会出现的ClassCastException。
try-catch-finally
finally关闭输入输出流、关闭数据库连接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| public class FinallyDemo {
public static void main(String[] args) {
System.out.println(testReturn());
}
public static int testReturn() {
int num = 10;
try {
num = 20;
return num;
} catch (Exception e) {
e.printStackTrace();
} finally {
num = 30;
System.out.println("finally块执行了,num的值为:" + num);
}
return num;
}
}
|
如果在try里面return了,finally还是会被执行。num的值被修改为 30,但是返回的值仍然是 20. 因为在 try里面return的时候,返回值就已经被确定下俩了。
如果在try或者 catch里面执行了System.exit(), 那么 finally块不会执行。
多个异常怎么处理?
- 多个 catch 块分别捕获不同的异常。
1
2
3
4
5
6
7
| try {
// 可能抛出异常的代码
} catch (IOException e) {
// 处理IO异常的逻辑
} catch (SQLException e) {
// 处理数据库异常的逻辑
}
|
1
| catch (IOException | SQLException e) {... }
|
- 一个 catch 块捕获这些异常的父类。
最好把捕获子类异常的 catch 写在前面,这样就不会被统一视为父类异常处理。在catch 块里面可以增加一些日志记录,方便排查问题。
throw和throws
throw 是主动抛异常.
1
| throw new RuntimeException("自定义错误信息")
|
throws是放在函数签名里面,声明这个方法可能会抛出哪些异常,让调用者去处理。如果调用者没有 catch来处理,就会一层一层抛出到调用栈,直到main函数。
1
| public void readFile() throws IOException {... }
|
泛型
泛型方法
1
2
3
4
5
6
7
8
9
10
11
12
| //void前面的<T>用来声明这是一个泛型方法
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
Integer[] intArray = {1, 2, 3};
swap(intArray, 0, 1);
String[] strings = {"apple", "banana", "cherry"};
swap(strings, 0, 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
| public class GenericClassExample {
// 定义 static 内部类
static class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
public static void main(String[] args) {
// 使用
Pair<String, Integer> pair = new Pair<>("age", 25);
System.out.println("Key: " + pair.getKey() + ", Value: " + pair.getValue());
Pair<String, String> pair2 = new Pair<>("name", "bob");
System.out.println("Key: " + pair2.getKey() + ", Value: " + pair2.getValue());
}
}
|
泛型接口
在实现该接口时,指定一个具体的类。
1
2
3
4
5
6
7
8
9
10
11
12
13
| interface DataAccessObject<T> {
T getById(int id);
void save(T entity);
}
// 实现类
class UserDataAccessObject implements DataAccessObject<User> {
@Override
public User getById(int id) {
// 从数据库获取用户数据的逻辑
return null;
}
}
|
也可以在实现接口时不指定一个具体的类,允许在创建 GeneratorImpl 类的实例时再指定具体的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| interface Generator<T> {
T method();
}
class GeneratorImpl<T> implements Generator<T> {
@Override
public T method() {
return null;
}
}
GeneratorImpl<String> stringGenerator = new GeneratorImpl<>();
String result = stringGenerator.method();
GeneratorImpl<Integer> integerGenerator = new GeneratorImpl<>();
Integer num = integerGenerator.method();
|
限定类型
1
| class GenericClass<T extends Number> { }
|
字节码文件
- 类型擦除:编译时,泛型类型参数会被擦除,通常被替换为 Object
- 桥接方法:子类中可能会重写父类的泛型方法。为了确保子类方法能够正确覆盖父类方法,编译器会生成一个桥接方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| class Parent<T> {
public void set(T value) {
System.out.println("Parent set: " + value);
}
}
class Child extends Parent<String> {
@Override
public void set(String value) {
System.out.println("Child set: " + value);
}
}
public class Main {
public static void main(String[] args) {
Parent<String> parent = new Child();
parent.set("Hello");
}
}
|
显然,最后会输出的是 Child Set。但是这其实调用的是子类中我们看不到的桥接方法。
1
2
3
| public void set(Object value) {
set((String) value); // 调用子类的 set(String value) 方法
}
|
- 泛型签名:泛型类、方法、字段的泛型信息会以特殊格式存储在字节码中,这些信息可以通过反射API(如getGenericType())获取。