JAVA的泛型

128 阅读6分钟

泛型的核心目的--编写类型安全的代码

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中“假泛型”存在的问题

  1. Java的泛型是假泛型,是编译器的泛型,泛型信息在运行期间完全不保留
  2. 如果不指定泛型想绕过编译器的警告,可以使用限定符List<?>
  3. 由于存在泛型擦除,函数的重载一个带泛型,另一个不带,编译器都会报错,因为泛型被擦除后就成一样的函数了
    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

  1. 对于编译器来说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方法调用传入IntegerString会报错的原因是裸泛型,运行报错的原因是因为多态,由于第一个参数是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是表示可比较的,需要实现的方法是compareToComparator是表示可排序的,需要实现的方法是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 类型

泛型中的限定符

  1. <?> 任意类型
  2. <? extends Object> 要求泛型是某种类型及其子类型
  3. <? 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的等)