这两天看了晚上看了一些泛型的解释,现在脑子有点晕乎乎的。做个笔记梳理一下。
1.泛型使干什么用的。
为了集合的类型安全。
早期java没有泛型的时候每次取元素都是需要手动强转为某个具体类型的,而集合里面可能会有不同的类型存在,因此这种方式是很不安全的。
即使是现在你没有指定的话,依旧会有这个问题。
List list = new ArrayList();
list.add(new Integer(1));
list.add("123");
//未指定类型的情况下只能用Object类型接受,因为是最顶级父类。
Object integer = list.get(0);
//抛出 ClassNotFoundException
Integer integer1 = (Integer)list.get(1);
2.泛型类的使用
然后看泛型下面这段代码
List<Integer> list = new ArrayList();
list.add(new Integer(1));
//编译不通过
list.add("123");
//取出来不用强转,直接就是Integer类型
Integer integer = list.get(1);
泛型用<>包围,一般里面的称之为 泛型类型。因为引用的时候就指定了类型,所以后面的new编译器就帮我省去了一步,你可以不写。
如果你点ArrayList进去灰看到ArrayList是这样定义的
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
这里注意E表示的是元素Element,这里的并不是非要写E,如果自己定义的话甚至可以写A,B,C,D都可以。只不过为了见名知意。
你可以自己定义一个泛型尝试一下。
3.泛型方法的使用
定义1:明确定义
明确定义需要在访问修饰符之后,返回值之前。传入的参数是对象是引用。
public <T> void say(T t,int id){
System.out.println(t.getClass());
}
定义2:宽松定义
public void say(T t){
System.out.println(t.getClass());
}
这里会有一个地方需要注意一下,即是说泛型类与泛型方法有什么关系,或者说两者应该如何区分。
是这样的。
如果你明确定义这是一个方形方法(定义1)那么,他就和你的泛型类无关(言下之意即使两者的T是无关的。不要看成是一个东西)。
如果没有明确定义一个泛型方法(定义2)那么该方法就和泛型类的T绑定,也就是说T会替换为泛型类型。
看如下图片所示;
可以看到say方法的两个参数完全不一样,明确定义泛型方法它的参数就是T(运行时绑定类型参数)。
反观宽松定义它就会绑定你的泛型类 类型参数(Apple,编译时绑定参数)。
当调用宽松定义方法时传入和类型参数不一致参数时就会报错。
那么有人说我一定要添加进去可以么。这当然是可以的,你在Fruit方法中添加一个say方法参数为Fruit就可以了。
但是等等!不同类型放进去这不就和没泛型一样了么,你取出来的时候不是还是会有类型安全问题了么!
那么既要保证类型安全问题又要保证我可以放入不同的类型,这能办到么?答案是可以办到的。
4.? (未知类型)
泛型中有 未知类型 的概念,用 ? 来表示。
我们来看ArrayList中的addAll方法
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
这是ArrayList源码贴过来的。
我们来分析一下
Collection<? extends E>
这句代码。
首先参数为Collection
那么我们先就创建一个Collection类的实例
List<Fruit> fruits2 = new ArrayList<>();
这里我选择Collection的子类ArrayList,类型参数为Fruit。因为addAll是宽松泛型方法,所以依照我们之前所说。该方法E跟随泛型类。
那么E就被替换为了Fruit
Collection<? extends E> 就变为了 Collection<? extends Fruit>
口语化后面那句代码既是 Collection里面的位置类型继承了Fruit;
那么我们就再来创建两个集合;
List<Apple> apples3 = new ArrayList<>();//继承自Fruit
List<Paper> papers = new ArrayList<>();//papers没有继承Fruit
然后分别对这两个实力调用addAll方法,你会发现第二行代码编译不通过了!
这也是泛型中很多资料提到的边界中的上边界,即规定你的集合中能放入的不同类型的共同的最顶级父类。
既然有有上边界那当然是有下边的。
5.<? super T>
首先需要明确super这个东西在我看来至少和子类构造函数中的super这个东西是没有什么关系的。
<? super T> 这意思在我看来口语化即为:某个未知类型是T的父类或者说超类。而我T则是继承自未知类型,到底什么继承什么类型我不需要关心。
还是来看ArrayList中的源码分析。
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
这是jdk1.8中 Iterable<T>
接口新定义的方法。(题外话defult这个关键字是jdk1.8中的新特性,添加了这个关键字后接口中是可以写具体的实现代码的。方法在你的实现类中它并不是必须要被实现的,你的实现类调用defult修饰的方法后会自动调用接口中的实现。)
创建集合
List<Apple> apples3 = new ArrayList<>(); apples3.add(new Apple("红富士")); apples3.add(new Apple("水晶")); apples3.forEach(apple1 -> { if(apple1.getName().equals("红富士")){ System.out.println("处理红富士苹果的分支。。"); }else if(apple1.getName().equals("水晶")){ System.out.println("处理水晶苹果的分支。。"); } });
首先上面一段代码的T为宽松泛型方法,
那么froeach方法中T被替换为了Apple就成了这样 Consumer<? super Apple>
口语化即为Apple类实现了某个未知类型。
foreach中for循环action.accept(t) 这个t应该想来就是Apple的类型引用。因此第二段代码中的apple1就是指向了一个Apple实例。
当然你放入一个Apple的子类也是可以的。
这就是泛型边界中的下边界。
6.泛型集合子父关系。
类与类是有子父关系的。那么泛型集合之后是否还具有这种关系呢?答案是没有关系。
就像这样
List<Fruit> fruits = new ArrayList<>(); List<Apple> apples = new ArrayList<>(); //编译不通过 // fruits = apples;
编译器也不会让你放进上边界为Fruit之外的任何东西。
那么如何做可让集合之间具有子父关系呢。运用上面学到的。
告诉编译器继承关系就可以了。
List<Apple> apples2 = new ArrayList<Apple>(); //? extends 让泛型集合之间具有了子父相关性。 //List<Apple> 是 List<? extends Fruit> 的子类型 //这句话的意思就是虽然这个List集合我不知道是什么类型的,但是他肯定继承了Fruit。 List<? extends Fruit> a = apples2;
这是很像类型转换中的向上造型。这是这时候你会发现不能a.add()添加任何东西,因为从编译器的角度来说,不知道你这个集合中到底是什么类型,所以不能贸然的添加任何东西。否则还是会有类型安全问题(这是设计泛型的初衷)。你只告诉了它这个集合中的类型肯定继承了Fruit这一信息(上边界)。那么我非要添加呢。运用之前学到的告诉集合下边界就可以了。这是很像类型中的向下造型。List<Fruit> oranges = new ArrayList<Fruit>(); // ? super Orange Orange是某个未知类型(?)的子类,我们不知道父类的具体类型,但是Orange类必然是继承了这个父类,那么Orange类以及子类必然是可以和这个父类集合兼容的。 List<? super Orange> b = oranges; b.add(new Orange()); //取出的是Object因为不知道父类是什么,但是我们又可能不止放进一种类型。所以取出的时候必然是转换为了顶级父类Object; b.get(0);
说穿了泛型即为保证集合中每一个元素取出的时候可以得到真正的类型,因为集合放进一个元素之后他就不知道这个元素的类型了。所以只能在取出或者放入的时候作必要的一些限制,保证每一个元素取出的时候都是它真正的类型。7.类型擦除jdk1.5之前是没有泛型概念的,而java语言是向后兼容的,所以为了兼容老的应用程序。必须想一个完全的办法兼容两者。以前是没有泛型,那么我们在jvm底层依旧看成是没有泛型不就可以兼容以前的程序了。因此编译器编译成java字节码文件的时候是会把泛型的信息全部擦除掉,即是说它不认为存在泛型这个东西。List<Fruit>和List<Apple>在编译之后都是List。而你的泛型类 Fruit<T> 中的T它会替换为所有类的祖先类Object;这是需要注意的。关于擦除请看这边文章http://blog.youkuaiyun.com/caihaijiang/article/details/6403349