【effective java读书笔记】泛型(二)
上篇讲了泛型的擦除、数组是协变的,泛型约束更安全、类泛型、方法泛型、接口泛型的运用 没看的可以去看看再读这个第二篇。
一、泛型上界
上一把写了一个简单的栈,这波就写个简单的泛型的ArrayList吧。代码就不多说了。随便看看就好,这都不是重点。
public class MyArrayList<E> {写完之后做个简单的测试:
private static int DEFAULT_CAPACITY = 10;
private int size = 0;
private E[] elements;
@SuppressWarnings("unchecked")
public MyArrayList() {
this.elements = (E[]) new Object[DEFAULT_CAPACITY];
}
private void ensureCapacity() {
// TOD确保大小自增
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
System.out.println("栈的长度增加" + elements.length);
}
}
public void add(E e) {
ensureCapacity();
elements[size++] = e;
}
public E get(int index) {
if (index >= size || index < 0) {
throw new IndexOutOfBoundsException();
}
return elements[index];
}
public int getSize() {
return size;
}
public void set(int index, E e) {
if (index >= size || index < 0) {
throw new IndexOutOfBoundsException();
}
elements[index] = e;
}
}
public static void main(String[] args) {执行结果:
MyArrayList<String> strs = new MyArrayList<>();
for (int i = 0; i < 10; i++) {
strs.add("bbb"+i);
}
System.out.println("-----1----");
for (int i = 0; i < 10; i++) {
strs.add("aaa"+i);
}
System.out.println("-----2----");
System.out.println("当前实际长度"+strs.getSize());
System.out.println("获得第8个位置"+strs.get(7));
System.out.println("获得第18个位置"+strs.get(17));
System.out.println("获得第21个位置"+strs.get(20));
//strs.set(7, "bobobobo");
//System.out.println("获得第8个位置"+strs.get(7));
}
-----1----
栈的长度增加21
-----2----
当前实际长度20
获得第8个位置bbb7
获得第18个位置aaa7
Exception in thread "main"java.lang.IndexOutOfBoundsException
at com.generic.MyArrayList.get(MyArrayList.java:30)
at com.generic.TestMyArrayList.main(TestMyArrayList.java:18)
一切正常,和预料的一样。
在MyArrayList中新增一个方法:
public void addAll(MyArrayList<E> c) {使用如下,发现这么一个问题:
for(int i=0; i<c.size; i++){
add(c.get(i));
}
}
MyArrayList<Number> numbers = new MyArrayList<>();最后一行代码编译器直接不允许添加MyArrayList<Integer>到MyArrayList<Number>。合理的约束。但是不合情。为什么呢?因为Integer是Number的子类。
MyArrayList<Integer> inits = new MyArrayList<>();
inits.add(100);
inits.add(24);
numbers.addAll(inits);
public final class Integer extends Number implements Comparable<Integer>那么我对新增的addAll方法进行修改如下:<T extends E>,E为T的上界。即可用泛型的上界父类最多为E,例如本例就是最多为Number,因此子类Interger是可以的。
public <T extends E> void addAll(MyArrayList<T> c) {
for(int i=0; i<c.size; i++){
add(c.get(i));
}
}
执行结果就不看了。完全可行。
二、通配符提升API灵活性
当然,到此处,还未到结束的时候,我们还可以这么写这个代码: 此处?为无限定通配符
举个例子,例一:
public void addAll(MyArrayList<? extends E> c) {看上去区别不大。实际上还是有区别的。将泛型的类型利用通配符的特性写在参数上,显得更加形象。?extends E
for(int i=0; i<c.size; i++){
add(c.get(i));
}
}
如果按照生产者消费者模型,来看此处添加可以看作生产者(提供数据,比如此处,则为从c中取出数据放入当前自定义数组中),那么消费者如何写呢。比如需要取出某个对象的时候,如何通过这种类似的方式取出?
举个例子,例二:
写一个拷贝代码:作用将一个数组copy到另一个自定义可变长数组中。
public void copyTo(DynamicArray<E> dest){使用一下试试:
for(int i=0; i<size; i++){
dest.add(get(i));
}
}
MyArrayList<Integer> inits = new MyArrayList<>();然而,编译器直接在最后一行报错了。原因很简单,inits泛型是Integer类型的,输出到MyArrayList<Number>当然是不允许的。又回到了上一个包含的问题。可Integer是包含在Number中的,不纠结为什么不能,只看如何实现呢?
inits.add(100);
inits.add(24);
MyArrayList<Number> numbers = new MyArrayList<>();
inits.copyTo(numbers);
通过super E(与之前的extend E遥相呼应),通过super参数处理意思为E的某种超类的集合。对上copyTo代码修改为如下:
public void copyTo(MyArrayList<? super E> dest){为什么此处为super呢,而不用extends呢。看看前后两个例子(例一,例二)的区别,前一个addall是父类主动add子类的每一项,父类需兼容子类,则使用任意项只需要继承于父类即可。使用? extend E;
for(int i=0; i<size; i++){
dest.add(get(i));
}
}
后一项是子类主动拷贝到父类,则必须使用当前子类的所属父类才可以添加。使用? super E;
如果我这个理解方式比较困难的话,可以考虑按照effective java的理解方式,生产者提供给消费者,用extend;消费者接受生产者,用super。
but通配符并不是万能的。
此种通配符写法为只能读不能写。(例如List<?>就只能读,不能写):例如下面的例子,两行set方法均无法通过编译。因为不知道arr为什么类型,如果写入不同类型,比如先读一个integer类型,再读入一个string类型,由于?的类型本身未能够具体化,则类型不再安全,因为不知道其本身究竟是什么类型。
public static void swap(MyArrayList<?> arr, int i, int j){
Object tmp = arr.get(i);
arr.set(i, arr.get(j));
arr.set(j, tmp);
}
但这么写是可行的:
public static <T> void swap2(MyArrayList<T> arr, int i, int j){
T tmp = arr.get(i);
arr.set(i, arr.get(j));
arr.set(j, tmp);
}
因此就出现了一个这样的写法:先确定泛型类型,再进行处理。
public static void swap(MyArrayList<?> arr, int i, int j){
swap2(arr, i, j);
}
public static <T> void swap2(MyArrayList<T> arr, int i, int j){
T tmp = arr.get(i);
arr.set(i, arr.get(j));
arr.set(j, tmp);
}
三、类型安全的异构容器
泛型常用于集合、及ThreadLocal等。后续我会详细分析这部分源码:
书中有这样一个异构的例子:作用用来将不同类型的对象存入Map中。通过泛型作出约束。例如,存入String,取出String。
public class Favorites {使用如下:
private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();
public <T> void putFavorite(Class<T> type,T instance){
if (type == null) {
throw new NullPointerException("Type is null");
}
favorites.put(type, instance);
}
public <T> T getFavorite(Class<T> type){
return type.cast(favorites.get(type));
}
}
public static void main(String[] args) {结果如下:
// TODO Auto-generated method stub
Favorites favorites = new Favorites();
favorites.putFavorite(String.class, "Java");
favorites.putFavorite(Integer.class, 0xcafebabe);
favorites.putFavorite(Class.class,Favorites.class );
System.out.println(favorites.getFavorite(String.class)+favorites.getFavorite(Integer.class)+favorites.getFavorite(Class.class).getName());
}
Java-889275714com.generic.Favorites