文章摘要
GPT 4
此内容根据文章生成,仅用于文章内容的解释与总结
投诉

一、函数式接口

只有一个抽象方法的接口就成为函数式接口。同时,引入了一个新的注解:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。在接口中添加了 @FunctionalInterface 的接口,只允许有一个抽象方法,否则编译器也会报错。

举例几个Java常用的函数式接口:predicate断言型接口、consumer消费者接口、supplier提供者接口、Function接口等。

记好一句话:函数式接口可以接收匿名内部类和lambda表达式

记好一句话:函数式接口可以接收匿名内部类和lambda表达式

记好一句话:函数式接口可以接收匿名内部类和lambda表达式

全文重点:

俗话说,new一个接口就要new他的实现类对象,但是假设我不存在函数时接口的实现类对象,那么一般就使用匿名内部类的形式创建对象。创建出来的匿名内部类需要实现函数式接口中全部(其实就一个)的抽象方法。既然是匿名内部类的抽象方法,当然可以使用lambda表达式代替方法的实现。然而使用lambda表达式虽然简便了书写,但是代码的可读性也直线下降,没有注解的情况去读lambda表达式十分痛苦,为了针对代码可读性的问题,方法引用应运而生。方法引用作为lambda表达式的语法糖,可以简化lambda表达式的同时,提高代码的可读性,方法引用通过本篇博客的三种情况,代替lambda表达式的书写。

1.1 Predicate

java.util.function.Predicate为函数式接口,predicate的中文意思是“断定”,即判断的意思,判断某个东西是否满足某种条件; 因此它包含test方法,根据输入值来做逻辑判断,其结果为True或者False。

  • Predicate 是一个布尔类型的函数,该函数只有一个输入参数。
  • Predicate接口包含了多种默认方法,用于处理复杂的逻辑动词(and, or, negate
1
2
3
public interface Predicate<T>{
boolean test(T t);
}

下面是使用Predicate的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Predicate<Integer> atLeast5 = x -> x>5;
predicate.test(3); // false

Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false

Predicate<Boolean> nonNull = Objects::nonNull;
System.out.println(nonNull.test(null)); //false

Predicate<Boolean> isNull = Objects::isNull;
System.out.println(isNull.test(null)); //true

Predicate<String> isEmpty = String::isEmpty;
System.out.println(isEmpty.test("")); //true

Predicate<String> isNotEmpty = isEmpty.negate();
System.out.println(isNotEmpty.test("")); //false

x>5 是表达式的主体,返回值就是表达式主体的值。

1.2 Function

java.util.function.Function也是一个函数式编程接口;它代表的含义是“函数”,而函数经常是有输入输出的,因此它含有一个apply方法包含一个输入(一个参数)与一个输出(返回值)
除apply方法外,它还有composeandThen方法。

  • **高阶函数: **

    接受一个函数或返回一个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Function<Integer, Integer> f = s -> s + 1;
Function<Integer, Integer> g = s -> s * 2;

/**
* 高阶函数: 接受一个函数或返回一个函数
*
* 下面表示在执行F时,先执行G,并且执行F时使用G的结果当作输入。
* 相当于以下代码:
* Integer a = g.apply(1);
* System.out.println(f.apply(a));
*/
System.out.println(f.compose(g).apply(1)); //output: 3

/**
* 表示执行F的 apply 后使用其返回的值当作输入再执行G的Apply;
* 相当于以下代码
* Integer a = f.apply(1);
* System.out.println(g.apply(a));
*/
System.out.println(f.apply(1)); //output: 2
System.out.println(f.andThen(g).apply(1)); //output: 4
  • 转换器实例
1
2
3
4
5
6
7
8
/**定义一个转换的函数式接口
* T:参数类型
* R:返回值类型
*/
@FunctionalInterface
public interface Converter<T,R> {
R convert(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
public class ConverterTest {
public static void main(String[] args) {
Converter<String,Integer> intConverter = (s) -> Integer.valueOf(s);
Integer value = intConverter.convert("123");
System.out.println("String to Integer:" + value);

Converter<String,Double> doubleConverter = (s) -> Double.valueOf(s);
Double dblValue = doubleConverter.convert("123.99");
System.out.println("String to Double:" + dblValue);

//直接使用 Function 接口
Function<String,Integer> function = s -> Integer.valueOf(s);
Integer value2 = function.apply("123");
System.out.println(value2);

//方法引用
// :: 表示引用方法
//引用静态方法,类名::类方法名;如:Integer.valueOf(s)
//引用实例方法,对象::方法名
Function<String,Integer> function2 = Integer::valueOf;
Integer value3 = function2.apply("9999");
System.out.println(value3);
}
}

1.3 Consumer 消费者(重点)

它定义了一个名为accept抽象方法,该方法接受一个参数并且不返回任何结果。换句话说,Consumer接口表示一个消费者,它可以对给定的对象执行某些操作,但不产生任何结果。

accept(T) 方法:是 Consumer 函数式接口的函数方法,传入一个任意类型,无返回值,可以用于 Lambda 表达式和方法引用。
andThen(Consumer) 方法:可以传入一个 Consumer ,返回组合了两个 Consumer 后的 Consumer ,传入的 Consumer 不能为 null,否则会得到 NullPointerException 。

通俗的来说:
Consumer 相当于一个放东西的容器,你可以在这个容器里定义一个带有入参的代码块,然后返回 T 类型,当调用 accept(T) 方法的时候才会去执行容器里的代码。

例子1:使用类名引用成员方法(类名引用成员方法见本篇博客第三节3.3)

1
2
3
4
5
6
7
8
//引用实例方法
Consumer<String> enameConsumer = employee::setEname;
enameConsumer.accept("mike");
// test(enameConsumer);
System.out.println(employee.getName());

Consumer consumer = System.out::println;
Arrays.asList("james","andy","scott").forEach(consumer);

例子2:使用Consumer函数式接口接受lambda表达式

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.function.Consumer;

public class MainServer {
public static void main(String[] args) throws InterruptedException {
Consumer<String> first = x -> System.out.println("1."+x.toLowerCase());
Consumer<String> second = y -> System.out.println("2." + y);
System.out.println("开始");
Consumer<String> result = first.andThen(second);
//调用了accept 后,会先执行 first 容器里的代码,再执行 second 容器里的代码
result.accept("A");
}
}

1.4 Supplier 供应者

代表无参数,返回一个结果。

1
2
3
4
5
6
7
//引用默认构造函数
Supplier<Employee> supplier = Employee::new;
Employee employee = supplier.get();
System.out.println(employee);

//引用对象的String getName()方法
Supplier<String> stringSupplier = employee::getEname;

1.5 BinaryOperator

  • BinaryOperator接口接收两个参数,返回一个值,返回值和参数的数据类型相同。

  • BinaryOperator接口是BiFunction (接收两个参数)的子接口。

1
2
BinaryOperator<Long> addLongs = (x,y) -> x + y;
System.out.println(addLongs.apply(1L, 2L));

==Lambda表达式是一个匿名方法,将行为像数据一样传递。==

二、Lambda表达式

1、Lambda表达式的基本作用?
简化函数式接口的匿名内部类的写法。

2、Lambda表达式有什么使用前提?
必须是接口的匿名内部类,接口中只能有一个抽象方法

3、Lambda的好处?
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码,它可以写出更简洁、更灵活的代码,作为一种更紧凑的代码风格,使Java语言表达能力得到了提升。

举个例子:单列集合arraylist调用foreach方法进行遍历。

其中方法的参数是consumer接口(留意一下consumer接口)的实现类,因此需要使用匿名内部类的形式调用方法,改进后可以使用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
public class test1 {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
coll.add("博主你这个博客是自己写的吗");
coll.add("我的天,好有水平,但是我看不懂");

//1.使用匿名内部类进行遍历
coll.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});

//2.使用lambda表达式进行遍历
coll.forEach(
(String s)-> {
System.out.println(s);
}
);

//3.lambda简化版
coll.forEach(
(String s)-> System.out.println(s)
);
}
}

三、方法引用

只有使用匿名内部类和lambda表达式的地方才可以使用方法引用。

它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。但是,显式地指明方法的名称,你的代码的可读性会更好。

方法引用的规则:

  1. 需要有函数式接口
  2. 被引用的方法必须已经存在
  3. 被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致, 返回值需要保持一致。
  4. 被引用方法的功能需要满足当前的需求

抽象方法形参的详解:
第一个参数:表示被引用方法的调用者,决定了可以引用哪些类中的方法
在Stream流当中,第一个参数一般都表示流里面的每一个数据。假设流里面的数据是字符串,那么使用这种方式进行方法引用,只能引用String这个类中的方法。
第二个参数到最后一个参数:跟被引用方法的形参保持一致,如果没有第二个参数,说明被引用的方法需要是无参的成员方法

3.1 静态方法引用

举个例子

3.2 对象引用成员方法

this::方法名和super::方法名,这两个的被引用处不能是静态方法,因为静态方法没有this、super关键字

举个例子:

3.3 类名引用成员方法

举个例子:我现在需要把集合中的字母全部变成大写,使用匿名内部类语法如下

现在使用类名引用方法的形式,首先看String类中的toUpperCase方法

按照抽象方法参数的详解(3.1节),匿名内部类中抽象方法apply中的参数String s是抽象方法的第一个参数,它决定能使用什么类的方法。

第二个参数到末尾的参数要和成员方法的一致,如果抽象方法只有一个参数,那么应该调用这个类的无参方法。

String类中的toUpperCase方法提供了两个,符合可以用空参的那个。

因此可以把匿名内部类的写法改成使用方法引用的形式