函数式编程思想,让编码偏向于过程,让我们书写代码是关注关键所在而不在于固定形式。
函数式编程思想概述
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。
做什么,而不是怎么做
传递一段代码——这才是我们真正的目的。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。那,有没有更加简单的办法?如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要。
2.2 Lambda的优化
Runnable接口简化:
1. () -> System.out.println("多线程任务执行!")
Comparator接口简化:
2. Arrays.sort(array, (a, b) -> a.getAge() - b.getAge()); a-b 是升序 ,b -a 是降序
本着“一切皆对象”的思想,这种做法是无可厚非的
- 而实际上,有时候似乎只有方法体才是关键所在。
2.3 Lambda的格式
标准格式:
Lambda省去面向对象的条条框框,格式由3个部分组成:
- 一些参数
- 一个箭头
- 一段代码
Lambda表达式的标准格式为:
(参数类型 参数名称) -> { 代码语句 }
格式说明:
- 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
->
是新引入的语法格式,代表指向动作。- 大括号内的语法与传统方法体要求基本一致。
Runnable接口简化:
即制定了一种做事情的方案(其实就是一个方法):
- 无参数:不需要任何条件即可执行该方案。
- 无返回值:该方案不产生任何结果。
- 代码块(方法体):该方案的具体执行步骤。
同样的语义体现在Lambda
语法中,要更加简单:
- 前面的一对小括号代表不需要任何条件;
- 中间的一个箭头代表将前面的参数传递给后面的代码;
- 后面的输出语句即业务逻辑代码。
参数和返回值:
Comparator接口简化:
- 为了排序,
Arrays.sort
方法需要排序规则,即Comparator
接口的实例,抽象方法compare
是关键; - 为了指定
compare
的方法体,不得不需要Comparator
接口的实现类; - 为了省去定义一个
ComparatorImpl
实现类的麻烦,不得不使用匿名内部类; - 必须覆盖重写抽象
compare
方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错; - 实际上,只有参数和方法体才是关键。
省略格式:
在Lambda标准格式的基础上,使用省略写法的规则为:
- 小括号内参数的类型可以省略;
- 如果小括号内有且仅有一个参,则小括号可以省略;
- 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
备注:掌握这些省略规则后,请对应地回顾本章开头的多线程案例。
可推导即可省略
Lambda强调的是“做什么”而不是“怎么做”,所以凡是可以根据上下文推导得知的信息,都可以省略。
Lambda的前提条件
Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:
- 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的Runnable
、Comparator
接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。 - 使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个抽象方法的接口,称为“函数式接口”。
函数式接口
概述
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
备注:从应用层面来讲,Java中的Lambda可以看做是匿名内部类的简化格式,但是二者在原理上不同。
格式
只要确保接口中有且仅有一个抽象方法即可:
修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
}
自定义函数式接口
对于刚刚定义好的MyFunctionalInterface
函数式接口,典型使用场景就是作为方法的参数:
public class FunctionalInterface {
// 使用自定义的函数式接口作为方法参数
private static void doSomething(MyFunctionalInterface inter) {
inter.myMethod(); // 调用自定义的函数式接口方法
}
public static void main(String[] args) {
// 调用使用函数式接口的方法
doSomething(() -> System.out.println("Lambda执行啦!"));
}
}
FunctionalInterface注解
与@Override
注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解:@FunctionalInterface
。该注解可用于一个接口的定义上:
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
调用过程
常用函数式接口
JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在java.util.function
包中被提供。前文的MySupplier
接口就是在模拟一个函数式接口:java.util.function.Supplier<T>
。其实还有很多,下面是最简单的几个接口及使用示例。
Supplier接口
java.util.function.Supplier<T>
接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
抽象方法 : get
仅包含一个无参的方法:T get()
。用来获取一个泛型参数指定类型的对象数据。
Consumer接口
java.util.function.Consumer<T>
接口则正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。
抽象方法:accept
Consumer
接口中包含抽象方法void accept(T t)
,意为消费一个指定泛型的数据。
默认方法:andThen
如果一个方法的参数和返回值全都是Consumer
类型,那么就可以实现效果:消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer
接口中的default方法andThen
。下面是JDK的源代码:
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
备注:
java.util.Objects
的requireNonNull
静态方法将会在参数为null时主动抛出NullPointerException
异常。这省去了重复编写if语句和抛出空指针异常的麻烦。
要想实现组合,需要两个或多个Lambda表达式即可,而andThen
的语义正是“一步接一步”操作。例如两个步骤组合的情况:
Function接口
java.util.function.Function<T,R>
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有进有出,所以称为“函数Function”。
抽象方法:apply
Function
接口中最主要的抽象方法为:R apply(T t)
,根据类型T的参数获取类型R的结果。
默认方法:andThen
Function
接口中有一个默认的andThen
方法,用来进行组合操作。JDK源代码如:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
该方法同样用于“先做什么,再做什么”的场景,和Consumer
中的andThen
差不多:
`
请注意,Function的前置条件泛型和后置条件泛型可以相同。
Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T>
接口。
抽象方法:test
Predicate
接口中包含一个抽象方法:boolean test(T t)
。用于条件判断的场景:
条件判断的标准是传入的Lambda表达式逻辑,只要字符串长度大于5则认为很长。
默认方法:and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个Predicate
条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法and
。其JDK源码为:
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
// 去重
private <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
List<SeededAccountManagerDto> SeededAccountManagerDtoList = initList.stream().filter(distinctByKey((p) -> (p.getPhoneNum()))).collect(Collectors.toList());
默认方法:or
与and
的“与”类似,默认方法or
实现逻辑关系中的“或”。JDK源码为:
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
如果希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or”名称即可
默认方法:negate
“与”、“或”已经了解了,剩下的“非”(取反)也会简单。默认方法negate
的JDK源代码为:
default Predicate<T> negate() {
return (t) -> !test(t);
}
从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。
举个栗子
package com.youngchan.practice;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* @author youngchan
* @version V1.0
* @Package com.youngchan.practice
* @date 2020/4/27 19:34
*/
public class LambdaPractice {
public static void practiceLambda() {
myStaticMethod(() -> System.out.println("无参无返回值方法"));
String superlier = "superlier interface 有返回值无参方法";
System.out.println(myStaticSupplierMethod(() -> superlier.toUpperCase()));
String consumer = "consumer interface 有参数没有返回值方法";
myStaticConsumerMethod(consumerTemp -> System.out.println(consumerTemp), consumer);
String function = "function interface 有参有返回值方法";
String functionAdd = "加工返回值";
System.out.println(myStaticFunctionMethod(functionTemp -> functionTemp + functionAdd, function));
System.out.println(myStaticFunctionMethod(functionTemp -> {
return functionTemp + functionAdd;
}, function));
String predicate = "predicate interface 有参返回值为布尔值的方法";
System.out.println(myStaticPredicateMethod(predicateTemp -> predicate.length() > 10, predicate));
}
/**
* 无参无返回值 方法实现
* @param inter
*/
private static void myStaticMethod(MyInterface inter) {
inter.myMethod();
}
/**
* 有返回值无参 方法实现
* @param interfaceSupplier
* @return
*/
private static String myStaticSupplierMethod(MyInterfaceSupplier interfaceSupplier) {
return interfaceSupplier.myMethodSupplier();
}
/**
* 有参无返回值方法实现
* @param interfaceConsumer
* @param str
*/
private static void myStaticConsumerMethod(MyInterfaceConsumer<String> interfaceConsumer, String str) {
interfaceConsumer.myMethodConsumer(str);
}
/**
* 有参有返回值方法实现
* @param interfaceFunction
* @param function
* @return
*/
private static String myStaticFunctionMethod(MyInterfaceFunction<String, String> interfaceFunction, String function) {
return interfaceFunction.myMethodFunction(function);
}
/**
* 有参有布尔返回值方法实现
* @param interfacePredicate
* @param predicate
* @return
*/
private static Boolean myStaticPredicateMethod(MyInterfacePredicate<String> interfacePredicate, String predicate) {
return interfacePredicate.myMethodPredicate(predicate);
}
/**
* 无参无返回值接口
*/
@FunctionalInterface
interface MyInterface {
void myMethod();
}
/**
* 有返回值无参Supplier
*/
@FunctionalInterface
interface MyInterfaceSupplier {
String myMethodSupplier();
}
interface MyInterfaceSupplier2 extends Supplier<String> {
@Override
String get();
}
/**
* 有参无返回值Consumer
*/
@FunctionalInterface
interface MyInterfaceConsumer<T> {
void myMethodConsumer(T t);
}
interface MyInterfaceConsumer2<T> extends Consumer<String> {
@Override
void accept(String s);
@Override
default Consumer<String> andThen(Consumer<? super String> after) {
return null;
}
}
/**
* 有参有返回值Function
*/
@FunctionalInterface
interface MyInterfaceFunction<T, R> {
R myMethodFunction(T t);
}
interface MyInterfaceFunction2<T, R> extends Function<String, String> {
@Override
String apply(String s);
@Override
default <V> Function<V, String> compose(Function<? super V, ? extends String> before) {
return null;
}
@Override
default <V> Function<String, V> andThen(Function<? super String, ? extends V> after) {
return null;
}
}
/**
* 有参布尔返回值Function
*/
@FunctionalInterface
interface MyInterfacePredicate<T> {
Boolean myMethodPredicate(T t);
}
interface MyInterfacePredicate2<T> extends Predicate<String> {
@Override
boolean test(String s);
@Override
default Predicate<String> and(Predicate<? super String> other) {
return null;
}
@Override
default Predicate<String> negate() {
return null;
}
@Override
default Predicate<String> or(Predicate<? super String> other) {
return null;
}
}
}
Lambda表达式和匿名内部类的区别
方法引用
概述
在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作。那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑?
冗余的Lambda场景优化
来看一个简单的函数式接口以应用Lambda表达式 , 那么通过Lambda来使用它的代码很简单:
// 不使用方法引用
printString(s -> System.out.println(s), "Hello World");
// 使用方法引用
printString(System.out::println, "HelloWorld");
请注意其中的双冒号::
写法,这被称为“方法引用”,而双冒号是一种新的语法。
方法引用符
符号表示 : ::
符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
**应用场景 : **如果Lambda要表达的函数方案 , 已经存在于某个方法的实现中,那么则可以使用方法引用。
如上例中,System.out对象中有个println(String)方法 , 恰好就是我们所需要的 , 那么对于Consumer接口作为参数,对比下面两种写法,完全等效:
- Lambda表达式写法:s -> System.out.println(s);
拿到参数之后经Lambda之手,继而传递给System.out.println方法去处理。- 方法引用写法:System.out::println
直接让System.out中的println方法来取代Lambda。
**推导与省略 : ** 如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式——它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。函数式接口是Lambda的基础,而方法引用是Lambda的简化形式。
常见的方法引用
对象名–引用成员方法
这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法,代码为:
public class DemoOne {
public static void main(String[] args) {
String str = "hello";
printUP(str::toUpperCase);
}
public static void printUP(Supplier< String> sup ){
String apply =sup.get();
System.out.println(apply);
}
}
类名–引用静态方法
由于在java.lang.Math
类中已经存在了静态方法random
,所以当我们需要通过Lambda来调用该方法时,可以使用方法引用 , 写法是:
public class DeomTwo{
public static void main(String[] args) {
printRanNum(Math::random);
}
public static void printRanNum(Supplier<Double> sup ){
Double apply =sup.get();
System.out.println(apply);
}
}
在这个例子中,下面两种写法是等效的:
- Lambda表达式:
n -> Math.abs(n)
- 方法引用:
Math::abs
类–构造引用
由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用类名称::new
的格式表示。首先是一个简单的Person
类:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
要使用这个函数式接口,可以通过方法引用传递:
public class DemoThree {
public static void main(String[] args) {
String name = "tom";
Person person = createPerson(Person::new, name);
System.out.println(person);
}
public static Person createPerson(Function<String, Person> fun , String name){
Person p = fun.apply(name);
return p;
}
}
在这个例子中,下面两种写法是等效的:
- Lambda表达式:
name -> new Person(name)
- 方法引用:
Person::new
数组–构造引用
数组也是Object
的子类对象,所以同样具有构造器,只是语法稍有不同。如果对应到Lambda的使用场景中时,需要一个函数式接口:
在应用该接口的时候,可以通过方法引用传递:
public class DemoFour {
public static void main(String[] args) {
int[] array = createArray(int[]::new, 3);
System.out.println(array.length);
}
public static int[] createArray(Function<Integer , int[]> fun , int n){
int[] p = fun.apply(n);
return p;
}
}
在这个例子中,下面两种写法是等效的:
- Lambda表达式:
length -> new int[length]
- 方法引用:
int[]::new
注意 :
方法引用是对Lambda表达式符合特定情况下的一种缩写,它使得我们的Lambda表达式更加的精简,也可以理解为Lambda表达式的缩写形式 , 同学们可以尝试着 , 将之前使用lambda的地方 , 改写成方法引用的形式 ,不过要注意的是方法引用只能"引用"已经存在的方法!