Java基础20:Java8新特性终极指南
毫无疑问,Java 8发行版是自Java 5(发行于2004,已通过了至关一段时间了)以来最具革命性的版本。Java 8 为Java语言、编译器、类库、开发工具与JVM(Java虚拟机)带来了大量新特性。在这篇教程中,咱们将一一探索这些变化,并用真实的例子说明它们适用的场景。html
本文由如下几部分组成,它们分别涉及到Java平台某一特定方面的内容:java
Java语言 编译器 类库 工具 Java运行时(JVM)git
本文参考http://www.importnew.com/11908.html程序员
具体代码在个人GitHub中能够找到github
喜欢的话麻烦点一下星哈谢谢。数据库
文章首发于个人我的博客:express
更多关于Java后端学习的内容请到个人CSDN博客上查看:后端
这是一个Java8新增特性的总结图。接下来让咱们一次实践一下这些新特性吧
Java语言新特性
Lambda表达式
Lambda表达式(也称为闭包)是整个Java 8发行版中最受期待的在Java语言层面上的改变
Lambda容许把函数做为一个方法的参数(函数做为参数传递进方法中),或者把代码当作数据:
使用Lambda 表达式可使代码变的更加简洁紧凑
lambda 表达式的语法格式以下:
(parameters) -> expression或(parameters) ->{statements; }
如下是lambda表达式的重要特征:
· 可选类型声明:不须要声明参数类型,编译器能够统一识别参数值。
· 可选的参数圆括号:一个参数无需定义圆括号,但多个参数须要定义圆括号。
· 可选的大括号:若是主体包含了一个语句,就不须要使用大括号。
· 可选的返回关键字:若是主体只有一个表达式返回值则编译器会自动返回值,大括号须要指定明表达式返回了一个数值。
lambda 表达式的局部变量能够不用声明为 final,可是必须不可被后面的代码修改(即隐性的具备final 的语义)
关于Lambda设计的讨论占用了大量的时间与社区的努力。可喜的是,最终找到了一个平衡点,使得可使用一种即简洁又紧凑的新方式来构造Lambdas。在最简单的形式中,一个lambda能够由用逗号分隔的参数列表、–>符号与函数体三部分表示。例如:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
请注意参数e的类型是由编译器推测出来的。同时,你也能够经过把参数类型与参数包括在括号中的形式直接给出参数的类型:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
在某些状况下lambda的函数体会更加复杂,这时能够把函数体放到在一对花括号中,就像在Java中定义普通函数同样。例如:
Arrays.asList( "a", "b", "d" ).forEach( e -> {
System.out.print( e );
System.out.print( e );
} );
Lambda能够引用类的成员变量与局部变量(若是这些变量不是final的话,它们会被隐含的转为final,这样效率更高)。例如,下面两个代码片断是等价的:
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );
和:
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );
Lambda可能会返回一个值。返回值的类型也是由编译器推测出来的。若是lambda的函数体只有一行的话,那么没有必要显式使用return语句。下面两个代码片断是等价的:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
和:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
语言设计者投入了大量精力来思考如何使现有的函数友好地支持lambda。
最终采起的方法是:增长函数式接口的概念。
函数式接口就是一个具备一个方法的普通接口。像这样的接口,能够被隐式转换为lambda表达式。
java.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的两个例子。
在实际使用过程当中,函数式接口是容易出错的:若有某我的在接口定义中增长了另外一个方法,这时,这个接口就再也不是函数式的了,而且编译过程也会失败。
Java8增长了一种特殊的注解@FunctionalInterface(Java8中全部类库的已有接口都添加了@FunctionalInterface注解)。让咱们看一下这种函数式接口的定义:
@FunctionalInterface public interface Functional { void method(); } 须要记住的一件事是:默认方法与静态方法并不影响函数式接口的契约,能够任意使用:
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
} Lambda是Java 8最大的卖点。它具备吸引愈来愈多程序员到Java平台上的潜力,而且可以在纯Java语言环境中提供一种优雅的方式来支持函数式编程。更多详情能够参考官方文档。
下面看一个例子:
public class lambda和函数式编程 { @Test public void test1() { List names = Arrays.asList("peter", "anna", "mike", "xenia"); Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return b.compareTo(a); } }); System.out.println(Arrays.toString(names.toArray())); } @Test public void test2() { List<String> names = Arrays.asList("peter", "anna", "mike", "xenia"); Collections.sort(names, (String a, String b) -> { return b.compareTo(a); }); Collections.sort(names, (String a, String b) -> b.compareTo(a)); Collections.sort(names, (a, b) -> b.compareTo(a)); System.out.println(Arrays.toString(names.toArray())); } } static void add(double a,String b) { System.out.println(a + b); } @Test public void test5() { D d = (a,b) -> add(a,b); // interface D { // void get(int i,String j); // } //这里要求,add的两个参数和get的两个参数吻合而且返回类型也要相等,不然报错 // static void add(double a,String b) { // System.out.println(a + b); // } } @FunctionalInterface interface D { void get(int i,String j); }
函数式接口
所谓的函数式接口就是只有一个抽象方法的接口,注意这里说的是抽象方法,由于Java8中加入了默认方法的特性,可是函数式接口是不关心接口中有没有默认方法的。 通常函数式接口可使用@FunctionalInterface注解的形式来标注表示这是一个函数式接口,该注解标注与否对函数式接口没有实际的影响, 不过通常仍是推荐使用该注解,就像使用@Override注解同样。
lambda表达式是如何符合 Java 类型系统的?每一个lambda对应于一个给定的类型,用一个接口来讲明。而这个被称为函数式接口(functional interface)的接口必须仅仅包含一个抽象方法声明。每一个那个类型的lambda表达式都将会被匹配到这个抽象方法上。所以默认的方法并非抽象的,你能够给你的函数式接口自由地增长默认的方法。
咱们可使用任意的接口做为lambda表达式,只要这个接口只包含一个抽象方法。为了保证你的接口知足需求,你须要增长@FunctionalInterface注解。编译器知道这个注解,一旦你试图给这个接口增长第二个抽象方法声明时,它将抛出一个编译器错误。
下面举几个例子
public class 函数式接口使用 { @FunctionalInterface interface A { void say(); default void talk() { } } @Test public void test1() { A a = () -> System.out.println("hello"); a.say(); } @FunctionalInterface interface B { void say(String i); } public void test2() { //下面两个是等价的,都是经过B接口来引用一个方法,而方法能够直接使用::来做为方法引用 B b = System.out::println; B b1 = a -> Integer.parseInt("s");//这里的a其实换成别的也行,只是将方法传给接口做为其方法实现 B b2 = Integer::valueOf;//i与方法传入参数的变量类型一直时,能够直接替换 B b3 = String::valueOf; //B b4 = Integer::parseInt;类型不符,没法使用 } @FunctionalInterface interface C { int say(String i); } public void test3() { C c = Integer::parseInt;//方法参数和接口方法的参数同样,能够替换。 int i = c.say("1"); //当我把C接口的int替换为void时就会报错,由于返回类型不一致。 System.out.println(i); //综上所述,lambda表达式提供了一种简便的表达方式,能够将一个方法传到接口中。 //函数式接口是只提供一个抽象方法的接口,其方法由lambda表达式注入,不须要写实现类, //也不须要写匿名内部类,能够省去不少代码,好比实现runnable接口。 //函数式编程就是指把方法当作一个参数或引用来进行操做。除了普通方法之外,静态方法,构造方法也是能够这样操做的。 } }
请记住若是@FunctionalInterface 这个注解被遗漏,此代码依然有效。
方法引用
Lambda表达式和方法引用
当要传递给Lambda体的操做,已经有实现的方法了,可使用方法引用! (实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致! ) 方法引用:使用操做符 “::” 将方法名和对象或类的名字分隔开来。 以下三种主要使用状况:
对象::实例方法
类::静态方法
类::实例方法
构造器引用
ClassName::new
数组引用
type[]::new
有了函数式接口以后,就可使用Lambda表达式和方法引用了。其实函数式接口的表中的函数描述符就是Lambda表达式,在函数式接口中Lambda表达式至关于匿名内部类的效果。 举个简单的例子:
public class TestLambda { public static void execute(Runnable runnable) { runnable.run(); } public static void main(String[] args) { //Java8以前 execute(new Runnable() { @Override public void run() { System.out.println("run"); } }); //使用Lambda表达式 execute(() -> System.out.println("run")); } }
能够看到,相比于使用匿名内部类的方式,Lambda表达式可使用更少的代码可是有更清晰的表述。注意,Lambda表达式也不是彻底等价于匿名内部类的, 二者的不一样点在于this的指向和本地变量的屏蔽上。
方法引用能够看做Lambda表达式的更简洁的一种表达形式,使用::操做符,方法引用主要有三类:
指向静态方法的方法引用(例如Integer的parseInt方法,写做Integer::parseInt); 指向任意类型实例方法的方法引用(例如String的length方法,写做String::length); 指向现有对象的实例方法的方法引用(例如假设你有一个本地变量localVariable用于存放Variable类型的对象,它支持实例方法getValue,那么能够写成localVariable::getValue)。
举个方法引用的简单的例子:
Function<String, Integer> stringToInteger = (String s) -> Integer.parseInt(s);
//使用方法引用
Function<String, Integer> stringToInteger = Integer::parseInt;
方法引用中还有一种特殊的形式,构造函数引用,假设一个类有一个默认的构造函数,那么使用方法引用的形式为:
Supplier<SomeClass> c1 = SomeClass::new; SomeClass s1 = c1.get();
//等价于
Supplier<SomeClass> c1 = () -> new SomeClass(); SomeClass s1 = c1.get();
若是是构造函数有一个参数的状况:
Function<Integer, SomeClass> c1 = SomeClass::new; SomeClass s1 = c1.apply(100);
//等价于
Function<Integer, SomeClass> c1 = i -> new SomeClass(i); SomeClass s1 = c1.apply(100);
class Car { @FunctionalInterface public interface Supplier<T> { T get(); } //Supplier是jdk1.8的接口,这里和lamda一块儿使用了 public static Car create(final Supplier<Car> supplier) { return supplier.get(); } public static void collide(final Car car) { System.out.println("Collided " + car.toString()); } public void follow(final Car another) { System.out.println("Following the " + another.toString()); } public void repair() { System.out.println("Repaired " + this.toString()); } public static void main(String[] args) { //构造器引用:它的语法是Class::new,或者更通常的Class< T >::new实例以下: Car car = Car.create(Car::new); Car car1 = Car.create(Car::new); Car car2 = Car.create(Car::new); Car car3 = new Car(); List<Car> cars = Arrays.asList(car,car1,car2,car3); System.out.println("===================构造器引用========================"); //静态方法引用:它的语法是Class::static_method,实例以下: cars.forEach(Car::collide); System.out.println("===================静态方法引用========================"); //特定类的任意对象的方法引用:它的语法是Class::method实例以下: cars.forEach(Car::repair); System.out.println("==============特定类的任意对象的方法引用================"); //特定对象的方法引用:它的语法是instance::method实例以下: final Car police = Car.create(Car::new); cars.forEach(police::follow); System.out.println("===================特定对象的方法引用==================="); } }
接口的默认方法
Java 8 新增了接口的默认方法。
简单说,默认方法就是接口能够有实现方法,并且不须要实现类去实现其方法。
咱们只需在方法名前面加个default关键字便可实现默认方法。
为何要有这个特性?
首先,以前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当须要修改接口时候,须要修改所有实现该接口的类,目前的java 8以前的集合框架没有foreach方法,一般能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是无法在给接口添加新方法的同时不影响已有的实现。因此引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。
public class 接口的默认方法 { class B implements A { // void a(){}实现类方法不能重名 } interface A { //能够有多个默认方法 public default void a(){ System.out.println("a"); } public default void b(){ System.out.println("b"); } //报错static和default不能同时使用 // public static default void c(){ // System.out.println("c"); // } } public void test() { B b = new B(); b.a(); } }
默认方法出现的缘由是为了对原有接口的扩展,有了默认方法以后就不怕因改动原有的接口而对已经使用这些接口的程序形成的代码不兼容的影响。 在Java8中也对一些接口增长了一些默认方法,好比Map接口等等。通常来讲,使用默认方法的场景有两个:可选方法和行为的多继承。
默认方法的使用相对来讲比较简单,惟一要注意的点是如何处理默认方法的冲突。关于如何处理默认方法的冲突能够参考如下三条规则:
类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
若是没法依据第一条规则进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口。即若是B继承了A,那么B就比A更具体。
最后,若是仍是没法判断,继承了多个接口的类必须经过显式覆盖和调用指望的方法,显式地选择使用哪个默认方法的实现。那么如何显式地指定呢:
接口默认方法的” 类优先” 原则
若一个接口中定义了一个默认方法,而另一个父类或接口中 又定义了一个同名的方法时
选择父类中的方法。若是一个父类提供了具体的实现,那么 接口中具备相同名称和参数的默认方法会被忽略。
接口冲突。若是一个父接口提供一个默认方法,而另外一个接 口也提供了一个具备相同名称和参数列表的方法(无论方法 是不是默认方法), 那么必须覆盖该方法来解决冲突
public class C implements B, A { public void hello() { B.super().hello(); } }
使用X.super.m(..)显式地调用但愿调用的方法。
Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。默认方法使接口有点像Traits(Scala中特征(trait)相似于Java中的Interface,但它能够包含实现代码,也就是目前Java8新增的功能),但与传统的接口又有些不同,它容许在已有的接口中添加新方法,而同时又保持了与旧版本代码的兼容性。
默认方法与抽象方法不一样之处在于抽象方法必需要求实现,可是默认方法则没有这个要求。相反,每一个接口都必须提供一个所谓的默认实现,这样全部的接口实现者将会默认继承它(若是有必要的话,能够覆盖这个默认实现)。让咱们看看下面的例子:
private interface Defaulable { // Interfaces now allow default methods, the implementer may or // may not implement (override) them. default String notRequired() { return "Default implementation"; } } private static class DefaultableImpl implements Defaulable { } private static class OverridableImpl implements Defaulable { @Override public String notRequired() { return "Overridden implementation"; } }
Defaulable接口用关键字default声明了一个默认方法notRequired(),Defaulable接口的实现者之一DefaultableImpl实现了这个接口,而且让默认方法保持原样。Defaulable接口的另外一个实现者OverridableImpl用本身的方法覆盖了默认方法。
Java 8带来的另外一个有趣的特性是接口能够声明(而且能够提供实现)静态方法。例如:
private interface DefaulableFactory { // Interfaces now allow static methods static Defaulable create( Supplier< Defaulable > supplier ) { return supplier.get(); } }
下面的一小段代码片断把上面的默认方法与静态方法黏合到一块儿。
public static void main( String[] args ) { Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new ); System.out.println( defaulable.notRequired() ); defaulable = DefaulableFactory.create( OverridableImpl::new ); System.out.println( defaulable.notRequired() ); }
这个程序的控制台输出以下:
Default implementation Overridden implementation 在JVM中,默认方法的实现是很是高效的,而且经过字节码指令为方法调用提供了支持。默认方法容许继续使用现有的Java接口,而同时可以保障正常的编译过程。这方面好的例子是大量的方法被添加到java.util.Collection接口中去:stream(),parallelStream(),forEach(),removeIf(),……
尽管默认方法很是强大,可是在使用默认方法时咱们须要当心注意一个地方:在声明一个默认方法前,请仔细思考是否是真的有必要使用默认方法,由于默认方法会带给程序歧义,而且在复杂的继承体系中容易产生编译错误。更多详情请参考官方文档
重复注解
自从Java 5引入了注解机制,这一特性就变得很是流行而且广为使用。然而,使用注解的一个限制是相同的注解在同一位置只能声明一次,不能声明屡次。Java 8打破了这条规则,引入了重复注解机制,这样相同的注解能够在同一地方声明屡次。
重复注解机制自己必须用@Repeatable注解。事实上,这并非语言层面上的改变,更多的是编译器的技巧,底层的原理保持不变。让咱们看一个快速入门的例子:
package com.javacodegeeks.java8.repeatable.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; public class RepeatingAnnotations { @Target( ElementType.TYPE ) @Retention( RetentionPolicy.RUNTIME ) public @interface Filters { Filter[] value(); } @Target( ElementType.TYPE ) @Retention( RetentionPolicy.RUNTIME ) @Repeatable( Filters.class ) public @interface Filter { String value(); }; @Filter( "filter1" ) @Filter( "filter2" ) public interface Filterable { } public static void main(String[] args) { for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) { System.out.println( filter.value() ); } } }
正如咱们看到的,这里有个使用@Repeatable( Filters.class )注解的注解类Filter,Filters仅仅是Filter注解的数组,但Java编译器并不想让程序员意识到Filters的存在。这样,接口Filterable就拥有了两次Filter(并无提到Filter)注解。
同时,反射相关的API提供了新的函数getAnnotationsByType()来返回重复注解的类型(请注意Filterable.class.getAnnotation( Filters.class )经编译器处理后将会返回Filters的实例)。
程序输出结果以下:
filter1 filter2 更多详情请参考官方文档
Java编译器的新特性
方法参数名字能够反射获取
很长一段时间里,Java程序员一直在发明不一样的方式使得方法参数的名字能保留在Java字节码中,而且可以在运行时获取它们(好比,Paranamer类库)。最终,在Java 8中把这个强烈要求的功能添加到语言层面(经过反射API与Parameter.getName()方法)与字节码文件(经过新版的javac的–parameters选项)中。
package com.javacodegeeks.java8.parameter.names; import java.lang.reflect.Method; import java.lang.reflect.Parameter; public class ParameterNames { public static void main(String[] args) throws Exception { Method method = ParameterNames.class.getMethod( “main”, String[].class ); for( final Parameter parameter: method.getParameters() ) { System.out.println( “Parameter: “ + parameter.getName() ); } } }
若是不使用–parameters参数来编译这个类,而后运行这个类,会获得下面的输出:
Parameter: arg0 若是使用–parameters参数来编译这个类,程序的结构会有所不一样(参数的真实名字将会显示出来):
Parameter: args
Java 类库的新特性
Java 8 经过增长大量新类,扩展已有类的功能的方式来改善对并发编程、函数式编程、日期/时间相关操做以及其余更多方面的支持。
Optional
到目前为止,臭名昭著的空指针异常是致使Java应用程序失败的最多见缘由。之前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava经过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
Optional 类是一个能够为null的容器对象。若是值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它能够保存类型T的值,或者仅仅保存null。Optional提供不少有用的方法,这样咱们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常。
咱们下面用两个小例子来演示如何使用Optional类:一个容许为空值,一个不容许为空值。
public class 空指针Optional { public static void main(String[] args) { //使用of方法,仍然会报空指针异常 // Optional optional = Optional.of(null); // System.out.println(optional.get()); //抛出没有该元素的异常 //Exception in thread "main" java.util.NoSuchElementException: No value present // at java.util.Optional.get(Optional.java:135) // at com.javase.Java8.空指针Optional.main(空指针Optional.java:14) // Optional optional1 = Optional.ofNullable(null); // System.out.println(optional1.get()); Optional optional = Optional.ofNullable(null); System.out.println(optional.isPresent()); System.out.println(optional.orElse(0));//当值为空时给与初始值 System.out.println(optional.orElseGet(() -> new String[]{"a"}));//使用回调函数设置默认值 //即便传入Optional容器的元素为空,使用optional.isPresent()方法也不会报空指针异常 //因此经过optional.orElse这种方式就能够写出避免空指针异常的代码了 //输出Optional.empty。 } }
若是Optional类的实例为非空值的话,isPresent()返回true,否从返回false。为了防止Optional为空值,orElseGet()方法经过回调函数来产生一个默认值。map()函数对当前Optional的值进行转化,而后返回一个新的Optional实例。orElse()方法和orElseGet()方法相似,可是orElse接受一个默认值而不是一个回调函数。下面是这个程序的输出:
Full Name is set? false Full Name: [none] Hey Stranger! 让咱们来看看另外一个例子:
Optional< String > firstName = Optional.of( "Tom" ); System.out.println( "First Name is set? " + firstName.isPresent() ); System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) ); System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) ); System.out.println();
下面是程序的输出:
First Name is set? true First Name: Tom Hey Tom!
Stream
Stream 是 Java8 中处理集合的关键抽象概念,它能够指定你但愿对 集合进行的操做,能够执行很是复杂的查找、过滤和映射数据等操做。 使用Stream API 对集合数据进行操做,就相似于使用 SQL 执行的数据库查询。也可使用 Stream API 来并行执行操做。简而言之, Stream API 提供了一种高效且易于使用的处理数据的方式。
集合讲的是数据, Stream讲的是计算
什么是 Stream? Stream(流)是一个来自数据源的元素队列并支持聚合操做(数据渠道,用于操做数据源(集合、数组等)所生成的元素序列 )
元素:是特定类型的对象,造成一个队列。Java中的Stream并不会存储元素,而是按需计算。
数据源 :流的来源。能够是集合,数组,I/O channel,产生器generator等。
聚合操做: 相似SQL语句同样的操做,好比filter, map, reduce, find,match, sorted等。
和之前的Collection操做不一样,Stream操做还有两个基础的特征:
Pipelining::中间操做都会返回流对象自己。这样多个操做能够串联成一个管道,如同流式风格(fluent style)。这样作能够对操做进行优化,好比延迟执行(laziness)和短路( short-circuiting)。
内部迭代:之前对集合遍历都是经过Iterator或者For-Each的方式,显式的在集合外部进行迭代,这叫作外部迭代。Stream提供了内部迭代的方式,经过访问者模式(Visitor)实现。
注意:
①Stream 本身不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操做是延迟执行的。这意味着他们会等到须要结果的时候才执行。
Stream 的操做三个步骤
建立 Stream 一个数据源(如: 集合、数组), 获取一个流
建立 Stream方式一:经过集合 (Collection)
default Stream<E> stream() : 返回一个顺序流 default Stream<E> parallelStream() : 返回一个并行流
建立 Stream方式二:经过数组
Arrays 的静态方法 stream() 能够获取数组流: static <T> Stream<T> stream(T[] array): 返回一个流
建立 Stream方式三:经过Stream的of()
public static<T> Stream<T> of(T... values) : 返回一个流
中间操做 一个中间操做链,对数据源的数据进行处理
终止操做(终端操做) 一个终止操做,执行中间操做链,并产生结果
并行流与串行流
并行流就是把一个内容分红多个数据块,并用不一样的线程分别处理每一个数据块的流。
Java 8 中将并行进行了优化,咱们能够很容易的对数据进行并 行操做。 Stream API 能够声明性地经过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。
生成流 在Java 8中,集合接口有两个方法来生成流:
stream() −为集合建立串行流。
parallelStream() − 为集合建立并行流。
public static void main(String[] args) { List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); }
5.3 forEach Stream 提供了新的方法 'forEach' 来迭代流中的每一个数据。如下代码片断使用forEach 输出了10个随机数:
Random random = new Random(); random.ints().limit(10).forEach(System.out::println);
5.4 map map 方法用于映射每一个元素到对应的结果,如下代码片断使用 map 输出了元素对应的平方数:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); // 获取对应的平方数 List<Integer> squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
5.5 filter filter 方法用于经过设置条件过滤出元素。如下代码片断使用filter 方法过滤出空字符串:
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); // 获取空字符串的数量 int count = (int) strings.stream().filter(string -> string.isEmpty()).count();
5.6 limit limit 方法用于获取指定数量的流。如下代码片断使用 limit 方法打印出 10 条数据:
Random random = new Random(); random.ints().limit(10).forEach(System.out::println);
5.7 sorted sorted 方法用于对流进行排序。如下代码片断使用 sorted 方法对输出的 10 个随机数进行排序:
Random random = new Random(); random.ints().limit(10).sorted().forEach(System.out::println);
5.8 并行(parallel)程序 parallelStream 是流并行处理程序的代替方法。如下实例咱们使用parallelStream 来输出空字符串的数量:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl"); // 获取空字符串的数量 int count = (int) strings.parallelStream().filter(string -> string.isEmpty()).count(); 咱们能够很容易的在顺序运行和并行直接切换。
5.9 Collectors Collectors 类实现了不少归约操做,例如将流转换成集合和聚合元素。Collectors可用于返回列表或字符串:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println("筛选列表: " + filtered); String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("合并字符串: " + mergedString);
5.10 统计 另外,一些产生统计结果的收集器也很是有用。它们主要用于int、double、long等基本类型上,它们能够用来产生相似以下的统计结果。
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("列表中最大的数 : " + stats.getMax()); System.out.println("列表中最小的数 : " + stats.getMin()); System.out.println("全部数之和 : " + stats.getSum()); System.out.println("平均数 : " + stats.getAverage());
5.11 Stream 完整实例 将如下代码放入Java8Tester.java 文件中:
Java8Tester.java文件
public class Java8Tester { public static void main(String args[]) { System.out.println("使用 Java 7: "); // 计算空字符串 List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl"); System.out.println("列表: " + strings); long count = getCountEmptyStringUsingJava7(strings); System.out.println("空字符数量为: " + count); count = getCountLength3UsingJava7(strings); System.out.println("字符串长度为 3 的数量为: " + count); // 删除空字符串 List<String> filtered = deleteEmptyStringsUsingJava7(strings); System.out.println("筛选后的列表: " + filtered); // 删除空字符串,并使用逗号把它们合并起来 String mergedString = getMergedStringUsingJava7(strings, ", "); System.out.println("合并字符串: " + mergedString); List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); // 获取列表元素平方数 List<Integer> squaresList = getSquares(numbers); System.out.println("平方数列表: " + squaresList); List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19); System.out.println("列表: " + integers); System.out.println("列表中最大的数 : " + getMax(integers)); System.out.println("列表中最小的数 : " + getMin(integers)); System.out.println("全部数之和 : " + getSum(integers)); System.out.println("平均数 : " + getAverage(integers)); System.out.println("随机数: "); // 输出10个随机数 Random random = new Random(); for (int i = 0; i < 10; i++) { System.out.println(random.nextInt()); } System.out.println("使用 Java 8: "); System.out.println("列表: " + strings); count = strings.stream().filter(string -> string.isEmpty()).count(); System.out.println("空字符串数量为: " + count); count = strings.stream().filter(string -> string.length() == 3).count(); System.out.println("字符串长度为 3 的数量为: " + count); filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println("筛选后的列表: " + filtered); mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("合并字符串: " + mergedString); squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList()); System.out.println("Squares List: " + squaresList); System.out.println("列表: " + integers); IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("列表中最大的数 : " + stats.getMax()); System.out.println("列表中最小的数 : " + stats.getMin()); System.out.println("全部数之和 : " + stats.getSum()); System.out.println("平均数 : " + stats.getAverage()); System.out.println("随机数: "); random.ints().limit(10).sorted().forEach(System.out::println); // 并行处理 count = strings.parallelStream().filter(string -> string.isEmpty()).count(); System.out.println("空字符串的数量为: " + count); } private static int getCountEmptyStringUsingJava7(List<String> strings) { int count = 0; for (String string : strings) { if (string.isEmpty()) { count++; } } return count; } private static int getCountLength3UsingJava7(List<String> strings) { int count = 0; for (String string : strings) { if (string.length() == 3) { count++; } } return count; } private static List<String> deleteEmptyStringsUsingJava7(List<String> strings) { List<String> filteredList = new ArrayList<String>(); for (String string : strings) { if (!string.isEmpty()) { filteredList.add(string); } } return filteredList; } private static String getMergedStringUsingJava7(List<String> strings, String separator) { StringBuilder stringBuilder = new StringBuilder(); for (String string : strings) { if (!string.isEmpty()) { stringBuilder.append(string); stringBuilder.append(separator); } } String mergedString = stringBuilder.toString(); return mergedString.substring(0, mergedString.length() - 2); } private static List<Integer> getSquares(List<Integer> numbers) { List<Integer> squaresList = new ArrayList<Integer>(); for (Integer number : numbers) { Integer square = new Integer(number.intValue() * number.intValue()); if (!squaresList.contains(square)) { squaresList.add(square); } } return squaresList; } private static int getMax(List<Integer> numbers) { int max = numbers.get(0); for (int i = 1; i < numbers.size(); i++) { Integer number = numbers.get(i); if (number.intValue() > max) { max = number.intValue(); } } return max; } private static int getMin(List<Integer> numbers) { int min = numbers.get(0); for (int i = 1; i < numbers.size(); i++) { Integer number = numbers.get(i); if (number.intValue() < min) { min = number.intValue(); } } return min; } private static int getSum(List numbers) { int sum = (int) (numbers.get(0)); for (int i = 1; i < numbers.size(); i++) { sum += (int) numbers.get(i); } return sum; } private static int getAverage(List<Integer> numbers) { return getSum(numbers) / numbers.size(); } } 执行以上脚本,输出结果为: 使用Java7: 列表:[abc,, bc, efg, abcd,, jkl] 空字符数量为:2 字符串长度为3的数量为:3 筛选后的列表:[abc, bc, efg, abcd, jkl] 合并字符串: abc, bc, efg, abcd, jkl 平方数列表:[9,4,49,25] 列表:[1,2,13,4,15,6,17,8,19] 列表中最大的数:19 列表中最小的数:1 全部数之和:85 平均数:9 随机数: -393170844 -963842252 447036679 -1043163142 -881079698 221586850 -1101570113 576190039 -1045184578 1647841045 使用Java8: 列表:[abc,, bc, efg, abcd,, jkl] 空字符串数量为:2 字符串长度为3的数量为:3 筛选后的列表:[abc, bc, efg, abcd, jkl] 合并字符串: abc, bc, efg, abcd, jkl SquaresList:[9,4,49,25] 列表:[1,2,13,4,15,6,17,8,19] 列表中最大的数:19 列表中最小的数:1 全部数之和:85 平均数:9.444444444444445 随机数: -1743813696 -1301974944 -1299484995 -779981186 136544902 555792023 1243315896 1264920849 1472077135 1706423674 空字符串的数量为:2
Date/Time API (JSR 310)
在旧版的Java 中,日期时间API 存在诸多问题,其中有:
· 非线程安全 − java.util.Date 是非线程安全的,全部的日期类都是可变的,这是Java日期类最大的问题之一。
· 设计不好 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其归入java.sql包并不合理。另外这两个类都有相同的名字,这自己就是一个很是糟糕的设计。
· 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,所以Java引入了java.util.Calendar和java.util.TimeZone类,但他们一样存在上述全部的问题。
Java 8 在 java.time 包下提供了不少新的 API。如下为两个比较重要的 API:
· Local(本地) − 简化了日期时间的处理,没有时区的问题。
· Zone(时区) − 经过制定的时区处理日期时间。
新的java.time包涵盖了全部处理日期(localDateTime),时间,日期/时间,时区(zone),时刻(instants),过程(Duration)与时钟(clock)的操做。
Java 8经过发布新的Date-Time API (JSR 310)来进一步增强对日期与时间的处理。对日期与时间的操做一直是Java程序员最痛苦的地方之一。标准的 java.util.Date以及后来的java.util.Calendar一点没有改善这种状况(能够这么说,它们必定程度上更加复杂)。
这种状况直接致使了Joda-Time——一个可替换标准日期/时间处理且功能很是强大的Java API的诞生。Java 8新的Date-Time API (JSR 310)在很大程度上受到Joda-Time的影响,而且吸收了其精髓。新的java.time包涵盖了全部处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操做。在设计新版API时,十分注重与旧版API的兼容性:不容许有任何的改变(从java.util.Calendar中获得的深入教训)。若是须要修改,会返回这个类的一个新实例。
让咱们用例子来看一下新版API主要类的使用方法。第一个是Clock类,它经过指定一个时区,而后就能够获取到当前的时刻,日期与时间。Clock能够替换System.currentTimeMillis()与TimeZone.getDefault()。
// Get the system clock as UTC offset final Clock clock = Clock.systemUTC(); System.out.println( clock.instant() ); System.out.println( clock.millis() );
下面是程序在控制台上的输出:
2014-04-12T15:19:29.282Z 1397315969360
咱们须要关注的其余类是LocaleDate与LocalTime。LocaleDate只持有ISO-8601格式且无时区信息的日期部分。相应的,LocaleTime只持有ISO-8601格式且无时区信息的时间部分。LocaleDate与LocalTime均可以从Clock中获得。
// Get the local date and local time final LocalDate date = LocalDate.now(); final LocalDate dateFromClock = LocalDate.now( clock ); System.out.println( date ); System.out.println( dateFromClock ); // Get the local date and local time final LocalTime time = LocalTime.now(); final LocalTime timeFromClock = LocalTime.now( clock ); System.out.println( time ); System.out.println( timeFromClock );
下面是程序在控制台上的输出:
2014-04-12 2014-04-12 11:25:54.568 15:25:54.568
下面是程序在控制台上的输出:
2014-04-12T11:47:01.017-04:00[America/New_York] 2014-04-12T15:47:01.017Z 2014-04-12T08:47:01.017-07:00[America/Los_Angeles] 最后,让咱们看一下Duration类:在秒与纳秒级别上的一段时间。Duration使计算两个日期间的不一样变的十分简单。下面让咱们看一个这方面的例子。
// Get duration between two dates final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 ); final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 ); final Duration duration = Duration.between( from, to ); System.out.println( "Duration in days: " + duration.toDays() ); System.out.println( "Duration in hours: " + duration.toHours() );
上面的例子计算了两个日期2014年4月16号与2014年4月16号之间的过程。下面是程序在控制台上的输出:
Duration in days: 365 Duration in hours: 8783 对Java 8在日期/时间API的改进总体印象是很是很是好的。一部分缘由是由于它创建在“久战杀场”的Joda-Time基础上,另外一方面是由于用来大量的时间来设计它,而且此次程序员的声音获得了承认。更多详情请参考官方文档。
并行(parallel)数组
Java 8增长了大量的新方法来对数组进行并行处理。能够说,最重要的是parallelSort()方法,由于它能够在多核机器上极大提升数组排序的速度。下面的例子展现了新方法(parallelXxx)的使用。
package com.javacodegeeks.java8.parallel.arrays; import java.util.Arrays; import java.util.concurrent.ThreadLocalRandom; public class ParallelArrays { public static void main( String[] args ) { long[] arrayOfLong = new long [ 20000 ]; Arrays.parallelSetAll( arrayOfLong, index -> ThreadLocalRandom.current().nextInt( 1000000 ) ); Arrays.stream( arrayOfLong ).limit( 10 ).forEach( i -> System.out.print( i + " " ) ); System.out.println(); Arrays.parallelSort( arrayOfLong ); Arrays.stream( arrayOfLong ).limit( 10 ).forEach( i -> System.out.print( i + " " ) ); System.out.println(); } }
上面的代码片断使用了parallelSetAll()方法来对一个有20000个元素的数组进行随机赋值。而后,调用parallelSort方法。这个程序首先打印出前10个元素的值,以后对整个数组排序。这个程序在控制台上的输出以下(请注意数组元素是随机生产的):
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 Sorted: 39 220 263 268 325 607 655 678 723 793
CompletableFuture
在Java8以前,咱们会使用JDK提供的Future接口来进行一些异步的操做,其实CompletableFuture也是实现了Future接口, 而且基于ForkJoinPool来执行任务,所以本质上来说,CompletableFuture只是对原有API的封装, 而使用CompletableFuture与原来的Future的不一样之处在于能够将两个Future组合起来,或者若是两个Future是有依赖关系的,能够等第一个执行完毕后再实行第二个等特性。
先来看看基本的使用方式:
public Future<Double> getPriceAsync(final String product) { final CompletableFuture<Double> futurePrice = new CompletableFuture<>(); new Thread(() -> { double price = calculatePrice(product); futurePrice.complete(price); //完成后使用complete方法,设置future的返回值 }).start(); return futurePrice; }
获得Future以后就可使用get方法来获取结果,CompletableFuture提供了一些工厂方法来简化这些API,而且使用函数式编程的方式来使用这些API,例如:
Fufure price = CompletableFuture.supplyAsync(() -> calculatePrice(product)); 代码是否是一会儿简洁了许多呢。以前说了,CompletableFuture能够组合多个Future,无论是Future之间有依赖的,仍是没有依赖的。
若是第二个请求依赖于第一个请求的结果,那么可使用thenCompose方法来组合两个Future
public List<String> findPriceAsync(String product) { List<CompletableFutute<String>> priceFutures = tasks.stream() .map(task -> CompletableFuture.supplyAsync(() -> task.getPrice(product),executor)) .map(future -> future.thenApply(Work::parse)) .map(future -> future.thenCompose(work -> CompletableFuture.supplyAsync(() -> Count.applyCount(work), executor))) .collect(Collectors.toList()); return priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList()); }
上面这段代码使用了thenCompose来组合两个CompletableFuture。supplyAsync方法第二个参数接受一个自定义的Executor。 首先使用CompletableFuture执行一个任务,调用getPrice方法,获得一个Future,以后使用thenApply方法,将Future的结果应用parse方法, 以后再使用执行完parse以后的结果做为参数再执行一个applyCount方法,而后收集成一个CompletableFuture的List, 最后再使用一个流,调用CompletableFuture的join方法,这是为了等待全部的异步任务执行完毕,得到最后的结果。
注意,这里必须使用两个流,若是在一个流里调用join方法,那么因为Stream的延迟特性,全部的操做仍是会串行的执行,并非异步的。
再来看一个两个Future之间没有依赖关系的例子:
Future<String> futurePriceInUsd = CompletableFuture.supplyAsync(() -> shop.getPrice(“price1”)) .thenCombine(CompletableFuture.supplyAsync(() -> shop.getPrice(“price2”)), (s1, s2) -> s1 + s2);
这里有两个异步的任务,使用thenCombine方法来组合两个Future,thenCombine方法的第二个参数就是用来合并两个Future方法返回值的操做函数。
有时候,咱们并不须要等待全部的异步任务结束,只须要其中的一个完成就能够了,CompletableFuture也提供了这样的方法:
//假设getStream方法返回一个Stream<CompletableFuture<String>> CompletableFuture[] futures = getStream(“listen”).map(f -> f.thenAccept(System.out::println)).toArray(CompletableFuture[]::new); //等待其中的一个执行完毕 CompletableFuture.anyOf(futures).join(); 使用anyOf方法来响应CompletableFuture的completion事件。
Java虚拟机(JVM)的新特性
PermGen(永久代)空间被移除了,取而代之的是Metaspace(JEP 122)。JVM选项-XX:PermSize与-XX:MaxPermSize分别被-XX:MetaSpaceSize与-XX:MaxMetaspaceSize所代替。
总结
更多展望:Java 8经过发布一些能够增长程序员生产力的特性来推动这个伟大的平台的进步。如今把生产环境迁移到Java 8还为时尚早,可是在接下来的几个月里,它会被大众慢慢的接受。毫无疑问,如今是时候让你的代码与Java 8兼容,而且在Java 8足够安全稳定的时候迁移到Java 8。