《Head First Java》读书笔记(伍)

泛型(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) {}

那么我们可以传入的参数,只能是ArraList<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都失效了!

StringInteger这些类都实现了比如Comparable这样的接口,也拥有hashCode这样的方法——这是可以调用API的前提

我们学习泛型,一个重点就是,如何让这些Mountain,Song这些自定义类也能使用API

这当然是通过实现某些接口补充某些方法重写某些方法实现的
 
 
⑤ 思路就是上面这样。下面回到泛型。

如果一个方法的的形参为func(ArrayList<Animal>){},那么便只能传入ArrayList<Animal>;ArrayList<Cat> 和 ArrayList<Cat>是不允许传入的

这点与数组是不同的:

如果是fun(Animal[]){},那么ArrayList<Animal>,ArrayList<Cat> 和 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

猜你喜欢

转载自blog.csdn.net/m0_46202073/article/details/106487700