java泛型,怎么这么难
2017-02-11 11:18
197 查看
泛型,就是参数化类型。好吧,这是我抄的定义,自己都觉得难以理解。还是举个简单例子吧。
看见没,就是类型不确定,使用参数进行表示,那这么写有什么好处呢?我们继续讨论。
看上面代码,我们发现T和直接用object类型没什么两样。仔细看,发现没,sj.getT()和sj1.getT()的返回值能够自动进行正确的类型转换。我们知道,所有的类型转换都是在运行期进行,如果出错,会抛出类型转换异常:ClassCastException,而使用泛型则不会,我们看下编译后的汇编:
我们进行有泛型和没泛型编码的对比:
从上面可以看出,这是编译器对代码的优化,帮助进行类型转换,人会犯错,而编译器则不允许(不然谁用)。编译器既然能够进行正确的类型转换,那么必然的知道正确的类型,机制就是类型声明时<>中的具体类型信息。
由此看来,就编译期类型检查这一项的便利,就有足够的理由让我们使用泛型了。
通过代码 ,我们可以看见List的构造会报错,如果不用泛型进行实例化,却又能正常运行,这是为什么呢?大家还记得不,前部分讲得,泛型的重要特性:编译期类型检查,如果上面的list能够成功实例化,那么由于List的参数化类型,任何对象都可以加入正确的加入到list中,而实际我们想要的却是Integer类型,从而破坏泛型的安全机制,所以,不变性换句话说,就是同一个类的不同参数化类型造成了不同的类型,直白点就是,List< A >,List< B >是完全不同的类型。有代码可知,数组是另一种行为,协变性,从代码中可以看到,是非常不安全的。
由此,我们有理由认为:非必要的场景(如:性能需要极致等),都不应该使用数组,而应该用list集合代替。
由于泛型的不变,使的有些情况,变的非常的复杂,举个例子:
看上面的fn方法,应该是我们编程的规范吧,接口与实现分离,利用接口进行编程。然而,泛型的不变性,使的list< B >类型,也就是接口的具体实现的泛型无法使用。难道使用泛型后,就只能编写处理具体类的代码了吗?可想而知,肯定不是,不能对接口进行编程,那泛型也太失败了,如何解决呢?通过限制的通配符,看下面代码:
我们利用< ? extends A >和< ? super A >进行泛型边界的限定,进行安全控制同时提升了泛型的灵活性,这到底是什么原因呢,看点简单点的代码,看下面:
现在,我们可以确定,变量的赋值操作,可以通配符,尤其是方法调用时候的赋值,可以提高编码的灵活性(不用面向具体的类),可以编写出更符合面向对象的代码。
同时为了保证类型安全,通配符有着严格的使用限制,例如:List< ? extends A >时,不允许写入除了null之外的任何类型,因为编译器并不能知道A的子类具体是哪个,为了类型安全考虑,不允许写入,使用List< ? super A >时,可以进行写入A的子类,因为A的子类肯定是A,但是取出时却会失去类型信息,编译器并不知道< ? super A >具体是哪个,唯一可以肯定的是Object。
这也是《Effective Java》中所说的生产者,消费者,有兴趣可以自行研读。
由上面的描述可以看出,java泛型中有着极其严格的限制,不过只要知道了,泛型使用的边界,也就不是太难了。
public class SimpleJava<T> { T t; public static void main(String[] args) { SimpleJava<Integer> sj = new SimpleJava<>(); sj.t = Integer.valueOf(1); } }
看见没,就是类型不确定,使用参数进行表示,那这么写有什么好处呢?我们继续讨论。
为什么使用泛型?
我们看段代码,来感受下泛型的便利。public class SimpleJava<T> { private T t; public SimpleJava(T t) { this.t = t; } public T getT() { return t; } public static void main(String[] args) { SimpleJava<Integer> sj = new SimpleJava<Integer>(1); Integer result = sj.getT(); SimpleJava<String> sj1 = new SimpleJava<String>("a"); String result1 = sj1.getT(); } }
看上面代码,我们发现T和直接用object类型没什么两样。仔细看,发现没,sj.getT()和sj1.getT()的返回值能够自动进行正确的类型转换。我们知道,所有的类型转换都是在运行期进行,如果出错,会抛出类型转换异常:ClassCastException,而使用泛型则不会,我们看下编译后的汇编:
public static void main(java.lang.String[]); Code: 0: new #1 // class com/esy/rice/tool/simple/SimpleJava 3: dup 4: iconst_1 5: invokestatic #29 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 8: invokespecial #35 // Method "<init>":(Ljava/lang/Object;)V 11: astore_1 12: aload_1 13: invokevirtual #37 // Method getT:()Ljava/lang/Object; //返回的还是Object 16: checkcast #30 // class java/lang/Integer //编译器自动插入类型转换指令 19: astore_2 20: new #1 // class com/esy/rice/tool/simple/SimpleJava 23: dup 24: ldc #39 // String a 26: invokespecial #35 // Method "<init>":(Ljava/lang/Object;)V 29: astore_3 30: aload_3 31: invokevirtual #37 // Method getT:()Ljava/lang/Object;//返回的还是Object 34: checkcast #41 // class java/lang/String //编译器自动插入类型转换指令 37: astore 4 39: return } }
我们进行有泛型和没泛型编码的对比:
public class SimpleJava { public static void main(String[] args) { List list = new ArrayList(); list.add(1); list.add("2"); List<Integer> list2 = new ArrayList<Integer>(); list2.add(1); list2.add("2"); //编译期会直接报错 } }
从上面可以看出,这是编译器对代码的优化,帮助进行类型转换,人会犯错,而编译器则不允许(不然谁用)。编译器既然能够进行正确的类型转换,那么必然的知道正确的类型,机制就是类型声明时<>中的具体类型信息。
由此看来,就编译期类型检查这一项的便利,就有足够的理由让我们使用泛型了。
泛型的不变性
不变性?说出来有些唬人,什么叫不变呢?来个比较,可能更明了,看代码:public static void main(String[] args) { List<Object> list = new ArrayList<Integer>(); //语法错误,编译器报错,泛型不变 Object[] arr = new Integer[10]; //正常 arr[0] = "aa"; //运行期报错ArrayStoreException }
通过代码 ,我们可以看见List的构造会报错,如果不用泛型进行实例化,却又能正常运行,这是为什么呢?大家还记得不,前部分讲得,泛型的重要特性:编译期类型检查,如果上面的list能够成功实例化,那么由于List的参数化类型,任何对象都可以加入正确的加入到list中,而实际我们想要的却是Integer类型,从而破坏泛型的安全机制,所以,不变性换句话说,就是同一个类的不同参数化类型造成了不同的类型,直白点就是,List< A >,List< B >是完全不同的类型。有代码可知,数组是另一种行为,协变性,从代码中可以看到,是非常不安全的。
由此,我们有理由认为:非必要的场景(如:性能需要极致等),都不应该使用数组,而应该用list集合代替。
由于泛型的不变,使的有些情况,变的非常的复杂,举个例子:
interface A {} class B implements A {} public class SimpleJava{ static void fn(List<A> list) { System.out.println(list); } public static void main(String[] args) { List<B> list = new ArrayList<B>(); // f(list); //直接语法报错 } }
看上面的fn方法,应该是我们编程的规范吧,接口与实现分离,利用接口进行编程。然而,泛型的不变性,使的list< B >类型,也就是接口的具体实现的泛型无法使用。难道使用泛型后,就只能编写处理具体类的代码了吗?可想而知,肯定不是,不能对接口进行编程,那泛型也太失败了,如何解决呢?通过限制的通配符,看下面代码:
interface A {} class B implements A {} public class SimpleJava{ static void fn(List<? extends A> list) { for (A a : list) { } } static void fn1(List<? super A> list) { list.add(new B()); } public static void main(String[] args) { List<B> list = new ArrayList<B>(); fn(list); List<A> list1 = new ArrayList<A>(); fn1(list1); } }
我们利用< ? extends A >和< ? super A >进行泛型边界的限定,进行安全控制同时提升了泛型的灵活性,这到底是什么原因呢,看点简单点的代码,看下面:
interface A {} class B implements A {} public class SimpleJava{ public static void main(String[] args) { List<? extends A> list = new ArrayList<B>(); //合法,通配符特性 // list.add(new B());//非法,因为泛型的不变性<? extends A>并不一定是<B>这种固定的类型 List<? super A> list2 = new ArrayList<A>(); ////合法,通配符特性 list2.add(new B());//合法,因为继承关系,B肯定是A类型, } }
现在,我们可以确定,变量的赋值操作,可以通配符,尤其是方法调用时候的赋值,可以提高编码的灵活性(不用面向具体的类),可以编写出更符合面向对象的代码。
同时为了保证类型安全,通配符有着严格的使用限制,例如:List< ? extends A >时,不允许写入除了null之外的任何类型,因为编译器并不能知道A的子类具体是哪个,为了类型安全考虑,不允许写入,使用List< ? super A >时,可以进行写入A的子类,因为A的子类肯定是A,但是取出时却会失去类型信息,编译器并不知道< ? super A >具体是哪个,唯一可以肯定的是Object。
这也是《Effective Java》中所说的生产者,消费者,有兴趣可以自行研读。
由上面的描述可以看出,java泛型中有着极其严格的限制,不过只要知道了,泛型使用的边界,也就不是太难了。
相关文章推荐
- csdn 技术这么差,怎么还有人敢用啊
- proxool使用起来怎么这么麻烦?
- 怎么也没想到这么轻易就患上了传说中的“鼠标病”
- 怎么java的代码看着这么恶心~~?
- 都是搜索,差距怎么这么大呢,坑爹啊!!
- 我来告诉你怎么计算i++ + ++i + i++以及为什么i++和++i区别这么大?
- 老外怎么这么聪明呢,开发出这么好的网站。
- 王宝强深夜公布妻子出轨经纪人,微博互撕,贵圈怎么这么乱?
- 又一天的学习生活,今天CSDN怎么这么差总是出错
- 有这么一项技术,据说可能取代云计算,你怎么看?
- 安卓模拟器这么慢,大家都怎么调试的?
- java泛型是如何工作的,为什么泛型这么重要
- 大哥你需求里说只要工作流引擎组件[行政审批流程组件],怎么真正需要的东西这么....悲剧了,客户需求无止境.........
- 生活啊,怎么这么烦啊?
- 群消息这么复杂,怎么能做到不丢不重?
- 又看见定时器了,怎么这么想写了~~
- HTML跟C语言怎么这么难啊 天啊
- 计算机学院怎么这么特殊呢!!!!
- 怎么这么懒
- 大数据这么火,怎么学大数据开发?