lambda

前言

函数式编程:没有共享的可变数据 + 将方法和函数(代码)作为一等值来传递

行为参数化:一个方法接收多个不同的行为作为参数,并在内部使用它们,完成不同行为的能力

名词解释:

  • predicate(谓词):函数以及方法引用。

  • lambda表达式:匿名函数

java8主要的新特性:

  • lambda表达式
  • 方法引用
  • 流(Collections用来存储和访问数据 ;Stream描述对数据的计算,用来并行的处理数据)
  • 默认方法(扩展接口)

通过行为参数传递代码

将行为参数化是可以帮助我们处理频繁变更的需求的一种软件开发模式,它能够轻松的适应不断变化的需要,可以把一个行为(一段代码)封装起来,并通过传递和使用创建的行为(创建实现谓词的接口的类的实例)将方法的行为参数化。

在java8之前,我们会定义一个接口(谓词)来将行为参数化。然后我们创建不同的实现类继承该接口来实现不同的行为,最后将不同实现类的实例对象传递给对应的函数就完成了行为(代码)传递工作。这种做法就类似于在内联”传递代码”,将需要传递的代码包裹在一个实现了接口的类里面传递给调用的方法。

同时为了改善代码,让它更加简洁,我们面对很多只要实例化一次的类,可以采用匿名类来同时声明和实例化一个类,也就是说匿名类允许我们随建随用。

但是使用匿名类还是不够好,代码不易读,我们还是需要创建一个对象,明确地实现一个方法来定义一个新的行为。为了更加契合软件工程中DPY原则(Don’t Repeat Yourself),java8引人了lambda表达式(匿名函数)!

Lambda表达式

Lambda表达式:匿名的方法,可以作为参数传递或者存储在变量中

Lambda表达式语法结构:

  • (parameters) -> expression
  • (parameters) -> {statements;}

parameters是参数列表,后续是Lambda表达式的主体,主体内如果是表达式则不需要花括号和分号,如果是语句的化则需要花括号与分号。注意区分。

例子:

1
2
3
4
5
Comparator<Apple> byWeight = new Comparator<Apple>() {
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
};

我们使用Lambda表达式(直接赋值给变量)

1
Comparator<Apple> byWeight = (Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

在哪可以使用Lambda表达式

函数式接口:只定义了一个抽象方法的接口;

在定义函数式接口时我们为该接口添加了@FunctionalInterface注解,用于标记该接口是函数式接口,不过这个接口是可选的,当添加了该接口之后,编译器就限制了该接口只允许有一个抽象方法,否则报错,所以推荐为函数式接口添加该注解。

Lambda表达式允许我们直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。(具体来说,是函数式接口的一个具体实现的实例)当然,我们使用匿名类同样可以完成这个操作,只不过是比较笨拙。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
Runnable r1 = () -> System.out.println("Hello World 1") ;
Runnable r2 = new Runnable(){
public void run(){
System.out.println("Hello World 2");
}
};
public static void process(Runnable r){
r.run();
}
process(r1); //打印Hello World1
process(r2);//打印Hello World 2
process(() -> System.out.println("Hello World 3"));//打印Hello World3

函数描述符:函数式接口的抽象方法的签名基本上就是Lambda表达式的签名,这种抽象方法叫作函数描述符。

JavaApi中常用的函数式接口(只列举了一部分,最终以java官方的javadoc为主):

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
@FunctionalInterface
public interface BiFunction<T, U, R> {
/**
* Applies this function to the given arguments.
*
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u);
}
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}

我们知道java的类型,要不是引用类型,要不是原始数据类型(int,double等等)。但是由于历史原因,泛型只能绑定到引用类型。为了使用原始类型的数据,java引人了自动装箱和自动拆箱的优化,但是装箱和拆箱的过程是对性能有损失的。

于是,java8为了避免在使用函数式接口的时候发生装箱和拆箱的操作,专门定义了原始数据类型的接口,叫作原始类型的特化版本。例如:

1
2
3
pubic interface IntPredicate(){
boolean test(int t);
}

关于Lambda表达式使用局部变量:

Lambda表达式可以无限制的捕获(在其主体中引用)实例变量和静态变量,但是局部变量必须显式声明为final或事实上是final的。因为实例变量存储在堆中,局部变量保存在栈上,Lambda表达式在另一个线程里面执行,当线程试图访问该局部变量的时候,变量存在着被修改和收回的可能,所以用final修饰就不会存在线程安全的问题了。

简单介绍方法引用

方法引用可以重复使用现有的方法定义,并像Lambda一样传递。方法引用相当于仅仅涉及单一方法的Lambda表达式的语法糖,是一种快捷的写法。

方法引用实例代码:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class TestJ8MethodReference {
public static void main(String[] args) {
// static method
Function<Integer, Integer> f1 = TestJ8MethodReference::add;
System.out.println(f1.apply(1));
// instance method
Function<String, String> f2 = String::trim;
System.out.println(f2.apply(" abd b"));
TestJ8MethodReference testJ8MethodReference = new TestJ8MethodReference();
Function<Integer, String> f3 = testJ8MethodReference::getStr;
System.out.println(f3.apply(3));
// super
testJ8MethodReference.testSuper();
// explicit type arguments for generic type
testJ8MethodReference.testExplicitType();
// implicit type arguments for generic type
testJ8MethodReference.testImplicitType();
// new
Supplier s1 = TestJ8MethodReference::new;
System.out.println(s1.get());
// type arguments inferred from context
Consumer<int[]> c1 = Arrays::sort;
int[] array = new int[]{4, 3, 2, 1};
c1.accept(array);
// explicit type arguments
Consumer<int[]> c2 = Arrays::<int[]>sort;//泛型方法的运用:在方法名前面加泛型信息
c2.accept(array);
// new array
Function<Integer, int[]> f4 = (int[]::new);
int[] a = f4.apply(10);
System.out.println(a.length);
}
public static int add(int x) {
return x + 1;
}
public String getStr(int x) {
return "" + x;
}
public void testSuper() {
Supplier<String> f = super::toString;
System.out.println(f.get());
}
public void testExplicitType() {
List<String> list = new ArrayList<>();
Function<String, Boolean> func = list::add;
System.out.println(func.apply("a"));
}
public void testImplicitType() {
List list = new ArrayList();
Function<String, Boolean> func = list::add;
System.out.println(func.apply("a"));
}
}

复合Lambda表达式的有用方法

  • 比较器复合:比较器链或者逆序。参考Comparator的Api

  • 谓词复合:negate,and,or。可以重用已有的Predicate来创建新的谓词。negate表示一个Predicate的非;and用于把2个Lambda组合起来;or表示2个Lambda的或逻辑。

  • 函数复合:把Function接口所代表的Lambda表达式复合。有andThen和compose2个默认方法。具体区别参考javadoc文档。

0%