学习Lambda表达式

59.jpg

Lambda表达式

Lambda表达式是什么?

  •   在编程语言中,例如Python和Scala,Lambda表达式是一个用于表示匿名函数或者闭包的操作符.Lambda表达式同时也是Java8很重要的新特性;

  •   lambda表达式也是实现SAM接口的的语法糖,是为了给SAM接口的变量和形参赋值的一种语法.使得Java也支持函数式编程的语言,让Java代码变得更加简洁、干净和可读性强.

为何要引入Lambda表达式?

  • lambda允许把函数作为一个方法参数(函数作为参数传递进方法中);
  • 使得Java也支持函数式编程的语言,让Java代码变得更加简洁、干净和可读性强.

Lambda表达式的作用

  • 为Java添加函数式编程的特性
  • 在Java中,Lambda表达式是对象,它们必须依附于一类特别的对象类型——函数式接口

SAM接口

SAM接口定义:

​ Single Abstract Method的缩写,顾名思义: 单个抽象方法

  • 该接口中只有一个抽象方法需要实现.

由于Java8在接口interface新增了静态方法和默认方法的特性,所以该接口可以包含其他非抽象方法(默认方法和静态方法,或者是从Object类继承的方法)

  • 如果我们在某个接口上声明了@FunctionalInterface注解,那么编译器就会按照函数式接口的定义要求该接口;
  • 如果某个接口只有一个抽象方法,但我们并没有给该接口声明@FunctionalInterface注解,那么编译器依旧会将该接口看作是函数式接口.
  • 只要是符合SAM接口的特征,都可以使用Lambda表达式;
  • 但是Java建议只针对标记了@FunctionalInterface注解的SAM接口使用
  • 函数式接口集中在java.util.function包中

SAM接口分类:

​ (1)消费型接口: 有参无返回值 void accept(T t);

​ (2)供给型接口: 无参有返回值 T get();

​ (3)判断型接口: 有参有返回值 boolean test(T t);

​ (4)功能型接口: 有参有返回值 R apply(T t)

初入SAM接口

我们首先阅读@FunctionalInterface注解源码文档说明

1
2
3
4
5
6
7
//元注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
* Conceptually, a functional interface has exactly one abstract
概念上,一个函数式接口有且只有一个抽象方法
* method. Since {@linkplain java.lang.reflect.Method#isDefault()
自从默认方法有实现之后,他们不再是抽象方法了.
* default methods} have an implementation, they are not abstract. If
* an interface declares an abstract method overriding one of the
如果一个接口声明了一个重写Object类的公共方法的抽象方法,
* public methods of {@code java.lang.Object}, that also does
那么该方法不计入该接口的抽象方法
* <em>not</em> count toward the interface's abstract method count
* since any implementation of the interface will have an
因为该接口的任何实现都会有来自Object类的实现或者其他的
* implementation from {@code java.lang.Object} or elsewhere.
*

用代码演示一下上述文档翻译的情况:

1
2
3
4
5
//此时没有报错,因为该函数式接口有且只有一个抽象方法test()
@FunctionalInterface
interface MyInterface{
void test();
}
1
2
3
4
5
6
7
8
9
10
11
//此时@FunctionalInterface报错
/*
报错信息为:
Multiple non-overriding abstract methods found in interface com.atjava.java8.function.MyInterface
com.atjava.java8.function.MyInterface中可以找到多个非覆盖的抽象方法
*/
@FunctionalInterface
interface MyInterface{
void test();
void test1();
}
1
2
3
4
5
6
//com.atjava.java8.function.MyInterface中可以找到多个非覆盖的抽象方法
@FunctionalInterface
interface MyInterface{
void test();
String myString();
}

那么如果我们换成下面代码,代码没有报错:

1
2
3
4
5
6
7
8
9
@FunctionalInterface
interface MyInterface{
void test();
/*
* String toString();因为此方法重写了Object类的toString()方法,所以该方法不算是抽象方法.
因为MyInterface接口的实现类对象继承了Object类的toString()方法,会进行自定义操作等实现,从这个角度而言,它就不是抽象方法了
*/
String toString();
}

进一步研究函数式接口,代码如下:

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
package com.atjava.java8.function;
/**
* @author atjava
* @create 2020/2/12
*/
public class Test1 {

public void myTest(MyInterface myInterface) {
System.out.println(1);
myInterface.test();
System.out.println(2);
}

public static void main(String[] args) {
Test1 t1 = new Test1();
//使用匿名内部类的方式
t1.myTest(new MyInterface() {
@Override
public void test() {
System.out.println("2020,武汉加油");
}
});
}
}
@FunctionalInterface
interface MyInterface {

void test();

String toString();
}

输出结果为:

1
2
3
1
2020,武汉加油
2

上述代码使用Lambda表达式看下输出结果:

1
2
3
4
5
6
7
8
    public static void main(String[] args) {
Test1 t1 = new Test1();
//使用Lambda表达式的方式
//因为函数式接口里的抽象方法test没有参数和返回值,所以->左边是(),不能省略
t1.myTest(() -> System.out.println("2020,武汉加油"));
}
}
//输出结果和上面使用匿名内部类的方式的结果一样

因为函数式接口有且只有一个抽象方法,所以在使用Lambda表达式时,该接口的修饰符、返回值类型以及方法名就显得不是很重要了(该方法具有唯一确定性,只有它),一般情况下可以省略,编译器会通过上下文进行类型推断.

可能有人会问() -> System.out.println(“2020,武汉加油”),这个Lambda表达式到底是啥?,我们可以认定为是MyInterface函数式接口的实现对象

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
Test1 t1 = new Test1();
//使用匿名内部类的方式
// t1.myTest(() -> System.out.println("2020,武汉加油"));
//MyInterface函数式接口的实现对象
//多态:父类引用指向子类对象
MyInterface myInterface = () -> System.out.println("2020,武汉加油");
t1.myTest(myInterface);
}
//输出结果和上面使用匿名内部类的方式的结果一样

为了更加清楚明白myInterface代表的是啥?,用如下代码进行演示:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
Test1 t1 = new Test1();
//使用匿名内部类的方式
// t1.myTest(() -> System.out.println("2020,武汉加油"));
MyInterface myInterface = () -> System.out.println("2020,武汉加油");
t1.myTest(myInterface);
System.out.println(myInterface.getClass());
System.out.println(myInterface.getClass().getSuperclass());//父类
System.out.println(myInterface.getClass().getInterfaces()[0]);
}

输出结果如下

1
2
3
4
5
6
1
2020,武汉加油
2
class com.atjava.java8.function.Test1$$Lambda$1/1831932724
class java.lang.Object
interface com.atjava.java8.function.MyInterface
1
2
3
4
5
/*
* Note that instances of functional interfaces can be created with
* 可以通过Lambda表达式、方法引用和构造引用来创建函数式接口的实例
* lambda expressions, method references, or constructor references.
*/
外部迭代与内部迭代

外部迭代需要一个迭代器对象,内部迭代集合本身就可以完成.

深入理解SAM接口

Function接口

首先我们先看下官方文档对Function接口的说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 返回一个接口 :接收一个参数和返回一个结果
*
* <p>This is a functional interface</a>
这是一个函数式接口,它的函数式方法是apply
* whose functional method is {apply(Object)}.
*
* @param <T> 该接口的输入参数类型
* @param <R> 该接口的结果类型
*
* @since 1.8 JDK8引入
*/
//函数式接口注解
@FunctionalInterface
//Function<T, R> 泛型,输入参数类型为T,返回结果类型为R
public interface Function<T, R> {

/**
* 应用该接口给指定的参数
* @param t 函数的参数
* @return 函数的结果
*/
R apply(T t);

通过对官方文档的了解,Function接口的抽象方法apply提供一个输入参数,返回一个结果;

apply方法
1
R apply(T t);

代码示例:

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
package com.atjava.java8.function;

import java.util.function.Function;
public class TestFunction2 {
public static void main(String[] args) {
TestFunction2 testFunction2 = new TestFunction2();
int compute = testFunction2.compute(1, value -> value << 1);
System.out.println("compute = " + compute);
System.out.println();
System.out.println(testFunction2.convert(2020,str->String.valueOf(str+ "武汉加油")));
}

public int compute(int a , Function<Integer,Integer> function){
//负责传递动作,具体由调用做进行操作
int result = function.apply(a);
return result;
}

public String convert(int num,Function<Integer,String> integerStringFunction){
return integerStringFunction.apply(num);
}
public int add(int a){
return a << 2;
}
public int add1(int a){
return a >> 2;
}
}

函数式编程与面向对象编程:

  • 函数式编程通过方法传递行为(函数),提升编程的抽象层次,具体操作由调用者实现,从而实现更复杂的功能,大大提高编程的效率;
  • 面向对象编程:需要什么行为,完成什么功能,都需要我们提前一一声明定义好,自行调用
compose 和andThen方法

首先看下官方文档对这两个方法的说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 返回一个组合的函数:首先对输入参数应用before函数
*然后应用当前函数(将before函数的结果作为输入参数),返回结果
*组合函数,可以多个函数进行组合
*/
//默认方法,返回Function类型
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
//校验before是否为空
Objects.requireNonNull(before);
//先应用参数的function,再应用当前对象的function
return (V v) -> apply(before.apply(v));
}

/**
*先应用当前对象的function,再应用参数的function
*与compose()方法正好相反
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
//校验after是否为空
Objects.requireNonNull(after);
//先应用当前对象的function,再应用参数的function
return (T t) -> after.apply(apply(t));
}

看到这里,脑袋瓜有点懵懵的,用代码举例子进行演示一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.atjava.java8.function;
import java.util.function.Function;

public class TestFunction3 {
public static void main(String[] args) {
//compose和andThen方法演示
TestFunction3 testFunction3 = new TestFunction3();
System.out.println(testFunction3.compuse(2, v1 -> v1 * 3, v2 -> v2 * v2));
System.out.println(testFunction3.compute(2, v1 -> v1 * 3, v2 -> v2 * v2));
}

// return (V v) -> apply(before.apply(v));
public int compuse(int a, Function<Integer, Integer> function, Function<Integer, Integer> function2) {
return function.compose(function2).apply(a);
}

// return (T t) -> after.apply(apply(t));
public int compute(int a, Function<Integer, Integer> function, Function<Integer, Integer> function2) {
return function.andThen(function2).apply(a);
}
}
identity方法
1
2
3
4
//静态方法
static <T> Function<T, T> identity() {
return t -> t;
}

Function.identity()就等价于t -> t的方式

1
2
3
//上述例子改下,结果输出为4  
System.out.println(testFunction3.compuse(2, Function.identity(), v2 -> v2 * v2));
System.out.println(testFunction3.compuse(2, t -> t, v2 -> v2 * v2));
Bifunction接口

该接口位于java.util.function.Bifunction,我们先看下该接口的官方文档说明

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 代表一个接口:接收两个参数和返回一个结果 that accepts two arguments and produces a result.
*
* @param <T> 该接口第一个输入参数的类型
* @param <U> 该接口第二个输入参数的类型
* @param <R> 该接口返回结果的类型
* @see Function
* @since 1.8
*/
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.atjava.java8.function;
import java.util.function.BiFunction;
public class TestBiFunction {
public static void main(String[] args) {
//方法调用
TestBiFunction testBiFunction = new TestBiFunction();
System.out.println(testBiFunction.caculaute(2, 4, (v1, v2) -> v1 + v2));
System.out.println(testBiFunction.caculaute(2, 4, (v1, v2) -> v1 - v2));
System.out.println(testBiFunction.caculaute(2, 4, (v1, v2) -> v1 * v2));
System.out.println(testBiFunction.caculaute(2, 4, (v1, v2) -> v1 / v2));
}

//定义一个方法完成四则运算的功能
//提前把抽象行为定义好,具体由调用者进行具体功能的制定
public int caculaute(int num1, int num2, BiFunction<Integer, Integer, Integer> integerBiFunction) {
return integerBiFunction.apply(num1, num2);
}
}
andThen方法

与Function接口的andThen方法意义相同,直接上代码演示:

1
2
3
4
5
6
7
8
9
10
public class TestBiFunction {
public static void main(String[] args) {
//方法调用
TestBiFunction testBiFunction = new TestBiFunction();
System.out.println(testBiFunction.caculaute1(1,2,(v1,v2)-> v1*v2,v3->v3+5));//7
}
public int caculaute1(int num1, int num2, BiFunction<Integer, Integer, Integer> integerBiFunction,Function<Integer,Integer> function) {
return integerBiFunction.andThen(function).apply(num1,num2);
}
}

问题一: 为啥Bifunction接口没有compose()方法?

因为Bifunction接口抽象方法需要接收两个参数,完成之后却只有一个返回结果,无法满足另外一个Bifunction接口需要两个输入参数的要求,编译器会报错

问题一: 为啥Bifunction接口andThen()方法是接收一个Function接口对象而不是Bifunction接口对象?

andThen()方法是先应用当前对象的函数,再调用传入参数的函数,而在再调用传入参数的函数之前却只有一个返回结果(Java只允许一个返回结果),就与Bifunction接口对象接收两个参数不符.

predicate接口

我们首先看下官方文档的说明:

1
2


Lambda语法结构

  Lambda表达式是用来给【函数式接口】的变量或形参赋值用的。

  其实本质上,Lambda表达式是用于实现【函数式接口】的“抽象方法”

  Lambda表达式语法格式:

1
(形参列表) -> {Lambda体}

Lambda表达式实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 不需要参数,返回值为 5  
() -> 5

// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x

// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y

// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y

// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)

Lambda表达式特征

1
2
3
4
5
//Lambda表达式是匿名函数,没有方法的声明:没有修饰符、没有返回值类型、方法名
//传递行为,而不是仅仅传值
//2.1提升抽象层次;
//2.2API重用性更好;
//2.3灵活性更强.

方法引用

使用条件

使用格式

类名::实例方法

代码示例一:

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
package com.atjava.java8.function;
import java.util.function.Function;
public class TestFunction {
public static void main(String[] args) {
/*
* 方法引用:
* 类名::实例方法名
* 分析:
* 为了更加方便清楚这种方法引用怎么变化过来的,这里通过匿名子类对象--->Lambda表达式---->方法引用
* (1)匿名子类对象
* 通过重写抽象方法apply,该方法接收一个参数String s,返回一个String s1;
*
* (2)Lambda表达式
* Function<String, String> function1 = s -> s.toLowerCase();
* 等号左边是Function接口对象,等号右边是Function接口实现类对象,相当于重写
* @Override
* public String apply(String s) {
* return s.toLowerCase();
* (3)方法引用
* Function<String, String> function2 = String::toLowerCase;
* 首先Function接口抽象方法接收一个参数,返回一个结果;
* String toLowerCase() 由当前对象调用toLowerCase方法,返回一个String类型的结果;
* 就相当于输入一个参数toLowerCase(this),返回一个参数;
* 那么此时toLowerCase方法就与Function接口抽象方法类似,就可以使用类名::实例方法名
* 方法引用使用条件比较严格,后续会接着讲方法引用其他三种方式
*
* */
Function<String, String> function = new Function<String, String>() {
@Override
public String apply(String s) {
return s.toLowerCase();
}
};
System.out.println(function.getClass().getInterfaces()[0]);
Function<String, String> function1 = s -> s.toLowerCase();
//调用函数的对象是第一个传入对象的调用
Function<String, String> function2 = String::toLowerCase;
}
}

代码示例二:

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
package com.atjava.java8.function;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class TestFunction1 {
public static void main(String[] args) {
//借助数组转换成集合List
List<String> names = Arrays.asList("lizhien","dilireba","baijingting","wangzeke");
//接收两个参数,第一个参数为List集合,第二个参数为comparator函数式接口对象
/*
* comparator函数式接口抽象方法compare接收两个参数,返回一个int类型结果
*
* */
/* Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//对该集合进行倒序排序
return o2.compareTo(o1);
}
});

System.out.println(names);*/
System.out.println("----------------------------------");
//Lambda表达式方式
/*
* 函数式接口抽象方法参数个数决定Lambda表达式->左边参数的个数
*
* (1)代码块语句风格;
* (2)表达式风格
* */
Collections.sort(names,((o1, o2) -> o2.compareTo(o1)));

//方法引用
Collections.sort(names,(String::compareTo));
/*
* Comparator接口
* static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {return Collections.reverseOrder();
*
* (1)调用工具类Collections的静态方法reverseOrder
*
* return (Comparator<T>) ReverseComparator.REVERSE_ORDER;
* (2)ReverseComparator是一个静态内部类,实现comparator接口,重写compare方法
*
* */
Collections.sort(names,(Comparator.reverseOrder()));
System.out.println("names = " + names);
}
}

gz.gif

-------------------本文结束 感谢您的阅读-------------------
0%