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

【effective java读书笔记】泛型

一、泛型擦除的概念

例:代码一:

package com.generic;

public class Pair<U, V> {

    U first;
    V second;
    
    public Pair(U first, V second){
        this.first = first;
        this.second = second;
    }
    
    public U getFirst() {
        return first;
    }

    public V getSecond() {
        return second;
    }

	@Override
	public String toString() {
		return "Pair [first=" + first + ", second=" + second + "]";
	}
    
}
代码二:

package com.generic;

public class GenericDemo {
	public static void main(String[] args) {
		Pair<Integer,String> kv = new Pair<Integer,String>(1,"bobo");
		System.out.println(kv.toString());
	}
}
javac编译后,阅读.class文件如下:编译器将泛型(Pair<Integer,String>)编译后转换成原生态类型(本例中Pair即原生态类型)。jvm执行的时候并没有泛型的概念,泛型已经被擦除。

package com.generic;

import java.io.PrintStream;

public class GenericDemo
{
  public static void main(String[] paramArrayOfString)
  {
    Pair localPair = new Pair(Integer.valueOf(1), "bobo");
    System.out.println(localPair.toString());
  }
}

ok,理解了擦除的概念,那么问题就来了,

<1>既然编译器最终得到的是原生态类型,那么我们为什么不直接就用原生态类型?帮编译器省事些不更好么?

泛型的作用一:可以告诉编译器每个集合中接受哪些类型,使程序更加安全清楚。编译时即可发现错误。将运行时错误提前到编译时发现并处理。

<2>什么情况必须用原生态类型而不能用泛型?需要使用类文字的时候,例如DataBean.class。反射时多用到类似用法。

总之,除了以上必须使用原生态类型的时候,都使用泛型就好。


二、列表优先数组:(数组是协变的)

协变(这个词汇就理解为数组的一个特性吧,虽然这个特性不知道能做什么)。

例:在一个Long数组中添加了一个String对象;编译不报错!(如下第一行代码就是协变,与向上转型区分开)

Object[] objs = new Long[1];
		objs[0] = "i am string";
		System.out.println(objs[0]);
运行时提示:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.String
	at com.generic.GenericDemo.main(GenericDemo.java:17)

当然我们可以让它在编译时就报错:

Long[] objs = new Long[1];
		objs[0] = "i am string";
		System.out.println(objs[0]);
第2行就提示错误:cannot convert string to long

例:在向上转型的容器类中,编译时就提示错误。

List<Long> objstrs = new ArrayList<Long>();
		objstrs.add("i am strings");


三、类泛型改造

用原生态类型类写一个栈:

public class Stack {
	private Object[] elements;
	private int size = 0;
	private static final int DEFAULT_CAPACITY=16;
	public Stack(){
		elements = new Object[DEFAULT_CAPACITY];
	}
	
	public void push(Object e){
		ensureCapacity();
		elements[size++] = e;
		System.out.println("栈的大小"+size);
	}
	
	private void ensureCapacity() {
		// TOD确保大小自增
		if (elements.length==size) {
			elements = Arrays.copyOf(elements, 2*size+1);
			System.out.println("栈的长度"+elements.length);
		}
	}

	public Object pop(){
		if(isEmpty()){
			throw new EmptyStackException();
		}
		Object result = elements[--size];
		System.out.println("栈的大小"+size);
		//弹出的元素置空
		elements[size] = null;
		return result;
	}

	public boolean isEmpty() {
		// TODO Auto-generated method stub
		return size==0;
	}
	
}
说明:

使用Object数组。push任何类型都可以。并且可以混用。混用举例如下。

public static void main(String[] args) {
		Stack stack = new Stack();
		String str2 = "bobo";
		//压入字符串
		stack.push(str2);
		//压入数值
		stack.push(10010);
		while (!stack.isEmpty()) {
			String strpop = (String) stack.pop();
			//将栈中弹出数据大小写转换
			System.out.println(strpop.toUpperCase());
		}
	}
这种用法push进去是完全没有问题的。编译也不会报错。但是当pop出来的时候,如果不做任何处理的pop也不会有错误。

但是一般来说,我们都会对它进行处理。比如类型转换。由于没有约束,很可能就给其他使用者一种兼容所有类型的假象。然而运行时,却会提示错误如下:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
	at com.generic.TestStack.main(TestStack.java:14)
当然,我们确实可以去兼容它(通过instance of)。但是实际生活中我们很少去这样做。例如:

public static void main(String[] args) {
		Stack stack = new Stack();
		String str2 = "bobo";
		//压入字符串
		stack.push(str2);
		//压入数值
		stack.push(10010);
		while (!stack.isEmpty()) {
			Object obj = stack.pop();
			//对弹出对象判断所属是否字符串
			if (obj instanceof String) {
				//将栈中弹出数据大小写转换
				System.out.println(((String)obj).toUpperCase());
			}
			//对弹出对象判断所属是否整数
			if(obj instanceof Integer){
				System.out.println(obj);
			}
		}
	}
我们更常用的是对stack栈进行泛型约束:这样做就人性化许多,编译时报多少错误都无所谓,只要改了就好。但是运行时错误,如果一旦发生,可能就是线上用户反馈而来的。可想而知。孰轻孰重。
public static void main(String[] args) {
		 Stack2<String> stk = new Stack2<>();
		 stk.push("haibobo");
		 //编译时就提示不允许压入其他类型,此处编译错误
		 stk.push(10001);
		 while (!stk.isEmpty()) {
		 System.out.println(stk.pop().toUpperCase());
		 }
	}
对栈的泛型改造如下:(基于上一版本各种注解即改造的位置,不再赘叙)

//添加类的泛型约束
public class Stack2<E> {
	//泛型定义类的数组
	private E[] elements;
	private int size = 0;
	private static final int DEFAULT_CAPACITY=16;
	@SuppressWarnings("unchecked")
	public Stack2(){
		//此处由于数组必须是具体的某种类型,需要强转
		elements = (E[]) new Object[DEFAULT_CAPACITY];
	}
	
	//push压入泛型类的元素
	public void push(E e){
		ensureCapacity();
		elements[size++] = e;
		System.out.println("栈的大小"+size);
	}
	
	private void ensureCapacity() {
		// TOD确保大小自增
		if (elements.length==size) {
			elements = Arrays.copyOf(elements, 2*size+1);
			System.out.println("栈的长度"+elements.length);
		}
	}

	//弹出泛型的结果集
	public E pop(){
		if(isEmpty()){
			throw new EmptyStackException();
		}
		E result = elements[--size];
		System.out.println("栈的大小"+size);
		//弹出的元素置空
		elements[size] = null;
		return result;
	}

	public boolean isEmpty() {
		// TODO Auto-generated method stub
		return size==0;
	}
}

四、泛型方法改造

原生态类型的方法如下:此方法的作用:将两个Set集合的内容整合成一个。当然这个方法,在如今的开发环境下,例如eclipse下都会有警告,也就是通常意义上讲的编译警告。

public static Set union(Set s1,Set s2){
		Set result = new HashSet(s1);
		result.addAll(s2);
		return result;
	}

至于警告的原因,此处我的理解应该还是因为没有约束容易导致多种类型联合的警告。例如:

public static void main(String[] args) {
		Set<String> strs1 = new HashSet<>(Arrays.asList("Tom","Dick","Harry")); 
		Set<Integer> strs2 = new HashSet<>(Arrays.asList(1001,1002,1003)); 
		Set result = union(strs1, strs2);
		System.out.println(result);
	}
得到结果如下:

[Tom, Harry, 1001, 1002, 1003, Dick]

这样子不加约束的放在一起,放进去容易,想要取出来用的时候,肯定是要操碎心的,毕竟各种类型的数据,你需要做各种类型的处理,但,如果你是给别人提供方法,你觉得别人真的能理解你么?背锅侠还是少做好。还是通过约束分开吧。
改造后方法如下:

public static <E> Set<E> union(Set<E> s1,Set<E> s2){
		Set<E> result = new HashSet<E>(s1);
		result.addAll(s2);
		return result;
	}

这样子,任何人使用的时候,都会遵循约束条件了:你把你的约束交给了编译器,谁敢不服?

public static void main(String[] args) {
		Set<String> strs1 = new HashSet<>(Arrays.asList("Tom","Dick","Harry")); 
		Set<Integer> strs2 = new HashSet<>(Arrays.asList(1111,2222,3333)); 
		//这么使用,编译器提示错误
		Set<String> result = union(strs1, strs2);
		System.out.println(result);
	}
来,只能这么用:

public static void main(String[] args) {
		Set<String> strs1 = new HashSet<>(Arrays.asList("Tom","Dick","Harry")); 
		Set<Integer> strs2 = new HashSet<>(Arrays.asList(1111,2222,3333)); 
		//这么使用,编译器提示错误
		Set<String> result = union(strs1, strs2);
		System.out.println(result);
	}
告诉他,结果集也是全部都是字符串结果集,放心大胆的用吧!

[Moe, Tom, Harry, Larry, Curly, Dick]


五、泛型接口的改造

查看一个普通的接口。这样一个接口自身也是无任何问题的。那么问题在哪呢?也就是如果是一个针对各种类型的处理,相同逻辑的接口,那么接口必须写多个。例如:string如下,Integer肯定也得新写,这就失去了写接口的初衷了。

public interface UnaryFunction<String> {
	String apply(String arg);
}
代码一:改造如下:

public interface UnaryFunction<T> {
	T apply(T arg);
}
代码二:然后写一个单例模式对象:

private static UnaryFunction<Object> IDENTITY_FUNCTION = new UnaryFunction<Object>() {
		@Override
		public Object apply(Object arg) {
			return arg;
		}
	};
代码三:返回获得这个单例对象的方法(由于单例对象是通过Object实现的,通过它接口的类型对它进行转型):

public static <T> UnaryFunction<T> identityFunction(){
		return (UnaryFunction<T>) IDENTITY_FUNCTION;
	}
使用这个单例模式获取对象主方法如下:

public static void main(String[] args) {
		String[] strings = {"jute","hemp","nylon"};
		UnaryFunction<String> sameString = identityFunction();
		for (String s:strings) {
			System.out.println(sameString.apply(s));
		}
	}
第3行代码可见:调用代码三的identityFunction方法,返回参数String,根据类型推导,得到接口UnaryFunction即代码一也同时约束为String。其中apply方法也受到String约束,例如本例改为sameString.apply(1001);则会提示整形不能cast为String类型。

总结为也就是一个类型推导的概念。比如该接口使用泛型,并没有传递进一个String参数,但是通过返回值UnaryFunction<String>对应接口中
(UnaryFunction<T>) IDENTITY_FUNCTION,推导出其中参数为String。然后对整个接口T即为String类型,通过String类型约束其他(包括apply(T arg))方法。





















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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值