泛型(Generics)
泛型(Generics)
從 Java 5 開始,增加了泛型技術
什麼是 泛型?
- 將類型變爲參數,提高代碼複用率
建議的類型參數名稱:
• T :Type
• E :Element
• K :Key
• N :Number
• V :Value
• S、U、V :2nd, 3rd, 4th types
泛型類型(Generic Type)
什麼是泛型類型?
- 使用了泛型的類或者接口
比如:java.util.Comparator
、java.util.Comparable
public class Student <T> {
private T score;
public T getScore() {
return score;
}
public void setScore() {
this.score = score;
}
}
// Java 7 以前的寫法
Student<String> stu = new Student<String>();
// Java 7開始, 可以省略右邊<>中的類型(鑽石語法)
Student<String> stu1 = new Student<>();
stu1.setScore("A");
String score1 = stu1.getScore();
Student<Double> stu2 = new Student<>();
stu2.setScore(98.5);
Double score2 = stu2.getScore();
多個類型參數
public class Student <N, S> {
private N no;
private S score;
public Student(N no, S score) {
this.no = no;
this.score = score;
}
}
Student<String, String> s1 = new Student<>("E9527", "A++");
Student<Integer, Double> s2 = new Student<>(17210224, 96.5);
泛型類型的繼承
如果沒有泛型類型的類之間有繼承關係,加了泛型類型後不一定會保持繼承關係。
例如 Integer
類繼承自 Number
,但是 Box<Integer>
不繼承 Box<Number>
只有泛型類型也相同時,原本有繼承關係的類會繼續保持繼承關係。
如果擁有多個泛型類型,第一個泛型類型與父類的泛型類型相同,即可保持繼承關係。
例如,下圖中 MyList<String, Object>
與 List<String>
保持了繼承關係。
原始類型(Raw Type)
泛型方法(Generic Method)
什麼是泛型方法?
- 使用了泛型的方法(實例方法、靜態方法、構造方法),比如
Arrays.sort(T[], Comparator<T>)
public class Main {
public static void main(String[] args) {
Student<String, String> s1 = new Student<>();
// 可以像下面這麼寫, 但一般不會寫的這麼麻煩
Main.<String, String>set(s1, "K99", "C++");
Student<Integer, Double> s2 = new Student<>();
// 編譯器可以自動推斷出類型參數的具體類型
set(s2, 25, 99.5);
}
// 泛型方法(靜態方法)
static <T1, T2> void set(Student<T1, T2> stu, T1 no, T2 score) {
stu.setNo(no);
stu.setScore(score);
}
}
泛型方法示例:
public class Box<E> {
private E element;
public Box() {}
public Box(E element) {
this.element = element;
}
}
public class Main {
public static void main(String[] args) {
List<Box<Integer>> boxes = new ArrayList<>();
addBox(11, boxes);
addBox(22, boxes);
addBox(33, boxes);
}
// 泛型方法(靜態方法)
static <T> void addBox(T element, List<Box<T>> boxes) {
Box<T> box = new Box<>(element);
boxes.add(box);
}
}
泛型方法 - 類型推斷
上圖是 jdk 中 Collections
類的源碼,可以看出 emptyList()
是泛型方法,可以自動推斷類型。
// 根據接收的類是 List<String> 推斷出了泛型是 String 類型
List<String> list1 = Collections.emptyList();
// 根據接收的類是 List<Integer> 推斷出了泛型是 Integer 類型
List<Integer> list2 = Collections.emptyList();
泛型方法 – 構造方法
限制類型參數
可以通過 extends
對類型參數增加一些限制條件,比如 <T extends A>
extends
後面可以跟上類名、接口名,代表 T 必須是 A 類型,或者繼承、實現 A
// <T extends Number> 表示傳入的類型參數必須是Numbe及其子類
public class Person<T extends Number> {
private T age;
public <E> Person(T age) {
this.age = age;
}
public int getAge() {
// 傳入null則視爲0, 否則轉爲int
return (age == null) ? 0 : age.intValue();
}
}
public static void main(String[] args) {
Person<Double> p1 = new Person<>(18.7);
System.out.println(p1.getAge()); // 18
Person<Integer> p2; // 正常運行, Integer是Number的子類
// Person<String> p3; // 會報錯, 因爲String不是Number的子類
}
- 可以同時添加多個限制,比如
<T extends A & B & C>
,代表 T 必須同時滿足 A、B、C,只能有一個類,但是可以有多個接口,類必須放在前面
限制類型參數 — 接收泛型數組返元素最大值方法
public class Main {
public static void main(String[] args) {
Double[] ds = {5.6, 3.4, 8.8, 4.6};
System.out.println(getMax(ds)); // 8.8
Integer[] is = {4, 19, 3, 28, 56};
System.out.println(getMax(is)); // 56
}
static <T extends Comparable<T>> T getMax(T[] array) {
if (array == null || array.length == 0) return null;
T max = array[0];
for (int i = 0; i < array.length; i++) {
if (array[i] == null) continue;
if (array[i].compareTo(max) <= 0) continue;
max = array[i];
}
return max;
}
}
限制類型參數 — 要求傳入的類型必須可比較
public class Student <T extends Comparable<T>> implements Comparable<Student<T>> {
private T score; // 傳入的 T 繼承了 Comparable<T>, 表示是可比較的
public Student(T score) {
this.score = score;
}
@Override
public int compareTo(Student<T> s) {
// 傳入若爲null, 必然比本身小
if (s == null) return 1;
// 若自身的score不爲null, 則調用compareTo與傳入的進行比較
if (score != null) return score.compareTo(s.score);
// 此時, 自身的score爲null, 若傳入的score也爲null則視爲相同, 否則就是傳入的大
return s.score == null ? 0 : -1;
}
@Override
public String toString() {
return "Student [score=" + score + "]";
}
}
public class Main {
public static void main(String[] args) {
Student<Integer>[] stus = new Student[3];
stus[0] = new Student<>(18);
stus[1] = new Student<>(38);
stus[2] = new Student<>(28);
// Student [score=38]
System.out.println(getMax(stus));
}
static <T extends Comparable<T>> T getMax(T[] array) {
if (array == null || array.length == 0) return null;
T max = array[0];
for (int i = 0; i < array.length; i++) {
if (array[i] == null) continue;
if (array[i].compareTo(max) <= 0) continue;
max = array[i];
}
return max;
}
}
通配符(Wildcards)
- 在泛型中,問號
?
被稱爲是通配符 - 通常用作變量類型、返回值類型的類型參數
- 不能用作泛型方法調用、泛型類型實例化、泛型類型定義的類型參數
通配符 — 上界(extends
)
- 可以通過
extends
設置類型參數的上界
基本使用:
// 類型參數必須是Number類型或者是Number的子類型
void testUpper(Box<? extends Number> box) {}
// Integer是Number的子類
Box<Integer> p1 = null;
testUpper(p1); // 可以
// Number類型可以作爲參數
Box<Number> p2 = null;
testUpper(p2); // 可以
// ? extends Number 表示參數是Number的子類
Box<? extends Number> p3 = null; // 可以
testUpper(p3);
// ? extends Integer 表示參數是Integer的子類, Integer是Number的子類
Box<? extends Integer> p4 = null; // 可以
testUpper(p4);
// String不是Number的子類, 傳入testUpeer會報錯
Box<String> p5 = null;
// testUpper(p5); // 報錯
示例:對泛型數組的元素求和方法(傳入的必須是Number的子類型)
public class Main {
public static void main(String[] args) {
List<Integer> is = Arrays.asList(1, 2, 3);
System.out.println(sum(is)); // 6.0
List<Double> ds = Arrays.asList(1.2, 2.3, 3.5);
System.out.println(sum(ds)); // 7.0
}
static double sum(List<? extends Number> list) {
double s= 0.0;
for (Number n : list) {
s += n.doubleValue();
}
return s;
}
}
通配符 — 下界(super
)
- 可以通過
super
設置類型參數的下界
基本使用:
// 類型參數必須是Integer類型或者是Integer的父類型
void testLower(Box<? super Integer> box) {
Box<Integer> p1 = null;
testLower(p1);
Box<Number> p2 = null;
testLower(p2);
Box<? super Integer> p3 = null;
testLower(p3);
Box<? super Number> p4 = null;
testLower(p4);
示例:往泛型數組中添加元素(傳入的必須是Integer的父類型)
public class Main {
public static void main(String[] args) {
List<Integer> is = new ArrayList<>();
addNumbers(is);
System.out.println(is); // [1, 2, 3, 4, 5]
List<Number> ns = new ArrayList<>();
addNumbers(ns);
System.out.println(ns); // [1, 2, 3, 4, 5]
}
static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 5; i++) {
list.add(i);
}
}
}
通配符 — 無限制
// 類型參數是什麼類型都可以
void test(Box<?> box) {}
Box<Integer> p1 = null;
Box<Integer> p2 = null;
Box<Integer> p3 = null;
Box<? extends Number> p4 = null;
Box<? super String> p5 = null;
Box<?> p6 = null;
test(p1);
test(p2);
test(p3);
test(p4);
test(p5);
test(p6);
示例:打印泛型數組的元素(無限制,任何類型都可以)
public class Main {
public static void main(String[] args) {
List<Integer> is = Arrays.asList(1, 2, 3);
printList(is); // 1 2 3
List<Double> ds = Arrays.asList(1.2, 2.3, 3.5);
printList(ds); // 1.2 2.3 3.5
}
static void printList(List<?> list) {
for (Object object : list) {
System.out.print(object + " ");
}
System.out.println();
}
}
通配符 – 繼承
無限制的絕對是最頂層的,其餘的按正常繼承關係理解即可。
通配符 — 注意點
泛型的使用限制
這裏列出一些常用的誤區,不必死記,過一遍理解即可。