Java 8 新特性概述.docx
《Java 8 新特性概述.docx》由会员分享,可在线阅读,更多相关《Java 8 新特性概述.docx(17页珍藏版)》请在冰点文库上搜索。
Java8新特性概述
Java8新特性概述
函数式接口
Java8引入的一个核心概念是函数式接口(FunctionalInterfaces)。
通过在接口里面添加一个抽象方法,这些方法可以直接从接口中运行。
如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。
同时,引入了一个新的注解:
@FunctionalInterface。
可以把他它放在一个接口前,表示这个接口是一个函数式接口。
这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解@FunctionalInterface进行声明。
在接口中添加了@FunctionalInterface的接口,只允许有一个抽象方法,否则编译器也会报错。
java.lang.Runnable就是一个函数式接口。
[java]viewplaincopyprint?
在CODE上查看代码片派生到我的代码片
@FunctionalInterface
publicinterfaceRunnable{
publicabstractvoidrun();
}
Lambda表达式
函数式接口的重要属性是:
我们能够使用Lambda实例化它们,Lambda表达式让你能够将函数作为方法参数,或者将代码作为数据对待。
Lambda表达式的引入给开发者带来了不少优点:
在Java8之前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性很差,Lambda表达式的应用则使代码变得更加紧凑,可读性增强;Lambda表达式使并行操作大集合变得很方便,可以充分发挥多核CPU的优势,更易于为多核处理器编写代码;
Lambda表达式由三个部分组成:
第一部分为一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数;第二部分为一个箭头符号:
->;第三部分为方法体,可以是表达式和代码块。
语法如下:
1.方法体为表达式,该表达式的值作为返回值返回。
(parameters)->expression
2.方法体为代码块,必须用{}来包裹起来,且需要一个return返回值,但若函数式接口里面方法返回值是void,则无需返回值。
(parameters)->{statements;}//例如,下面是使用匿名内部类和Lambda表达式的代码比较。
下面是用匿名内部类的代码:
[java]viewplaincopyprint?
在CODE上查看代码片派生到我的代码片
button.addActionListener(newActionListener(){
pre">@Override
pre">publicvoidactionPerformed(ActionEvente){
pre">System.out.print("HellloLambdainactionPerformed");
pre">}
});
下面是使用Lambda表达式后:
button.addActionListener(
pre">//actionPerformed有一个参数e传入,所以用(ActionEvente)
pre">(ActionEvente)->
pre">System.out.print("HellloLambdainactionPerformed")
);
上面是方法体包含了参数传入(ActionEvente),如果没有参数则只需(),例如Thread中的run方法就没有参数传入,当它使用Lambda表达式后:
[java]viewplaincopyprint?
在CODE上查看代码片派生到我的代码片
Threadt=newThread(
pre">//run没有参数传入,所以用(),后面用{}包起方法体
pre">()->{
pre">System.out.println("Hellofromathreadinrun");
pre">}
);
/*通过上面两个代码的比较可以发现使用Lambda表达式可以简化代码,并提高代码的可读性。
为了进一步简化Lambda表达式,可以使用方法引用。
例如,下面三种分别是使用内部类,使用Lambda表示式和使用方法引用方式的比较:
*/
//1.使用内部类
Functionf=newFunction(){
pre">@Override
pre">publicStringapply(Integert){
pre">returnnull;
pre">}
};
//2.使用Lambda表达式
Functionf2=(t)->String.valueOf(t);
//3.使用方法引用的方式
Functionf1=String:
:
valueOf;
要使用Lambda表达式,需要定义一个函数式接口,这样往往会让程序充斥着过量的仅为Lambda表达式服务的函数式接口。
为了减少这样过量的函数式接口,Java8在java.util.function中增加了不少新的函数式通用接口。
例如:
Function:
将T作为输入,返回R作为输出,他还包含了和其他函数组合的默认方法。
Predicate:
将T作为输入,返回一个布尔值作为输出,该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(与、或、非)。
Consumer:
将T作为输入,不返回任何内容,表示在单个参数上的操作。
[java]viewplaincopyprint?
在CODE上查看代码片派生到我的代码片
//例如,People类中有一个方法getMaleList需要获取男性的列表,这里需要定义一个函数式接口PersonInterface:
interfacePersonInterface{
pre">publicbooleantest(Personperson);
}
publicclassPeople{
pre">privateListpersons=newArrayList();
pre">publicListgetMaleList(PersonInterfacefilter){
pre">Listres=newArrayList();
pre">persons.forEach(
pre">(Personperson)->
pre">{
pre">if(filter.test(person)){//调用PersonInterface的方法
pre">res.add(person);
pre">}
pre">}
);
returnres;
}
}
//为了去除PersonInterface这个函数式接口,可以用通用函数式接口Predicate替代如下:
classPeople{
pre">privateListpersons=newArrayList();
pre">publicListgetMaleList(Predicatepredicate){
pre">Listres=newArrayList();
pre">persons.forEach(
pre">person->{
pre">if(predicate.test(person)){//调用Predicate的抽象方法test
pre">res.add(person);
pre">}
});
returnres;
}
}
接口的增强
Java8对接口做了进一步的增强。
在接口中可以添加使用default关键字修饰的非抽象方法。
还可以在接口中定义静态方法。
如今,接口看上去与抽象类的功能越来越类似了。
默认方法
Java8还允许我们给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个特征又叫做扩展方法。
在实现该接口时,该默认扩展方法在子类上可以直接使用,它的使用方式类似于抽象类中非抽象成员方法。
但扩展方法不能够重载Object中的方法。
例如:
toString、equals、hashCode不能在接口中被重载。
例如,下面接口中定义了一个默认方法count(),该方法可以在子类中直接使用。
[java]viewplaincopyprint?
在CODE上查看代码片派生到我的代码片
publicinterfaceDefaultFunInterface{
pre">//定义默认方法count
pre">defaultintcount(){
pre">return1;
pre">}
}
publicclassSubDefaultFunClassimplementsDefaultFunInterface{
pre">publicstaticvoidmain(String[]args){
pre">//实例化一个子类对象,改子类对象可以直接调用父接口中的默认方法count
pre">SubDefaultFunClasssub=newSubDefaultFunClass();
pre">sub.count();
pre">}
}
静态方法
在接口中,还允许定义静态的方法。
接口中的静态方法可以直接用接口来调用。
例如,下面接口中定义了一个静态方法find,该方法可以直接用StaticFunInterface.find()来调用。
[java]viewplaincopyprint?
在CODE上查看代码片派生到我的代码片
publicinterfaceStaticFunInterface{
pre">publicstaticintfind(){
pre">return1;
pre">}
}
publicclassTestStaticFun{
pre">publicstaticvoidmain(String[]args){
pre">//接口中定义了静态方法find直接被调用
pre">StaticFunInterface.fine();
pre">}
}
集合之流式操作
Java8引入了流式操作(Stream),通过该操作可以实现对集合(Collection)的并行处理和函数式操作。
根据操作返回的结果不同,流式操作分为中间操作和最终操作两种。
最终操作返回一特定类型的结果,而中间操作返回流本身,这样就可以将多个操作依次串联起来。
根据流的并发性,流又可以分为串行和并行两种。
流式操作实现了集合的过滤、排序、映射等功能。
Stream和Collection集合的区别:
Collection是一种静态的内存数据结构,而Stream是有关计算的。
前者是主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算。
串行和并行的流
流有串行和并行两种,串行流上的操作是在一个线程中依次完成,而并行流则是在多个线程上同时执行。
并行与串行的流可以相互切换:
通过stream.sequential()返回串行的流,通过stream.parallel()返回并行的流。
相比较串行的流,并行的流可以很大程度上提高程序的执行效率。
下面是分别用串行和并行的方式对集合进行排序。
串行排序:
[java]viewplaincopyprint?
在CODE上查看代码片派生到我的代码片
Listlist=newArrayList();
for(inti=0;i<1000000;i++){
pre">doubled=Math.random()*1000;
pre">list.add(d+"");
}
longstart=System.nanoTime();//获取系统开始排序的时间点
intcount=(int)((Stream)list.stream().sequential()).sorted().count();
longend=System.nanoTime();//获取系统结束排序的时间点
longms=TimeUnit.NANOSECONDS.toMillis(end-start);//得到串行排序所用的时间
System.out.println(ms+”ms”);
并行排序:
[java]viewplaincopyprint?
在CODE上查看代码片派生到我的代码片
Listlist=newArrayList();
for(inti=0;i<1000000;i++){
pre">doubled=Math.random()*1000;
pre">list.add(d+"");
}
longstart=System.nanoTime();//获取系统开始排序的时间点
intcount=(int)((Stream)list.stream().parallel()).sorted().count();
longend=System.nanoTime();//获取系统结束排序的时间点
longms=TimeUnit.NANOSECONDS.toMillis(end-start);//得到并行排序所用的时间
System.out.println(ms+”ms”);
串行输出为1200ms,并行输出为800ms。
可见,并行排序的时间相比较串行排序时间要少不少。
中间操作
该操作会保持stream处于中间状态,允许做进一步的操作。
它返回的还是的Stream,允许更多的链式操作。
常见的中间操作有:
filter():
对元素进行过滤;
sorted():
对元素排序;
map():
元素的映射;
distinct():
去除重复元素;
subStream():
获取子Stream等。
例如,下面是对一个字符串集合进行过滤,返回以“s”开头的字符串集合,并将该集合依次打印出来:
[java]viewplaincopyprint?
在CODE上查看代码片派生到我的代码片
list.stream().filter((s)->s.startsWith("s")).forEach(System.out:
:
println);
这里的filter(...)就是一个中间操作,该中间操作可以链式地应用其他Stream操作。
终止操作
该操作必须是流的最后一个操作,一旦被调用,Stream就到了一个终止状态,而且不能再使用了。
常见的终止操作有:
forEach():
对每个元素做处理;
toArray():
把元素导出到数组;
findFirst():
返回第一个匹配的元素;
anyMatch():
是否有匹配的元素等。
例如,下面是对一个字符串集合进行过滤,返回以“s”开头的字符串集合,并将该集合依次打印出来:
[java]viewplaincopyprint?
在CODE上查看代码片派生到我的代码片
list.stream()//获取列表的stream操作对象
.filter((s)->s.startsWith("s"))//对这个流做过滤操作
.forEach(System.out:
:
println);
这里的forEach(...)就是一个终止操作,该操作之后不能再链式的添加其他操作了。
注解的更新
对于注解,Java8主要有两点改进:
类型注解和重复注解。
Java8的类型注解扩展了注解使用的范围。
在该版本之前,注解只能是在声明的地方使用。
现在几乎可以为任何东西添加注解:
局部变量、类与接口,就连方法的异常也能添加注解。
新增的两个注释的程序元素类型ElementType.TYPE_USE和ElementType.TYPE_PARAMETER用来描述注解的新场合。
ElementType.TYPE_PARAMETER表示该注解能写在类型变量的声明语句中。
而ElementType.TYPE_USE表示该注解能写在使用类型的任何语句中(例如声明语句、泛型和强制转换语句中的类型)。
对类型注解的支持,增强了通过静态分析工具发现错误的能力。
原先只能在运行时发现的问题可以提前在编译的时候被排查出来。
Java8本身虽然没有自带类型检测的框架,但可以通过使用CheckerFramework这样的第三方工具,自动检查和确认软件的缺陷,提高生产效率。
例如,下面的代码可以通过编译,但是运行时会报NullPointerException的异常。
[java]viewplaincopyprint?
在CODE上查看代码片派生到我的代码片
publicclassTestAnno{
pre">publicstaticvoidmain(String[]args){
pre">Objectobj=null;
pre">obj.toString();
pre">}
}
为了能在编译期间就自动检查出这类异常,可以通过类型注解结合CheckerFramework提前排查出来:
[java]viewplaincopyprint?
在CODE上查看代码片派生到我的代码片
importorg.checkerframework.checker.nullness.qual.NonNull;
publicclassTestAnno{
pre">publicstaticvoidmain(String[]args){
pre">@NonNullObjectobj=null;
pre">obj.toString();