1. Hello World

导入依赖

<dependencies>

<dependency>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

<version>1.18.12</version>

</dependency>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.12</version>

<scope>test</scope>

</dependency>

<dependency>

<groupId>cn.hutool</groupId>

<artifactId>hutool-all</artifactId>

<version>5.4.6</version>

</dependency>

</dependencies>


有一个需求:希望对一系列字符串按照长度进行升序排列。为了完成这个需求,我们需要自定义Comparator,代码看起来是这个样子:

@Test

public void test() {

Comparator<String> comparator = new Comparator<String>() {

@Override

public int compare(String o1, String o2) {

return Integer.compare(o1.length(), o2.length());

}

};


Set<String> set = new TreeSet(comparator);

set.add("Andy");

set.add("Eason");

set.add("G.E.M");

set.add("Ekin");

set.add("Donnie");

System.out.println(set);

}


而使用Lambda的话,代码看起来就是这个样子了:

@Test

public void test() {

Comparator<String> comparator =

(o1, o2) -> Integer.compare(o1.length(), o2.length());


Set<String> set = new TreeSet(comparator);

set.add("Andy");

set.add("Eason");

set.add("G.E.M");

set.add("Ekin");

set.add("Donnie");

System.out.println(set);

}

这个例子只是牛刀小试。


2. Lambda初显锋芒

实体类:

@Data

@AllArgsConstructor

@NoArgsConstructor

public class User {

private Integer id;

private String name;

private LocalDate birthday;

private Double balance;

}

测试数据

public class AppTest {


private List<User> userList = null;


@Before

public void before() {

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

User user = new User(1, "Andy", LocalDate.parse("1961-09-27", formatter), 3000d);

User user2 = new User(2, "Eason", LocalDate.parse("1974-07-27", formatter), 2000d);

User user3 = new User(3, "G.E.M", LocalDate.parse("1991-08-16", formatter), 4500d);

蔡徐坤", LocalDate.parse("1998-08-02", formatter), 5000d);

关晓彤", LocalDate.parse("1997-09-17", formatter), 1500d);


userList = new ArrayList<>(Arrays.asList(user, user2, user3, user4, user5));

}


}


现在有一个需求:查询出年龄大于25岁的用户

方法一:

private List<User> chooseUserByAge(List<User> userList, Integer age) {

List<User> resultList = new ArrayList<>();

for (User user : userList) {

int userAge = DateUtil.age(DateUtil.date(user.getBirthday()), DateUtil.date(LocalDate.now()));

if (userAge > age) {

resultList.add(user);

}

}

return resultList;

}

这种方法确实解决了需求,但是如果需求发生变化,则代码会变得越来越冗余,比如,现在又想要按照余额筛选用户,则需要再添加以下代码:

private List<User> chooseUserByBalance(List<User> userList, Double balance) {

List<User> resultList = new ArrayList<>();

for (User user : userList) {

if (user.getBalance() >= balance) {

resultList.add(user);

}

}

return resultList;

}


方法二:为了减少上面代码的冗余,我们可以采用:“策略模式”来优化代码


定义接口:

public interface Predicate<T> {

boolean test(T user);

}


定义接口的实现类1

public class UserAgePredicate implements Predicate<User> {

private Integer age;


public UserAgePredicate(Integer age) {

this.age = age;

}


@Override

public boolean test(User user) {

int userAge = DateUtil.age(DateUtil.date(user.getBirthday()), DateUtil.date(LocalDate.now()));

return userAge > age;

}

}


定义接口的实现类2

public class UserBalancePredicate implements Predicate<User> {


private Double balance;


public UserBalancePredicate(Double balance) {

this.balance = balance;

}


@Override

public boolean test(User user) {

return user.getBalance() > balance;

}

}


测试:

private List<User> chooseUser(List<User> userList, Predicate<User> predicate) {

List<User> resultList = new ArrayList<>();

for (User user : userList) {

if (predicate.test(user)) {

resultList.add(user);

}

}

return resultList;

}


@Test

public void test() {

List<User> reslutList = chooseUser(userList, new UserBalancePredicate(40d));

for (User user : reslutList) {

System.out.println(user);

}

}


方法三:

使用“策略模式”确实减少了代码冗余,当然也提高了程序的扩展性。但是这需要我们定义很多的Predicate接口的实现类来满足各种条件变化,此时我们可以使用匿名内部类来解决这个问题:

@Test

public void test() {

List<User> reslutList = chooseUser(userList, new Predicate<User>() {

@Override

public boolean test(User user) {

return user.getBalance() < 5000;

}

});


for (User user : reslutList) {

System.out.println(user);

}

}


匿名内部类的可读性不好,所以我们再使用lambda改写以上的例子:

@Test

public void test() {

List<User> reslutList = chooseUser(userList, (user) -> user.getBalance() < 5000);

reslutList.forEach(System.out::println);

}


方法四:

@Test

public void test() {

userList.stream()

.filter(u -> u.getBalance() >= 3000)

.forEach(System.out::println);

}

注意,方法四中,已经不需要自定义接口Predicate了。



3. Lambda介绍


3.1 Lambda是什么

Lambda是一个匿名函数,我们可以把Lambda理解为一段可以传递的代码。 (Lambda表达式本质上是一个对象)


3.2 Lambda的作用

利用Lambda可以写出更简洁、更灵活的代码,从而使Java语言的表达能力得到了提升。

不懂lambda的人,总会说,lambda可读性不高,懂的都懂!


3.3 函数式接口

lambda表达式必须搭配函数式接口使用,所谓函数式接口,就是只有一个抽象方法的接口,如下:

@FunctionalInterface

public interface Foo {

void f1();

}

注解@FuncationalInterface强制限制接口中只能有一个抽象方法。


3.4 Lambda语法


无参,无返回值

@FunctionalInterface

public interface Foo {

void f1();

}


@Test

public void test() {

Foo foo = () -> {

System.out.println("A");

System.out.println("B");

System.out.println("C");

};


foo.f1();

}


当lambda体中只有一句话的时候,lambda体的“{ }”写不写都行,如下

@Test

public void test() {

Foo foo = () -> System.out.println("A");

foo.f1();

}


有1个参数,无返回值

@FunctionalInterface

public interface Foo {

void f1(int a);

}


@Test

public void test() {

Foo foo = (x) -> System.out.println(x);

foo.f1(10);

}


当参数只有1个时,可以把lambda参数列表的小括号去掉:

@Test

public void test() {

Foo foo = x -> System.out.println(x);

foo.f1(10);

}


有1个以上的参数,无返回值

@FunctionalInterface

public interface Foo {

void f1(int a, int b);

}


当参数有1个以上的时候,lambda参数列表的小括号不能去掉!

@Test

public void test() {

Foo foo = (x, y) -> System.out.println(x + y);

foo.f1(10, 20);

}


无参数,有返回值

@FunctionalInterface

public interface Foo {

int f1();

}


@Test

public void test() {

Foo foo = () -> {

Random random = new Random();

int a = random.nextInt(100);

int b = random.nextInt(100);

return a + b;

};


System.out.println(foo.f1());

}


当lambda体只有一句话的时候,{}可以省略,如果省略了{},则必须同时省略return关键字:

@Test

public void test() {

Foo foo = () -> 10 + 20;

foo.f1();

}


有参数、有返回值

@Test

public void test() {

Foo foo = (a, b) -> a * a + b * b;

int result = foo.f1(2, 3);

System.out.println("result = " + result);

}


小结

1. lambda参数列表如果只有一个参数,则参数列表的小括号可以省略

2. lambda体如果只有一条语句,则lambda体的大括号可以省略

3. lambda体如果只有一条return语句,且已经省略了lambda体的大括号,则必须去掉return关键字。

4. lambda参数列表中,参数的类型可以省略。如果要写出类型,就必须为所有参数都写上类型!如果要省略类型,就必须让所有参数都省略类型!



4. 内置函数式接口

以上例子中的Foo,是我们自定义的函数式接口,而Jdk1.8中的内置函数式接口有很多,我们重点学习以下4个:

函数式接口

参数类型

返回类型

用途

Consumer<T>

T

void

对T类型的参数进行操作

Supplier<T>

T

返回类型为T的对象

Function<T, R>

T

R

对T类型的参数进行操作,并返回R类型的对象

Predicate<T>

T

boolean

确定T类型的参数是否满足某个条件,返回ture表示满足,返回false表示不满足

有了以上这4个内置的函数式接口,再完成上面的练习时,就不需要自定义的函数式接口了!


4.1 Consumer

Consumer接口中的抽象方法的特点:接受一个参数,没有返回值

@Test

public void test() {

Consumer<Integer> c = new Consumer<Integer>() {

@Override

public void accept(Integer i) {

System.out.println(i * i);

}

};

}

用lambda改写:

Consumer<Integer> c = i -> System.out.println(i * i);


4.2 Function

Function接口中的抽象方法的特点:接受一个参数,有返回值

@Test

public void test() {

Function<Integer, Integer> f = new Function<Integer, Integer>() {

@Override

public Integer apply(Integer i) {

return i * i * i;

}

};

}

用lambda改写:

Function<Integer, Integer> f = i -> i * i * i;


4.3 Supplier

Supplier接口中的抽象方法的特点:没有参数,有返回值

@Test

public void test() {

Supplier<Integer> s = new Supplier<Integer>() {

@Override

public Integer get() {

return new Random().nextInt(10);

}

};

}

用lambda改写:

Supplier<Integer> s = () -> new Random().nextInt(10);


4.4 Predicate

Predicate接口中的抽象方法的特点:有一个参数,有返回值,且返回值是boolean类型

@Test

public void test() {

Predicate<Integer> p = new Predicate<Integer>() {

@Override

public boolean test(Integer i) {

return i % 2 == 0;

}

};

}

用lambda改写:

Predicate<Integer> p = i -> i % 2 == 0;



4.5 其他函数式接口

以下的函数式接口都可以处理俩个参数

函数式接口

参数类型

返回类型

用途

BinaryOperator<T>

T、T

T

对类型为T的对象进行二元操作,返回结果也是T类型

BiFuncation<T,U,R>

T、U

R

对类型为T和U的参数进行操作,返回结果是R类型

BiConsumer<T,U>

T、U

void

对类型为T和U的参数进行操作

还有其他的函数式接口,这里就不再列举了。


务必记住以下接口的方法的特点

☐ Consumer的抽象方法:接受一个参数,没有返回值

☐ Funcation的抽象方法:接受一个参数,有返回值

☐ Supplier的抽象方法:没有参数,有返回值

☐ Predicate的抽象方法:有参数,有返回值,且返回值是boolean类型


至于这些接口中的方法具体是什么名字,不用刻意去记忆。


5. 方法引用


5.1 方法引用快速起步

如果lambda表达式的参数类型、返回值类型,刚好与某个方法一致,我们就可以使用“方法引用”。


例1:

public class Apply {

public void apply(Consumer<Integer> consumer) {

consumer.accept(10);

}

}


public class App {

@Test

public void test() {

Apply a = new Apply();

以下lambda接受一个类型为Integer的参数,并且没有返回值

a.apply(x -> System.out.println(x + x));

}

}

恰好有一个Foo类,该类的f1方法也接受一个Integer类型的参数,也没有返回值:

public class Foo {

public void f1(Integer x) {

System.out.println(x * x);

}

}

此时就可以使用方法引用了:

public class App {

@Test

public void test() {

Apply a = new Apply();

Foo f = new Foo();

a.apply(f::f1);

}

}


5. 2 方法引用的三种语法格式:


5.2.1 对象::实例方法名

以下两行代码等价:

@Test

public void test() {

Consumer<String> c = s -> System.out.println(s);

Consumer<String> c2 = System.out::println;

}

同理,以下两行代码也是等价的:

@Test

public void test() {

User user = new User();

Supplier<Integer> s = () -> user.getId();

Supplier<Integer> s2 = user::getId;

}


5.2.2 类::静态方法名

只有静态方法才可以通过 类名::方法名 的方式书写,如下:

public class Foo {

public void f1(Integer x, Integer y) {

System.out.println(x + y);

}


public static void f2(Integer x, Integer y) {

System.out.println(x + y);

}

}

以下bc和bc3是等价的,bc2则是错误的,无法通过编译:

BiConsumer<Integer, Integer> bc = (o, o2) -> System.out.println(o + o2);

// Can't compile

// BiConsumer<Integer, Integer> bc2 = Foo::f1;

BiConsumer<Integer, Integer> bc3 = Foo::f2;


举一反三:以下两行代码等价

@Test

public void test() {

Comparator<Integer> c = (a, b) -> Integer.compare(a, b);

Comparator<Integer> c2 = Integer::compare;

}



5.2.3 类::实例方法名

以下两行代码等价

@Test

public void test() {

BiPredicate<Integer, Integer> bp = (a, b) -> a.equals(b);

BiPredicate<Integer, Integer> bp2 = Integer::equals;

}

问题是:equals是一个实例方法,又不是一个静态方法,为什么可以通过Integer::equals来调用呢?

这是因为,以上的(a,b)->a.equals(b);满足了这样一个条件:

3. Lambda_User


只要满足了以上这个条件,就可以直接使用“类名::实例方法”的语法了。


举一反三,以下两行代码也是等价的

@Test

public void test() {

BiFunction<String, Integer, String> bf = (a, b) -> a.substring(b);

BiFunction<String, Integer, String> bf2 = String::substring;

}


举一反三,以下两行代码也是等价的

@Test

public void test() {

BiFunction<String, String, Boolean> bf = (a, b) -> a.contains(b);

BiFunction<String, String, Boolean> bf2 = String::contains;

}


6. 构造器引用

为User类再添加两个构造器

@Data

@AllArgsConstructor

@NoArgsConstructor

public class User {

private Integer id;

private String name;

private LocalDate birthday;

private Double balance;


public User(Integer id) {

this.id = id;

System.out.println("User(Integer)");

}


public User(Integer id, String name) {

this.id = id;

this.name = name;

System.out.println("User(Integr, String))");

}

}



当lambda表达式的lambda体:

1. 只有一个语句

2. 该语句是一个return语句

3. return的是一个" new 构造器" 的形式

就可以使用构造器引用了


以下 f 和 f2 是等价的;f3 和 f4 是等价的。注意User::new调用的是哪个构造器,取决于Function的apply()方法接收的是什么参数。比如 Function<Integer, User>的apply方法的参数是Integer,则User::new调用的就是User(Integer)这个构造器!

@Test

public void test() {

Function<Integer, User> f = (id) -> new User(id);

User user = f.apply(10);

System.out.println("user = " + user);


Function<Integer, User> f2 = User::new;

User user2 = f2.apply(20);

System.out.println("user2 = " + user2);


BiFunction<Integer, String, User> f3 = (id, name) -> new User(id, name);

User user3 = f3.apply(22, "G.E.M");

System.out.println("user3 = " + user3);


BiFunction<Integer, String, User> f4 = User::new;

User user4 = f4.apply(23, "Andy");

System.out.println("user4 = " + user4);

}


3. Lambda_User_02