Java - 泛型的理解2

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呢?我得去问问老师。
就先写到

    原文作者:Richardo92
    原文地址: https://www.jianshu.com/p/d1251c81daa7#comments
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞