• 什么是泛型。
  • Java标准库提供的ArrayList内部就是一个Object[]数组,配合存储一个当前分配的长度,就可以充当“可变数组”。
public class ArrayList {
    private Object[] array;
    private int size;
    public void add(Object e) {...}
    public void remove(int index) {...}
    public Object get(int index) {...}
}
  • 如果用上述ArrayList存储String类型,会有这么几个缺点:
  • 需要强制转型;
  • 不方便,易出错。
  • 代码必须这么写:
ArrayList list = new ArrayList();
list.add("Hello");
// 获取到Object,必须强制转型为String:
String first = (String) list.get(0);

很容易出现ClassCastException,因为容易“误转型”。

list.add(new Integer(123));
// ERROR: ClassCastException:
String second = (String) list.get(1);

要解决上述问题,我们可以为String单独编写一种ArrayList。

public class StringArrayList {
    private String[] array;
    private int size;
    public void add(String e) {...}
    public void remove(int index) {...}
    public String get(int index) {...}
}

这样一来,存入的必须是String,取出的也一定是String,不需要强制转型,因为编译器会强制检查放入的类型。

StringArrayList list = new StringArrayList();
list.add("Hello");
String first = list.get(0);
// 编译错误: 不允许放入非String类型:
list.add(new Integer(123));

问题暂时解决。然而,新的问题是,如果要存储Integer,还需要为Integer单独编写一种ArrayList。实际上,还需要为其他所有class单独编写一种ArrayList。

这是不可能的,JDK的class就有上千个,而且它还不知道其他人编写的class。

为了解决新的问题,我们必须把ArrayList变成一种模板:ArrayList<T>

public class ArrayList<T> {
    private T[] array;
    private int size;
    public void add(T e) {...}
    public void remove(int index) {...}
    public T get(int index) {...}
}

T可以是任何class。这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList。

// 创建可以存储String的ArrayList:
ArrayList<String> strList = new ArrayList<String>();
// 创建可以存储Float的ArrayList:
ArrayList<Float> floatList = new ArrayList<Float>();
// 创建可以存储Person的ArrayList:
ArrayList<Person> personList = new ArrayList<Person>();

这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。

向上转型

  • 在Java标准库中的ArrayList<T>实现了List<T>接口,它可以向上转型为List<T>。
public class ArrayList<T> implements List<T> {
    ...
}

List<String> list = new ArrayList<String>();

类型ArrayList<T>可以向上转型为List<T>。不能把ArrayList<Integer>向上转型为ArrayList<Number>List<Number>。ArrayList<Integer>和ArrayList<Number>两者完全没有继承关系。

使用泛型

  • 使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是Object
  • 编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型。
// 可以省略后面的Number,编译器可以自动推断泛型类型:
List<Number> list = new ArrayList<>();

泛型接口

  • 除了ArrayList<T>使用了泛型,还可以在接口中使用泛型。例如,Arrays.sort(Object[])可以对任意数组进行排序,但待排序的元素必须实现Comparable<T>这个泛型接口。
public interface Comparable<T> {
    /**
     * 返回负数: 当前实例比参数o小
     * 返回0: 当前实例与参数o相等
     * 返回正数: 当前实例比参数o大
     */
    int compareTo(T o);
}

可以直接对String数组进行排序。

// sort
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
            String[] ss = new String[] { "Orange", "Apple", "Pear" };
            Arrays.sort(ss);
            System.out.println(Arrays.toString(ss));    
            }
}

这是因为String本身已经实现了Comparable<String>接口。如果换成我们自定义的Person类型试试。

1 // sort
 2 import java.util.Arrays;
 3 
 4 public class Main {
 5     public static void main(String[] args) {
 6         Person[] ps = new Person[] {
 7             new Person("Bob", 61),
 8             new Person("Alice", 88),
 9             new Person("Lily", 75),
10         };
11         Arrays.sort(ps);
12         System.out.println(Arrays.toString(ps));
13 
14     }
15 }
16 
17 class Person {
18     String name;
19     int score;
20     Person(String name, int score) {
21         this.name = name;
22         this.score = score;
23     }
24     public String toString() {
25         return this.name + "," + this.score;
26     }
27 }

运行程序,我们会得到ClassCastException,即无法将Person转型为Comparable。我们修改代码,让Person实现Comparable<T>接口。

1 // sort
 2 import java.util.Arrays;
 3 
 4 public class Main {
 5     public static void main(String[] args) {
 6         Person[] ps = new Person[] {
 7             new Person("Bob", 61),
 8             new Person("Alice", 88),
 9             new Person("Lily", 75),
10         };
11         Arrays.sort(ps);
12         System.out.println(Arrays.toString(ps));
13     }
14 }
15 class Person implements Comparable<Person> {
16     String name;
17     int score;
18     Person(String name, int score) {
19         this.name = name;
20         this.score = score;
21     }
22     public int compareTo(Person other) {
23         return this.name.compareTo(other.name);
24     }
25     public String toString() {
26         return this.name + "," + this.score;
27     }
28 }

运行上述代码,可以正确实现按name进行排序。

编写泛型

  • 通常来说,泛型类一般用在集合类中,例如ArrayList<T>,我们很少需要编写泛型类。
  • 如果我们确实需要编写一个泛型类,可以按照以下步骤来编写一个泛型类。首先,按照某种类型,例如:String,来编写类。
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;
    }
}

然后,标记所有的特定类型,这里是String。

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;
    }
}

最后,把特定类型String替换为T,并申明<T>

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;
    }
}

熟练后即可直接从T开始编写。

静态方法

  • 编写泛型类时,要特别注意,泛型类型<T>不能用于静态方法。
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() { ... }

    // 对静态方法使用<T>:
    public static Pair<T> create(T first, T last) {
        return new Pair<T>(first, last);
    }
}

上述代码会导致编译错误,我们无法在静态方法create()的方法参数和返回类型上使用泛型类型T

  • 在网上搜索发现,可以在static修饰符后面加一个<T>,编译就能通过:
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 <T> Pair<T> create(T first, T last) {
        return new Pair<T>(first, last);
    }
}

但实际上,这个<T>Pair<T>类型的<T>已经没有任何关系了。

  • 对于静态方法,我们可以单独改写为“泛型”方法,只需要使用另一个类型即可。对于上面的create()静态方法,我们应该把它改为另一种泛型类型,例如,<K>
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);
    }
}

这样才能清楚地将静态方法的泛型类型和实例类型的泛型类型区分开。

多个泛型类型

  • 泛型还可以定义多种类型。例如,我们希望Pair不总是存储两个类型一样的对象,就可以使用类型<T, K>。
public class Pair<T, K> {
    private T first;
    private K last;
    public Pair(T first, K last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() { ... }
    public K getLast() { ... }
}

使用的时候,需要指出两种类型。

Pair<String, Integer> p = new Pair<>("test", 123);

Java标准库的Map<K, V>就是使用两种泛型类型的例子。它对Key使用一种类型,对Value使用另一种类型。