配色: 字号:
Java 8 新特性概述
2016-12-10 | 阅:  转:  |  分享 
  
Java8新特性概述



Oracle在2014年3月发布了Java8正式版,该版本是一个有重大改变的版本,对Java带来了诸多新特性。其中主要的新特性涵盖:函数式接口、Lambda表达式、集合的流式操作、注解的更新、安全性的增强、IO\NIO的改进、完善的全球化功能等。本文将对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(){

@Override

publicvoidactionPerformed(ActionEvente){

System.out.print("HellloLambdainactionPerformed");

}

});

下面是使用Lambda表达式后:

button.addActionListener(

//actionPerformed有一个参数e传入,所以用(ActionEvente)

(ActionEvente)->

System.out.print("HellloLambdainactionPerformed")

);

上面是方法体包含了参数传入(ActionEvente),如果没有参数则只需(),例如Thread中的run方法就没有参数传入,当它使用Lambda表达式后:

[java]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

Threadt=newThread(

//run没有参数传入,所以用(),后面用{}包起方法体

()->{

System.out.println("Hellofromathreadinrun");

}

);

/通过上面两个代码的比较可以发现使用Lambda表达式可以简化代码,并提高代码的可读性。

为了进一步简化Lambda表达式,可以使用方法引用。例如,下面三种分别是使用内部类,使用Lambda表示式和使用方法引用方式的比较:/

//1.使用内部类

Functionf=newFunction(){

@Override

publicStringapply(Integert){

returnnull;

}

};

//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{

publicbooleantest(Personperson);

}

publicclassPeople{

privateListpersons=newArrayList();

publicListgetMaleList(PersonInterfacefilter){

Listres=newArrayList();

persons.forEach(

(Personperson)->

{

if(filter.test(person)){//调用PersonInterface的方法

res.add(person);

}

}

);

returnres;

}

}

//为了去除PersonInterface这个函数式接口,可以用通用函数式接口Predicate替代如下:

classPeople{

privateListpersons=newArrayList();

publicListgetMaleList(Predicatepredicate){

Listres=newArrayList();

persons.forEach(

person->{

if(predicate.test(person)){//调用Predicate的抽象方法test

res.add(person);

}

});

returnres;

}

}

接口的增强

Java8对接口做了进一步的增强。在接口中可以添加使用default关键字修饰的非抽象方法。还可以在接口中定义静态方法。如今,接口看上去与抽象类的功能越来越类似了。

默认方法

Java8还允许我们给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个特征又叫做扩展方法。在实现该接口时,该默认扩展方法在子类上可以直接使用,它的使用方式类似于抽象类中非抽象成员方法。但扩展方法不能够重载Object中的方法。例如:toString、equals、hashCode不能在接口中被重载。

例如,下面接口中定义了一个默认方法count(),该方法可以在子类中直接使用。

[java]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

publicinterfaceDefaultFunInterface{

//定义默认方法count

defaultintcount(){

return1;

}

}

publicclassSubDefaultFunClassimplementsDefaultFunInterface{

publicstaticvoidmain(String[]args){

//实例化一个子类对象,改子类对象可以直接调用父接口中的默认方法count

SubDefaultFunClasssub=newSubDefaultFunClass();

sub.count();

}

}

静态方法

在接口中,还允许定义静态的方法。接口中的静态方法可以直接用接口来调用。

例如,下面接口中定义了一个静态方法find,该方法可以直接用StaticFunInterface.find()来调用。

[java]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

publicinterfaceStaticFunInterface{

publicstaticintfind(){

return1;

}

}

publicclassTestStaticFun{

publicstaticvoidmain(String[]args){

//接口中定义了静态方法find直接被调用

StaticFunInterface.fine();

}

}

集合之流式操作

Java8引入了流式操作(Stream),通过该操作可以实现对集合(Collection)的并行处理和函数式操作。根据操作返回的结果不同,流式操作分为中间操作和最终操作两种。最终操作返回一特定类型的结果,而中间操作返回流本身,这样就可以将多个操作依次串联起来。根据流的并发性,流又可以分为串行和并行两种。流式操作实现了集合的过滤、排序、映射等功能。

Stream和Collection集合的区别:Collection是一种静态的内存数据结构,而Stream是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算。

串行和并行的流

流有串行和并行两种,串行流上的操作是在一个线程中依次完成,而并行流则是在多个线程上同时执行。并行与串行的流可以相互切换:通过stream.sequential()返回串行的流,通过stream.parallel()返回并行的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。

下面是分别用串行和并行的方式对集合进行排序。

串行排序:

[java]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

Listlist=newArrayList();

for(inti=0;i<1000000;i++){

doubled=Math.random()1000;

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++){

doubled=Math.random()1000;

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(Systemwww.wang027.com.out::println);

这里的forEach(...)就是一个终止操作,该操作之后不能再链式的添加其他操作了。

注解的更新

对于注解,Java8主要有两点改进:类型注解和重复注解。

Java8的类型注解扩展了注解使用的范围。在该版本之前,注解只能是在声明的地方使用。现在几乎可以为任何东西添加注解:局部变量、类与接口,就连方法的异常也能添加注解。新增的两个注释的程序元素类型ElementType.TYPE_USE和ElementType.TYPE_PARAMETER用来描述注解的新场合。ElementType.TYPE_PARAMETER表示该注解能写在类型变量的声明语句中。而ElementType.TYPE_USE表示该注解能写在使用类型的任何语句中(例如声明语句、泛型和强制转换语句中的类型)。

对类型注解的支持,增强了通过静态分析工具发现错误的能力。原先只能在运行时发现的问题可以提前在编译的时候被排查出来。Java8本身虽然没有自带类型检测的框架,但可以通过使用CheckerFramework这样的第三方工具,自动检查和确认软件的缺陷,提高生产效率。

例如,下面的代码可以通过编译,但是运行时会报NullPointerException的异常。

[java]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

publicclassTestAnno{

publicstaticvoidmain(String[]args){

Objectobj=null;

obj.toString();

}

}

为了能在编译期间就自动检查出这类异常,可以通过类型注解结合CheckerFramework提前排查出来:

[java]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

importorg.checkerframework.checker.nullness.qual.NonNull;

publicclassTestAnno{

publicstaticvoidmain(String[]args){

@NonNullObjectobj=null;

obj.toString();

}

}

编译时自动检测结果如下:

另外,在该版本之前使用注解的一个限制是相同的注解在同一位置只能声明一次,不能声明多次。Java8引入了重复注解机制,这样相同的注解可以在同一地方声明多次。重复注解机制本身必须用@Repeatable注解。

例如,下面就是用@Repeatable重复注解的例子:

[java]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

@Retention(RetentionPolicy.RUNTIME)\\该注解存在于类文件中并在运行时可以通过反射获取

@interfaceAnnots{

Annot[]value();

}

@Retention(RetentionPolicy.RUNTIME)\\该注解存在于类文件中并在运行时可以通过反射获取

@Repeatable(Annots.class)

@interfaceAnnot{

Stringvalue();

}

@Annot("a1")@Annot("a2")

publicclassTest{

publicstaticvoidmain(String[]args){

Annotsannots1=Test.class.getAnnotation(Annots.class);

System.out.println(annots1.value()[0]+","+annots1.value()[1]);

//输出:@Annot(value=a1),@Annot(value=a2)

Annot[]annots2=Test.class.getAnnotationsByType(Annot.class);

System.out.println(annots2[0]+","+annots2[1]);

//输出:@Annot(value=a1),@Annot(value=a2)

}

}

注释Annot被@Repeatable(Annots.class)注解。Annots只是一个容器,它包含Annot数组,编译器尽力向程序员隐藏它的存在。通过这样的方式,Test类可以被Annot注解两次。重复注释的类型可以通过getAnnotationsByType()方法来返回。

安全性

现今,互联网环境中存在各种各种潜在的威胁,对于Java平台来说,安全显得特别重要。为了保证新版本具有更高的安全性,Java8在安全性上对许多方面进行了增强,也为此推迟了它的发布日期。下面例举其中几个关于安全性的更新:

支持更强的基于密码的加密算法。基于AES的加密算法,例如PBEWithSHA256AndAES_128和PBEWithSHA512AndAES_256,已经被加入进来。

在客户端,TLS1.1和TLS1.2被设为默认启动。并且可以通过新的系统属性包jdk.tls.client.protocols来对它进行配置。

Keystore的增强,包含新的Keystore类型java.security.DomainLoadStoreParameter和为Keytool这个安全钥匙和证书的管理工具添加新的命令行选项-importpassword。同时,添加和更新了一些关于安全性的API来支持KeyStore的更新。

支持安全的随机数发生器。如果随机数来源于随机性不高的种子,那么那些用随机数来产生密钥或者散列敏感信息的系统就更易受攻击。SecureRandom这个类的getInstanceStrong方法如今可以获取各个平台最强的随机数对象实例,通过这个实例生成像RSA私钥和公钥这样具有较高熵的随机数。

JSSE(Java(TM)SecureSocketExtension)服务器端开始支持SSL/TLS服务器名字识别SNI(ServerNameIndication)扩展。SNI扩展目的是SSL/TLS协议可以通过SNI扩展来识别客户端试图通过握手协议连接的服务器名字。在Java7中只在客户端默认启动SNI扩展。如今,在JSSE服务器端也开始支持SNI扩展了。

安全性比较差的加密方法被默认禁用。默认不支持DES相关的Kerberos5加密方法。如果一定要使用这类弱加密方法需要在krb5.conf文件中添加allow_weak_crypto=true。考虑到这类加密方法安全性极差,开发者应该尽量避免使用它。

IO/NIO的改进

Java8对IO/NIO也做了一些改进。主要包括:改进了java.nio.charset.Charset的实现,使编码和解码的效率得以提升,也精简了jre/lib/charsets.jar包;优化了String(byte[],)构造方法和String.gwww.baiyuewang.netetBytes()方法的性能;还增加了一些新的IO/NIO方法,使用这些方法可以从文件或者输入流中获取流(java.util.stream.Stream),通过对流的操作,可以简化文本行处理、目录遍历和文件查找。

新增的API如下:

BufferedReader.line():返回文本行的流Stream

File.lines(Path,Charset):返回文本行的流Stream

File.list(Path):遍历当前目录下的文件和目录

File.walk(Path,int,FileVisitOption):遍历某一个目录下的所有文件和指定深度的子目录

File.find(Path,int,BiPredicate,FileVisitOption...):查找相应的文件

下面就是用流式操作列出当前目录下的所有文件和目录:

[java]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

Files.list(newFile(".").toPath()).forEach(System.out::println);

全球化功能

Java8版本还完善了全球化功能:支持新的Unicode6.2.0标准,新增了日历和本地化的API,改进了日期时间的管理等。

Java的日期与时间API问题由来已久,Java8之前的版本中关于时间、日期及其他时间日期格式化类由于线程安全、重量级、序列化成本高等问题而饱受批评。Java8吸收了Joda-Time的精华,以一个新的开始为Java创建优秀的API。新的java.time中包含了所有关于时钟(Clock),本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的Date类新增了toInstant()方法,用于把Date转换成新的表示形式。这些新增的本地化时间日期API大大简化了了日期时间和本地化的管理。

例如,下面是对LocalDate,LocalTime的简单应用:

[java]viewplaincopyprint?在CODE上查看代码片派生到我的代码片

//LocalDate

LocalDatelocalDate=LocalDate.now();//获取本地日期

localDate=LocalDate.ofYearDay(2014,200);//获得2014年的第200天

System.out.println(localDate.toString());//输出:2014-07-19

localDate=LocalDate.of(2014,Month.SEPTEMBER,10);//2014年9月10日

System.out.println(localDate.toString());//输出:2014-09-10

//LocalTime

LocalTimelocalTime=LocalTime.now();//获取当前时间

System.out.println(localTime.toString());//输出当前时间

localTime=LocalTime.of(10,20,50);//获得10:20:50的时间点

System.out.println(localTime.toString());//输出:10:20:50

//Clock时钟

Clockclock=Clock.systemDefaultZone();//获取系统默认时区(当前瞬时时间)

longmillis=clock.millis();//

Java8开发环境

随着Java8正式发布,许多IDE也开始提供对Java8的支持。Eclipse是Java开发人员最为常用集成开发环境,在最新的EclipseKepler4.3.2版本中已经默认增加了对Java8的支持。要想在EclipseKepler的前期版本中添加对Java8的支持,可以通过下面步骤来完成:

1.选择"Help>EclipseMarketplace..."。

2.在搜索框中输入"Java8Kepler"。

3.点击安装Java8supportforEclipseKeplerSR2。

献花(0)
+1
(本文系thedust79首藏)