- 我发现我记得太过详细了,接下来因为有其他书要看,我决定记录的稍微简单一点。
——————–分割线——————-
泛型
通配符
- 关于通配符之前在类型信息那一块也提到过,下面就简单介绍一下。
//Type mismatch: cannot convert from ArrayList<String> to List<Object>
List<Object> list = new ArrayList<String>();
- 我们以为Object和String存在继承关系,故写出了上面的代码,殊不知其会导致编译错误,原因为何,看一下下面关于数组的例子。
Object[] objs = new String[10];
objs[0] = new Integer(0);
- 这两句代码是能通过编译的,但是执行的时候会抛CCE异常,因为objs的本质是String数组而不是Object数组。类比于上一个例子。如果我们能将一个泛型指定为String的对象赋给一个泛型指定为Object的引用,那么是否意味着list可以往其插入Integer数据?这势必会引起错误。
- 用白话描述一下,
ArrayList<String>
是一个将持有String类型(如果有子类当然也可以持有,不过它是final的)的集合,List<Object>
是一个将持有Object或其子类的集合。虽然他们持有的类型具有继承关系或者说包含关系,但是他们本身没有这种继承关系,也就不能使用向上转型的这种写法。 - 不过有一种类似的写法可以做到这种赋值,那就是使用通配符。
List<?> list = new ArrayList<String>();
List<? extends Object> list2 = new ArrayList<String>();
- 上面这两种写法在这里是一个效果,但是编译器对于他们的处理方式却稍有差异。在这里都是一个意思,即list持有的类型可以是Object或其任何子类型。听起来好像没有区别,但最大的区别就是它的持有类型是未知的。我们也可以使用super关键字:
List<? super String> list2 = new ArrayList<String>();
- 在这里通配符代表的就是String类型或其父类型。
- 接下里我们说一下这个未知是什么意思。
List<?> list = new ArrayList<String>();
list.add(new String());
list.add(new Object());
- 当你使用通配符后,任何形参类型为泛型的方法都无法正确调用了。啥意思?其实很好理解。list在编译器的认知里是一个持有Object或其子类的一种集合。那它到底是String、Integer、Object就无从得知。所以就不允许你直接认定泛型为String,因为这有可能导致错误。
- 相比较于extends,super的用法就稍微宽一点。
List<? super String> list = new ArrayList<String>();
list.add(new String());
- 因为list的类型不管是String的任何父类型,它都是能够接收String的,所以这么写就没有毛病。
- 既然使用通配符造成了这么大的限制,那是不是可以说它没什么实际意义呢?当然不是的。1、对于那些参数类型不包含泛型的方法,我们依然能够正确调用。2、有时能帮我们写出更通用的方法。
public class Solution {
public static void main(String args[]) {
Colle<String> str = new Colle<String>();
str.set(new String("123"));
Colle<Integer> i = new Colle<Integer>();
i.set(new Integer(1));
test(str);
test(i);
}
public static void test(Colle<?> test) {
System.out.println(test.get());
}
}
class Colle <T> {
T obj;
public T get() {
return obj;
}
public void set(T obj) {
this.obj = obj;
}
}
- 通过通配符,我们就能提取一个公共方法test(),这样无论其持有什么类型,我们都能够正确的调用get方法(set的确是无法使用了)。
- 其实关于不加泛型和
<?>
和<? extends Object>
还有一点点的区别,不过我觉得这种东西没必要那么过分深入研究(因为使用才是最好的锻炼),这里就省略了。也许我以后去读关于框架的书籍会重新研究一下这个。
自限定的泛型
public class Solution {
public static void main(String ar[]) {
Holder h1 = new Holder();
Holder h2 = new Holder();
h1.set(h2);
Holder h3 = h1.get();
}
}
class Holder extends Colle<Holder> {
}
class Colle <T> {
T obj;
public T get() {
return obj;
}
public void set(T obj) {
this.obj = obj;
}
}
动态类型安全
public class Solution {
public static void main(String ar[]) {
List list1 = new ArrayList<String>();
list1.add(new String("2"));
list1.add(new Integer(1));
List list2 = Collections.checkedList(new ArrayList<String>(), String.class);
list2.add(new String("2"));
}
}
- 由于类型擦除的原因,导致前三句执行时并不会报错。但是这并非我们所愿。因为list1本质是一个
ArrayList<String>
,我们自然是不希望它能够插入其他类型的。除了将List指定为List<String>
之外,还有上面这样的另外一种做法。第六句会抛出一个CCE异常。除了checkedList之外,Collections还有很多类似的方法,这里就不介绍了。
小结
- 其实关于泛型本章后面还有大量内容,由于时间原因,我只是粗读了一下,就没有再记下来了。有兴趣的可以去看原书。
- 其实泛型带来的好处是使代码可读性更高,而安全不过是其额外的副作用而已。
- 下面这个例子是我对泛型的一个理解总结。这是我在编写第十六章读后感时想到的。
public class People {
public static void main(String args[]) {
Holder<Integer> ho1 = new Holder<Integer>();
Holder<String> ho2 = new Holder<String>();
Holder ho11 = ho1;
Holder ho22 = ho2;
Holder<String> ho111 = ho11;
Holder<Integer> ho222 = ho22;
ho111.set(new String("1"));
ho222.set(new Integer(1));
System.out.println(ho111.get());
System.out.println(ho222.get());
}
}
class Holder <T> {
T obj;
T get() {
return obj;
}
void set(T obj) {
this.obj = obj;
}
}
----------------运行结果
1
1
- 你也许会以为
new Holder<Integer>()
和new Holder<String>()
是两种不同的类型。但是事实告诉我们,我们试图往前者插入String,不会导致任何错误。这是为什么呢?因为在Holder内部,它只认为泛型是它的擦除边界Object而已。而真正能够限制我们的编译期间ho111此刻认为的泛型。我们当然不会这么写代码,此处也是为了让我们深刻的理解泛型的作用。再次强调,在泛型类内部,所有的泛型类型是擦除边界!