Generic types & Wildcards

Use generic types
泛型的好处

  1. 编译时期的强类型检查。有利于提早发现问题,解决问题。
  2. 不用再使用类型强转。
  3. 能够利用泛型编写简单易读的通用代码。

泛型通常命名规则:

E - Element
K - Key
N - Number
T - Type
V - Value

public class Stack<E> {
   ...
   public Stack() { ... }
   public void push(E e) { ... }
   public E pop() { ... }
   public boolean isEmpty { ... }

   public void pushAll(Iterable<E> src) {
       for (E e : src) {
           push(e);
       }
   }

   public void popAll(Collection<E> dst) {
       while (!isEmpty()) {
           dst.add(pop());
       }
   }
}

Use wildcards

public class Stack<E> {
    ...
    public Stack() { ... }
    public void push(E e) { ... }
    public E pop() { ... }
    public boolean isEmpty { ... }

    public void pushAll(Iterable<? extends E> src) {
        for (E e : src) {
            push(e);
        }
    }

    public void popAll(Collection<? super E> dst) {
        while (!isEmpty()) {
            dst.add(pop());
        }
    }
}

Wildcards and Subtyping
假设有以下两个类:

class A { /* ... */ }
class B extends A { /* ... */ }

像下面这么写是合理的:

B b = new B();
A a = b;

但是如果像下面这么写就会报错:

List<B> lb = new ArrayList<>();
List<A> la = lb;   // compile-time error

也就是说虽然B是A的子类,但是List却不是List的子类。那他们之间有什么关系呢:

Generic types & Wildcards

List和List的公共父类

除此之外,还有以下层级关系:

Generic types & Wildcards

A hierarchy of several generic List class declarations

/**
       * 上有界通配符
       * @param list
       */
      public static  void upperBoundedWildcards(List<? extends String> list){
      }

      /**
       * 无界通配符
       * @param list
       */
      public static  void unboundedWildcards(List<?> list){
      }

      /**
       * 下有界通配符
       * @param list
       */
      public static  void lowerBoundedWildcards(List<? super Integer> list){
      }
1.<T extends E>:用于定义类型参数;
它声明一个类型参数T,可以放在泛型类定义中类名后面、泛型方法的返回值前面;

2.<? extends E>:用于实例化类型参数;
它用于实例化泛型变量中的类型参数,只是这个具体类型是未知的,只知道他是E或者E的某个子类;
  1. 从泛型类派生子类
//这样写是错误的,实现的接口或父类不能再包含类型形参
public class ChildApple extends Apple<T>{
}

//正确的写法是以下三种:在使用接口或父类时传入类型实参或者不传
//继承泛型类(子类不是泛型类)
public class ChildApple extends Apple<String>{
}
//不传时,编译器会发出警告,系统默认把Apple<T>类中的T形参当成Object类型处理
public class ChildApple extends Apple{
}
//继承泛型类(子类也是泛型类)
public class ChildApple<T> extends Apple<T>{
}
public class Apple<T> {
    //不能在静态属性声明时使用类型形参,编译报错
    static T info ;
    //不能在静态方法声明中使用类型形参,编译报错
    public static void fun(T t){
    }
    //系统并不会真正生成泛型类,所以不能使用instanceof运算符,编译报错;
    if(a1 instanceof Apple<String>){}
}

类型擦除
什么是类型擦除
Java 的泛型使用了类型擦除机制,这个引来了很大的争议,以至于 Java 的泛型功能受到限制,只能说是”伪泛型“。什么叫类型擦除呢?简单的说就是,类型参数只存在于编译期,在运行时,Java 的虚拟机 ( JVM ) 并不知道泛型的存在。先看个例子:

public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList().getClass();
Class c2 = new ArrayList().getClass();
System.out.println(c1 == c2);
}
}
上面的代码有两个不同的 ArrayList:ArrayList 和 ArrayList。在我们看来它们的参数化类型不同,一个保存整性,一个保存字符串。但是通过比较它们的 Class 对象,上面的代码输出是 true。这说明在 JVM 看来它们是同一个类。而在 C++、C# 这些支持真泛型的语言中,它们就是不同的类。

泛型参数会擦除到它的第一个边界,比如说上面的 Holder2 类,参数类型是一个单独的 T,那么就擦除到 Object,相当于所有出现 T 的地方都用 Object 替换。所以在 JVM 看来,保存的变量 a 还是 Object 类型。之所以取出来自动就是我们传入的参数类型,这是因为编译器在编译生成的字节码文件中插入了类型转换的代码,不需要我们手动转型了。如果参数类型有边界那么就擦除到它的第一个边界,这个下一节再说。

擦除带来的问题
擦除会出现一些问题,下面是一个例子:

class HasF {
public void f() {
System.out.println(“HasF.f()”);
}
}
public class Manipulator {
private T obj;

public Manipulator(T obj) {
    this.obj = obj;
}

public void manipulate() {
    obj.f(); //无法编译 找不到符号 f()
}

public static void main(String[] args) {
    HasF hasF  = new HasF();
    Manipulator<HasF> manipulator = new Manipulator<>(hasF);
    manipulator.manipulate();

}

}

上面的 Manipulator 是一个泛型类,内部用一个泛型化的变量 obj,在 manipulate 方法中,调用了 obj 的方法 f(),但是这行代码无法编译。因为类型擦除,编译器不确定 obj 是否有 f() 方法。解决这个问题的方法是给 T 一个边界:

class Manipulator2 {
private T obj;
public Manipulator2(T x) { obj = x; }
public void manipulate() { obj.f(); }
}
现在 T 的类型是 ,这表示 T 必须是 HasF 或者 HasF 的导出类型。这样,调用 f() 方法才安全。HasF 就是 T 的边界,因此通过类型擦除后,所有出现 T 的
地方都用 HasF 替换。这样编译器就知道 obj 是有方法 f() 的。

但是这样就抵消了泛型带来的好处,上面的类完全可以改成这样:

class Manipulator3 {
private HasF obj;
public Manipulator3(HasF x) { obj = x; }
public void manipulate() { obj.f(); }
}
所以泛型只有在比较复杂的类中才体现出作用。但是像 这种形式的东西不是完全没有意义的。如果类中有一个返回 T 类型的方法,泛型就有用了,因为这样会返回准确类型。比如下面的例子:

class ReturnGenericType {
private T obj;
public ReturnGenericType(T x) { obj = x; }
public T get() { return obj; }
}
这里的 get() 方法返回的是泛型参数的准确类型,而不是 HasF。

我们现在可以下结论了,在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 则会被转译成普通的 Object 类型,如果指定了上限如 则类型参数就被替换成类型上限。
https://blog.****.net/briblue/article/details/76736356