Java JVM(五):JDK8 新特性
2015-08-29 19:05
639 查看
Java8 是一个重大改变的版本。比如说增加了Lambda表达式,集合的流式操作,函数式接口,接口默认方法
等。
一. 函数式接口
如果一个接口定义唯一一个抽象方法,那么这个接口就可以成为函数式接口,如 Runnable,Callable 接口。
可以在函数式接口前加一个 @FunctionalInterface注解,非必须。在接口中添加了@FunctionalInterface,只允许有一个抽象方法,否则编译器也报错。Java8
中 Runnable 接口就是一个函数式接口,如下:
像Runnable,Callable等有且只有一个抽象方法的接口,一般称为单一抽象方法接口,我们平常使用它的时候,比较习惯于使用一个内部类,这至少也要几行的代码,如下:
如果我们声明为函数式接口,再结合Lambda表达式,那么一行代码就可以搞定,比较方便。如下代码:
输出为:
当然,一个接口也可以继承另外一个函数式接口,只要它不再声明抽象方法(可以使用默认方法),那么它也是一个函数式接口。
例如:
总结:好处在哪里?
结合Lambda表达式,比之前更加的简便。
二.lambda 表达式
lambda 表达式是一个匿名方法,可由三部分组成: 参数列表,箭头( ->),表达式或者语句块。利用Lambda表达式,可以把一个几行的语句变成一个非常简单的语句。
lambda表达式我们也可以把它当做一种类型,像基本类型,引用类型一样,我们可以把它当做是一种目标类型,这种目标类型就是函数式接口。一个lambda表达式必须对应至少一个目标类型,也就是必须对应一种函数接口。
一个lambda表达式只有在转型成一个函数接口后才能被当做Object来进行使用,所以说我们要当Object来使用必须先转型。
Function:
在java.util.function包中 预定义了很多函数式接口以避免用户重复定义。最典型的就是Function:
这个接口代表一个函数,接受一个 T 类型的参数,并且返回一个 R 类型的返回值。
Consumer:
另一个预定义函数接口叫做 Consumer,跟 Function 的唯一不同是它没有返回值。
Predicate:
用于判断某项条件是否满足。经常用来进行筛选操作。
综合来说:
一个Lambda表达式就是定义了一个匿名方法,只不过这个方法必须符合至少一个函数接口。
用处:
lambda表达式主要用于替换以前广泛使用的匿名内部类,各种回调,事件响应器等。
例如:
在这里,比如说第二个线程中的lambda表达式,你并不需要显示地转换为一个Runnable,因为Java可以根据上下文自动推断出一个Thread 的构造函数接受一个Runnable
参数,而传入的 lambda表达式正好符合其 run() 函数,所以 Java编译器推断它为 Runnable。
从形式上来看, lambda 表达式只是节省了几行代码,但是从目前来说,lambda表达式可以配合
"集合类批处理操作"的内部迭代和并行处理;另外,长期来看,Java 也好像是要向函数式编程语言这个方面来引导。
三.集合的批处理和流式操作:
3.1 批处理
集合的批处理和流式操作再配合Lambda表达式应该算是Java8 中最重要的改进,主要是希望在对集合的操作中,可以充分利用现代多核 CPU 来进行并行计算。
我们之前 Java8 之前的集合操作都是外部迭代的,也就是写一些 for 循环这种。那么现在的集合类都有一个forEach()方法,来对元素实现迭代,这个forEach方法就在Iterable接口中,Collection接口继承了它,同时,Map接口也有一个forEach方法,forEach()
方法可以接受一个函数式接口Consumer作为参数,所以说可以使用lambda表达式。
3.2 流式操作
Java8为集合类引入了另外一个重要概念:流。一个流通常以一个集合类实例作为其数据源,然后在其上定义各种操作。流的 API 设计使用了管道。对一个流的操作会返回另一个流。如同 IO 的API 或者 StringBuffer的
append 方法那样,从而多个不同的操作可以在一个语句串起来。看下面的方法:
首先调用 stream() 方法,以集合类对象 shapes 里面的元素作为数据源,生成一个流。然后在这个流上调用 filter 方法,挑出蓝色的,返回另外一个流。最后调用forEach() 方法将这些蓝色的物体喷成红色。(forEach方法不再返回流,而是一个终端方法,类似于StringBuffer在调用若干append之后的那个toString)
filter 方法的参数是 Predicate 类型。forEach方法的参数是Consumer 类型,它们都是函数式接口,所以可以使用lambda表达式。
还有一个方法叫做parallelStream(),它和Stream()一样,只不过要指明并行处理,希望能充分利用现代CPU的多核特性。
例子: 给出一个String 类型的数组,找出其中所有不重复的素数
第一步:传入一系列String(假设都是合法的数字),转成一个List,然后调用stream()方法生成流。
第二步:调用流的map方法把每个元素由String转成Integer,得到一个新的流。map方法接受一个Function类型的参数,上面介绍了,Function是个函数接口,所以这里用λ表达式。
第三步:调用流的filter方法,过滤那些不是素数的数字,并得到一个新流。filter方法接受一个Predicate类型的参数,上面介绍了,Predicate是个函数接口,所以这里用λ表达式。
第四步:调用流的distinct方法,去掉重复,并得到一个新流。这本质上是另一个filter操作。
第五步:用collect方法将最终结果收集到一个List里面去。collect方法接受一个Collector类型的参数,这个参数指明如何收集最终结果。在这个例子中,结果简单地收集到一个List中。我们也可以用Collectors.toMap(e->e, e->e)把结果收集到一个Map中,它的意思是:把结果收到一个Map,用这些素数自身既作为键又作为值。toMap方法接受两个 Function类型的参数,分别用以生成键和值,Function是个函数接口,所以这里都用λ表达式。
你可能会觉得在这个例子里,List l被迭代了好多次,map,filter,distinct都分别是一次循环,效率会不好。实际并非如此。这些返回另一个Stream的方法都是“懒 (lazy)”的,而最后返回最终结果的collect方法则是“急(eager)”的。在遇到eager方法之前,lazy的方法不会执行。
当遇到eager方法时,前面的lazy方法才会被依次执行。而且是管道贯通式执行。这意味着每一个元素依次通过这些管道。例如有个元素“3”,首 先它被map成整数型3;然后通过filter,发现是素数,被保留下来;又通过distinct,如果已经有一个3了,那么就直接丢弃,如果还没有则保 留。这样,3个操作其实只经过了一次循环。
除collect外其它的eager操作还有forEach,toArray,reduce等。
下面来看一下也许是最常用的收集器方法,groupingBy:
注意这一行:
它的意思是:把结果收集到一个Map中,用统计到的各个素数自身作为键,其出现次数作为值。
下面是一个reduce的例子:
reduce方法用来产生单一的一个最终结果。
流有很多预定义的reduce操作,如sum(),max(),min()等。
再举个现实世界里的栗子比如:
Ps:
给Java 集合类增加批量数据的支持,通常称这种批量数据操作为 "Java 中的 filter/map/reduce"。批量操作有并行和串行两种操作模式。我们期望可以利用底层平台的并行特性。
JDK 中已经增加了一个新包:java.util.stream包,能够使java8 集合类库执行类似 filter/map/reduce 的操作。这个流式API 能够使我们在数据流之上编写串行或者并行的操作。比如说:
四. 接口默认方法
Java8 可以在 interface 中有方法的实现,也称为默认方法。那么 interface 中 有了方法的实现,也就是说,可以实现多继承,(当然之前利用一些方法也可以实现多继承,但是Java 一直不提倡多继承)。
那么为什么现在加入了默认方法实现多继承呢?因为 interface 太过依赖他们的实现类了,要往 interface中加入一个方法就必须修改所有的实现类。那么Java8 中加入一些特性可能需要修改一些核心类,但是很多核心类不仅仅JDK中实现,很多第三方库中也会有实现,那么一改动可能就会出现兼容性问题。那么默认方法就可以很好的处理这个问题。
代码示例:
注意,需要加一个 default。
参考:
1.http://www.ibm.com/developerworks/cn/java/j-lo-jdk8newfeature/index.html
2.http://blog.sanaulla.info/2013/03/21/introduction-to-functional-interfaces-a-concept-recreated-in-java-8/
3.http://blog.csdn.net/ioriogami/article/details/12782141
等。
一. 函数式接口
如果一个接口定义唯一一个抽象方法,那么这个接口就可以成为函数式接口,如 Runnable,Callable 接口。
可以在函数式接口前加一个 @FunctionalInterface注解,非必须。在接口中添加了@FunctionalInterface,只允许有一个抽象方法,否则编译器也报错。Java8
中 Runnable 接口就是一个函数式接口,如下:
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
像Runnable,Callable等有且只有一个抽象方法的接口,一般称为单一抽象方法接口,我们平常使用它的时候,比较习惯于使用一个内部类,这至少也要几行的代码,如下:
public class AnonymousInnerClassTest { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("A thread created and running ..."); } }).start(); } }
如果我们声明为函数式接口,再结合Lambda表达式,那么一行代码就可以搞定,比较方便。如下代码:
/* * Implementing the interface by creating an * anonymous inner class versus using * lambda expression. */ public class SimpleFunInterfaceTest { public static void main(String[] args) { carryOutWork(new SimpleFuncInterface() { @Override public void doWork() { System.out.println("Do work in SimpleFun impl..."); } }); carryOutWork(() -> System.out.println("Do work in lambda exp impl...")); } public static void carryOutWork(SimpleFuncInterface sfi){ sfi.doWork(); } }
输出为:
Do work in SimpleFun impl... Do work in lambda exp impl...
当然,一个接口也可以继承另外一个函数式接口,只要它不再声明抽象方法(可以使用默认方法),那么它也是一个函数式接口。
例如:
@FunctionalInterface public interface ComplexFunctionalInterface extends SimpleFuncInterface { default public void doSomeWork(){ System.out.println("Doing some work in interface impl..."); } default public void doSomeOtherWork(){ System.out.println("Doing some other work in interface impl..."); } }
总结:好处在哪里?
结合Lambda表达式,比之前更加的简便。
二.lambda 表达式
lambda 表达式是一个匿名方法,可由三部分组成: 参数列表,箭头( ->),表达式或者语句块。利用Lambda表达式,可以把一个几行的语句变成一个非常简单的语句。
lambda表达式我们也可以把它当做一种类型,像基本类型,引用类型一样,我们可以把它当做是一种目标类型,这种目标类型就是函数式接口。一个lambda表达式必须对应至少一个目标类型,也就是必须对应一种函数接口。
一个lambda表达式只有在转型成一个函数接口后才能被当做Object来进行使用,所以说我们要当Object来使用必须先转型。
Function:
在java.util.function包中 预定义了很多函数式接口以避免用户重复定义。最典型的就是Function:
@FunctionalInterface public interface Function<T, R> { R apply(T t); ...... }
这个接口代表一个函数,接受一个 T 类型的参数,并且返回一个 R 类型的返回值。
Consumer:
另一个预定义函数接口叫做 Consumer,跟 Function 的唯一不同是它没有返回值。
@FunctionalInterface public interface Consumer<T> { void accept(T t); ...... }
Predicate:
@FunctionalInterface public interface Predicate<T> { boolean test(T t); ...... }
用于判断某项条件是否满足。经常用来进行筛选操作。
综合来说:
一个Lambda表达式就是定义了一个匿名方法,只不过这个方法必须符合至少一个函数接口。
用处:
lambda表达式主要用于替换以前广泛使用的匿名内部类,各种回调,事件响应器等。
例如:
Thread oldSchool = new Thread( new Runnable () { @Override public void run() { System.out.println("This is from an anonymous class."); } });
Thread gaoDuanDaQiShangDangCi = new Thread( () -> { System.out.println("This is from an anonymous method (lambda exp)."); });
在这里,比如说第二个线程中的lambda表达式,你并不需要显示地转换为一个Runnable,因为Java可以根据上下文自动推断出一个Thread 的构造函数接受一个Runnable
参数,而传入的 lambda表达式正好符合其 run() 函数,所以 Java编译器推断它为 Runnable。
从形式上来看, lambda 表达式只是节省了几行代码,但是从目前来说,lambda表达式可以配合
"集合类批处理操作"的内部迭代和并行处理;另外,长期来看,Java 也好像是要向函数式编程语言这个方面来引导。
三.集合的批处理和流式操作:
3.1 批处理
集合的批处理和流式操作再配合Lambda表达式应该算是Java8 中最重要的改进,主要是希望在对集合的操作中,可以充分利用现代多核 CPU 来进行并行计算。
我们之前 Java8 之前的集合操作都是外部迭代的,也就是写一些 for 循环这种。那么现在的集合类都有一个forEach()方法,来对元素实现迭代,这个forEach方法就在Iterable接口中,Collection接口继承了它,同时,Map接口也有一个forEach方法,forEach()
方法可以接受一个函数式接口Consumer作为参数,所以说可以使用lambda表达式。
3.2 流式操作
Java8为集合类引入了另外一个重要概念:流。一个流通常以一个集合类实例作为其数据源,然后在其上定义各种操作。流的 API 设计使用了管道。对一个流的操作会返回另一个流。如同 IO 的API 或者 StringBuffer的
append 方法那样,从而多个不同的操作可以在一个语句串起来。看下面的方法:
List<Shape> shapes = ... shapes.stream() .filter(s -> s.getColor() == BLUE) .forEach(s -> s.setColor(RED));
首先调用 stream() 方法,以集合类对象 shapes 里面的元素作为数据源,生成一个流。然后在这个流上调用 filter 方法,挑出蓝色的,返回另外一个流。最后调用forEach() 方法将这些蓝色的物体喷成红色。(forEach方法不再返回流,而是一个终端方法,类似于StringBuffer在调用若干append之后的那个toString)
filter 方法的参数是 Predicate 类型。forEach方法的参数是Consumer 类型,它们都是函数式接口,所以可以使用lambda表达式。
还有一个方法叫做parallelStream(),它和Stream()一样,只不过要指明并行处理,希望能充分利用现代CPU的多核特性。
例子: 给出一个String 类型的数组,找出其中所有不重复的素数
public void distinctPrimary(String... numbers) { List<String> l = Arrays.asList(numbers); List<Integer> r = l.stream() .map(e -> new Integer(e)) .filter(e -> Primes.isPrime(e)) .distinct() .collect(Collectors.toList()); System.out.println("distinctPrimary result is: " + r); }
第一步:传入一系列String(假设都是合法的数字),转成一个List,然后调用stream()方法生成流。
第二步:调用流的map方法把每个元素由String转成Integer,得到一个新的流。map方法接受一个Function类型的参数,上面介绍了,Function是个函数接口,所以这里用λ表达式。
第三步:调用流的filter方法,过滤那些不是素数的数字,并得到一个新流。filter方法接受一个Predicate类型的参数,上面介绍了,Predicate是个函数接口,所以这里用λ表达式。
第四步:调用流的distinct方法,去掉重复,并得到一个新流。这本质上是另一个filter操作。
第五步:用collect方法将最终结果收集到一个List里面去。collect方法接受一个Collector类型的参数,这个参数指明如何收集最终结果。在这个例子中,结果简单地收集到一个List中。我们也可以用Collectors.toMap(e->e, e->e)把结果收集到一个Map中,它的意思是:把结果收到一个Map,用这些素数自身既作为键又作为值。toMap方法接受两个 Function类型的参数,分别用以生成键和值,Function是个函数接口,所以这里都用λ表达式。
你可能会觉得在这个例子里,List l被迭代了好多次,map,filter,distinct都分别是一次循环,效率会不好。实际并非如此。这些返回另一个Stream的方法都是“懒 (lazy)”的,而最后返回最终结果的collect方法则是“急(eager)”的。在遇到eager方法之前,lazy的方法不会执行。
当遇到eager方法时,前面的lazy方法才会被依次执行。而且是管道贯通式执行。这意味着每一个元素依次通过这些管道。例如有个元素“3”,首 先它被map成整数型3;然后通过filter,发现是素数,被保留下来;又通过distinct,如果已经有一个3了,那么就直接丢弃,如果还没有则保 留。这样,3个操作其实只经过了一次循环。
除collect外其它的eager操作还有forEach,toArray,reduce等。
下面来看一下也许是最常用的收集器方法,groupingBy:
//给出一个String类型的数组,找出其中各个素数,并统计其出现次数 public void primaryOccurrence(String... numbers) { List<String> l = Arrays.asList(numbers); Map<Integer, Integer> r = l.stream() .map(e -> new Integer(e)) .filter(e -> Primes.isPrime(e)) .collect( Collectors.groupingBy(p->p, Collectors.summingInt(p->1)) ); System.out.println("primaryOccurrence result is: " + r); }
注意这一行:
Collectors.groupingBy(p->p, Collectors.summingInt(p->1))
它的意思是:把结果收集到一个Map中,用统计到的各个素数自身作为键,其出现次数作为值。
下面是一个reduce的例子:
//给出一个String类型的数组,求其中所有不重复素数的和 public void distinctPrimarySum(String... numbers) { List<String> l = Arrays.asList(numbers); int sum = l.stream() .map(e -> new Integer(e)) .filter(e -> Primes.isPrime(e)) .distinct() .reduce(0, (x,y) -> x+y); // equivalent to .sum() System.out.println("distinctPrimarySum result is: " + sum); }
reduce方法用来产生单一的一个最终结果。
流有很多预定义的reduce操作,如sum(),max(),min()等。
再举个现实世界里的栗子比如:
// 统计年龄在25-35岁的男女人数、比例 public void boysAndGirls(List<Person> persons) { Map<Integer, Integer> result = persons.parallelStream().filter(p -> p.getAge()>=25 && p.getAge()<=35). collect( Collectors.groupingBy(p->p.getSex(), Collectors.summingInt(p->1)) ); System.out.print("boysAndGirls result is " + result); System.out.println(", ratio (male : female) is " + (float)result.get(Person.MALE)/result.get(Person.FEMALE)); }
Ps:
给Java 集合类增加批量数据的支持,通常称这种批量数据操作为 "Java 中的 filter/map/reduce"。批量操作有并行和串行两种操作模式。我们期望可以利用底层平台的并行特性。
JDK 中已经增加了一个新包:java.util.stream包,能够使java8 集合类库执行类似 filter/map/reduce 的操作。这个流式API 能够使我们在数据流之上编写串行或者并行的操作。比如说:
List persons = .. //串行操作 Stream stream=persons.stream(); //并行操作 Stream parallelStream=persons.parallelStream();
四. 接口默认方法
Java8 可以在 interface 中有方法的实现,也称为默认方法。那么 interface 中 有了方法的实现,也就是说,可以实现多继承,(当然之前利用一些方法也可以实现多继承,但是Java 一直不提倡多继承)。
那么为什么现在加入了默认方法实现多继承呢?因为 interface 太过依赖他们的实现类了,要往 interface中加入一个方法就必须修改所有的实现类。那么Java8 中加入一些特性可能需要修改一些核心类,但是很多核心类不仅仅JDK中实现,很多第三方库中也会有实现,那么一改动可能就会出现兼容性问题。那么默认方法就可以很好的处理这个问题。
代码示例:
interface ITest { public default void sayHello(){ System.out.println("Hello"); } } public class Test implements ITest{ public static void main(String[] args) { new Test().sayHello(); } }
注意,需要加一个 default。
参考:
1.http://www.ibm.com/developerworks/cn/java/j-lo-jdk8newfeature/index.html
2.http://blog.sanaulla.info/2013/03/21/introduction-to-functional-interfaces-a-concept-recreated-in-java-8/
3.http://blog.csdn.net/ioriogami/article/details/12782141
相关文章推荐
- jdk8的datetime时间函数使用示例
- C++中的Lambda表达式详解
- Java8新特性lambda表达式有什么用(用法实例)
- Linux(CentOs6.3_64)下JDK8_64安装
- 委托、Lambda表达式和事件
- 用Java8 Stream和 Lambda表达式来解析文件的一个例子
- GreenTeaJUG活动 第16期 性能调优利器——PerfJ
- Java PermGen 去哪里了?
- C# λ运算符=>匿名方法 lambda表达式
- asp.net lambda表达式的用法
- 認識 Lambda/Closure
- 鲜活的java 8 --- java 8 进化之路
- C# Lambda表达式
- JDK8 部分代码测试
- C# Lambda表达式 //作者:Kingmoon
- Java JVM(六):JDK8 元空间
- Java8新特性——Lambda表达式
- 解决MyEclipse jdk 8 报错问题
- Error: Registry key 'Software\JavaSoft\Java Runtime Environment'\CurrentVersion' 解决方案
- C++ Primer阅读心得(第十章、第十一章)