转载

【面向面试学习】Java的泛型机制是怎样的?

Java在开发时,通过使用菱形语法< >,使一个类、接口或者方法具有接受泛指的一群类作为参数,泛指的类最终由尖括号里的规则所限定,泛指类通常用大写字母表示,一般用字母T,后期经过Java编译器编译,泛型将被擦除,根据具体使用的类,替换泛型,生成class字节码,所以泛型是对Jvm透明的。

豪华版回答

泛型于JDK1.5正式引入

Java引入泛型背景

Java集合是允许放入多种类型的,例如

List list=new ArrayList();
    list.add("String");
    list.add(1024);
复制代码

这在JDK1.5之前是常见的使用方式,即便放在今天的JDK下,也是允许的,那么类似于这种集合的使用方式,就会带来一些问题,就是集合中究竟放了哪些类型,恐怕只有创建集合的人才知道,其他调用者,根本无法确定,这样在大型项目里,极易出现问题,调用者强转集合里的某个对象,一旦类型错误,就会报错,这种错在当时编译阶段是无法发现的,只有运行时才被发现,为了解决类似问题,JDK引入了泛型。

泛型类型

引入泛型后,从而可以在编译阶段,检查类型是否符合要求,很大程度上,杜绝了盲目的类型转换。 泛型主要工作在编译器,编译后,JVM运行时,是无法感知泛型的。 泛型分为普通泛型和通配泛型。

1.普通泛型

这类泛型顾名思义,就是支持调用时传入任意类型,但是调用时,等号左右俩边泛型必须一致。JDK1.7右侧菱形可以省略。

class Test<T>{...} //声明时
Test<Integer> test = new Test<Integer>(); //调用时
Test<Integer> test = new Test(); //1.7调用时
复制代码

3.界限泛型

<?>//无界泛型,任意类型都匹配
<T extends ClassA>//有界泛型 - 继承自某父类
<T extends InterfaceB>//有界泛型 - 实现某接口
<T extends ClassA & InterfaceB & InterfaceC ... >//有界泛型 - 多重边界
<T super ClassC>//有界泛型 - 指定类父类
复制代码

泛型擦除

刚才说到,泛型只在写代码时和编译时起作用,而Jvm加载class运行时是无感透明的,是因为编译器编译时将泛型擦除了,简单不严谨的说就是:把类或方法上的那个<>尖括号干掉了,根据尖括号里的规则换成具体的类,所以Jvm运行时根本不知道这段代码里之前有过泛型,这样做是为了兼容,前面说到泛型是JDK1.5才引入的,这样,即便引入泛型,现有的Jvm也不需要大改,只需要改一下编译器即可,对于这种编译器能感知的语法,而虚拟机无法感知的,人们把这种语法称作 语法糖(suger) ,编译器经过 脱糖(Desuger) 后,拿到干货,交给虚拟机去执行。

泛型擦除机制

//Pair的泛型
public class Pair<T> {

    private T mFirst;
    private T mSecond;

    public T getmFirst() {
        return mFirst;
    }

    public void setmFirst(T mFirst) {
        this.mFirst = mFirst;
    }

    public T getmSecond() {
        return mSecond;
    }

    public void setmSecond(T mSecond) {
        this.mSecond = mSecond;
    }
}

//Pair的原始类型
//无论何时定义一个泛型类型,都会自动提供一个相应的 原始类型
public class Pair {

    private Object mFirst;
    private Object mSecond;

    public Object getFirst() {
        return mFirst;
    }

    public void setFirst(Object mFirst) {
        this.mFirst = mFirst;
    }

    public Object getSecond() {
        return mSecond;
    }

    public void setmSecond(Object mSecond) {
        this.mSecond = mSecond;
    }
}

//如果调用Pair<T extends String>编译擦除后得到如下=
class Pair{
    private String mFirst;
    private String mSecond;
    ...
}
复制代码
  • 当泛型作为方法的入参时,擦除后换成通配泛型的下界,例如add方法
public static void insertElements(List<? super A> list){
    //Add进来的都是层级“小于等于”A的,也就是下界
    list.add(new A());
    list.add(new B());
    list.add(new C());
}
复制代码
  • 当泛型作为方法的回参时,擦除后换成通配泛型的上界,例如get方法
public void processElements(List<? extends A> elements){
   //for循环挨个get list 里的数据,都当做A,也就是都当做上界
   for(A a : elements){
      System.out.println(a.getValue());
   }
}
复制代码

对于泛型的类型擦除说再多,看再多博客,不如官方定义准确,放一段Oracle官方对于类型擦除的文档

Type Erasure

Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:

  • Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
  • Insert type casts if necessary to preserve type safety.
  • Generate bridge methods to preserve polymorphism in extended generic types.

Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.

参考

Java中泛型区别以及泛型擦除详解 Java 泛型:深入理解泛型的类型擦除

原文  https://juejin.im/post/5de486f46fb9a071a06d38eb
正文到此结束
Loading...