Java基础-泛型详解

个人博客 转载请注明原创出处。java

之因此会想来写泛型相关的内容,是由于看到这样的一段代码:程序员

当时个人心里是这样的:数组

因此就赶忙去复习了下,记录下来。基础不扎实,源码看不懂啊。安全

泛型介绍

Java 泛型(generics)是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制容许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操做的数据类型被指定为一个参数,在Java集合框架里使用的很是普遍。bash

定义的重点是提供了编译时类型安全检测机制。好比有这样的一个泛型类:markdown

public class Generics <T> {

    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}
复制代码

而后写这样一个类:框架

public class Generics  {

    private Object value;

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }
}
复制代码

它们一样都能存储全部值,可是泛型类有编译时类型安全检测机制:ide

泛型类

一个类定义了一个或多个类型变量,那么就是泛型类。语法是在类名后用尖括号括起来,类型变量写在里面用逗号分开。而后就能够在方法的返回类型、参数和域、局部变量中使用类型变量了,可是不能在有static修饰符修饰的方法或域中使用。例子:oop

类定义参考上文例子
使用形式
Generics<String> generics = new Generics<String>();
后面尖括号内的内容在Jdk7之后能够省略
Generics<String> generics = new Generics<>();
复制代码

泛型方法

一个方法定义了一个或多个类型变量,那么就是泛型方法。语法是在方法修饰符后面、返回类型前面用尖括号括起来,类型变量写在里面用逗号分开。泛型方法能够定义在普通类和泛型类中,泛型方法能够被static修饰符修饰。 例子:this

private <U> void out(U u) {
        System.out.println(u);
    }
    
    调用形式,
    Test.<String>out("test");
    大部分状况下<String>均可以省略,编译器能够推断出来类型
    Test.out("test");
    
复制代码

类型变量的限定

有时候咱们会有但愿限定类型变量的状况,好比限定指定的类型变量须要实现List接口,这样咱们就能够在代码对类型变量调用List接口里的方法,而不用担忧会没有这个方法。

public class Generics <T extends List> {

    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    public void add(Object u) {
        value.add(u);
    }

    public static void main(String[] args) {
        Generics<List> generics = new Generics<>();
        generics.setValue(new ArrayList<>());
        generics.add("ss");
        System.out.println(generics.getValue());
    }
    
}
复制代码

限定的语法是在类型变量的后面加extends关键字,而后加限定的类型,多个限定的类型要用&分隔。类型变量和限定的类型能够是类也能够是接口,由于Java中类只能继承一个类,因此限定的类型是类的话必定要在限定列表的第一个。

类型擦除

类型擦除是为了兼容而搞出来的,大意就是在虚拟机里是没有泛型类型,泛型只存在于编译期间。泛型类型变量会在编译后被擦除,用第一个限定类型替换(没有限定类型的用Object替换)。上文中的Generics <T>泛型类被擦除后会产生对应的一个原始类型:

public class Generics {

    private Object value;

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

}
复制代码

之因此咱们能设置和返回正确的类型是由于编译器自动插入了类型转换的指令。

public static void main(String[] args) {
        Generics<String> generics = new Generics<>();
        generics.setValue("ss");
        System.out.println(generics.getValue());
    }
    
    javac Generics.java
    javap -c Generics
    编译后的代码
     public static void main(java.lang.String[]);
        Code:
       0: new           #3 // class generics/Generics
       3: dup
       4: invokespecial #4 // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #5 // String ss
      11: invokevirtual #6 // Method setValue:(Ljava/lang/Object;)V
      14: getstatic     #7 // Field java/lang/System.out:Ljava/io/PrintStream;
      17: aload_1
      18: invokevirtual #8 // Method getValue:()Ljava/lang/Object;
      21: checkcast     #9 // class java/lang/String
      24: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      27: return

复制代码

咱们能够看到在21行插入了一条类型转换的指令。

类型擦除还带来了另外一个问题,若是咱们有一个类继承了泛型类并重写了父类的方法:

public class SubGenerics extends Generics<String> {

    @Override
    public void setValue(String value) {
        System.out.println(value);
    }

    public static void main(String[] args) {
        Generics<String> generics = new SubGenerics();
        generics.setValue("ss");
    }

}
复制代码

由于类型擦除因此SubGenerics实际上有两个setValue方法,SubGenerics本身的setValue(String value)方法和从Generics继承来的setValue(Object value)方法。例子中的generics引用的是SubGenerics对象,因此咱们但愿调用的是SubGenerics.setValue。为了保证正确的多态性,编译器在SubGenerics类中生成了一个桥方法:

public void setValue(Object value) {
    setValue((String) value);
}
复制代码

咱们能够编译验证下:

Compiled from "SubGenerics.java"
public class generics.SubGenerics extends generics.Generics<java.lang.String> {
  public generics.SubGenerics();
    Code:
       0: aload_0
       1: invokespecial #1 // Method generics/Generics."<init>":()V
       4: return

  public void setValue(java.lang.String);
    Code:
       0: getstatic     #2 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_1
       4: invokevirtual #3 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       7: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #4 // class generics/SubGenerics
       3: dup
       4: invokespecial #5 // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #6 // String ss
      11: invokevirtual #7 // Method generics/Generics.setValue:(Ljava/lang/Object;)V
      14: return

  public void setValue(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #8 // class java/lang/String
       5: invokevirtual #9 // Method setValue:(Ljava/lang/String;)V
       8: return
}
复制代码

引用《Java核心技术 卷一》

总之,须要记住有关 Java 泛型转换的事实: 1.虚拟机中没有泛型,只有普通的类和方法。 2.全部的类型参数都用它们的限定类型替换。 3.桥方法被合成来保持多态。 4.为保持类型安全性,必要时插人强制类型转换。

约束与局限性

  • 类型变量不能是基本变量,好比int,double等。应该使用它们的包装类Integer,Double
  • 虚拟机中没有泛型,因此不能使用运行时的类型查询,好比 if (generics instanceof Generics<String>) // Error
  • 由于类型擦除的缘由,不能建立泛型类数组,好比Generics<String>[] generics = new Generics<String>[10]; // Error
  • 不能实例化类型变量,好比new T(...) new T[...] 或 T.class

通配符类型

通配符类型和上文中的类型变量的限定有些相似,区别是通配符类型是运用在声明的时候而类型变量的限定是在定义的时候。好比通配符类型Generics<? extends List>表明任何泛型Generics类型的类型变量是ListList的子类。

Generics<? extends List> generics = new Generics<ArrayList>();

不过这样声明以后Generics的方法也发生了变化,变成了

这样就致使了不能调用setValue方法

getValue方法是正常的

超类型限定

通配符限定还能够限定超类,好比通配符类型Generics<? super ArrayList>表明任何泛型Generics类型的类型变量是ArrayListArrayList的超类。

Generics<? super ArrayList> generics = new Generics<List>();

一样的,Generics的方法也发生了变化,变成了

调用getValue方法只能赋值给Object变量

调用setValue方法只能传入ArrayListArrayList的子类,超类List,Object等都不行

反射和泛型

虽然由于类型擦除,在虚拟机里是没有泛型的。不过被擦除的类仍是保留了一些关于泛型的信息,可使用反射相关的Api来获取。

相似地,看一下泛型方法

public static <T extends Comparable<? super T>> T min(T[] a)

这是擦除后

public static Comparable min(Coniparable[] a)

可使用反射 API 来肯定:

  • 这个泛型方法有一个叫作T的类型参数。
  • 这个类型参数有一个子类型限定, 其自身又是一个泛型类型。
  • 这个限定类型有一个通配符参数。
  • 这个通配符参数有一个超类型限定。
  • 这个泛型方法有一个泛型数组参数。

后记

周一就建好的草稿,到了星期天才写好,仍是删掉了一些小节状况下,怕是拖延症晚期了......不过也是由于泛型的内容够多,虽然平常业务里不多本身去写泛型相关的代码,可是在阅读类库源码时要是不懂泛型就步履维艰了,特别是集合相关的。此次的大部份内容都是《Java核心技术 卷一》里的,这但是本关于Java基础的好书。不过仍是老规矩,光读可不行,仍是要用本身的语言记录下来。众所周知,人类的本质是复读机,把好书里的内容重复一遍,就等于我也有责任了!

相关文章
相关标签/搜索