为了解决新的问题,我们必须把ArrayList变成一种模板:ArrayList,代码如下:
:::info
publicclass ArrayList { private T[] array; privateint size; publicvoid add(T e) {…} publicvoid remove(int index) {…} public T get(int index) {…} }
:::
T可以是任何class。这样一来,我们就实现了:编写一次模版,可以创建任意类型的ArrayList:
:::info
// 创建可以存储String的ArrayList: ArrayList strList = new ArrayList(); // 创建可以存储Float的 ArrayList: ArrayList floatList = new ArrayList(); // 创建可以存储Person的 ArrayList: ArrayList personList = new ArrayList();
:::
因此,泛型就是定义一种模板,例如ArrayList,然后在代码中为用到的类创建对应的ArrayList<类型>:
:::info
ArrayList strList = new ArrayList();
:::
由编译器针对类型作检查:
:::info
strList.add(“hello”); // OK String s = strList.get(0); // OK strList.add(new Integer(123)); // compile error! Integer n = strList.get(0); // compile error!
:::
这样一来,既实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型。
向上转型
在Java标准库中的ArrayList实现了List接口,它可以向上转型为List:
:::info
publicclass ArrayList implements List { … } List list = new ArrayList();
:::
即类型ArrayList可以向上转型为List。
使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是Object:
:::info
// 编译器警告: Listlist = new ArrayList(); list.add(“Hello”); list.add(“World”); String first = (String) list.get(0); String second = (String) list.get(1);
:::
此时,只能把当作Object使用,没有发挥泛型的优势。
当我们定义泛型类型后,List的泛型接口变为强类型List:
:::info
// 无编译器警告: Listlist = new ArrayList(); list.add(“Hello”); list.add(“World”); // 无强制转型: String first = list.get(0); String second = list.get(1);
:::
当我们定义泛型类型后,List的泛型接口变为强类型List:
:::info
Listlist = new ArrayList(); list.add(new Integer(123)); list.add(new Double(12.34)); Number first = list.get(0); Number second = list.get(1);
:::
编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型。例如,对于下面的代码:
:::info
List list = new ArrayList();
:::
编译器看到泛型类型List就可以自动推断出后面的ArrayList的泛型类型必须是ArrayList,因此,可以把代码简写为:
:::info
// 可以省略后面的Number,编译器可以自动推断泛型类型: List list = new ArrayList<>();
:::
泛型接口
除了ArrayList使用了泛型,还可以在接口中使用泛型。例如,Arrays.sort(Object[])可以对任意数组进行排序,但待排序的元素必须实现Comparable这个泛型接口:
:::info
publicinterface Comparable { /** * 返回负数: 当前实例比参数o小 * 返回0: 当前实例与参数o相等 * 返回正数: 当前实例比参数o大 */ int compareTo(T o); }
:::
可以直接对String数组进行排序:
:::info
// sort import java.util.Arrays; public class Main { public static void main(String[] args) {
:::
String[] ss =newString[]{"Orange","Apple","Pear"};
public class Main { public static void main(String[] args) {
:::
Person[] ps =newPerson[]{
newPerson("Bob",61),
newPerson("Alice",88),
newPerson("Lily",75),
};
Arrays.sort(ps);
System.out.println(Arrays.toString(ps));
:::info
} } class Person { String name; int score; Person(String name, int score) { this.name = name; this.score = score; } public String toString() { return this.name + “,” + this.score; } }
:::
运行程序,我们会得到ClassCastException,即无法将Person转型为Comparable。我们修改代码,让Person实现Comparable接口:
:::info
// sort import java.util.Arrays;
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)); } }
:::
classPersonimplementsComparable<Person>{
String name;
int score;
Person(String name,int score){
this.name = name;
this.score = score;
}
publicint compareTo(Person other){
returnthis.name.compareTo(other.name);
}
publicString toString(){
returnthis.name +","+this.score;
}
}
运行上述代码,可以正确实现按name进行排序。
也可以修改比较逻辑,例如,按score从高到低排序。请自行修改测试。
本章小结
使用泛型时,把泛型参数替换为需要的class类型,例如:ArrayList,ArrayList等; 可以省略编译器能自动推断出的类型,例如:List list = new ArrayList<>();; 不指定泛型参数类型时,编译器会给出警告,且只能将视为Object类型; 可以在接口中定义泛型类型,实现此接口的类必须实现正确的泛型类型。
首先,按照某种类型,例如:String,来编写类:
:::info
publicclass 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:
:::info
publicclass 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,并申明:
:::info
publicclass 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; } }
:::
熟练后即可直接从T开始编写。
静态方法
编写泛型类时,要特别注意,泛型类型不能用于静态方法。例如:
:::info
publicclass Pair { private T first; private T last; public Pair(T first, T last) { this.first = first; this.last = last; } public T getFirst() { … } public T getLast() { … } // 对静态方法使用: publicstatic Pair create(T first, T last) { returnnew Pair(first, last); } }
:::
上述代码会导致编译错误,我们无法在静态方法create()的方法参数和返回类型上使用泛型类型T。
有些同学在网上搜索发现,可以在static修饰符后面加一个,编译就能通过:
:::info
publicclass Pair { private T first; private T last; public Pair(T first, T last) { this.first = first; this.last = last; } public T getFirst() { … } public T getLast() { … } // 可以编译通过: publicstatic Pair create(T first, T last) { returnnew Pair(first, last); } }
:::
但实际上,这个和Pair类型的已经没有任何关系了。
对于静态方法,我们可以单独改写为“泛型”方法,只需要使用另一个类型即可。对于上面的create()静态方法,我们应该把它改为另一种泛型类型,例如,:
:::info
publicclass Pair { private T first; private T last; public Pair(T first, T last) { this.first = first; this.last = last; } public T getFirst() { … } public T getLast() { … } // 静态泛型方法应该使用其他类型区分: publicstatic Pair create(K first, K last) { returnnew Pair(first, last); } }
:::
这样才能清楚地将静态方法的泛型类型和实例类型的泛型类型区分开。
多个泛型类型
泛型还可以定义多种类型。例如,我们希望Pair不总是存储两个类型一样的对象,就可以使用类型:
:::info
publicclass Pair { private T first; private K last; public Pair(T first, K last) { this.first = first; this.last = last; } public T getFirst() { … } public K getLast() { … } }
:::
使用的时候,需要指出两种类型:
:::info
Pair p = new Pair<>(“test”, 123);
:::
Java标准库的Map就是使用两种泛型类型的例子。它对Key使用一种类型,对Value使用另一种类型。
例如,我们编写了一个泛型类Pair,这是编译器看到的代码:
:::info
publicclass 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; } }
:::
而虚拟机根本不知道泛型。这是虚拟机执行的代码:
:::info
publicclass 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使用擦拭法实现泛型,导致了:
编译器把类型视为Object;
编译器根据实现安全的强制转型。
使用泛型的时候,我们编写的代码也是编译器看到的代码:
:::info
Pair p = new Pair<>(“Hello”, “world”); String first = p.getFirst(); String last = p.getLast();
:::
而虚拟机执行的代码并没有泛型:
:::info
Pair p = new Pair(“Hello”, “world”); String first = (String) p.getFirst(); String last = (String) p.getLast();
:::
所以,Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。
了解了Java泛型的实现方式——擦拭法,我们就知道了Java泛型的局限:
局限一:不能是基本类型,例如int,因为实际类型是Object,Object类型无法持有基本类型:
:::info
Pair p = new Pair<>(1, 2); // compile error!
:::
局限二:无法取得带泛型的Class。观察以下代码:
:::info
public class Main { public static void main(String[] args) {
:::
Pair<String> p1 =newPair<>("Hello","world");
Pair<Integer> p2 =newPair<>(123,456);
Class c1 = p1.getClass();
Class c2 = p2.getClass();
System.out.println(c1==c2);// true
System.out.println(c1==Pair.class);// true
:::info
} } 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; } }
:::
因为T是Object,我们对Pair和Pair类型获取Class时,获取到的是同一个Class,也就是Pair类的Class。