【effective java读书笔记】泛型(二)

【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> inits = new MyArrayList<>();
		inits.add(100);
		inits.add(24);
		numbers.addAll(inits);
最后一行代码编译器直接不允许添加MyArrayList<Integer>到MyArrayList<Number>。合理的约束。但是不合情。为什么呢?因为Integer是Number的子类。

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) {
	    for(int i=0; i<c.size; i++){
	        add(c.get(i));
	    }
	}
看上去区别不大。实际上还是有区别的。将泛型的类型利用通配符的特性写在参数上,显得更加形象。?extends E

如果按照生产者消费者模型,来看此处添加可以看作生产者(提供数据,比如此处,则为从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.add(100);
		inits.add(24);
		MyArrayList<Number> numbers = new MyArrayList<>();
		inits.copyTo(numbers);
然而,编译器直接在最后一行报错了。原因很简单,inits泛型是Integer类型的,输出到MyArrayList<Number>当然是不允许的。又回到了上一个包含的问题。可Integer是包含在Number中的,不纠结为什么不能,只看如何实现呢?

通过super E(与之前的extend E遥相呼应),通过super参数处理意思为E的某种超类的集合。对上copyTo代码修改为如下:

public void copyTo(MyArrayList<? super E> dest){
	    for(int i=0; i<size; i++){
	        dest.add(get(i));
	    }
	}
为什么此处为super呢,而不用extends呢。看看前后两个例子(例一,例二)的区别,前一个addall是父类主动add子类的每一项,父类需兼容子类,则使用任意项只需要继承于父类即可。使用? extend E;

后一项是子类主动拷贝到父类,则必须使用当前子类的所属父类才可以添加。使用? 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







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值