泛型的核心目的--编写类型安全的代码
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
在没有泛型之前,要确保类型安全如List或者Map的使用,要像下面这样比较繁琐
// 自己封装一个类型安全的List
public class StringListDecorator {
List list = new ArrayList();
public void add(String str) {
list.add(str);
}
public String get(Integer index) {
return (String) list.get(index);
}
public int size(){
return list.size();
}
}
// 自己封装一个类型安全的Map
public class StringObjectMapDecorator{
Map map = new HashMap();
public void put(String key,Object value){
map.put(key,value);
}
public Object get(String key){
return map.get(key);
}
}
此时使用StringList
就只能存放String
类型的,这种实现模式叫装饰器模式,也可以叫组合模式
StringListDecorator sList = new StringListDecorator();
sList.add("test");
但每需要一个类型,就得封装一次
但有了泛型后,使用起来就方便且简洁
List<String> list = new ArrayList<>();
Map<String,Object> map = new HashMap<>();
泛型可以声明在类上,传入String,那泛型就是String,由此带来一个疑问,下面这两个类是同一个类吗?
new ArrayList<String>();
new ArrayList<Integer>();
既是,也不是,说它是是因为运行时泛型会被擦除,说它不是,是因为不能执行ArrayList<String> list = new ArrayList<Integer>();
这种赋值
泛型的擦除:在没有泛型的世界进化到有泛型的世界,需要做向后兼容,好处是老代码也能继续跑,Java就选择了这个方案
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
}
这段代码,在生成的字节码文件的第四行中,<String>
已经不存在了,JVM
只能运行字节码,和java
代码没关系,字节码中则没有泛型系统,这种现象被称为泛型擦除
public static main([Ljava/lang/String;)V
L0
LINENUMBER 7 L0
NEW java/util/ArrayList // 看这里:)
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
ASTORE 1
L1
LINENUMBER 8 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
LOCALVARIABLE list Ljava/util/ArrayList; L1 L2 1
// signature Ljava/util/ArrayList<Ljava/lang/String;>;
// declaration: java.util.ArrayList<java.lang.String>
MAXSTACK = 2
MAXLOCALS = 2
Java中“假泛型”存在的问题
Java
的泛型是假泛型,是编译器的泛型,泛型信息在运行期间完全不保留- 如果不指定泛型想绕过编译器的警告,可以使用限定符
List<?>
- 由于存在泛型擦除,函数的重载一个带泛型,另一个不带,编译器都会报错,因为泛型被擦除后就成一样的函数了
public void test(ArrayList list){}
public void test(ArrayList<String> list){}
// 这两段代码会报错
甚至可以通过一些手段绕过类型检查
ArrayList<String> list = new ArrayList<>();
ArrayList rawList = list;
rawList.add(new Object());
rawList.add(123);
rawList.add(new HashMap<>());
ArrayList<Date> dateList = (ArrayList<Date>) rawList;
由于泛型擦除,上面这一段代码并不会报错,但代码的本意是需要一个Date
类型的List
- 对于编译器来说
List<String>
并不是List<Object>
的子类型,无法互相传递引用(假设可以,那就存在类型转换,会破坏类型安全检测)
泛型的绑定
不使用泛型的情况下,想实现一个找最大值的函数,我们可能需要根据不同类型去编写几个函数
public static Integer max(Integer a, Integer b) {
return a.compareTo(b) >= 0 ? a : b;
}
public static Double max(Double a, Double b) {
return a.compareTo(b) >= 0 ? a : b;
}
public static String max(String a, String b) {
return a.compareTo(b) >= 0 ? a : b;
}
使用泛型的话只需要抽象成一个方法即可,Integer,Double,String
都实现了Comparable
接口
public static <T extends Comparable> T max(T a,T b){
return a.compareTo(b) >= 0 ? a : b;
}
当试图调用带泛型的方法的时候,编译器会尝试把传入的类型和泛型进行绑定,绑定成功则编译通过,不过则失败。第四个max
方法调用传入Integer
和String
会报错的原因是裸泛型,运行报错的原因是因为多态,由于第一个参数是Integer,调用过程变成了调用Integer.compareTo(Integer anotherInt)
,但传入的实际是String
,所以抛出运行时异常。想要编译就报错只需要把泛型T
传入Comparable
中<T extends Comparable<T>>
max(1.0,2.0);
// Double,Double
max(1,2);
// Integer,Integer
max("abc","efg");
// String,String
max(1,"abc");
// 这里编译不会报错,但运行会,因为实际上是max(Comparable,Comparable)
在传入两个参数后,这个max方法就变成了全新的方法,同时会检查传入的参数类型是否为Comparable
类型或者Comparable
的子类型。总结就是可以把一个方法的参数变成泛型,并且约束泛型的条件
Comparable
是表示可比较的,需要实现的方法是compareTo
,Comparator
是表示可排序的,需要实现的方法是compare
,名字很像容易搞混。如果实现类没有实现Comparable
接口,又想对两个类进行比较(或者实现类实现了Comparable
接口,但是对compareTo
方法内的比较算法不满意),那么可以实现Comparator
接口,自定义一个比较器
泛型按照返回值自动绑定
public static <T> T cast(Object obj){
return (T) obj;
}
此时使用时,声明成什么类型,返回值就是什么类型,根据接收者绑定,虽然没什么乱用:)
String s = cast("test");
// 返回值就是String
Integer s = cast(123);
// 返回值就是Integer
泛型的命名没有特定约束,可以用任意想用的命名,但有一些命名对照可以参考
java 中泛型标记符:
- E - Element (在集合中使用,因为集合中存放的是元素)
- T - Type(Java 类)
- K - Key(键)
- V - Value(值)
- N - Number(数值类型)
- ? - 表示不确定的 java 类型
泛型中的限定符
<?>
任意类型<? extends Object>
要求泛型是某种类型及其子类型<? super Object>
要求泛型是某种类型及其父类型
演示一个泛型<? super 类型>
的写法,假设我们有3个继承关系的类,并有3个对应的实现了Comparator接口的类
static class Grandpa {
}
static class Father extends Grandpa {
}
class Son extends Father {
}
static class GrandpaComparator implements Comparator<Grandpa> {
@Override
public int compare(Grandpa o1, Grandpa o2) {
return 0;
}
}
static class FatherComparator implements Comparator<Father> {
@Override
public int compare(Father o1, Father o2) {
return 0;
}
}
static class SonComparator implements Comparator<Son> {
@Override
public int compare(Son o1, Son o2) {
return 0;
}
}
再定义一个泛型方法
public static <T> void sort(T pre, Comparator<? super T> next) {
}
当调用时,前两行能正常通过编译,第三行会编译报错。当把第一个参数的Father
类型绑定到T的时候,第二个的参数泛型约束了必须是Father
类型或Father
的父类型。前两行都符合约束,第三行报错的原因是Son
并不是Father
的父类型。
public static void main(String[] args) {
sort(new Father(), new GrandpaComparator());
sort(new Father(), new FatherComparator());
sort(new Father(), new SonComparator());
}
注意类型参数只能代表引用型类型,不能是原始类型(像int,double,char的等)