更多 Java 高级知识方面的文章,请参见文集《Java 高级知识》
类型系统
Liskov 里氏替换原则,子类可以替换父类。
- 需要
Object
的引用时,可以传入String
对象 - 需要
String
的引用时,传入Object
对象需要强制类型转换,运行时可能抛出ClassCastException
类型擦除 Type Erasure
Java 的泛型是在 编译器 层次实现的。
在编译生成的字节码中不包含泛型中的类型参数,类型参数会在编译时去掉。
例如:List<String>
和 List<Integer>
在编译后都变成 List
。
类型擦除的基本过程:将代码中的类型参数替换为具体的类,同时去掉 <>
的内容。
泛型
- 类型参数只能是类,例如
List<Integer>
,不能是简单类型,例如List<int>
- 类型参数可以有多个,例如
HashMap<String, Integer>
泛型的优势
- 编译时更强大的类型检测。Java 编译器对泛型应用了强大的类型检测,如果代码违反了类型安全就会报错。修复编译时错误比修复运行时错误更加容易,因为运行时错误很难查找到。
例如如下代码: 方法传入一个 String
对象,传出一个 String
对象,并强制转换为 Integer
对象。
这段代码编译可以通过,因为都是 Object
的子类,但是运行时会产生 ClassCastException
。
public static Object setAndReturn(Object obj) {
return obj;
}
public static void main(String[] args) {
Integer i = (Integer) setAndReturn(new String("abc"));
}
而如果通过泛型来实现,则会在编译时进行类型的检测。例如如下代码:会产生编译错误。
public static <T> T setAndReturn(T t) {
return t;
}
public static void main(String[] args) {
Integer i = (Integer) setAndReturn(new String("abc"));
}
-
提供自动和隐式的类型转换
例如如下代码:在函数返回时,不需要显示的类型转换<Integer>setAndReturn(new Integer("123"));
public static <T> T setAndReturn(T t) {
return t;
}
public static void main(String[] args) {
// 不需要使用 = <Integer>setAndReturn(new Integer("123"));
Integer i = setAndReturn(new Integer("123"));
}
- 实现泛型算法,类似于 C++ 中的模板
泛型的奇怪特性
- 泛型类并没有独有的
Class
对象。即不存在List<String>.class
,只存在List.class
。 - 泛型类中的静态变量被所有实例共享。
- 泛型类中的类型参数不能用在异常处理的 catch 中。
因为异常处理是 JVM 运行时刻来进行的,此时类型参数已被擦除。
<T> VS <?>
不同点:
-
<T>
用于 泛型的定义,例如class MyGeneric<T> {...}
-
<?>
用于 泛型的声明,即泛型的使用,例如MyGeneric<?> g = new MyGeneric<>();
相同点:都可以指定上界和下界,例如:
class MyGeneric<T extends Collection> {...}
class MyGeneric<T super List> {...}
MyGeneric<? extends Collection> g = new MyGeneric<>();
MyGeneric<? super List> g = new MyGeneric<>();
泛型类
示例:
public class Generic_Test {
public static void main(String[] args) {
MyGeneric<String> g1 = new MyGeneric<String>(new String("123"));
g1.print();
MyGeneric<Integer> g2 = new MyGeneric<Integer>(new Integer("123"));
g2.print();
}
}
class MyGeneric<T> {
private T t;
public MyGeneric(T t) {
this.t = t;
}
public void print() {
System.out.println(t.getClass().getName());
}
}
Java7 的泛型类型推断改进
在上面的代码中,MyGeneric<String> g1 = new MyGeneric<String>(new String("123"));
。可以看出,需要在声明和赋值的时候,两侧都加上泛型类型 <String>
。
在Java 7中,这种方式得以改进,可以使用如下语句进行声明和赋值:
MyGeneric<String> g1 = new MyGeneric<>(new String("123"));
在这条语句中,编译器会根据变量声明时的泛型类型自动推断出实例化 MyGeneric
时的泛型类型。
泛型方法
示例:
public static <T> void print(T t) {
System.out.println(t.getClass().getName());
}
public static void main(String[] args) {
print(new String("123"));
print(new Integer("123"));
}
使用泛型类
- 指定具体类型,如
List<String>
- 指定未知类型,如
List<?>
。
List<?>
不等于List<Object>
,例如:
List<Object> list1 = new ArrayList();
list1.add(1); // 编译通过
List<?> list2 = new ArrayList();
list2.add(1); // 编译错误
泛型的继承关系及扩展
-
String
是Object
的子类,但是List<String>
不是List<Object>
的子类。
例如:如下代码会导致编译错误。
如果将List<Object>
换成List<?>
,则可以编译通过。
public static void f(List<Object> list) {
}
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
f(list); // 编译错误.
}
- 相同参数类型的泛型类的继承关系取决于泛型类自身的继承结构。
例如List<String>
是Collection<String>
的子类 - 当类型声明中使用通配符
?
时,其子类型可以在两个维度上扩展。
例如Collection<? extends Number>
在维度1上扩展:List<? extends Number>
在维度2上扩展:Collection<Integer>
两个维度上同时扩展:List<Integer>