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);满足了这样一个条件:
只要满足了以上这个条件,就可以直接使用“类名::实例方法”的语法了。
举一反三,以下两行代码也是等价的
@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);
}