lambda 是一个匿名函数,我们可以把 lambda 表达式理解为是一段可以传递的代码。
lambda 简明地将代码或方法作为参数传递进去执行。
函数式编程:核心是把函数作为值。
函数式接口 :只有一个抽象方法的接口称之为函数式接口。
函数式接口可以使用@FunctionalInterface
进行注解。
lambda表达式拆分为两部分:
左侧:lambda 表达式的参数列表
右侧:lambda 表达式中所需要执行的功能,即 lambda 体
Lambda 表达式非常方便,在项目中一般在 stream 编程中用的比较多。
package com.lambda;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Test1 {
public static void main(String[] args) {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student(1, "tom"));
studentList.add(new Student(2, "marry"));
// Collectors.toMap的用法有很多
Map<Integer, String> map = studentList.stream().collect(Collectors.toMap(Student::getId, Student::getName));
// {1=tom, 2=marry}
System.out.println(map);
}
}
class Student {
private int id;
private String name;
public Student() {
}
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
理解一个 Lambda 表达式就三步:
1、确认 Lambda 表达式的类型
2、找到要实现的方法
3、实现这个方法
就这三步,没其他的了。
能用 Lambda 表达式来表示的类型,必须是一个函数式接口,而函数式接口,就是只有一个抽象方法的接口。
例如 JDK 中 Runnable 接口:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
这就是一个标准的函数式接口,因为只有一个抽象方法
,而且这个接口上有个注解 @FunctionalInterface
。
这个仅仅是在编译期帮你检查你这个接口是否符合函数式接口的条件,比如你没有任何抽象方法,或者有多个抽象
方法,编译是无法通过的。
再稍稍复杂一点,Java 8 之后接口中是允许使用默认方法
和静态方法
的,而这些都不算抽象方法,所以也可以加在
函数式接口里。看看你可能不太熟悉又有点似曾相识的一个接口。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {}
}
只有一个抽象方法,还有一个默认方法(方法体的代码省略了),这个也不影响它是个函数式接口。再看一个更复杂
的,多了静态方法,这同样也是个函数式接口,因为它仍然只有一个抽象方法。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {...}
default Predicate<T> negate() {...}
default Predicate<T> or(Predicate<? super T> other) {...}
static <T> Predicate<T> isEqual(Object targetRef) {...}
static <T> Predicate<T> not(Predicate<? super T> target) {...}
}
先不用管这些方法都是干嘛的,这些类在 Stream 设计的方法中比比皆是,我们就先记住这么一句话,Lambda表
达式需要的类型为函数式接口,函数式接口里只有一个抽象方法,就够了,以上三个例子都属于函数式接口。
Lambda 表达式就是实现一个方法,什么方法呢?就是刚刚那些函数式接口中的抽象方法。
那就太简单了,因为函数式接口有且只有一个抽象方法,找到它就行了。我们尝试把刚刚那几个函数式接口的抽象
方法找到。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {...}
}
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {...}
default Predicate<T> negate() {...}
default Predicate<T> or(Predicate<? super T> other) {...}
static <T> Predicate<T> isEqual(Object targetRef) {...}
static <T> Predicate<T> not(Predicate<? super T> target) {...}
}
Lambda 表达式就是要实现这个抽象方法,如果不用 Lambda 表达式,你一定知道用匿名类如何去实现吧?比如
我们实现刚刚 Predicate 接口的匿名类。
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length() != 0;
}
};
那如果换成 Lambda 表达式呢?就像这样。
Predicate<String> predicate = (String s) -> {
return s.length() != 0;
};
看出来了么?这个 Lambda 语法由三部分组成:
1、参数块:就是前面的 (String s),就是简单地把要实现的抽象方法的参数原封不动写在这。
2、小箭头:就是 -> 这个符号。
3、代码块:就是要实现的方法原封不动写在这。
首先看参数快部分,(String s) 里面的类型信息是多余的,因为完全可以由编译器推导,去掉它。
注意:lambda 表达式的参数类型可以省略不写,因为 jvm 编译器可以从上下文推断出数据类型。
即“类型推断”如果要在参数里面写数据类型,都要写上。
Predicate<String> predicate = (s) -> {
return s.length() != 0;
};
当只有一个参数时,括号也可以去掉。
Predicate<String> predicate = s -> {
return s.length() !=0;
};
再看代码块部分,方法体中只有一行代码,可以把花括号和 return 关键字都去掉。
Predicate<String> p = s -> s.length() != 0;
来,再让我们实现一个 Runnable 接口。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Runnable r = () -> System.out.println("I am running");
你看,这个方法没有入参,所以前面括号里的参数就没有了,这种情况下括号就不能省略。
通常我们快速新建一个线程并启动时,是不是像如下的写法,熟悉吧?
new Thread(() -> System.out.println("I am running")).start();
之前我们只尝试了一个入参,接下来我们看看多个入参的。
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
然后看看一个用法,是不是一目了然。
BiConsumer<Random, Integer> randomNumberPrinter = (random, number) -> {
for (int i = 0; i < number; i++) {
System.out.println("next random = " + random.nextInt());
}
};
randomNumberPrinter.accept(new Random(314L), 5);
刚刚只是多个入参,那我们再加个返回值:
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
BiFunction<String, String, Integer> findWordInSentence =
(word, sentence) -> sentence.indexOf(word);
Integer result = findWordInSentence.apply("e","hello");
// 1
System.out.println(result);
其实函数式接口里那个抽象方法,无非就是入参的个数,以及返回值的类型。入参的个数可以是一个或者两个,返
回值可以是 void,或者 boolean,或者一个类型。那这些种情况的排列组合,就是 JDK 给我们提供的
java.util.function
包下的类。
别看晕了,我们分分类就好了。可以注意到很多类前缀是 Int,Long,Double 之类的,这其实是指定了入参的特
定类型,而不再是一个可以由用户自定义的泛型,比如说 DoubleFunction。
@FunctionalInterface
public interface DoubleFunction<R> {
R apply(double value);
}
这完全可以由更自由的函数式接口 Function 来实现。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
函数式接口几个简单分类就是:
supplier: 没有入参,有返回值。
function: 有入参,有返回值。
consumer: 有入参,无返回值。
predicate: 有入参,返回 boolean 值。
Consumer: 消费性接口,void accept(T t)。
Supplier: 共给性接口,T get()。
Function
: 函数性接口,T代表参数,R代表返回值,R apply(T t)。 Predicate: 断言性接口,boolean test(T t)。
其实就是给我们提供了一个函数的模板,区别仅仅是入参返参个数的排列组合。
package com.lambda;
import java.util.function.Consumer;
public class Test2 {
public static void main(String[] args) {
happy(10000, (money) -> System.out.println("happy消费" + money + "元"));
}
public static void happy(double money, Consumer<Double> con) {
con.accept(money);
}
}
下面这段代码如果你项目中有用 stream 编程那肯定很熟悉,有一个 Student 的 list,你想把它转换成一个 map,
key 是 student 对象的 id,value 就是 student 对象本身。
package com.lambda;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Test3 {
public static void main(String[] args) {
List<Student> studentList = new ArrayList<>();
studentList.add(new Student(1, "tom"));
studentList.add(new Student(2, "marry"));
// Collectors.toMap的用法有很多
Map<Integer, Student> map = studentList.stream().collect(Collectors.toMap(Student::getId, a -> a, (a, b) -> a));
// {1=com.lambda.Student@2f4d3709, 2=com.lambda.Student@4e50df2e}
System.out.println(map);
}
}
把 Lambda 表达式的部分提取出来。
Collectors.toMap(Student::getId, a -> a, (a, b) -> a)
由于我们还没见过 ::
这种形式,先打回原样,这里只是让你预热一下。
Collectors.toMap(a -> a.getId(), a -> a, (a, b) -> a)
为什么它被写成这个样子呢?我们看下 Collectors.toMap 这个方法的定义就明白了。
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
看,入参有三个,分别是 Function,Function,BinaryOperator,其中 BinaryOperator 只是继承了 BiFunction
并扩展了几个方法,我们没有用到,所以不妨就把它当做 BiFunction。还记得 Function 和 BiFunction 吧?
Function R apply(T t)
BiFunction R apply(T t, U u)
第一个参数 a -> a.getId()
就是 R apply(T t) 的实现,入参是 Student 类型的对象 a,返回 a.getId()。
第二个参数 a -> a
也是 R apply(T t) 的实现,入参是 Student 类型的 a,返回 a 本身。
第三个参数 (a, b) -> a
是 R apply(T t, U u) 的实现,入参是Student 类型的 a 和 b,返回是第一个入参 a,
Stream 里把它用作当两个对象 ,a 和 b 的 key 相同时,value 就取第一个元素 a,其中第二个参数 a -> a 在
Stream 里表示从 list 转为 map 时的 value 值,就用原来的对象自己,你肯定还见过这样的写法。
Collectors.toMap(a -> a.getId(), Function.identity(), (a, b) -> a)
为什么可以这样写,给你看 Function 类的全貌你就明白了。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
看到了吧,identity 这个方法,就是帮我们把表达式给实现了,就不用我们自己写了,其实就是包了个方法。这回
知道一个函数式接口,为什么有好多还要包含一堆默认方法和静态方法了吧?就是干这个事用的。
我们再来试一个,Predicate 里面有这样一个默认方法 and。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
它能干嘛用呢?我来告诉你,如果没有这个方法,有一段代码你可能会这样写。
Predicate<String> p = s -> (s != null) && !s.isEmpty() && s.length() < 5;
如果利用上这个方法,就可以变成如下这种优雅形式。
Predicate<String> nonNull = s -> s != null;
Predicate<String> nonEmpty = s -> s.isEmpty();
Predicate<String> shorterThan5 = s -> s.length() < 5;
Predicate<String> p = nonNull.and(nonEmpty).and(shorterThan5);
主要有三种语法格式:
对象::实例方法名
类::静态方法名
类::实例方法名
那我们回过头再看刚刚的 Student::getId
这种写法。当方法体中只有一个方法调用时,就可以作这样的简化。
比如这个 a -> a.getId() 就只是对 Student 对象上 getId() 这个方法的调用,那么就可以写成 Student::getId 这种形
式。
package com.lambda;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
public class Test9 {
public static void main(String[] args) {
// 类::实例方法名
Function<User, String> n = User::getName;
Function<User, Integer> a = User::getAge;
// 类::静态方法名
Function<String, String> func1 = User::concat2;
Supplier<String> func2 = User::getAddress;
// 对象::实例方法名
User user = new User();
Function<String, String> func3 = user::concat1;
Supplier<String> func4 = user::getSchool;
// 无参构造方法引用
Supplier<Student> supplier = Student::new;
Student student = supplier.get();
student.setName("tom");
student.setId(10);
// com.lambda.Student@5b480cf9
System.out.println(student);
Supplier<Student> supplier2 = () -> (new Student());
Student student2 = supplier2.get();
student2.setName("tom");
student2.setId(10);
// com.lambda.Student@723279cf
System.out.println(student2);
// 有参构造方法引用
BiFunction<Integer, String, Student> biFunction = Student::new;
Student stu = biFunction.apply(11, "marry");
// com.lambda.Student@b4c966a
System.out.println(stu);
BiFunction<Integer, String, Student> bf = (id, name) -> new Student(id, name);
Student s = bf.apply(12, "john");
// com.lambda.Student@4e50df2e
System.out.println(s);
// 自定义有参构造函数
StudentBuilder sb1 = ((id, name) -> new Student(id, name));
Student s1 = sb1.build(13, "xm");
// com.lambda.Student@7cc355be
System.out.println(s1);
StudentBuilder sb2 = Student::new;
Student s2 = sb2.build(14, "xh");
// com.lambda.Student@12edcd21
System.out.println(s2);
// 自定义无参构造函数
StudentBuilderNoArgs sb3 = Student::new;
Student s3 = sb3.build();
// com.lambda.Student@5b6f7412
System.out.println(s3);
StudentBuilderNoArgs sb4 = () -> (new Student());
Student s4 = sb4.build();
// com.lambda.Student@312b1dae
System.out.println(s4);
}
}
class User {
private static int id;
private String name;
public static int getId() {
return id;
}
public static void setId(int id) {
User.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String concat1(String str) {
return str + "!";
}
public static String concat2(String str) {
return str + "!";
}
public int getAge() {
return 12;
}
public static String getAddress() {
return "天津";
}
public String getSchool() {
return "小学";
}
}
interface StudentBuilder {
Student build(int id, String name);
}
interface StudentBuilderNoArgs {
Student build();
}
再看几个例子:
Function<String, Integer> toLength1 = s -> s.length();
Function<String, Integer> toLength2 = String::length;
Function<User, String> getName = user -> user.getName();
Function<User, String> toLength = User::getName;
如果是构造方法的话,也可以简化。
Supplier<List<String>> newListOfStrings1 = () -> new ArrayList<>();
Supplier<List<String>> newListOfStrings2 = ArrayList::new;
// ClassName::new
Supplier<String> sup = () -> new String();
Supplier<String> sup2 = String::new;
String str = sup.get();
String str2 = sup2.get();
// false
System.out.println(str == str2);
// 对象::实例方法名
Consumer<String> con = (x) -> System.out.println(x);
con.accept("hello");
Consumer<String> con2 = System.out::println;
con2.accept("world");
// 类::静态方法名
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
Comparator<Integer> com2 = Integer::compare;
System.out.println(com.compare(1, 2));
System.out.println(com2.compare(1, 2));
// 类::实例方法名
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System.out.println(bp.test("a", "a"));
BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("a", "a"));
实例1:
package com.lambda;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Test6 {
public static List<Employee> list = Arrays.asList(new Employee("张三", 10, 1), new Employee("里斯", 20, 1), new Employee("王五", 16, 1), new Employee("二三", 30, 1));
public static List<Employee> filterEmployee(List<Employee> list, MyPredicate<Employee> mp) {
List<Employee> emps = new ArrayList<>();
for (Employee employee : list) {
if (mp.test(employee)) {
emps.add(employee);
}
}
return emps;
}
public static void main(String[] args) {
test1();
test2();
}
public static void test1() {
//需要使用自定义的方法
List<Employee> list2 = filterEmployee(list, (e) -> e.getAge() >= 15);
list2.stream().map(Employee::getName).forEach(System.out::println);
}
public static void test2() {
//可以使用stream进行list集合的过滤 不使用自定义接口
List<Employee> list2 = list.stream().filter((e) -> e.getAge() >= 15).collect(Collectors.toList());
list2.stream().map(Employee::getName).forEach(System.out::println);
}
}
class Employee {
private String name;
private int age;
private double salary;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
}
interface MyPredicate<T> {
boolean test(T t);
}
# 程序输出
里斯
王五
二三
里斯
王五
二三
实例2:
创建一个MyFun接口使用@FunctionalInterface注解,并创建一个抽象方法Integer getValue(Integer num);在
Test类对变量进行某种操作。
package com.lambda;
@FunctionalInterface
interface MyFun {
Integer getValue(Integer num);
}
public class Test7 {
public static void test() {
int a = 10;
System.out.println(a);
a = operation(a, num -> ++num);
System.out.println(a);
}
public static void main(String[] args) {
test();
}
/**
* param1 num : 传入的整形数
* param2 mf : 实现某种方式对 整形数 进行操作。
**/
public static Integer operation(Integer num, MyFun mf) {
return mf.getValue(num);
}
}
# 程序输出
10
11
package com.lambda;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class Test8 {
public static List<Employee> list = Arrays.asList(
new Employee("张三", 10, 1),
new Employee("里斯", 20, 1),
new Employee("王五", 16, 1),
new Employee("二三", 30, 1)
);
public static void test() {
Collections.sort(list, (e1, e2) -> {
if (e1.getAge() == e2.getAge()) {
return e1.getName().compareTo(e2.getName());
} else {
//比较年龄大小
return Integer.compare(e1.getAge(), e2.getAge());
}
});
for (Employee e : list) {
System.out.println(e);
}
}
public static void main(String[] args) {
test();
}
}
# 程序输出
com.lambda.Employee@404b9385
com.lambda.Employee@6d311334
com.lambda.Employee@682a0b20
com.lambda.Employee@3d075dc0