【effective java读书笔记】泛型(一)
来源:互联网 发布:js确认密码与密码验证 编辑:程序博客网 时间:2024/04/24 13:31
【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; }@Overridepublic 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.Stringat 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 stubreturn 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.Stringat 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 stubreturn 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>() {@Overridepublic 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))方法。
- 【effective java读书笔记】泛型(一)
- Effective Java 读书笔记(一)
- Effective Java读书笔记(一)
- Effective Java读书笔记(一)
- Effective Java读书笔记一
- effective java读书笔记一
- 《Effective Java》读书笔记一
- Effective java 读书笔记(一)
- Effective Java读书笔记一
- Effective Java读书笔记一
- 《effective java》读书笔记——(一)
- 【effective java读书笔记】枚举(一)
- 【effective Java读书笔记】注解(一)
- 【effective Java读书笔记】方法(一)
- 【effective java读书笔记】通用程序设计(一)
- Effective Java读书笔记一:并发
- 【读书笔记】《Effective Java》(4)--泛型
- 【effective java读书笔记】泛型(二)
- 欧几里德算法与扩展欧几里德算法
- ADO.Net对Oracle数据库的操作(转载:自备学习)
- 【R的网络提取】CSDN博客列表和url的提取
- Android中使用ViewStub提高布局性能
- json-lib 和 jackson 性能对比
- 【effective java读书笔记】泛型(一)
- jquery 全选和全不选合集
- cocos2d imageview 设置固定大小 不自适应图片大小
- apache与iis并行
- 一步一步学MySQL---18 MySQL常用函数(3)
- MSCI:无计划修改MSCI中国A股国际指数编制方法
- Setup LVS and Keepalived on Debian
- Linux内建命令(built-in)与外部命令
- 处理hdfs上错误的block块并修复