Java 8 新特性之泛型的类型推导
1. 泛型究竟是什么?
在讨论类型推导(type inference)之前,必须回顾一下什么是泛型(Generic).泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。通俗点将就是“类型的变量”。这种类型变量可以用在类、接口和方法的创建中。理解Java泛型最简单的方法是把它看成一种便捷语法,能节省你某些Java类型转换(casting)上的操作:
List<Apple> box = new ArrayList<Apple>();box.add(new Apple());
Apple apple =box.get(0);
上面的代码自身已表达的很清楚:box是一个装有Apple对象的List。get方法返回一个Apple对象实例,这个过程不需要进行类型转换。没有泛型,上面的代码需要写成这样:
Apple apple = (Apple)box.get(0);
当然,泛型绝不像我在这里描述的这么简单,但这不是我们今天的主角,对于泛型还不是很明白的同学需要补课了~当然,最好的参考资料还是官方文档。
2. 泛型带来的问题(Java 7之前)
泛型的最大优点是提供了程序的类型安全同时可以向后兼容,但也有让开发者不爽的地方,就是每次定义时都要写明泛型的类型,这样显示指定不仅感觉有些冗长,最主要是很多程序员不熟悉泛型,因此很多时候不能够给出正确的类型参数,现在通过编译器自动推断泛型的参数类型,能够减少这样的情况,并提高代码可读性。
3. Java 7中对于泛型的类型推导方面的改进
在Java 7以前的版本中使用泛型类型,需要在声明并赋值的时候,两侧都加上泛型类型。比方说这样:
Map<String,Integer> map = new HashMap<String,Integer>();
很多人当初肯定和我一样,对此感到很不解:我在变量声明中不是已经声明了参数类型了吗?为什么在对象初始化的时候还要显示的写出来?这也是泛型在一开始出现的时候受到很多人吐槽的地方。不过,让人欣慰的是,java在进步的同时,那些设计者们也在不断的改进java的编译器,让它变的更加智能与人性化。这里,就是我们今天的主角:类型推倒...额...不是推倒,是类型推导,即type inference,这哥们儿的出现,再写上面这样的代码的时候,可以很开心地省略掉对象实例化时的参数类型,也就变成了这个样子:
Map<String,Integer> map = new HashMap<>();
在这条语句中,编译器会根据变量声明时的泛型类型自动推断出实例化HashMap时的泛型类型。再次提醒一定要注意new HashMap后面的“<>”,只有加上这个“<>”才表示是自动类型推断,否则就是非泛型类型的HashMap,并且在使用编译器编译源代码时会给出一个警告提示(unchecked conversion warning)。这一对尖括号"<>"官方文档中叫做"diamond"。
但是,这时候的类型推导做的并不完全(甚至算是一个半成品),因为在Java SE 7中创建泛型实例时的类型推断是有限制的:只有构造器的参数化类型在上下文中被显著的声明了,才可以使用类型推断,否则不行。例如:下面的例子在java 7无法正确编译(但现在在java8里面可以编译,因为根据方法参数来自动推断泛型的类型):
List<String> list = new ArrayList<>();
list.add("A");// 由于addAll期望获得Collection<? extends String>类型的参数,因此下面的语句无法通过
list.addAll(new ArrayList<>());
4. 在Java8中的再进化
在最新的java官方文档之中,我们可以看到对于类型推导的定义:
Type inference is a Java compiler's ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable. The inference algorithm determines the types of the arguments and, if available, the type that the result is being assigned, or returned. Finally, the inference algorithm tries to find the most specific type that works with all of the arguments.
简言之,类型推导也就是指编译器能够根据你调用的方法和相应的声明来确定需要的参数类型的能力。并且官方文档中还给出了一个例子加以诠释:
static <T> T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList<String>());
在这里,编译器能够推导出传入pick方法中的第二个参数的类型是Serializable的。
在之前的java版本当中,上面的例子要能够通过编译的话需要这要写:
Serializable s = this.<Serializable>pick("d", new ArrayList<String>());
这样写的详细原因可以在Bruce Eckel的java编程思想(第四版)的泛型一章看得到,当然这本书是基于java6的,这个版本还没有类型推导这个概念。看到这里,很多人已经明显能看得出来最新版本中类型推导的强力之处了。已经不仅仅局限于泛型类的声明与实例化过程了,而是延伸到了具有泛型参数的方法当中了。
4.1 类型推导和泛型方法(Type Inference and Generic Methods)
关于新版本中的类型推导和泛型方法,文档中还给了一个稍微复杂一点的例子,我在这里贴出来,原理和上面的Serializable例子都是一样就不再赘述,想巩固的可以再看一下:
public class BoxDemo {
public static <U> void addBox(U u,
java.util.List<Box<U>> boxes) {
Box<U> box = new Box<>();
box.set(u);
boxes.add(box);
}
public static <U> void outputBoxes(java.util.List<Box<U>> boxes) {
int counter = 0;
for (Box<U> box: boxes) {
U boxContents = box.get();
System.out.println("Box #" + counter + " contains [" +
boxContents.toString() + "]");
counter++;
}
}
public static void main(String[] args) {
java.util.ArrayList<Box<Integer>> listOfIntegerBoxes =
new java.util.ArrayList<>();
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes);
BoxDemo.outputBoxes(listOfIntegerBoxes);
}
}
上面这段代码输出为:
Box #0 contains [10]
Box #1 contains [20]
Box #2 contains [30]
提一下,泛型方法addBox重点就在于在新java版本中你不需要再在方法调用中进行显示的类型说明,像这样:
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
编译器能够从传入addBox中的参数自动推断出参数类型是Integer.
4.2 类型推导与泛型类和非泛型类的泛型构造器(Type Inference and Generic Constructors of Generic and Non-Generic Classes)
额...这个也许英语的更好断句一点:Type Inference and Generic Constructors of Generic and Non-Generic Classes
其实,泛型构造器并不是泛型类的专利品,非泛型类也完全可以有自己的泛型构造器,看一下这个例子:
class MyClass<X> {
<T> MyClass(T t) {
// ...
}
}
假如对 MyClass类做出下面这样的实例化:
new MyClass<Integer>("")
OK,这里我们显示地指出了MyClass的泛参类型X是Integer,而对于构造器,编译器根据传入的String对象("")推导出形式参数T是String,这个在java7版本之中已经实现了,在Java8中有了什么改进呢?在Java8之后,对于这种具有泛型构造器的泛型类的实例化我们可以这么写:
MyClass<Integer> myObject = new MyClass<>("");
对,还是这一对尖括号(<>),江湖人称diamond,这样我们的编译器就能够自动推导出形式参数X是Integer,T是String了。这个其实和我们一开始Map<String,String>的例子很像,只是多了个构造器的泛型化。
需要注意的是:类型推导只能根据调用的参数类型、目标类型(这个马上会讲到)和返回类型(如果有返回的话)进行推导,而不能根据程序后面的一些需求来进行推导。
4.3 目标类型(Target Type)
前文已经提到过,编译器能够根据目标类型进行类型推导。一个表达式的目标类型指的是一种编译器根据表达式出现的位置而需要的正确的数据类型。比如这个例子:
static <T> List<T> emptyList();
List<String> listOne = Collections.emptyList();
在这里,List<String>就是目标类型,因为这里需要的是List<String>,而Collections.emptyList()返回的是List<T>,所以这里编译器就推断T一定是String。这个在Java 7 和 8 中都OK。但是在java 7 中,在下面这种情况中就不能正常编译了:
void processStringList(List<String> stringList) {
// process stringList
}
processStringList(Collections.emptyList());
这个时候,java7就会给出这种错误提示:
//List<Object> cannot be converted to List<String>
原因:Collections.emptyList() 返回的是List<T> ,这里的T需要一个具体类型,但是因为不能从方法声明中推断出所需的是String,所以编译器就给T了一个Object的值,很明显,List<Object>不能转型到List<String>.所以在java7版本中你需要这样调用这个方法:
processStringList(Collections.<String>emptyList());
但是,在java8中,由于目标类型概念的引入,这里,很明显编译器需要的是List<String>(也就是这里的Target Type),所以编译器推断返回的List<T>中的T一定是String,所以processStringList(Collections.emptyList());这种描述是OK的。
目标类型的使用在Lambda表达式中优势最为明显,相关内容我会慢慢整理出来。
好了,以上就是关于java中类型推导的一些跟人见解,总结来说,越来越完善的类型推导就是完成了一些本来就感觉很理所当然的类型转换工作,只是这些工作满满地全交给了编译器去自动推导而不是让开发者显示地去指定。
Java 8 新特性之泛型的类型推导的更多相关文章
- Java 8新特性探究(三)泛型的目标类型推断
简单理解泛型 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.通俗点将就是"类型的变量".这种类型变量可以用在类.接口和方法 ...
- Java 8新特性前瞻
快端午小长假了,要上线的项目差不多完结了,终于有时间可以坐下来写篇博客了. 这是篇对我看到的java 8新特性的一些总结,也是自己学习过程的总结. 几乎可以说java 8是目前为止,自2004年jav ...
- iOS9新特性之泛型
iOS9新特性之泛型 作用:限制类型 好处:1.提高开发规范,减少程序员之间的交流 2.通过集合取出来的对象,直接当作泛型对象使用,可以直接使用点语法(id类型不能使用点语法) 使用场景: 1.在集 ...
- Java 8新特性之旅:使用Stream API处理集合
在这篇“Java 8新特性教程”系列文章中,我们会深入解释,并通过代码来展示,如何通过流来遍历集合,如何从集合和数组来创建流,以及怎么聚合流的值. 在之前的文章“遍历.过滤.处理集合及使用Lambda ...
- Java 8 新特性终极指南
1.前言 毫无疑问,Java 8的发布是自从Java5以来Java世界中最重大的事件,它在编译器.工具类和Java虚拟机等方面为Java语言带来的很多新特性.在本文中我们將一起关注下这些新变化,使用实 ...
- Java 5-11新特性的整理(转)
Java 5-11新特性的整理(转) 作者:拔剑少年 简书地址:https://www.jianshu.com/u/dad4d9675892博客地址:https://it18monkey.github ...
- Java 8 新特性——检视阅读
Java 8 新特性--检视阅读 参考 Java 8 新特性--菜鸟 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的 ...
- Java 8 新特性——实践篇
Java 8 新特性--实践篇 参考 Java8新特性 重要更新:Lambda 表达式和Stream API Lambda 表达式 Lambda 表达式引入之前: 举个场景例子:当我们要对一个班级里的 ...
- JAVA 8 新特性实用总JAVA 8 新特性实用总结结
JAVA 8 新特性实用总结 作为一个工作两年多的 老 程序猿,虽然一开始就使用 jdk1.8 作为学习和使用的版本,随着技术的迭代,现有的 JDK 版本从两年前到现在,已经飞速发展到了 JDK 15 ...
随机推荐
- HTML5学习总结-番外05 移动终端适配
一 viewport 在使用移动端设备浏览网页时,移动端浏览器是直接把整个页面放到一个虚拟的视图里来显示的,通常来说这个虚拟的视图大小会比手机屏幕大,用户可以通过手势操作来平移.缩放这个视图. 如果不 ...
- php实现数据粘性例子
php实现数据粘性例子 在填表单的时候,有时候会出现表单未填完就提交的情况,这时候若是想要回到原来的表单页面,一般之前填的内容都会消失掉. 故使用PHP实现回到原来表单但是填写数据不消失,代码
- 二维码相关---java生成二维码名片,并且自动保存到手机通讯录中...
http://blog.csdn.net/lidew521/article/details/24441825
- 轮播插件unsilder 源码解析(二)
$.fn._active = function(className) { //当前的添加class,相邻元素去除class return this.addClass(className).siblin ...
- 轮播插件unsilder 源码解析(一)---使用
啰嗦几句:学习的可以直接省略,一直本着写原生的插件想法,但是前天看了吕大豹的博客觉得自己都没有正经的写个jquery插件:所以在开始写之前我会先对几个比较热门的jquery的插件进行源码分析:至于为什 ...
- CSS样式收集
1.改变浏览器滚动条样式(适合webkit内核,摘自www.webhek.com/scrollbar) ::-webkit-scrollbar{width:2px;} ::-webkit-scroll ...
- PHP框架模板原理
PHP框架现在是一种很流行的东西了,很多朋友开发应用与网站都会选择一个PHP框架或模板了,下面我们来看看PHP框架是如何实现的吧. 本文主要来聊聊框架理论,但不针对任何一款框架,不过任何 ...
- String StringBuffer StringBuilder
package com.test; import java.util.Date; /*** * * // 输出的结果是:// 来一个测试// 来一个测试如果只输出这句就证明了String是不可变的// ...
- 解决qq互联回调地址错误redirect uri is illegal(100010)的方法,不同于网上大部分方法
我是在用ecshop自己搭建的一个网店系统安装了第三方登录的插件,包括qq登录插件.网上大部分情况都是discuz论坛的这个回调地址出错及解决办法.所以在我出错的时候参考他们的办法并不管用.但是在后面 ...
- java程序员快速掌握python系列——概述
这一系列主要是总结学习python过程中的方方面面(已经学完,时间大概是一周左右).当然限于个人水平java也就是够用,python短时间内也不可能深入到哪里去.所以这次的分享的目的是能够快速使用py ...