http://www.jianshu.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呢?我得去问问老师。
就先写到