http://www.lxweimin.com/p/7e3e2b898143
這是上次寫的泛型,當時其實還是一知半解。
今天再做個小總結,但也還是一知半解吧。
今天老師上課,講了很多泛型的東西。
generics
第一個東西,叫做 type parameter
其中有部分,我到現在還是不懂。
<T>void print(Collection<T> c) {
for (T x : c)
print("elem " + x);
}
至于這個,void 左邊的 <T>到底是干什么的,老師給我解釋了兩遍還是不能很理解。他的意思就是,這是一種規矩。
public class Demo<T>{
public T a;
public Demo(T k) {
a = k;
}
<T>void change(T k) {
a = k;
}
}
這樣是有編譯錯誤的。
如果把void 左邊<T>去掉就對了。
所以我當時做的結果是,如果這個函數是要修改自己的元素,那么不能加<T>,如果是要修改外面傳進來的元素,那么就加<T>,不會報錯。
比如,
<T> void change(T k) {
k = null;
}
但是老師說不是。他說我第一個為什么會錯呢?只要把<T>換個名字不和T重復就行了。我換了,果然就對了。為什么呢?下次office hour得去問下。
下面進入一個正題。
ArrayList<Object> b = new ArrayList<Object>();
b.add(5);
b.add("abc");
char a = (char) b.get(0);
編譯時不報錯,運行時報錯。
generics是編譯時可見,運行時擦除的。
所以編譯的時候,編譯器知道,b.get(0)返回的是一個Object類型,語法上可以被強制轉換成其他類型。于是就通過了。運行時,擦除了<T>的信息。于是,系統嘗試著將Object類型強制轉換成char。但是我們都知道,這個object對象的內存塊,本質是Integer。
所以,只能被強制轉換成Integer,不能被強制轉換成char。
于是報錯了。
char a = b.get(0);
編譯時報錯。
因為編譯時是知道generics的<T>的。所以一匹配,一邊是char,一邊是object,不匹配,直接編譯錯誤,static error
Integer a = b.get(0);
編譯錯誤,不能自動cast。這是超類轉子類,必須cast
Integer a = (Integer) b.get(0);
編譯通過,運行通過。
改一下,
ArrayList<Integer> b = new ArrayList<Integer>();
b.add(5);
Object a = b.get(0);
Object c = (Object) b.get(0);
都是對的。
子類轉超類,不需要cast,會自己轉。
然后是上篇文章討論的問題。
LinkedList<ArrayList<Integer>>[] a = new LinkedList<ArrayList<Integer>>[100];
這個在編譯時是無法通過的。
因為數組是運行時才會開始著手考慮元素類型的問題。但是這個時候<T>已經被擦除了,返回的內存可能不安全。于是Java提前在編譯時就把這個錯誤找了出來,提前規避這個風險。
但其實,我測試過,
int[] a = new int[5];
a[0] = "abc";
a[100] = 5;
第一個是編譯錯誤,第二個是運行錯誤。
也就是說,在編譯時,數組是可以知道,他的元素的種類的。
我還過另外一個說法。這里為什么會編譯錯誤,因為,Java的作者當時忘記寫這塊了。。。于是乎。。。
不管怎么樣,記得不能這么用吧。具體改造方法那篇文章里有。
其中,強制轉換是這樣的,
LinkedList<ArrayList<Integer>>[] c = (LinkedList<ArrayList<Integer>>[]) new LinkedList[100];
或者,采用ArrayList來做。
List<Object> a = new ArrayList<String>();
為什么是不對的?
List<String> stringList = new ArrayList<String>();
List<Object> objectList = stringList ;
objectList .add(new Integer(5));
String temp = stringList.get(0);
按道理,stringList.get(0); 應該返回一個String
但是這里返回的是一個Integer
為了增加安全性,Java同樣決定在編譯時,就提前規避這種風險,不讓這種東西可以被寫出來,可以通過編譯,可以運行。
但是可以這么改。
List<Object> objectList = (List<Object>) (List<?>) stringList;
先轉換成,一種不知道什么類型的類型。在轉換成object。
但是這是有警告的,而且如果你之前不知道所存的元素本質是什么,這么用是很危險的。
所以,<>所存放的東西,他們必須,完全一致。
List<List<Integer>> = new ArrayList<ArrayList<Integer>>(); // ERROR!
List<List<Integer>> = new ArrayList<List<Integer>>(); // RIGHT!
然后老師提到了這么一個運算符 <?>
LinkedList<?> means LinkedList<T> for some T
但是,之后這個鏈表返回的元素,只能用Object來接收,因為他自己都不知道,他返回的是什么東西。
Object o = llist.first();
還可以這么用,
LinkedList<? extends Puzzle> means LinkedList<T> for some T extends <Puzzle>
使用泛型的限制。
不能使用primitive types as T
比如, Collection<int> 錯誤!!!
cannot create a T like:
new T();
or
T[] a = new T[n];
因為在運行時會把編譯時知道的T信息擦除掉,所以不知道T到底是什么,不知道怎么申請什么樣的內存。
cannot inspect T at run time
like
instance of T
instance of Collection<String>
因為編譯時,不知道<T> 是String
最后說下,Comparator
這塊只是一直沒怎么學習過。
Comparator是一個interface
里面有兩個方法,
public class A implements Comparator<T> {
public int compare(T a, T b) {....}
}
因為傳入的T不知道類型,這是復雜版本,不知道怎么處理。
如果傳入的類知道類型,復雜的情況,就是不能簡單地用java系統方法,compareTo,就得自己寫一個compareTo去判斷大小。
簡單地話,傳進來的類自己實現了Comparable 接口,于是直接調用compareTo() 方法就行了。
如:
public class A implements Comparator<Integer> {
public int compare(Integer a, Integer b) {
if (a.compareTo(b) < 0)
return -1;
else if (a.compareTo(b) > 0)
return 1;
else
return 0;
}
}
然后在一個新類中,
public class B {
public void sort(int[] a, Comparator<Integer> q) {
for (int i = 1; i < a.length; i++) {
for (int j = i; j >= 1; j--) {
if (q.compare(a[j - 1], a[j]) > 0) {
int temp = a[j];
a[j] = a[j - 1];
a[j - 1] = temp; }
}
}
}
public static void main(String[] args) {
int[] array = {3, 4, 2, 6,19, 1};
B test = new B();
test.sort(array, new A());
}
}
就可以實現排序了。
Comparator 比較的是兩個數的大小,他的角度是,客觀的看到一串數,是旁觀者。
public int compare(T a, T b) {....}
Comparable 比較的是自己和另外一個數的大小,他的角度是,當局者,他只看到另外一個數。
public int compareTo(T other) {....}
他們都是接口。主要就是本身的意義不同。
然后還有個問題。
Arrays.sort(array, new A());
是報錯的,那么Arrays.sort()這個方法該如何使用comparator呢?我得去問問老師。
就先寫到