java 8 lambdas深度研究

博客地址 http://colobu.com

Java 8发布有一段日子, 大家关注Java 8中的lambda可能更早, 对Java 8中这一最重要的语言变化也基本熟悉了。这篇文章将深入研究Java 8中的lambda特性以及Stream接口等, 讨论一些深层次的技术细节。

比如, 一个lambda表达式序列化反序列化后, 对捕获的上下文变量的引用的情况。 Lambda表达式递归。 类方法的引用和实例方法的引用的区别。 菱形继承的问题。 Stream接口的Lazy和eager模式。 Lambda的性能。

Java Lambda语法

尽管你已经很熟悉了, 我们还是先回顾一下lambda表达式的语法。

“A lambda expression is like a method: it provides a list of formal parameters and a body—an expression or block—expressed in terms of those parameters,”

JSR 335

1
Function<Integer, Integer> fun = (Integer x, Integer y) -> {return x + y;}

如果body只有一个表达式,可以省略body的·大括号 和 return

1
Function<Integer, Integer> fun = (Integer x, Integer y) -> x + y

参数可以声明类型,也可以根据类型推断而省略。

1
Function<Integer, Integer> fun = (x, y) -> x + y

但是不能部分省略。

1
Function<Integer, Integer> fun = (x, Integer y) -> x + y //wrong

单个的参数可以省略括号。

1

2
Function<Integer, Integer> fun = (x) -> x+1

Function<Integer, Integer> fun = x -> x+1

但是不能加上类型声明。

1
Function<Integer, Integer> fun = Integer x -> x+1 //wrong

如果没有参数, 括号是必须的。

1

2
() -> 1995

() -> { System.gc(); }

Java Lambda递归

匿名函数是没有名字的, 但是Lambda表达式可以赋值给一个变量或者作为参数传递, 这意味着它有”名字”。 那么可以利用这个名字进行递归吗?

lambdafaq网站说可以。

1

2
Function<Long, Long> fib = x -> {if (x ==1 || x == 2) return 1L; else return fib.apply(x -1) + x;};

System.out.println(fib.apply(3L));

实际你并不能编译这段代码, 因为编译器认为fib可能没有初始化。

1
The local variable fib may not have been initialized

没办法递归了吗?

有一些hacked方法, 如

1

2
IntToDoubleFunction[] foo = { null };

foo[0] = x -> { return  ( x == 0)?1:x* foo[0].applyAsDouble(x-1);};

或者 (泛型数组的创建有些麻烦)

1

2

3

4

5

6

7

8

9

10

11

12

13

14
   @SuppressWarnings("unchecked")

private static <E> E[] newArray(Class clazz, int size)

{

return (E[]) Array.newInstance(clazz, size);  

}

public static void main(String[] args) throws InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException {

//Function<Long, Long> fib = x -> {if (x ==1 || x == 2) return 1L; else return fib.apply(x -1) + x;};

Function<Long, Long>[] funs = newArray(Function.class, 1);

funs[0] = x -> {if (x ==1 || x == 2) return 1L; else return funs[0].apply(x -1) + x;};

System.out.println(funs[0].apply(10L));

}

或者使用一个helper类。

1

2
BiFunction<BiFunction, Long, Long> factHelper = (f, x) -> {if (x ==1 || x == 2) return 1L; else return x + (long)f.apply(f,x-1);};

Function<Long, Long> fib = x -> factHelper.apply(factHelper, x);

捕获变量

就像本地类和匿名类一样, Lambda表达式可以捕获变量(capture variable)。

In addition, a local class has access to local variables. However, a local class can only access local variables that are declared final. When a local class accesses a local variable or parameter of the enclosing block, it captures that variable or parameter

但是Lambda表达式不强迫你将变量声明为final, 只要它的行为和final 变量一样即可,也就是等价final.

下面的例子s不必声明为final,实际加上final也不会编译出错。

1

2

3
String s = "smallnest";

Runnable r = () -> System.out.println("hello " + s);

r.run();

但是下面的例子s实际已经不是final了,编译会出错。,

1

2

3

4
String s = "smallnest";

Runnable r = () -> System.out.println("hello " + s);

s = "colobu";

r.run();

下面的代码一样也会编译不成功:

1

2

3
String s = "smallnest";

Runnable r = () -> {s = "abc"; System.out.println("hello " + s);};

r.run();

注意final仅仅是变量不能再被赋值, 而变量字段的值是可以改变的。

1

2

3

4

5
Sample s = new Sample();

s.setStr("smallnest");

Runnable r = () -> System.out.println("hello " + s.getStr());

s.setStr("colobu");

r.run();

这里我们可以更改s的str字段的值。

序列化

一般序列化/反序列化

Lambda表达式可以被序列化。下面是一个简单的例子。

1

2

3

4

5

6

7

8

9

10
Runnable r = (Runnable & Serializable)() -> System.out.println("hello serialization");

FileOutputStream fos = new FileOutputStream("Runnable.lambda");

ObjectOutputStream os = new ObjectOutputStream(fos);

os.writeObject(r);

FileInputStream fis = new FileInputStream("Runnable.lambda");

ObjectInputStream is = new ObjectInputStream(fis);

r = (Runnable) is.readObject();

r.run();

注意(Runnable & Serializable)是Java 8中新的语法。 cast an object to an intersection of types by adding multiple bounds.

一个Lambda能否序列化, 要以它捕获的参数以及target type能否序列化为准。当然,不鼓励在实践中使用序列化。上面的例子r实现了Serializable接口,而且没有captured argument,所以可以序列化。

带捕获变量的序列化/序列化

再看一个带captured argument的例子。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28
	class Sample implements Serializable {

private String str;

public String getStr() {

return str;

}

public void setStr(String str) {

this.str = str;

}

}

public static void serializeLambda() throws Exception {

Sample s = new Sample();

s.setStr("smallnest");

SampleSerializableInterface r = () -> System.out.println("hello " + s.getStr());

FileOutputStream fos = new FileOutputStream("Runnable.lambda");

ObjectOutputStream os = new ObjectOutputStream(fos);

os.writeObject(r);

s.setStr("colobu");

}

public static void deserializeLambda() throws Exception {

FileInputStream fis = new FileInputStream("Runnable.lambda");

ObjectInputStream is = new ObjectInputStream(fis);

SampleSerializableInterface r = (SampleSerializableInterface) is.readObject();

r.run();

}

可以看到连同captured argument s一同序列化了。 即使反序列化出来,captured argument也不是原来的s了。

结果输出hello smallnest

方法引用

方法引用是一个有趣的特性, 方法类似指针一样可以被直接引用。 新的操作符”::”用来引用类或者实例的方法。

static method reference

1

2

3

4
BiFunction<Long,Long,Integer> bf = Long::compare;

Long a = 10L;

Long b = 11L;

System.out.println(bf.apply(a, b));

instance method reference

1

2
Consumer<String> c = System.out::println;

c.accept("hello colobu");

以上两种情况引用的方法签名应和 target type的方法签名一样,方法的名字不一定相同。

arbitrary instance reference

1

2
String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };

Arrays.sort(stringArray, String::compareToIgnoreCase);

这是一个很有趣的使用方法。 可以引用任意的一个类型的实例。等价的lambda表达式的参数列表为(String a, String b),方法引用会调用a.compareToIgnoreCase(b)。

constructor reference

另一种特殊的方法引用是对构造函数的引用。

对构造函数的引用类似对静态方法的引用,只不过方法名是new。 一个类有多个构造函数, 会根据target type选择最合适的构造函数。

多继承

由于Java 8引入了缺省方法(default method)的特性,Java也想其它语言如C++一样遇到了多继承的问题。这里列出两个典型的多继承的情况。

三角继承

三角继承如下图所示。

A

|\

| \

| B

| /

C

1

2

3

4

5

6

7

8

9

10
	class A {

public A () {

System.out.println("A()");

}

public A(int x) {

System.out.println("A(int x)");

}

}
1

2
C c = new C();

c.say(); //B says

菱形继承

菱形继承如下图所示

A

/\

/ \

B C

\ /

D

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23
interface A {	 

default void say() {

System.out.println("A says");

}

}

interface B extends A{

default void say() {

System.out.println("B says");

}

}

interface C extends A{

default void say() {

System.out.println("C says");

}

}

class D implements A, B, C{

}

直接编译出错。原因是Duplicate default methods.

你需要在D中重载say方法, 自定义或者使用父类/接口的方法。 注意其写法接口.super.default_method_name

1

2

3

4

5
	class D implements A, B, C{

public void say() {

B.super.say();

}	 

}

叉型继承

叉型继承如下图所示

B C

\ /

D

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16
interface B {	 

default void say() {

System.out.println("B says");

}

}

class C {

public void say() {

System.out.println("C says");

}

}

class D extends C implements  B{

}

上面的代码输出C says

原则:

基本上,你可以根据以下三条原则判断多继承的实现规则。

  1. 类优先于接口。 如果一个子类继承的父类和接口有相同的方法实现。 那么接口中的定义会被忽略。 如第三个例子。
  2. 子类型中的方法优先于府类型中的方法。 如第一个例子。
  3. 如果以上条件都不满足, 如第二个例子,则必须显示覆盖/实现其方法,或者声明成abstract。

Stream接口的lazy方法和eager方法

新增加的Stream接口有很多方法。

lazy例子:

1
allStudents.stream().filter(s -> as.age> 16);

filter并不会马上对列表进行遍历筛选, 它只是为stream加上一些”秘方”。当前它的方法实现不会被执行。直到遇到eager类型的方法它才会执行。

eager例子:

1
allStudents.stream().filter(s -> as.age> 16).count();

原则:

看方法的返回值。 如果返回Stream对象,那么它是lazy的, 如果返回其它类型或者void,那它是eager的,会立即执行。

Functional interface只能有一个方法吗?

Functional interface又被称作Single Abstract Method (SAM)或者Role Interface

那么接口中只能声明一个方法吗?

上面的例子也表明, 你可以在functional interface中定义多个default method。 事实上java.util.function下好多functional interface都定义default method.

那么除去default method, functional interface可以声明多个的方法吗?看个例子

1

2

3

4

5
interface MyI {

void apply(int i);

String toString();

}

MyI声明了两个方法apply和toString()。 它能作为一个lambda 表达式的target type吗?

1

2
MyI m = x -> System.out.println(x);	

m.apply(10);

没问题, 代码可以正常编译, 程序正常运行。

但是, 等等, 不是functional interface只能声明一个abstract的方法吗?

事实上你看第二个方法比较特殊,它和Object的方法签名相同。它是对象隐性实现的一个方法,所以可以忽略它。

同样,interface Foo { boolean equals(Object obj); }也不是一个functional interface,因为没有声明一个方法。

``` java

interface Foo {

int m();

Object clone();

}

也不是一个functional interface, 因为Object.clone不是public类型的。

Lambda的性能

Oracle公司的性能工程师Sergey Kuksenko有一篇很好的性能比较的文档: JDK 8: Lambda Performance study, 详细而全面的比较了lambda表达式和匿名函数之间的性能差别。这里是视频。 16页讲到最差(capture)也和inner class一样, non-capture好的情况是inner class的5倍。

lambda开发组也有一篇ppt, 其中也讲到了lambda的性能(包括capture和非capture的情况)。看起来lambda最差的情况性能内部类一样, 好的情况会更好。

Java 8 Lambdas - they are fast, very fast也有篇文章 (可能需要翻墙),表明lambda表达式也一样快。

Java
8 Lambda Performance Comparison

时间: 2024-10-19 13:52:04

java 8 lambdas深度研究的相关文章

Java 8: Lambdas和新的集合Stream API

Lambda是Java8的主要特色,Java 8: Lambdas & Java Collections | zeroturnaround.com一文介绍了使用Lambda集合处理大量数据的方法. 首先Java集合引入了内部遍历,原来 LambdaJ下面这种方法也可以在Java8中实现: List<Person> persons = asList(new Person("Joe"), new Person("Jim"), new Person(&

&quot;如何用70行Java代码实现深度神经网络算法&quot; 的delphi版本

http://blog.csdn.net/hustjoyboy/article/details/50721535 "如何用70行Java代码实现深度神经网络算法" 的delphi版本 2016-02-23 10:58 225人阅读 评论(0) 收藏 举报 版权声明:本文为博主原创文章,未经博主允许不得转载. =====ann.pas源程序=================================== { by 阿甘 2016.2.23 参考自此篇文档如何用70行Java代码实现

JAVA对象任意深度克隆clone工具类分享

原文:JAVA对象任意深度克隆clone工具类分享 源代码下载地址:http://www.zuidaima.com/share/1550463408114688.htm package com.zuidaima.n_app.util; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import jav

深度研究分析互联网地下产业链的目的

中国互联网行业有一定的特殊性:有国情决定了用户需求的多样性,而主流的互联网产品却很难满足所有的用户心理诉求:加上最近几年互联网高速发展带来的红利,以及技术成本的不断降低,都催生了中国互联网地下产业链. 地下产业链并不是消极的事物,在一定程度上讲,地下产业链更接近用户需求,更能还原互联网的本貌.在中国的传统行业都有一定的所谓黑市门槛,即属于潜规则范畴或者是不为人知的行业秘密,这种信息只流传在最信任的人脉圈子里,读懂了这些,才能算从根本上了解了这个行业. 对于互联网创新者来说,地下产业链代表着最接地

对java:comp/env的研究(转)

对java:comp/env的研究 http://f543711700.iteye.com/blog/1173618 这两天研究了一下 context.lookup("java:comp/env/XXX")和直接context.lookup("XXX")的区别 网上关于这两个的文章也很多,但是都说得很难理解,比如什么ENC环境啊什么的,各种概念. 其实说得简单点:context.lookup("java:comp/env/XXX")只能用在J2E

java 动态代理深度学习(Proxy,InvocationHandler),含$Proxy0源码

java 动态代理深度学习, 一.相关类及其方法: java.lang.reflect.Proxy,Proxy 提供用于创建动态代理类和实例的静态方法.newProxyInstance()返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序(详见api文档) java.lang.reflect.InvocationHandler,InvocationHandler 是代理实例的调用处理程序 实现的接口. invoke()在代理实例上处理方法调用并返回结果.在与方法关联的代理

Java 5 泛型深入研究

Java 5泛型深入研究 上接<Java 泛型的理解与等价实现>,这个仅仅是泛型的入门.有博友反映泛型很复杂,难以掌握.鉴于此,写一片续集. 实际上泛型可以用得很复杂,复杂到编写代码的人员自己也难以看懂.这往往是对泛型的滥用或者类或接口本身设计不合理导致的. 看来用好泛型还真不容易,为此必须从根源说起. 一.逐渐深入泛型 1.没有任何重构的原始代码: 有两个类如下,要构造两个类的对象,并打印出各自的成员x. public class StringFoo { private String x;

JAVA CAS原理深度分析(转)

看了一堆文章,终于把JAVA CAS的原理深入分析清楚了. 感谢GOOGLE强大的搜索,借此挖苦下百度,依靠百度什么都学习不到! 参考文档: http://www.blogjava.net/xylz/archive/2010/07/04/325206.html http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html http://www.searchsoa.com.cn/showcontent_69238.

JAVA CAS原理深度分析

参考文档: http://www.blogjava.net/xylz/archive/2010/07/04/325206.html http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html http://www.searchsoa.com.cn/showcontent_69238.htm http://ifeve.com/atomic-operation/ http://www.infoq.com/cn/ar