本文从泛型的基本概念,使用编写泛型及泛型的局限性,再到泛型实现的擦拭法分析,还从extends与super通配符对泛型的应用做了系统性介绍。

泛型概念

  1. ArrayList类内部方法
  1. public class ArrayList {
  2. private Object[] array;
  3. private int size;
  4. public void add(Object e) {...}
  5. public void remove(int index) {...}
  6. public Object get(int index) {...}
  7. }


如果将上述ArrayList存储为String,Integer等类型,需要强制转换,容易出错。

为了解决这个问题,我们必须把ArrayList变成一种模板:ArrayList,代码如下:

  1. public class ArrayList<T> {
  2. private T[] array;
  3. private int size;
  4. public void add(T e) {...}
  5. public void remove(int index) {...}
  6. public T get(int index) {...}
  7. }
  1. 泛型就是定义一种模板,编写模板代码来适应任意类型。例如ArrayList,然后在代码中为用到的类创建对应的ArrayList<类型>
  1. ArrayList<String> strList = new ArrayList<String>();
  1. 在Java标准库中的ArrayList实现了List接口,它可以向上转型为List 。但不能把ArrayList<Integer>向上转型为ArrayList<Number>List<Number>

使用泛型

  1. 使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是Object,没有发挥泛型的优势。```java List list = new ArrayList(); list.add(“Hello”); list.add(“World”); String first = (String) list.get(0); String second = (String) list.get(1);

    1. <br />当我们定义泛型类型`<String>`后,`List<T>`的泛型接口变为强类型`List<String>`:```java
    2. // 无编译器警告:
    3. List<String> list = new ArrayList<String>();
    4. list.add("Hello");
    5. list.add("World");
    6. // 无强制转型:
    7. String first = list.get(0);
    8. String second = list.get(1);
  2. 编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型。 如:java List<Number> list = new ArrayList<Number>();

  3. 泛型接口
    除了ArrayList使用了泛型,还可以在接口中使用泛型。例如,Arrays.sort(Object[])可以对任意数组进行排序,但待排序的元素必须实现Comparable这个泛型接口:```java public interface Comparable { /**

    • 返回-1: 当前实例比参数o小
    • 返回0: 当前实例与参数o相等
    • 返回1: 当前实例比参数o大 */ int compareTo(T o); } java public class Main { public static void main(String[] args) { Person[] ps = new Person[] {
       new Person("Bob", 61),
       new Person("Alice", 88),
       new Person("Lily", 75),
      
      }; Arrays.sort(ps); System.out.println(Arrays.toString(ps)); } }

class Person implements Comparable { String name; int score; Person(String name, int score) { this.name = name; this.score = score; } public int compareTo(Person other) { return this.name.compareTo(other.name); } public String toString() { return this.name + “,” + this.score; } }

输出: [Alice,88, Bob,61, Lily,75]

<a name="ed7e82a4"></a>
# 编写泛型

   1. 编写泛型步骤<br />(1)按照某种类型,如String,来编写类```java
public class Pair {
    private String first;
    private String last;
    public Pair(String first, String last) {
        this.first = first;
        this.last = last;
    }
    public String getFirst() {
        return first;
    }
    public String getLast() {
        return last;
    }
}


(2)标记所有的特征类型
(3)把特定类型String替换成T,并申明```java public class Pair { private T first; private T last; public Pair(T first, T last) { this.first = first; this.last = last; } public T getFirst() { return first; } public T getLast() { return last; } }


4. **静态方法**<br />编写泛型类时,要特别注意,泛型类型`<T>`不能用于静态方法。```java
public class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() { ... }
    public T getLast() { ... }

    // 静态泛型方法应该使用其他类型区分:
    public static <K> Pair<K> create(K first, K last) {
        return new Pair<K>(first, last);
    }
}
  1. 多个泛型类型```java public class Pair

Pair p = new Pair<>(“test”, 123);




<a name="0780da18"></a>
# 擦拭法

1.Java语言的泛型实现方式是擦拭法(Type Erasure)。所谓擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。

例如,我们编写了一个泛型类`Pair`,这是编译器看到的代码:<br />
```java
public class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}


而虚拟机根本不知道泛型。这是虚拟机执行的代码:

public class Pair {
    private Object first;
    private Object last;
    public Pair(Object first, Object last) {
        this.first = first;
        this.last = last;
    }
    public Object getFirst() {
        return first;
    }
    public Object getLast() {
        return last;
    }
}


因此,Java使用擦拭法实现泛型,导致了:

  • 编译器把类型<T>视为Object
  • 编译器根据<T>实现安全的强制转型。


使用泛型的时候,我们编写的代码也是编译器看到的代码, 而虚拟机执行的代码并没有泛型。

2.Java泛型的局限性

  • 不能是基本类型,如int
  • 无法取得带泛型的class
Pair<String> p1 = new Pair<>("Hello", "world");
Pair<Integer> p2 = new Pair<>(123, 456);
Class c1 = p1.getClass();
Class c2 = p2.getClass();
System.out.println(c1==c2); // true
System.out.println(c1==Pair.class); // true
  • 无法判断带泛型的class
Pair<Integer> p = new Pair<>(123, 456);
// Compile error:
if (p instanceof Pair<String>.class) {
}


原因和前面一样,并不存在Pair.class,而是只有唯一的Pair.class

  • 不能实例化类型
    要实例化T类型,我们必须借助额外的Class参数:```java public class Pair { private T first; private T last; public Pair(Class clazz) {
      first = clazz.newInstance();
      last = clazz.newInstance();
    
    } }
    <br />上述代码借助`Class`参数并通过反射来实例化`T`类型,使用的时候,也必须传入`Class`。例如:```java
    Pair<String> pair = new Pair<>(String.class);
    

    因为传入了Class的实例,所以我们借助String.class就可以实例化String类型。

extends通配符

1.泛型的继承关系:Pair不是Pair的子类, 使用Pair<? extends Number>使得方法接收所有泛型类型为NumberNumber子类的Pair类型。

 public class Main { 

 public static void main(String[] args) {
        Pair<Integer> p = new Pair<>(123, 456);
        int n = add(p);
        System.out.println(n);
    }

     //Pair<? extends Number> p
    static int add(Pair<? extends Number> p) {
        Number first = p.getFirst();
        Number last = p.getLast();
        return first.intValue() + last.intValue();
    }

}

class Pair<T> {
    private T first;
    private T last;
    public Pair(T first, T last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() {
        return first;
    }
    public T getLast() {
        return last;
    }
}


2.使用<? extends Number> 的泛型定义称之为上界通配符 ,即把泛型类型T的上界限定在Number了。

3.extends通配符作用:只可读,不可写

  • 允许调用get()方法获取Integer的引用;
  • 不允许调用set(? extends Integer)方法并传入任何Integer的引用(null除外)。


4.也可使用extends限定T类型,比如public class Pair<T extends Number> { ... },泛型类型就限定为Number以及Number的子类。

super通配符

  1. 注意到Pair<? super Integer>表示,方法参数接受所有泛型类型为Integer或Integer父类的Pair类型。```java public static void main(String[] args) {

     Pair<Number> p1 = new Pair<>(12.3, 4.56);
     Pair<Integer> p2 = new Pair<>(123, 456);
     setSame(p1, 100);
     setSame(p2, 200);
     System.out.println(p1.getFirst() + ", " + p1.getLast());
     System.out.println(p2.getFirst() + ", " + p2.getLast());
    

    }

    static void setSame(Pair<? super Integer> p, Integer n) { p.setFirst(n); p.setLast(n); } ```


2.<? super Integer> 不允许调用get()方法获得Integer的引用。 唯一例外是可以获取Object的引用:Object o = p.getFirst()

3.对比extends跟super通配符

作为方法参数,<? extends T>类型和<? super Integer>类型的区别在于:

  • <? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);
  • <? super Integer>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。


一个是允许读不允许写,另一个是允许写不允许读。

4.PECS原则

我们可以用PECS原则:Producer Extends Consumer Super。

即:如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。

Collectionscopy()方法为例:

public class Collections {
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i=0; i<src.size(); i++) {
            T t = src.get(i); // src是producer
            dest.add(t); // dest是consumer
        }
    }
}


需要返回Tsrc是生产者,因此声明为List,需要写入Tdest是消费者,因此声明为List

5.无限定通配符

因为<?>通配符既没有extends,也没有super,因此:

  • 不允许调用set(T)方法并传入引用(null除外);
  • 不允许调用T get()方法并获取T引用(只能获取Object引用)。


换句话说,既不能读,也不能写,那只能做一些null判断:

static boolean isNull(Pair<?> p) {
    return p.getFirst() == null || p.getLast() == null;
}


<?>通配符很少使用,有一个独特的特点,就是:Pair<?>是所有Pair<T>的超类。