如何自定義泛型
考慮我們要實現(xiàn)了一個節(jié)點對象,這個對象可以自定義類型,我們可以用泛型語法進行如下的定義:
package Generic;
public class Node<T> {
private T value;
Node<T> next;
T getValue() {
return value;
}
void setValue(T value) {
this.value = value;
}
public static void main(String[] args) {
Node<String> first = new Node<String>();
first.setValue("Justin");
first.next = new Node<String>();
first.next.setValue("momor");
}
}
同樣,在定義接口的時候,也可以使用泛型,例如iterator接口就是泛型定義的
package java.util;
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
自定義泛型的邊界
在定義泛型的時候,可以定義泛型的邊界,例如下面的例子
class Animal {}
class Human extends Animal {}
class Toy {}
class Duck<T extends Animal> {}
public class Main {
public static void main(String[] args) {
Duck<Animal> ad = new Duck<Animal>();
Duck<Human> hd = new Duck<Human>();
Duck<Toy> hd = new Duck<Toy>(); // 編譯錯誤
}
}
在這個例子中,使用extends定義指定泛型的真正的形態(tài)的時候,必須是animal的子類,你可以使用animal與human來指定形態(tài),但不可以使用toy來指定,因為toy不是animal的子類。
下面舉一個快速排序的例子來說明:
class Sort {
public void quick(int[] number) {
sort(number, 0, number.length-1);
}
private void sort(int[] number, int left, int right) {
if(left < right) {
int q = partition(number, left, right);
sort(number, left, q-1);
sort(number, q+1, right);
}
}
private int partition(int number[], int left, int right) {
int i = left - 1;
for(int j = left; j < right; j++) {
if(number[j] <= number[right]) {
i++;
swap(number, i, j);
}
}
swap(number, i+1, right);
return i+1;
}
private void swap(int[] number, int i, int j) {
int t = number[i];
number[i] = number[j];
number[j] = t;
}
}
在這個例子中,使用的是int寫死的類型,為了讓這個排序算法更為通用,我們可以使用泛型,但要求是該形態(tài)必須具有可比較的對象大小的方法,一個方法就是要求排序的對象實例化[java.lang.Comparable<T>]
class Sort<T extends Comparable<T>> {
void quick(T[] array) {
sort(array, 0, array.length-1);
}
private void sort(T[] array, int left, int right) {
if(left < right) {
int q = partition(array, left, right);
sort(array, left, q-1);
sort(array, q+1, right);
}
}
private int partition(T[] array, int left, int right) {
int i = left - 1;
for(int j = left; j < right; j++) {
if(array[j].compareTo(array[right]) <= 0) {
i++;
swap(array, i, j);
}
}
swap(array, i+1, right);
return i + 1;
}
private void swap(T[] array, int i, int j) {
T t = array[i];
array[i] = array[j];
array[j] = t;
}
}
若extends可以指定多個類和接口,想再指定其它接口,可以使用&連接。例如:
public class Some<T extends Iterable<T> & Comparable<T>> {
...
}
共變性,逆變性
假設(shè)我們定義了下列這種類別:
class Node<T> {
T value;
Node<T> next;
Node(T value, Node<T> next) {
this.value = value;
this.next = next;
}
}
在下面的例子中:
class Fruit {}
class Apple extends Fruit {
@Override
public String toString() {
return "Apple";
}
}
class Banana extends Fruit {
@Override
public String toString() {
return "Banana";
}
}
public class Main {
public static void main(String[] args) {
Node<Apple> apple = new Node<Apple>(new Apple(), null);
Node<Fruit> fruit = apple; // 編譯錯誤,incompatible types
}
}
在上面的例子中,apple的形態(tài)是Node<Apple>,而fruit的類型是Node<fruit>,我們將apple所指向的對象給fruit,那么Node<Apple>是否應(yīng)該是一種Node<Fruit>呢?編譯器告訴我們不是的。
在泛型中,如果B是A的子類,而Node<B>被視為一種Node<A>類型,就稱Node具有共變形(Covariance),反過來,如果Node<A>被視為一種Node<B>形態(tài),則成為具有逆變性(Contravariance),如果不具有共變形或者逆變性,則稱其是不可變的。
Java中的泛型不支持共變形和逆變性,不過可以使用通配字符?與extends或者super
來宣告達到類似的共變形和逆變性。如下面的例子:
public class Main {
public static void main(String[] args) {
Node<Apple> apple = new Node<Apple>(new Apple(), null);
Node<? extends Fruit> fruit = apple; // 類似共變性效果
}
}
一個實際應(yīng)用的例子是:
public class Main {
public static void main(String[] args) {
Node<Apple> apple1 = new Node<Apple>(new Apple(), null);
Node<Apple> apple2 = new Node<Apple>(new Apple(), apple1);
Node<Apple> apple3 = new Node<Apple>(new Apple(), apple2);
Node<Banana> banana1 = new Node<Banana>(new Banana(), null);
Node<Banana> banana2 = new Node<Banana>(new Banana(), banana1);
show(apple3);
show(banana2);
}
static void show(Node<? extends Fruit> n) {
Node<? extends Fruit> node = n;
do {
System.out.println(node.value);
node = node.next;
} while(node != null);
}
}
你的目的是可以顯示所有的水果節(jié)點,由於show()方法使用型態(tài)通配字元宣告參數(shù),使得n具備類似共變性的效果,因此show()方法就可以顯示Node<Apple>也可以顯示Node<Banana>。
Java的泛型亦不支援逆變性,不過可以使用型態(tài)通配字元?與super來宣告變數(shù),使其達到類似逆變性,例如:
我們的目的是可以顯示所有水果的節(jié)點,由于show方法使用通配字符宣告共變形,所以show方法既可以顯示apple,也能顯示banana。
java的泛型不支持逆變性,不過可以使用通配字符super來宣告逆變性,如下面的例子:
class Fruit {
int price;
int weight;
Fruit(int price, int weight) {
this.price = price;
this.weight = weight;
}
}
class Apple extends Fruit {
Apple(int price, int weight) {
super(price, weight);
}
}
class Banana extends Fruit {
Banana(int price, int weight) {
super(price, weight);
}
}
interface Comparator<T> {
int compare(T t1, T t2);
}
class Basket<T> {
private T[] things;
Basket(T... things) {
this.things = things;
}
void sort(Comparator<? super T> comparator) {
// 作一些排序
}
}
public class Main {
public static void main(String[] args) {
Comparator<Fruit> comparator = new Comparator<Fruit>() {
public int compare(Fruit f1, Fruit f2) {
return f1.price - f2.price;
}
};
Basket<Apple> b1 = new Basket<Apple>(
new Apple(20, 100), new Apple(25, 150));
Basket<Banana> b2 = new Basket<Banana>(
new Banana(30, 200), new Banana(25, 250));
b1.sort(comparator);
b2.sort(comparator);
}
}
泛型對象的比較
如果我們需要重寫泛型對象的equal方法,我們可能會這么寫:
import java.util.*;
class Basket<T> {
T[] things;
Basket(T... things) {
this.things = things;
}
@Override
public boolean equals(Object o) {
if(o instanceof Basket<T>) { // 編譯錯誤
Basket that = (Basket) o;
return Arrays.deepEquals(this.things, that.things);
}
return false;
}
}
但如果我們編譯這個程序,我們會發(fā)現(xiàn)如下的錯誤:
illegal generic type for instanceof
if(o instanceof Basket<T>) {
在 程式中instanceof對Basket<T>的型態(tài)判斷是不合法的,因為Java的泛型所採用的是型態(tài)抹除,也就是說,程式中泛型語法的 型態(tài)指定,僅提供編譯器使用,執(zhí)行時期無法獲型態(tài)資訊,因而instanceof在執(zhí)行時期比對時,僅能針對Basket型態(tài)比對,無法針對當中的泛型實 際型態(tài)進行比對。
如果想要通過編譯,可以使用型態(tài)通配字元?:
在程序中對Basket<T>的類型的判斷是不合法的,因為java泛型采用的是類型擦除,也就是說,在程序中泛型語法的類型指定,僅給編譯器使用,執(zhí)行時無法獲取類型的信息,因而instanceOf在執(zhí)行器對比時,僅能根據(jù)basket類型進行對比,無法針對當眾的泛型實際的類型進行對比
如果想要通過編譯,就要使用通配符?:
import java.util.*;
class Basket<T> {
T[] things;
Basket(T... things) {
this.things = things;
}
@Override
public boolean equals(Object o) {
if(o instanceof Basket<?>) {
Basket that = (Basket) o;
return Arrays.deepEquals(this.things, that.things);
}
return false;
}
}
我們可以來測試一下,這樣寫的效果:
public class Main {
public static void main(String[] args) {
Basket<Integer> b1 = new Basket<Integer>(1, 2);
Basket<Integer> b2 = new Basket<Integer>(1, 2);
Basket<Integer> b3 = new Basket<Integer>(2, 2);
Basket<String> b4 = new Basket<String>("1", "2");
System.out.println(b1.equals(b2)); // true
System.out.println(b1.equals(b3)); // false
System.out.println(b1.equals(b4)); // false
}
}
好像不錯,可以正確的比較,但我們看下面的例子:
public class Main {
public static void main(String[] args) {
Basket<String> b1 = new Basket<String>();
Basket<Integer> b2 = new Basket<Integer>();
System.out.println(b1.equals(b2)); // true
}
}
Basket<Integer>與Basket<String>本來應(yīng)該是被看作為不同的類型的,顯然比較的結(jié)果應(yīng)該為不相等,但實際上,由于java采用類型擦除的方式,結(jié)果就是在這種情況下,空對象的相等的,因為還沒有塞值進去。
public class Main {
public static void main(String[] args) {
List<Integer> l1 = new ArrayList<Integer>();
List<String> l2 = new ArrayList<String>();
System.out.println(l1.equals(l2)); // true
}
}
java中是這么理解的,l1,l2都是空串,那么他們不就是相等的么,這就是采取類型擦除的結(jié)果。