【泛型(generic)】
这里将用一个经典案例(根据山的名字或高度进行排序并输出)引入泛型;还要着重学习
sort()
如何利用泛型实现排序的
① 我们先来以ArrayList
为例,看看使用泛型的类是如何声明的,该类的方法又是如何利用泛型的?
public class ArrayList<E> extends AbstractList<E> implements List<E> ... {
public boolean add(E o) {...}
// 更多代码
}
Ⅰ 我们给ArrayList指定类型时候(ArrayList<Song>
) ,所有的E
(包括声明与方法中的)都会被编译器替换成Song
因此E
在某种语境下可以称为形参
Ⅱ <E>
和<T>
都是比较常见的,字母而已;与集合有关时习惯用E
(Element),其他就用T
(Type)
② 先来看这个方法的声明:
public void doThing(ArrayList<Animal> list) {}
那么我们可以传入的参数,只能是ArrayList<Animal>,这就局限了
public <T extends Animal> void doThing(ArrayList<T> list) {}
这种声明形式奇怪但强大:你可以传入ArrayList<Dog> , ArrayList<Cat> , ArrayList<Animal>
③ 下面是重点:sort()方法解析
想要可以排序,必须满足下面两种的任意一种:
❶ 该类内部实现Comparable接口并存在compareTo()方法
❷ 利用一个外部的自制比较器comparator并在比较器内部实现compare()方法
下面给出这两种方式的写法:
// method1
class Mountain implements Comparable<Mountain> { // Mountain类实现Comparable接口
String name; // 山的名字
int height; // 山的高度
public int compareTo(Mountain m) { // 存在compareTo方法
return name.compareTo(m.name); // 这里的compareTo()是字符串的方法
}
//...
}
// method2
class Mountain { // 类本身不需要实现Comparable接口
String name;
int height;
}
class myComparator implements Comparator<Mountain> { // 存在一个内部实现了compare方法的Comparator自定义比较器
public int compare(Mountain one, Mountain other) {
return one.name.compareTo(other.name); // 依旧利用的是字符串本身的comapreTo方法
}
}
一个经典案例:将山峰分别按名称和高度进行sort()后输出
// SorMountains.java文件
class Mountain {
String name;
int height;
public Mountain(String n, int h) { // 构造函数
name = n;
height = h;
}
public String toString() { // 重写toString()方法,来管理输出
return name + " " + height;
}
}
public class SortMountains {
LinkedList<Mountain> mtn = new LinkedList<>();
class NameComparator implements Comparator<Mountain> { //
public int compare(Mountain one, Mountain two) {
return one.name.compareTo(two.name);
}
}
class HeightComparator implements Comparator<Mountain>{
public int compare(Mountain one, Mountain two) {
return two.height - one.height; // 注意顺序(高度从高到低)
}
}
public void go() {
mtn.add(new Mountain("Qomolangma", 8844));
mtn.add(new Mountain("K2", 8611));
mtn.add(new Mountain("Lhotse", 8516));
NameComparator nc = new NameComparator(); // 实例化一个自定义构造器
Collections.sort(mtn, nc); // 给sort的重载方法传入构造器对象
System.out.print("Order by name :" + mtn);
HeightComparator hc = new HeightComparator();
Collections.sort(mtn, hc);
System.out.println("Order by height: " + mtn);
}
public static void main(String[] args) {
new SortMountains().go();
}
}
④ 我们应该从更高的维度去理解关于泛型问题 :
为什么这节讨论的是泛型,却又着重取讲如何重写如何实现诸如排序这样的问题呢?
实际上,我们无时无刻不在使用着集合与泛型:ArrayList<Integer>
, HashSet<String>
, HashMap<Integer, String>
我们自然而然又对其使用着诸多API,list.add()
, queue.peek()
, Collections.sort()
可是,当我们传入的泛型形参是其他的类时,比如ArrayList<Mountain>
, LinkedList<Song>
许多API都失效了!
String,Integer这些类都实现了比如Comparable这样的接口,也拥有hashCode这样的方法——这是可以调用API的前提
我们学习泛型,一个重点就是,如何让这些Mountain,Song这些自定义类也能使用API
这当然是通过实现某些接口,补充某些方法,重写某些方法实现的
⑤ 思路就是上面这样。下面回到泛型。
如果一个方法的的形参为func(ArrayList<Animal>){}
,那么便只能传入ArrayList<Animal>;ArrayList<Dog> 和 ArrayList<Cat>是不允许传入的
这点与数组是不同的:
如果是fun(Animal[]){}
,那么ArrayList<Animal>,ArrayList<Dog> 和 ArrayList<Cat>都可以作为实参传入
⑥ 这种对形参的严格类型检查可以保证集合绝对的安全性
上面已经提到过如何让这个传参不再那么死板,这里再给出一种等价的写法(使用万用字符):
public <T extends Animal> void doSomething(ArrayList<T> list) {}
public void doSomething(ArrayList<? extends Animal> list) {}
为了保证集合类型的安全性,编译器会阻止任何修改传入的集合的元素的行为(比如add);只允许调用
☀ 《Head First Java》Kathy Sierra & Bert Bates