java基础(6)-Generics

下面这段代码,在泛型JDK5之前常常看到

1
2
3
4
5
6
      // 1
      List myIntList = new LinkedList();
      // 2
      myIntList.add(new Integer(0));
      // 3
      Integer x = (Integer) myIntList.iterator().next();

JDK5以后上面的代码发生了部分改变

1
2
3
4
 // 使用泛型后
        List<Integer> myIntList1 = new LinkedList<>(); // 1'
        myIntList1.add(new Integer(0)); // 2'
        Integer x1 = myIntList1.iterator().next(); // 3'

其作用:

  1. 解决了转化类型的混乱
  2. 在编译期就检测类型是否正确
  3. 提高可读性

泛型可以使用到类与方法上。

泛型类

1
2
3
4
5
6
7
8
9
10
// 定义一个简单的泛型
public interface List <E> {
   void add(E x);
   Iterator<E> iterator();
}

public interface Iterator<E> {
   E next();
   boolean hasNext();
}

错误示范:

1
2
3
4
5
6
7
8
9
10
class Node<T> {
    // 在泛型类中定义静态泛型方法、静态泛型变量是不被允许的
    // 泛型类的泛型参数是在对象创建时,才指定的;
    // 而静态方法、静态变量,可以直接用类调用,此时,还没有实例化对象。
    public static T sta;

    public static T staticTest(){
        ...
    }
}

泛型方法

1
2
3
4
5
6
7
8
9
10
11
    // 参数与返回类型之间有依赖关系,可以使用泛型方法

    // 有返回值
    private <T> T test(T x){
        return x;
    }

    // 无返回值
    private <T> void test(T x){
        ...
    }

泛型的通配符

通配符分为:

  1. 无界通配符 <?> 任何参数类型都允许
  2. 有界通配符

    i. 下界通配符 <? super T>,表示最低的类是T

    ii. 上届通配符 <? extends T>,表示最高的类是T

1
2
3
4
5
6
7
8
9
10
11
public class Person{
  ...
}

    public void TestWildcards(List<? extends Person> list){

        // compile error
//        list.add()

        Person person = list.get(0);
    }

就上面 list.add() 编译失败,我们可以采取写一个helper来解决

1
2
3
4
5
6
7
    public void TestWildcards(List<? extends Person> list){
      wildcardsHelper(list);  
    }

    private static <T> void wildcardsHelper(List<T> list){
        list.add((T) new Person());
    }

因为允许编译器将通配符的未知类型作为泛型方法的类型参数进行推断,所以上面不会出错。

1
2
3
4
5
// 下界通配符
    public void TestWildcards(List<? super Person> list){
      list.add(new Person());
      Object person = list.get(0);
    }

其实对比上面对上界、下界通配符的操作,我们可以得到一个结论:

  1. 下界通配符,只存不取的时候使用
  2. 上界通配符,只取不存的时候使用
  3. 若既需要存又需要取,就避免使用通配符

泛型类型的擦除

1
2
3
4
5
    // 泛型的擦除

    List<String> l1 = new ArrayList<>();
    List<Integer> l2 = new ArrayList<>();
    System.out.println(l1.getClass() == l2.getClass());

上面这段代码,true or false? 结果是true,它们的类型被擦除了,只保留其原始类型(raw types)

jdk对类型擦除一段总结:

  • 使用它们的边界替代所有的泛型参数,如果是无边界就使用 Object 替代。因此,产生的字节码仅包含普通的类,接口和方法。
  • 必要时插入类型转化,以保持类型的安全性
  • 在有继承的泛型类型中,生成桥方法(bridge methods)来维持多态
  • Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.

有边界的泛型类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

// 编译器,将用第一个有界类,Comparable 取代泛型类型
public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Comparable getData() { return data; }
    // ...
}

// 泛型方法与泛型类是同理的;
// 无边界的就不列举出来了,较为简单

类型擦除带来的影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MyNode extends Node<Integer> {
    public MyNode(Integer data) { super(data); }

    @Override
    public void setData(Integer data) {
        super.setData(data);
        System.out.println("MyNode.setData");
    }
}

class Node<T> {

    public T data;

    public Node(T data) { this.data = data; }

    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

// 类型擦除后的Node类
class Node {

    public Object data;

    public Node(Object data) { this.data = data; }

    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

Node的方法变成了setData(object)、而MyNode却是setData(Integer),这两个类的setData方法就不存在覆写(Override)了.

为了解决这个问题,类型擦除后保持泛型类型的多态性,编译器就会生成桥方法(bridge methods)确保事情如预期进行。

我们反编译MyNode.class会发现

1
2
3
4
5
6
7
  public void setData(java.lang.Integer);
  
  // 编译器自动生成的桥方法
  public void setData(java.lang.Object);

// 子类真正覆写(Override)的是这个编译器生成的桥方法

When compiling a class or interface that extends a parameterized class or implements a parameterized interface, the compiler may need to create a synthetic(混合) method, called a bridge method, as part of the type erasure process.


参考: 1. Generics-oracle


对自己的现状不满意只有付出更多的努力去改变它

如果有不对的地方或建议,请指出,谢谢啦