lambda表达式,函数式接口和方法引用

结论

  • 函数式接口是接口的某种特定形式
  • lambda表达式是函数式接口的具体实现
  • lambda表达式是某种特定形式的匿名类的语法糖
  • 方法引用是某种特定形式的lambda表达式的语法糖

温故一下什么是匿名类

比如我们有一个接口HelloWorld,可以对这个世界Say Hi。

  • HelloWorld.java
package com.test;public interface HelloWorld {void greet();
}

它有两个实现类,分别为英文版的EnglishHelloWorld和中文版的ChineseHelloWorld。

  • EnglishHelloWorld.java
package com.test;public class EnglishHelloWorld implements HelloWorld {@Overridepublic void greet() {System.out.println("Hello world!");}
}
  • ChineseHelloWorld.java
package com.test;public class ChineseHelloWorld implements HelloWorld {@Overridepublic void greet() {System.out.println("你好,世界!");}
}

主程序中有一个greet方法,以HelloWorld接口类型为参数。调用时,如果实际传入的是EnglishHelloWorld类型,则打印【Hello world!】,而如果实际传入的是ChineseHelloWorld类型,则打印【你好,世界!】。下面是我们通常的写法。

  • App.java
package com.test;public class App {public static void greet(HelloWorld helloWorld) {helloWorld.greet();}public static void main(String[] args) {HelloWorld englishHelloWorld = new EnglishHelloWorld(); HelloWorld chineseHelloWorld = new ChineseHelloWorld();greet(englishHelloWorld);greet(chineseHelloWorld);}
}

如果englishHelloWorld和chineseHelloWorld这两个变量不会再被其它地方使用,我们偶尔也会像下面这样写。

  • App.java
package com.test;public class App {public static void greet(HelloWorld helloWorld) {helloWorld.greet();}public static void main(String[] args) {greet(new EnglishHelloWorld());greet(new ChineseHelloWorld());}
}

而当我们觉得EnglishHelloWorld和ChineseHelloWorld这两个实现类实际上只会在App.java中用到,或者我们并不想让别人使用这两个实现类的时候,我们还有一个选择——匿名类。这样,我们的项目中只需保留App.java和HelloWorld.java。

  • App.java
package com.test;public class App {public static void greet(HelloWorld helloWorld) {helloWorld.greet();}public static void main(String[] args) {greet(new HelloWorld() {@Overridepublic void greet() {System.out.println("Hello world!");}});greet(new HelloWorld() {@Overridepublic void greet() {System.out.println("你好,世界!");}});}
}

从匿名类到lambda表达式

实际上,lambda表达式是对匿名类的进一步简化。lambda表达式既可以作为值复制给变量再使用,也可以直接作为方法参数使用。当然,通常直接作为方法参数使用。例子如下。

  • 作为值复制给变量再使用
package com.test;public class App {public static void greet(HelloWorld helloWorld) {helloWorld.greet();}public static void main(String[] args) {HelloWorld englishHelloWorld = () -> System.out.println("Hello world!");HelloWorld chineseHelloWorld = () -> System.out.println("你好,世界!");greet(englishHelloWorld);greet(chineseHelloWorld);}
}
  • 直接作为方法参数使用
package com.test;public class App {public static void greet(HelloWorld helloWorld) {helloWorld.greet();}public static void main(String[] args) {greet(() -> System.out.println("Hello world!"));greet(() -> System.out.println("你好,世界!"));}
}

有些人刚接触lambda表达式时有一个困惑,为什么调用一下函数式接口中的方法,lambda表达式就执行了。实际上知道它和匿名类的关联后,我们可以把它当作是匿名类的语法糖,() -> System.out.println(“Hello world!”)实际上就是上面的匿名类去掉了类声明和方法声明只留下参数和方法体。换句话说,上面的例子中,lambda表达式中箭头后面的方法体就是HelloWorld接口的匿名实现类的greet方法的方法体,lambda表达式的箭头前面的参数就是HelloWorld接口的匿名实现类的greet方法的参数。

函数式接口来了

函数式接口指的是只有一个抽象方法的接口,上面例子中的HelloWorld接口就是一个函数式接口。函数式接口和普通的接口没有太大差别,只不过只有一个抽象方法而已,但是只有这种特定形式的接口才可以以lambda表达式作为实现。此处需要留意的是,函数式接口仍然可以以普通类和匿名类作为自己的实现。下面的例子中,英文版的HelloWorld以匿名类的形式实现,中文版的HelloWorld以lambda表达式的形式实现。

package com.test;public class App {public static void greet(HelloWorld helloWorld) {helloWorld.greet();}public static void main(String[] args) {greet(new HelloWorld() {@Overridepublic void greet() {System.out.println("Hello world!");}});greet(() -> System.out.println("你好,世界!"));}
}

而函数式接口上面的@FunctionalInterface注解只是一个标记,没有它lambda表达式也可以正常执行。只不过说有了它方便开发者之间的无声沟通,有了它在我们编写函数式接口的过程中IDE就能判断我们是否遵循了函数式接口的规范并在违反时给出提示,有了它在我们编译包含函数式接口的项目时编译器就能判断我们是否遵循了函数式接口的规范并在违反时给出警告。一切的一切,都是为了把原本的运行时错误尽可能地前置到开发阶段,前置到编译阶段。因此,这个注解的作用本质上和前面例子中@Override一样。

package com.test;@FunctionalInterface
public interface HelloWorld {void greet();
}

相比于lambda表达式更进一步的方法引用

lambda表达式还不是简化的终点。有些使用场景下,lambda表达式的函数体的内容仅仅是调用一下某个对象或者某个类的一个方法。这种情况下,lambda表达式可以进一步简化为方法引用。

比如,我们有一个POJO类Person,它有name和age两个属性,以及相应的getter,setter和toString方法。

  • Person.java
package com.test;public class Person {private String name;private Integer age;public Person() {}public Person(String name, Integer age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + "]";}
}

由于从业务上来讲,有可能按年龄排序,也有可能按姓名首字母排序,我们不想在排序规则变化时修改POJO,所以我们并不会直接让Person类实现Comparable接口,而是提供一个外置比较器。在调用集合工具类的排序方法实际进行排序时,在以匿名类的形式实现的比较器中灵活地调用我们自己写的比较逻辑。

  • PersonComparator.java
package com.test;public class PersonComparator {/*** 按年龄比较大小* * @param o1 比较对象1* @param o2 比较对象2* @return 比较结果*/public static int compareByAge(Person o1, Person o2) {return o1.getAge() - o2.getAge();}/*** 按姓名首字母比较大小* * @param o1 比较对象1* @param o2 比较对象2* @return 比较结果*/public static int compareByName(Person p1, Person p2) {return p1.getName().compareTo(p2.getName());}  
}
  • App.java
package com.test;import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;public class App {public static void main(String[] args) {Person tom = new Person("tom", 19);Person jack = new Person("Jack", 18);List<Person> persons = new ArrayList<>();persons.add(tom);persons.add(jack);Collections.sort(persons, new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {return PersonComparator.compareByAge(o1, o2);}});for (Person person : persons) {System.out.println(person);}}
}

实际上,java.util.Comparator也是只有一个抽象方法的接口,所以它也是函数式接口,那么我们可以用lambda表达式来实现。

package com.test;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;public class App {public static void main(String[] args) {Person tom = new Person("tom", 19);Person jack = new Person("Jack", 18);List<Person> persons = new ArrayList<>();persons.add(tom);persons.add(jack);Collections.sort(persons, (p1, p2) -> PersonComparator.compareByAge(p1, p2));for (Person person : persons) {System.out.println(person);}}
}

这个地方,我们实际上只是在lambda表达式中调用一下PersonComparator的compareByAge方法以提供具体的比较逻辑。也就是说,lambda表达式的方法体只是一个单纯的方法调用,没有其它处理。那么这种lambda表达式可以简化为方法引用的写法。

package com.test;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;public class App {public static void main(String[] args) {Person tom = new Person("tom", 19);Person jack = new Person("Jack", 18);List<Person> persons = new ArrayList<>();persons.add(tom);persons.add(jack);Collections.sort(persons, PersonComparator::compareByAge);for (Person person : persons) {System.out.println(person);}}
}

方法引用的4种形式

方法引用有4种形式,

  • 引用某个类的某个静态方法
  • 引用某个对象的某个实例方法
  • 引用某个内置类型的任意对象的某个方法
  • 引用某个类的构造器

引用某个类的某个静态方法

实际上,上面按年龄比较Person对象的例子体现的正是这种形式,此处略过。

引用某个对象的某个实例方法

我们把Person类改动一下,新增一个introduceSelf方法,调用该方法时可以自报姓名和年龄。

package com.test;public class Person {private String name;private Integer age;public Person() {}public Person(String name, Integer age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + "]";}public void introduceSelf() {System.out.println(String.format("Hi, I'm %s and I'm %s years old.", this.name, this.age));}
}

Stream API中的forEach方法接收一个Consumer类型的函数式接口作为参数,关于Consumer后面我们会讲到,此处略过。重点是,forEach方法会循环调用我们提供的lambda表达式,每次把数据源中的下一个对象作为参数传递给lambda表达式。我们可以在lambda表达式中调用参数对象的introduceSelf方法,让每个Person对象做一遍自我介绍。同样由于lambda表达式中只是单纯调用一下参数中Person对象的introduceSelf方法,我们可以使用函数式接口的写法。这种情况就是引用某个对象的某个实例方法。

package com.test;import java.util.ArrayList;
import java.util.List;public class App {public static void main(String[] args) {List<Person> persons = new ArrayList<>();persons.add(new Person("tom", 19));persons.add(new Person("Jack", 18));persons.stream().forEach(Person::introduceSelf);}
}
// 输出结果:
// Hi, I'm tom and I'm 19 years old.
// Hi, I'm Jack and I'm 18 years old.

引用某个内置类型的任意对象的某个方法

Stream API中的map方法接收一个Function类型的函数式接口作为参数,关于Function后面我们会讲到,此处略过。重点是,map方法会循环调用我们提供的lambda表达式,每次把数据源中的下一个对象作为参数传递给lambda表达式,并获取lambda表达式的执行结果。最后,我们通过收集器把lambda表达式每一次的执行结果放到List中。此处,lambda表达式每一次被调用时,其参数为names中的当前要处理的字符串。我们只是想把每一个字符串转成其相应的小写形式,而String本身提供了toLowerCase方法可以实现我们的需求,所以我们可以直接使用当前处理的参数,也就是Java内置类型String的toLowerCase方法进行操作。这种情况就是引用某个内置类型的任意对象的某个方法。其实这个例子中的方法引用和上一个例子没太大区别,只不过这个例子中的参数的类型是Java内置的String类型,所以可以调用String的某个方法,而上一个例子中的参数的类型是我们自定义的Person类型,所以可以调用Person的introduceSelf方法。

package com.test;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class App {public static void main(String[] args) {List<String> names = Arrays.asList("Tom", "Jack", "Mike");List<String> newNames = names.stream().map(String::toLowerCase).collect(Collectors.toList());for (String newName: newNames) {System.out.println(newName);}}
}

引用某个类的构造器

比如我们设计了一个通用的集合转换方法transferElements,给定一个原集合和目标集合,该方法可以把原集合中的每一个元素取出来,放到新创建的目标集合中。目标集合参数的类型我们设计为一个lambda表达式,在该表达式的方法体中,调用者可以自定义一些内容,不过,调用者也可以单纯地创建自己想要的目标集合。假如调用者只是单纯的想创建自己想要的目标集合,那么就可以使用引用某个类的构造器这种形式的方法引用。在下面的代码中,原集合是一个ArrayList<String>,目标集合是一个HashSet<String>,这种情况下,写成HashSet::new的形式即可,连HashSet后面的泛型声明<String>都可以省略。

package com.test;import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;public class App {public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>> DEST transferElements(SOURCE sourceCollection,Supplier<DEST> collectionFactory) {DEST result = collectionFactory.get();for (T t : sourceCollection) {result.add(t);}return result;}public static void main(String[] args) {List<String> names = Arrays.asList("Tom", "Jack", "Mike");Set<String> newNames = transferElements(names, HashSet::new);for (String newName : newNames) {System.out.println(newName);}}
}

常用的内置函数式接口

通过前面的说明,我们可以知道,函数式接口是只有一个抽象方法的接口,lambda表达式是函数式接口的实现,相当于函数式接口的匿名实现类的语法糖,而方法引用是lambda表达式在某些使用场景下的更进一步的简写形式,所以一切开始于函数式接口。我们来看一下函数式接口。

比如,我们有一个函数式接口Greeting,它接收一个参数,没有返回值。

  • Greeting.java
package com.test;@FunctionalInterface
public interface Greeting<T> {void sayHi(T t);
}

然后我们有一个greet方法,接收两个参数,第一个是对方的名字,定义了向谁打招呼。第二个是Greeting接口,定义了打招呼的方式,例子中只是简单地向对方说一句Hi。

package com.test;public class App {public static void greet(String name, Greeting<String> greeting) {greeting.sayHi(name);}public static void main(String[] args) {greet("Tom", name -> System.out.println(String.format("Hi, %s", name)));}
}

再比如,我们有一个函数式接口RandomValue,它没有参数,但是有返回值。

package com.test;@FunctionalInterface
public interface RandomValue<T> {T generate();
}

然后我们有一个getRandomNumber方法,以RandomValue接口为参数,以RandomValue接口的generate方法的返回值作为返回值。这个方法由调用者决定产生随机数的逻辑,例子中会产生一个1到10之间的随机正整数。

package com.test;import java.util.Random;public class App {public static int getRandomNumber(RandomValue<Integer> random) {return random.generate();}public static void main(String[] args) {System.out.println(getRandomNumber(() -> new Random().nextInt(10)));}
}

再比如,我们有一个函数式接口Computer,它有一个参数,并且有返回值。

package com.test;@FunctionalInterface
public interface Computer<T, R> {R compute(T t);
}

然后我们有一个analyze方法,接收两个参数,第一个是字符串,代表分析对象,第二个是Computer接口,代表分析方法。例子中调用analyze方法时,实际传入的是分析句子的长度。

package com.test;public class App {public static int analyze(String sentence, Computer<String, Integer> computer) {return computer.compute(sentence);}public static void main(String[] args) {System.out.println(String.format("The length of this sentence is %d", analyze("Hello World!", String::length)));}
}

实际上,上面例子中的3个函数式接口非常通用。只要我们需要一个只接受一个参数,没有返回值的类型,我们就可以写一个Greeting那样的函数式接口,只要我们需要没有参数,只有返回值的类型,我们就可以写一个RandomValue那样的函数式接口,只要我们需要一个既接收一个参数,又有返回值的类型,我们就可以写一个Computer那样的函数式接口。既然如此,我们何不为每种类型创建一个通用的函数式接口呢?

比如把Greeting这种类型设计为Consumer

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

那么可以把greet方法修改成下面这样。以后再有同样类型的函数式接口的需求,都可以来使用Consumer。

package com.test;public class App {public static void greet(String name, Consumer<String> consumer) {consumer.accept(name);}public static void main(String[] args) {greet("Tom", name -> System.out.println(String.format("Hi, %s", name)));}
}

再比如把RandomValue这种类型设计为Supplier

package com.test;@FunctionalInterface
public interface Supplier<T> {T get();
}

那么可以把getRandomNumber方法修改成下面这样。以后再有同样类型的函数式接口的需求,都可以来使用Supplier。

package com.test;import java.util.Random;public class App {public static int getRandomNumber(Supplier<Integer> supplier) {return supplier.get();}public static void main(String[] args) {System.out.println(getRandomNumber(() -> new Random().nextInt(10)));}
}

再比如把Computer这种类型设计为Function

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

那么可以把analyze方法修改成下面这样。以后再有同样类型的函数式接口的需求,都可以来使用Function。

package com.test;public class App {public static int analyze(String sentence, Function<String, Integer> function) {return function.apply(sentence);}public static void main(String[] args) {System.out.println(String.format("The length of this sentence is %d", analyze("Hello World!", String::length)));}
}

既然我们都想到了这一点,Java官方早就为我们考虑到了,所以Java内置了很多常用的函数式接口,不需要我们再单独定义了,拿来使用即可。如果官方提供的函数式接口不满足我们的需求,我们再自己定义即可。想必看了上面的那些例子,我们对函数式接口的使用不再陌生,只是想知道Java官方提供了哪些常用的函数式接口。下面列出了最常用的几个函数式接口,请参考java.util.function包以查看所有预定义的函数式接口。

函数式接口说明
java.util.function.Consumer有一个参数,没有返回值
java.util.function.Supplier没有参数,有返回值
java.util.function.Function有一个参数,有返回值
java.util.function.Predicate有一个参数,有返回值,返回值为boolean类型

lambda表达式在Stream API中的应用

函数式接口和lambda表达式比较适合做数据处理和事件处理,我们主要看一下四大常用内置函数式接口在Stream API中的使用。

java.util.function.Consumer

Stream API的forEach方法的参数为函数式接口Consumer,它会从数据源中逐个获取数据并调用lambda表达式,将获取到的数据作为lambda表达式的参数。下面是一个查看SpringBoot的IOC容器中的所有Bean定义的例子。

package com.test;import java.util.Arrays;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;@SpringBootApplication
public class App {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(App.class, args);Arrays.asList(context.getBeanDefinitionNames()).stream().forEach(System.out::println);}
}

java.util.function.Supplier

Stream API的静态方法generate的参数为函数式接口Supplier,它获取lambda表达式的执行结果。下面是一个生成10个随机整数的例子。

package com.test;import java.util.Random;
import java.util.stream.Stream;public class App {public static void main(String[] args) {Random random = new Random();Stream.generate(random::nextInt).limit(10).forEach(System.out::println);}
}

java.util.function.Function

Stream API的map方法的参数为函数式接口Function,它会从数据源中逐个获取数据并调用lambda表达式,将获取到的数据作为lambda表达式的参数,并且获取lambda表达式的执行结果。下面的例子中,如果学生成绩及格,则给其一个及格标注。

  • Student.java
package com.test;public class Student {private String name;private Integer score;private Boolean passed = Boolean.FALSE;public Student() {}public Student(String name, Integer score) {this.name = name;this.score = score;}public Student(String name, Integer score, Boolean passed) {this.name = name;this.score = score;this.passed = passed;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getScore() {return score;}public void setScore(Integer score) {this.score = score;}public Boolean getPassed() {return passed;}public void setPassed(Boolean passed) {this.passed = passed;}@Overridepublic String toString() {return "Student [name=" + name + ", score=" + score + ", passed=" + passed + "]";}
}
  • App.java
package com.test;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class App {public static void main(String[] args) {List<Student> students = Arrays.asList(new Student("Tom", 71),new Student("Jack", 59),new Student("Mike", 99));List<Student> mappedStudents = students.stream().map(student ->new Student(student.getName(), student.getScore(), student.getScore() >= 60)).collect(Collectors.toList());mappedStudents.forEach(System.out::println);}
}
// 输出结果:
// Student [name=Tom, score=71, passed=true]
// Student [name=Jack, score=59, passed=false]
// Student [name=Mike, score=99, passed=true]

java.util.function.Predicate

Stream API的filter方法的参数为函数式接口Predicate,通过返回值是否为true来判断是否满足过滤条件。下面是一个查看成绩及格的学生的例子。

  • Student.java
package com.test;public class Student {private String name;private Integer score;public Student() {}public Student(String name, Integer score) {this.name = name;this.score = score;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getScore() {return score;}public void setScore(Integer score) {this.score = score;}@Overridepublic String toString() {return "Student [name=" + name + ", score=" + score + "]";}
}
  • App.java
package com.test;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;public class App {public static void main(String[] args) {List<Student> students = Arrays.asList(new Student("Tom", 71),new Student("Jack", 59),new Student("Mike", 99));List<Student> filteredStudents = students.stream().filter(student -> student.getScore() >= 60).collect(Collectors.toList());filteredStudents.forEach(System.out::println);}
}
// 输出结果:
// Student [name=Tom, score=71]
// Student [name=Mike, score=99]

lambda表达式在Spring框架中的应用

Spring框架中大量使用了lambda表达式。比如,Spring框架其实有Servlet和Reactive两种实现版本,前者为经典的多线程框架,后者为异步框架。Spring Boot项目启动时,它会根据当前的应用环境是Servlet还是Reactive去创建相应的应用环境对象,把环境信息封装到该对象中。在2.7.6之前的Spring Boot版本中,获取到当前应用环境类型后,通过switch表达式匹配相应的应用环境类型,进而通过new创建相应的应用环境对象。

private ConfigurableEnvironment getOrCreateEnvironment() {if (this.environment != null) {return this.environment;}switch (this.webApplicationType) {case SERVLET:return new ApplicationServletEnvironment();case REACTIVE:return new ApplicationReactiveWebEnvironment();default:return new ApplicationEnvironment();}
}

从2.7.6版本开始,Spring Boot借助spring-boot-x.y.z.jar的META-INF/spring.factories文件,在该文件中定义ApplicationContextFactory的可取值为AnnotationConfigReactiveWebServerApplicationContext.Factory和AnnotationConfigServletWebServerApplicationContext.Factory,其中,Factory是AnnotationConfigReactiveWebServerApplicationContext和AnnotationConfigServletWebServerApplicationContext中的内部静态类。

  • spring.factories
# Application Context Factories
org.springframework.boot.ApplicationContextFactory=\
org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext.Factory,\
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory

取消switch表达式,改为从spring.factories中读取ApplicationContextFactory的实现类列表,然后遍历该列表,如果当前实现类支持当前应用环境类型,则返回相应的应用环境对象。例子中的BiFunction也是Java内置的函数式接口,它接收两个参数,有返回值。

  • DefaultApplicationContextFactory.createEnvironment
@Override
public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {return getFromSpringFactories(webApplicationType, ApplicationContextFactory::createEnvironment, null);
}
  • DefaultApplicationContextFactory.getFromSpringFactories
private <T> T getFromSpringFactories(WebApplicationType webApplicationType,BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class,getClass().getClassLoader())) {T result = action.apply(candidate, webApplicationType);// 如果webApplicationType为Servlet,则只有遍历到AnnotationConfigServletWebServerApplicationContext.Factory时result才有值if (result != null) {return result;}}return (defaultResult != null) ? defaultResult.get() : null;
}

所谓支持当前应用环境类型,对于AnnotationConfigServletWebServerApplicationContext.Factory来说,指的是如果应用环境类型为Servlet,则返回通过new创建的ApplicationServletEnvironment对象。

  • AnnotationConfigServletWebServerApplicationContext.Factory
@Override
public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {return (webApplicationType != WebApplicationType.SERVLET) ? null : new ApplicationServletEnvironment();
}

而对于AnnotationConfigReactiveWebServerApplicationContext.Factory来说,指的是如果应用环境类型为Reactive,则返回通过new创建的ApplicationReactiveEnvironment对象。

  • AnnotationConfigReactiveWebServerApplicationContext.Factory
@Override
public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {return (webApplicationType != WebApplicationType.REACTIVE) ? null : new ApplicationReactiveWebEnvironment();
}

乍一看,通过lambda表达式实现的版本比switch的版本要显得复杂,但是它也有好处。通过lambda表达式实现的版本中,要创建的应用环境对象的类型被定义到了外部属性文件中,便于维护,而且创建应用环境对象的方式被委托给了调用者,便于扩展。虽然此处只是单纯地通过new的方式创建了ApplicationServletEnvironment,但是如果以后有需要,AnnotationConfigServletWebServerApplicationContext.Factory可以对createEnvironment方法进行定制处理

其它

在函数式编程中,函数是一等公民,也就是说,函数可以作为值赋值给变量,可以作为参数传递给方法,也可以作为方法的返回值,和基本数据类型所能进行的操作几乎别无二致,是该编程语言世界中具有正常国民待遇的实体。在Java8之前,这一待遇由类享有,函数,Java中称为方法,只是类中用来描述某些行为的一段代码的集合,并不能脱离类单独使用。从Java8开始,函数获得了解放,虽然距离像JS那样自由奔放的使用方式还很遥远,但是一定程度上弥补了Java面向对象的不足。编程语言,尤其是以面向对象为主打的高级编程语言,在机器友好和人类友好之间选择了倾向于后者,所以它会不遗余力地模仿人类所处的现实世界。但是,现实世界并非全都是实体对象,也有过程,比如小学生正在作业本上进行的除法和求和,比如以加速度砸向牛顿脑门的那颗苹果的运动轨迹,比如看似重复其实每次都有些许变化的节拍。以实体对象的形式来容纳和描述这些过程并无不可,但是当计算非常庞杂,或者哪怕不庞杂,在这些场合下,相比于面向对象,面向过程的函数式编程显而易见地更加优雅与灵活。

参考

  • Oracle官方Java教程 - Anonymous Classes
  • Oracle官方Java教程 - Lambda Expressions
  • Oracle官方Java教程 - Method References

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/145566.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

三、2023.9.29.C++面向对象.3

文章目录 33、简述一下什么是面向对象&#xff1f;34、简述一下面向对象的三大特征&#xff1f;35、简述一下 C 的重载和重写&#xff0c;以及它们的区别&#xff1f;36、说说 C 的重载和重写是如何实现的&#xff1f;37、说说构造函数有几种&#xff0c;分别什么作用?38、只定…

SpringBoot+MinIO8.0开箱即用的启动器

一、代码拉取及安装 1.码云地址 https://gitee.com/qiangesoft/rdp-starter/tree/master/rdp-starter-minio 2.本地安装 二、代码接入 存储路径规则可配置桶访问权限可配置可配置初始生成多个桶 1.引入依赖 <dependency><groupId>com.qiangesoft.rdp</gro…

会议AISTATS(Artificial Intelligence and Statistics) Latex模板参考文献引用问题

前言 在看AISTATS2024模板的时候&#xff0c;发现模板里面根本没有教怎么引用&#xff0c;要被气死了。 如下&#xff0c;引用(Cheesman, 1985)的时候&#xff0c;模板是自己手打上去的&#xff1f;而且模板提供的那三个引用&#xff0c;根本也没有Cheesman这个人&#xff0c…

Mybatis 二级缓存(使用Ehcache作为二级缓存)

上一篇我们介绍了mybatis中二级缓存的使用&#xff0c;本篇我们在此基础上介绍Mybatis中如何使用Ehcache作为二级缓存。 如果您对mybatis中二级缓存的使用不太了解&#xff0c;建议您先进行了解后再阅读本篇&#xff0c;可以参考&#xff1a; Mybatis 二级缓存https://blog.c…

Fake Maxpooling 二维滑动窗口

先对每一行求一遍滑动窗口&#xff0c;列数变为(列数-k1) 再对每一列求一遍滑动窗口&#xff0c;行数变为(行数-k1) 剩下的就是每一个窗口里的最大值啦 #include<bits/stdc.h> #define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define endl \nusing nam…

AIGC 绘画Stable Diffusion工具的安装与使用

我们先让ChatGPT来帮我们回答一下,什么是Stable Diffusion Stable Diffusion 是一种基于概率模型的图像生成技术。它通过对图像空间中每个像素的颜色值进行推断,从而生成具有高度真实感和细节的图像。 Stable Diffusion 使用一种称为扩散过程的方法来生成图像。在生成过程中…

测试用例的编写(面试常问)

作者&#xff1a;爱塔居 专栏&#xff1a;软件测试 作者简介&#xff1a;不断总结&#xff0c;才能变得更好~踩过的坑&#xff0c;不能再踩~ 文章简介&#xff1a;常见的几个测试用例。 一、淘宝购物车 二、登录页面 三、三角形测试用例 abc结果346普通三角形333等边三角形334…

【计算机网络笔记五】应用层(二)HTTP报文

HTTP 报文格式 HTTP 协议的请求报文和响应报文的结构基本相同&#xff0c;由四部分组成&#xff1a; ① 起始行&#xff08;start line&#xff09;&#xff1a;描述请求或响应的基本信息&#xff1b;② 头部字段集合&#xff08;header&#xff09;&#xff1a;使用 key-valu…

[JAVAee]MyBatis

目录 MyBatis简介 MyBatis的准备工作 框架的添加 连接数据库字符串的配置 MyBatis中XML路径的配置 ​编辑 MyBatis的使用 各层的实现 进行数据库操作 增加操作 拓展 修改操作 删除操作 查询操作 结果映射 单表查询 多表查询 like模糊查询 动态SQL / MyBa…

第7讲:VBA中利用FIND的代码实现单值查找实例

【分享成果&#xff0c;随喜正能量】心真如&#xff0c;随缘生起一切法&#xff0c;一切法还归于真如。《大乘起信论》讲心真如门就是体&#xff0c;心生灭门就是相用&#xff0c;心生灭、心真如都从一心而起&#xff0c;离开心别无二法。我们想从心真如门修行不易进入&#xf…

基于PHP+MySQL的家教平台

摘要 设计和实现基于PHP的家教平台是一个复杂而令人兴奋的任务。这个项目旨在为学生、家长和教师提供一个便捷的在线学习和教授平台。本文摘要将概述这个项目的关键方面&#xff0c;包括用户管理、课程管理、支付处理、评价系统、通知系统和安全性。首先&#xff0c;我们将建立…

【JVM】双亲委派模型

双亲委派模型 1. 什么是双亲委派模型2. 双亲委派模型的优点 1. 什么是双亲委派模型 提到 类加载 机制&#xff0c;不得不提的一个概念就是“双亲委派模型”。 双亲委派模型指的就是 JVM 中的类加载器如何根据类的全限定名找到 .class 文件的过程 类加载器: JVM 里面专门提供…

小谈设计模式(6)—依赖倒转原则

小谈设计模式&#xff08;6&#xff09;—依赖倒转原则 专栏介绍专栏地址专栏介绍 依赖倒转原则核心思想关键点分析abc 优缺点分析优点降低模块间的耦合度提高代码的可扩展性便于进行单元测试 缺点增加代码的复杂性需要额外的设计和开发工作 Java代码实现示例分析 总结 专栏介绍…

正态分布的概率密度函数|多种正态分布检验|Q-Q图

正态分布的概率密度函数&#xff08;Probability Density Function&#xff0c;简称PDF&#xff09;的函数取值是指在给定的正态分布参数&#xff08;均值 μ 和标准差 σ&#xff09;下&#xff0c;对于特定的随机变量取值 x&#xff0c;计算得到的概率密度值 f(x)。这个值表示…

ISP图像信号处理——平场校正介绍以及C++实现

参考文章1&#xff1a;http://t.csdn.cn/h8TBy 参考文章2&#xff1a;http://t.csdn.cn/6nmsT 参考网址3&#xff1a;opencv平场定标 - CSDN文库 平场校正一般先用FPN(Fixed Pattern Noise)固定图像噪声校正,即暗场校正&#xff1b;再用PRNU(Photo Response Non Uniformity)…

Bug:elementUI样式不起作用、Vue引入组件报错not found等(Vue+ElementUI问题汇总)

前端问题合集&#xff1a;VueElementUI 1. Vue引用Element-UI时&#xff0c;组件无效果解决方案 前提&#xff1a; 已经安装好elementUI依赖 //安装依赖 npm install element-ui //main.js中导入依赖并在全局中使用 import ElementUI from element-ui Vue.use(ElementUI)如果此…

百度飞桨(PaddlePaddle) - PP-OCRv3 文字检测识别系统 预测部署简介与总览

1. 预测部署简介与总览 本章主要介绍PP-OCRv2系统的高性能推理方法、服务化部署方法以及端侧部署方法。通过本章的学习&#xff0c;您可以学习到&#xff1a; 根据不同的场景挑选合适的预测部署方法PP-OCRv2系列模型在不同场景下的推理方法Paddle Inference、Paddle Serving、…

MySQL学习笔记25

逻辑备份 物理备份 在线热备&#xff1a; 真实案例&#xff1a; 数据库架构是一主两从&#xff0c;但是两台从数据库和主数据不同步。但是每天会全库备份主服务器上的数据到从服务器上。需要解决主从不同步的问题。 案例背后的核心技术&#xff1a; 1、熟悉MySQL数据库常见…

Linux--进程间通信之命名管道

目录 前言概念命名管道的创建命名管道特性 命名管道通信建立连接资源处理 Client && Server通信总结 前言 上一篇文章介绍匿名管道的进程间通信只适合在具有血缘关系的进程间进行通信&#xff0c;但是如果我们想让两个不相关的进程实现通信&#xff0c;使用匿名管道显…

Kafka:安装与简单使用

文章目录 下载安装windows安装目录结构启动服务器创建主题发送一些消息启动消费者设置多代理集群常见问题 工具kafka tool 常用指令topic查看topic删除topic 常见问题参考文献 下载安装 下载地址&#xff1a;kafka-download windows安装 下载完后&#xff0c;找一个目录解压…