Spring Boot 2.x实战1 - 函数式编程1

1 什么是函数式编程

什么是“函数式编程”?顾名思义就是用函数来编程,那什么是函数呢?函数是一种特殊的方法,它声明了如何完成任务的定义,函数体内的不修改任何共享可变的数据,没有任何副作用。Java 8中的函数(方法)是“值”的一种新的形式,可以将方法作为参数进行传递。而主要作为参数进行传递的方法主要是“Lambda表达式”和“方法引用”。

2 Lambda表达式

Lambda表达式是一种匿名函数,在函数式编程里,它也可以作为参数进行传递。

2.1 了解Lambda表达式

我们来看一个简单的例子,先是用老式的匿名类定义,再使用等同的Lambda表达式。

  • 匿名类:
Comparator<Person> byWeightComparator = new Comparator<Person>() {
       @Override
       public int compare(Person person1, Person person2) {
           return person1.getWeight().compareTo(person2.getWeight());
       }
   };
  • Lambda表达式:
Comparator<Person> byWeightComparatorUsingLambda =
                (Person person1, Person person2) -> person1.getWeight().compareTo(person2.getWeight()); 

从上面的例子我们可以看出Lambda表达式分为三个部分:

  • 左侧:Lambda参数列表(Person person1, Person person2),等同于匿名类的compare方法的参数;
  • 中间:箭头->,分开Lambda参数和Lambda体;
  • 右侧:Lambda体person1.getWeight().compareTo(person2.getWeight()),等同于匿名类的compare方法的返回值

Lambda表达式还有以下的规则:

  • Lambda表达式可以有0到多个参数,如:

    • () -> {}
    • (Integer i) -> "wyf"
    • (Integer i, Integer j) -> { return "wyf";}
  • Java编译器有类型推断(Type inference)的能力,Lambda参数的类型可以省略,如:

(person1, person2) -> person1.getWeight().compareTo(person2.getWeight());
  • 若Lambda参数只有一个的话,可省略圆括号,如:a -> a+1

  • Lambda体需要用花括号围绕,如果体内只有一句表达式的话可省略。

  • Lambda体中使用return的话,需要使用花括号,如:() -> { return "wyf";}

2.2 Lambda表达式作为参数

Lambda表达式可以像参数一样传递给方法,看如下的示例:

List<Person> people = Arrays.asList(new Person("wyf", Gender.MALE, 100),
                                new Person("www", Gender.FEMALE, 80),
                                new Person("foo", Gender.FEMALE, 90));

people.sort((p1, p2) -> p1.getWeight().compareTo(p2.getWeight())); //1

people.forEach(person -> System.out.println(person.getName() + "的体重:" + person.getWeight())); //2
  1. Lambada表达式作为sort方法的参数,是Comparator函数接口的实现。
  2. Lambada表达式作为forEach方法的参数,是Consumer函数接口的实现。

Person类的定义如下:

public class Person {
    private String name; //名字
    private Gender gender; //性别
    private Integer weight = 0; //体重
    //无参构造
    public Person() {
        super();
    }
    //一个参数构造
    public Person(String name) {
        this.name = name;
    }
    //两个参数构造
    public Person(String name, Gender gender) {
        this.name = name;
        this.gender = gender;
    }
    //全参构造
    public Person(String name, Gender gender, Integer weight) {
        this.name = name;
        this.gender = gender;
        this.weight = weight;
    }

...省略getter、setter

    @Override
    public String toString() {
        return "名字是:" + name + "性别是:" + gender + "体重是:" + weight;
    }


}

Gender的定义如下:

public enum  Gender {
    MALE, FEMALE
}

3 函数接口

上面的例子里面我们涉及到了很多函数接口,如:

  • Function
  • Consumer
  • Comparator

它们都属于函数接口,都标记了**@FunctionalInterface**注解:如:

@FunctionalInterface
public interface Function<T, R>
...

@FunctionalInterface
public interface Consumer<T> 
...

@FunctionalInterface
public interface Comparator<T>
...

任意只有一个抽象方法的接口都是函数接口(Functional Interface),这类接口的实现都可以使用Lambda表达式(或方法引用)。在定义中说到函数接口只有一个抽象方法,而函数接口里其实还有其它很多的方法,这些方法将被定义忽略,这些方法分别归类为:

  • 静态方法:和接口有关的工具助手方法。使用static关键字,提供实现。
  • 默认方法:添加新的功能方法到已有的接口,老的代码中使用了该接口其它方法的代码不会受到影响。使用default关键字,提供实现。

这也意味着从Java 8开始,接口内不仅仅只有抽象方法,还可以有静态方法和默认方法。只要符合定义即使没有标记**@FunctionalInterface也是函数接口,当然如果不符合函数接口的定义即使标记了@FunctionalInterface**,编译器就会报错,这就是**@FunctionalInterface**的作用。

函数接口主要在java.util.function包下,主要分成下面几类:

  • Predicate:有输入,输出布尔值的函数;
  • Function:有输入有输出的函数;
  • Consumer:有输入无输出的函数;
  • Supplier:无输入有输出的函数;
  • Operator:输入输出为相同类型的函数;

3.1 Predicate-断言

Predicate的源码定义为:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Lambda表达式即为test方法的实现,从test方法的定义我们可以看出,test方法接受任意类型的参数T,返回值boolean,我们可以用下面的表达式定义:

Predicate<String> emptyPredicate = (String s) -> s.isEmpty()

根据类型推断可缩写为:Predicate<String> emptyPredicate = s -> s.isEmpty()

使用当前Predicate定义,可通过test方法执行:

Predicate<String> emptyPredicate = s -> s.isEmpty();
System.out.println(emptyPredicate.test("wyf"));

输出的emptyPredicate.test("wyf")返回值为false。

3.1.1 组合Predicate

Predicate接口包含:negate、and、和or方法,可以重用已有的Predicate并组成复杂的Predicate。

  • negate:已有Predicate的否定
Predicate<String> nonEmptyPredicate = emptyPredicate.negate();
  • and:相当于逻辑运算的&&。
Predicate<Integer> greaterThan0 = i -> i > 0;
Predicate<Integer> lessThan100 = i -> i < 100;
Predicate<Integer> between0And100 = greaterThan0.and(lessThan100);

只有在i > 0i < 100的情况下,test方法的返回值才为true。

  • or:相当于逻辑运算的||。
Predicate<Integer> greateThan0OrLessThan100 = greaterThan0.or(lessThan100);

i > 0i < 100时,test方法的返回值是true。

3.1.2 原始数据类型Predicate

Java会自动将包装类型拆包成原始数据类型,但这意味着性能的损失,所以当数据为原始数据类型时候,Java 8提供了一些特殊的Predicate。

  • IntPredicate:当入参是int型时;

Predicate<Integer> greaterThan0 = i -> i > 0;

可修改为:

IntPredicate intGreaterThan0 = i -> i > 0;

  • DoublePredicate:当入参为double型时;
  • LongPredicate:当入参味long型时;
3.1.3 两个参数的Predicate

Java还给我们提供了两个入参的Predicate叫做 - BiPredicate

@FunctionalInterface
public interface BiPredicate<T, U> {
    boolean test(T t, U u);
}

Test方法接受两个入参,类型分别为T和U,我们可以定义为:

BiPredicate<String, Integer> isLongThanGivenLength = (str, len) -> str.length() > len;

第一个入参T为类型为String(str),第二个入参U类型为Integer(len)。

3.2 Function-函数

Function的源码定义为:

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

同样Lambda表达式是apply方法的实现,apply方法接受任意类型的参数T,返回值类型为R,我们可以用下面的表达式定义:

Function<String, Integer> lengthFunction = str -> str.length();

入参T类型为String(str),返回值R类型为Integer(str.length()),使用当前Function定义,可通过apply方法执行:

Function<String, Integer> lengthFunction = str -> str.length();
System.out.println(lengthFunction.apply("wyf"));

输出的lengthFunction.apply("wyf")返回值为3。

3.2.1 组合Function

Function接口函数也为我们提供了andThen和compose方法组合已有的Function,组合的返回值仍为Function。先定义两个将被组合的Function:

Function<Integer, Integer> plusFunction = x -> x + x ;
Function<Integer, Integer> multipleFunction = x -> x * x;
  • andThen:新的Function是组合第一个函数的返回值作为第二个函数的输入。
Function<Integer, Integer> andThenFunction  = plusFunction.andThen(multipleFunction);
System.out.println(andThenFunction.apply(2));

执行时,plusFunction先执行,返回值作为multipleFunction的入参再执行,结果为16。

  • compose:新的Function是组合第二个函数的返回值作为第一个函数的输入。
Function<Integer, Integer> composeFunction = plusFunction.compose(multipleFunction);
System.out.println(composeFunction.apply(2));

执行时,multipleFunction先执行,返回值作为plusFunction的入参再执行,结果为8。

3.2.2 原始数据类型Function

像Predicate一样,Function也有原始数据类型的Function,主要有三类:

第一类入参固化为函数接口,返回值类型R,仍需再范型中定义:

  • IntFunction:入参为int。上面的plusFunction可修改为:
IntFunction<Integer> intPlusFunction = x -> x + x ;
  • LongFunction:入参为long。
  • DoubleFunction:入参为double。

第二类是返回值固化为函数接口,入参类型T仍需在范型中定义:

  • ToIntFunction:返回值类型为int。
ToIntFunction<String> toIntFunction = str -> str.length();
System.out.println(toIntFunction.applyAsInt("www"));
  • ToLongFunction:返回值类型为long。
  • ToDoubleFunction:返回值类型为double。

第三类是入参和返回值都固化为函数接口:

  • IntToLongFunction:入参为int,返回值为long。
IntToLongFunction intToLongFunction = i -> i;
System.out.println(intToLongFunction.applyAsLong(1));
  • IntToDoubleFunction:入参为int,返回值为double。
  • LongToIntFunction:入参为long,返回值为int。
  • LongToDoubleFunction:入参为long,返回值为double。
  • DoubleToIntFunction:入参为double,返回值为int。
  • DoubleToLongFunction:入参为double,返回值为long。
3.2.3 两个入参的Function

Function是一个入参T和一个返回值R,Java还提供了BiFunction,源码定义为:

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

Apply方法接受两个参数T和U,返回值为R,我们可以用下面的表达式定义:

 BiFunction<String, String, Integer> totalLengthBiFunction = (str1, str2) -> str1.length() + str2.length();
 System.out.println(totalLengthBiFunction.apply("wyf","www"));

apply方法的第一个入参T是String(str1),第二个入参U是String(str2),返回值是两个字符串长度之和,执行apply方法输出为6.

同样BiFunction也有很多的原始数据类型函数:

  • ToIntBiFunction:返回值为int型。
  • ToLongBiFunction:返回值为long型。
  • ToDoubleBiFunction:返回值为double型。

3.3 Consumer-消费者

Consumer顾名思义消费而不生产,Consumer的源码定义如下:

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

从accept方法的定义中可以看出,accept方法接受一个参数T,没有返回值,我们可以用下面的表达式定义:

Consumer<String> helloConsumer = str -> System.out.println("Hello " + str + "!");
helloConsumer.accept("wyf");

accept方法接受的参数T是String(str),没有返回值。

Consumer同样也有原始数据类型接口:

  • IntConsumer:入参为int。
  • LongConsumer:入参为long。
  • DoubleConsumer:入参为double。

Consumer也有两个参数的BiConsumer接口,包含:

  • ObjIntConsumer:第一个入参为任意类型T,第二个入参为int。
  • ObjLongConsumer第一个入参为任意类型T,第二个入参为long。
  • ObjDoubleConsumer:第一个入参为任意类型T,第二个入参为double。

3.4 Supplier-提供者

Supplier顾名思义是只生产不消费,Supplier的源码定义如下:

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

Get方法不接受参数,返回值为T,我们可以用下面的表达式定义:

Supplier<Long> systemTime = () -> System.currentTimeMillis();
System.out.println(systemTime.get());

get方法没有入参,返回值是Long型,输出当前系统事件。

Consumer同样也有原始数据类型接口:

  • IntSupplier:返回值是int型。
  • LongSupplier:返回值是long型。
  • DoubleSupplier:返回值是double型。
  • BooleanSupplier:返回值是boolean型。

3.5 Operator-操作者

Operator其实是一种特殊的Function,它的输入和返回值都是同一种类型。

3.5.1 UnaryOperator

UnaryOperator继承了Function接口,如定义:

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T>

UnaryOperator接受一个入参T,返回值也是类型T。

同样UnaryOperator也有原始数据类型的函数接口:

  • IntUnaryOperator:入参返回值都为int型。
  • LongUnaryOperator:入参返回值都为long型。
  • DoubleUnaryOperator:入参返回值都为double型。
3.5.2 BinaryOperator

BinaryOperator继承了Function接口,如定义:

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T>

BinaryOperator接受的两个入参类型都为T,返回值类型也是T。

同样BinaryOperator也有原始数据类型的函数接口:

  • IntBinaryOperator:两个入参和一个返回值都是int型。
  • LongBinaryOperator:两个入参和一个返回值都是long型。
  • DoubleBinaryOperator:两个入参和一个返回值都是double型。

3.6 Comparator

Comparator是我们经常用来比较排序所用的一个函数接口,它的定义:

@FunctionalInterface
public interface Comparator<T> {
      int compare(T o1, T o2);
}

若o1小于o2,返回负数;若o1等于o2,返回0;若o1大于o2,返回正数。我们可以用Lambda表达式来定义:

Comparator<Person> byWeightComparatorUsingLambda =
        (person1, person2) -> person1.getWeight().compareTo(person2.getWeight());

Comparator函数接口还为排序提供了comparing的静态方法,它接受一个Function来获取处理数据的排序key,上面的语句可以简写成:

Comparator<Person> byWeightComparatorUsingStatic =Comparator.comparing(Person::getWeight);

3.7 自定义函数接口

函数接口的定义主要是看入参和返回值,前面我们有一个入参的Function,有两个入参的BiFunction,我们现在自定义一个有三个入参的TriFunction。

@FunctionalInterface //1
public interface TriFunction<T, U, W, R> {
    R apply(T t, U u, W w); //2
}
  1. @FunctionalInterface标记为函数接口
  2. Apply方法接受三个入参T(t)、U(u)和W(w),返回值为R。

我们可以通过如下Lambda表达式调用:

TriFunction<String, String, String, Integer> lengthTriFuntion =
                (str1, str2, str3) -> str1.length() + str2.length() + str3.length();
System.out.println(lengthTriFuntion.apply("wyf", "www", "foo"));

第一个入参String(str1),第二个入参String(str2),第三个入参String(str3)以及返回值类型为Integer,计算输出三个字符串的长度之和,输出结果为9。

4 Method Reference-方法引用

方法引用让我们可以使用已有的方法定义,并像Lambda表达式一样可以作为方法的参数。Java中方法引用使用“::”两个冒号表示。

4.1 构造器方法引用

构造方法的引用使用“类名::new”来定义。

  • 无参数构造:构造器不接受参数,返回新建的Person对象,符合Supplier函数接口的定义,使用基于Lambda表达式的Supplier函数接口实现与方法引用是等同的。
Supplier<Person> emptyConstructor = Person::new;
Supplier<Person> emptyConstructorLambda = () -> new Person();
Person person1 = emptyConstructor.get();
Person person1Lambda = emptyConstructorLambda.get();
  • 一个参数构造:构造器接受一个参数,返回新建的Person对象,符合Function函数接口的定义,同样使用基于Lambda表达式的Function函数接口实现与方法引用是等同的。
Function<String, Person> nameConstructor = Person::new;
Function<String, Person> nameConstructorLambda = name -> new Person(name);
Person person2 = nameConstructor.apply("wyf");
Person person2Lambda = nameConstructorLambda.apply("wyf");
  • 两个参数构造:构造器接受两个参数,返回新建的Person对象,符合BiFunction函数接口的定义,同样使用基于Lambda表达式的BiFunction函数接口实现与方法引用是等同的。
BiFunction<String, Gender, Person> nameAndGenderConstructor = Person::new;
BiFunction<String, Gender, Person> nameAndGenderConstructorLambda =
        (name, gender) -> new Person(name, gender);
Person person3 = nameAndGenderConstructor.apply("www", Gender.FEMALE);
Person person3Lambda = nameAndGenderConstructorLambda.apply("www", Gender.FEMALE);
  • 三个参数构造:构造器接受三个参数,返回新建的Person对象,符合我们自定义的TriFunction函数接口的定义,同样使用基于Lambda表达式的TriFunction函数接口实现与方法引用是等同的。
TriFunction<String, Gender, Integer, Person> allConstructor = Person::new;
TriFunction<String, Gender, Integer, Person> allConstructorLambda =
        (name, gender, weight) -> new Person(name, gender, weight);
Person person4 = allConstructor.apply("www", Gender.FEMALE, 110);
Person person4Lambda = allConstructorLambda.apply("www", Gender.FEMALE, 110);

4.2 静态方法引用

静态方法引用可以让我们使用方法引用的方式调用类的静态方法,格式为“类名::静态方法”,如:

IntFunction<String> intToStringFunction = Integer::toString;//1
IntFunction<String> intToStringFunLambda = i -> Integer.toString(i);//2
System.out.println(intToStringFunction.apply(123));
System.out.println(intToStringFunLambda.apply(123));
  1. Integer::toString,toString方法是Integer类的静态方法,toString方法的参数和返回值符合IntFunction函数接口的定义;
  2. Lambda表达式等同于第1句。

4.3 实例方法引用

类的实例的方法的引用让我们使用方法引用的方式调用实例对象的方法,格式为“实例对象名::实例方法”,如:

Person person = new Person("www", Gender.FEMALE, 80);
Consumer<String> walkConsumer = person::walk;
walkConsumer.accept("黄山路");

walk方法是Person类实例的方法:

public void walk(String roadName){
    System.out.println(name + "在" + roadName +"上行走");
}

4.4 引用特定类的任意对象的方法

还有另外一种形式实例方法的引用,格式为"类型名::实例方法",如:

List<Person> people = Arrays.asList(new Person("wyf", Gender.MALE, 100),
            new Person("www", Gender.FEMALE, 80),
            new Person("foo", Gender.FEMALE, 90));
people.forEach(Person::sayName);
people.forEach(person -> person.sayName());

此处的sayName方法是实例对象方法,people列表里的三个任意的Person实例均可以调用这个方法;注意和上面“实例方法引用”的区别,“实例方法引用”只针对一个实例对象进行方法引用,而我们当前的“引用特定类的任意对象的方法”可以对people列表里任意Person实例对象进行方法引用。
sayName方法是Person类的方法:

public void sayName(){
    System.out.println("我的名字是:" + name);
}

从上面4种方法引用与Lambda表达式的对比我们可以知道,在调用现有类的已有方法的时候的时候,方法引用比Lambda表达式更自然,可读性更强。比如上面的例子:

people.sort((p1, p2) -> p1.getWeight().compareTo(p2.getWeight())); 

也可以用方法引用改写成:

people.sort(Comparator.comparing(Person::getWeight));

新书推荐:

我的新书《从企业级开发到云原生微服务:Spring Boot 实战》已出版,内容涵盖了丰富Spring Boot开发的相关知识
购买地址:https://item.jd.com/12760084.html
在这里插入图片描述

主要包含目录有:

第一章 初识Spring Boot(快速领略Spring Boot的美丽)
第二章 开发必备工具(对常用开发工具进行介绍:包含IntelliJ IDEA、Gradle、Lombok、Docker等)
第三章 函数式编程
第四章 Spring 5.x基础(以Spring 5.2.x为基础)
第五章 深入Spring Boot(以Spring Boot 2.2.x为基础)
第六章 Spring Web MVC
第七章 数据访问(包含Spring Data JPA、Spring Data Elasticsearch和数据缓存)
第八章 安全控制(包含Spring Security和OAuth2)
第九章 响应式编程(包含Project Reactor、Spring WebFlux、Reactive NoSQL、R2DBC、Reactive Spring Security)
第十章 事件驱动(包含JMS、RabbitMQ、Kafka、Websocket、RSocket)
第11章 系统集成和屁股里(包含Spring Integration和Spring Batch)
第12章 Spring Cloud与微服务
第13章 Kubernetes与微服务(包含Kubernetes、Helm、Jenkins、Istio)
多谢大家支持。

发布了116 篇原创文章 · 获赞 10 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/wiselyman/article/details/105731779