一、行为参数化
1.1 背景
软件工程必须要面对的一个问题就是,客户的需求总是在不断的变化当中的。我们在“你所不知道的Java8-前言”一文中已经讲到了客户挑苹果的需求在不断地变更,想要将我们的工作量降到最小就需要将代码行为进行参数化。以下是使用的“行为参数化”的思想编写的相应代码,如下所示。
public interface ApplePredicate {
boolean test (Apple apple);
}
public class AppleHeavyWeightPredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
return apple.getWeight()>120;
}
}
public class AppleGreenColorPredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
return "green".equals(apple.getColor());
}
}
//使用ApplePredicate,使用不同的苹果挑选策略
public static List<Apple> filterApples(List<Apple> source, ApplePredicate predicate){
List<Apple> apples = new ArrayList<>();
for (Apple apple : source) {
if(predicate.test(apple)){
apples.add(apple);
}
}
return apples;
}
1.2 策略设计模式
如上图所示,这个思想和“策略设计模式”相关,定义一个一系列算法,将它们封装起来(称之为“策略”),在代码运行时选择一个算法,这里的算法族时ApplePredicate,不同的策略是AppleHeavyWeightPredicate和AppleGreenColorPredicate。行为参数化就是所谓的让方法接受不同的行为(策略)作为参数,并在内部使用,来完成不同的行为。
后面,我们还使用了方法引用和lambda表达式来优化了这个需求,当时我们没有仔细探讨lambda表达式,只是使用了它,现在让我们来探讨一下。
二、函数式接口-lambda表达式
2.1 函数式接口
提到lambda表达式就不得不说函数式接口,因为lambda只用于函数式接口。所谓的函数式接口,就是Java 8中新增的一个特性,它是指仅包含一个抽象方法的接口。
Java 8提供了一些预定义的函数式接口,例如:Function、Consumer、Predicate、Supplier等。这些接口都只包含一个抽象方法,以便在使用Lambda表达式时可以简化代码,并且可以更方便地编写函数式风格的代码,也就是进行函数式编程。
- Function<T,R> 加工者
将T类型的对象转换为R类型的对象,示例代码如下所示:
Function<String, Integer> strToInt = Integer::parseInt; // 方法引用
int num = strToInt.apply("123"); // 将字符串"123"转换成整数123
- Consumer 消费者
对一个T类型的对象进行操作,不返回任何结果。例如:
Consumer<String> printStr = System.out::println; // 方法引用
printStr.accept("Hello World!"); // 打印输出"Hello World!"
- Predicate 断言
对一个T类型的对象进行判断,返回一个布尔类型的结果。例如:
Predicate<Integer> isPositive = num -> num > 0; // Lambda表达式
boolean result = isPositive.test(5); // 判断5是否大于0,返回true
- Supplier 生产者
不接受任何参数,返回一个T类型的结果。例如:
Supplier<String> getMessage = () -> "Hello World!"; // Lambda表达式
String message = getMessage.get(); // 返回字符串"Hello World!"
- Comparator 比较者
只包含一个抽象方法compare(T o1, T o2),用于比较两个对象的顺序。在Java中,Comparator通常用于对集合进行排序。例如,可以使用Comparator对一个List进行排序:
List<Integer> list = Arrays.asList(5, 2, 4, 1, 3);
Collections.sort(list, (a, b) -> a - b); // 使用Lambda表达式对集合进行升序排序
2.2 lambda表达式概述
Lambda表达式,也称为匿名函数,是Java 8中引入的一种新的语言特性,它为Java提供了一种新的编程方式。Lambda表达式可以使我们的代码更加简洁,提高编程效率。Lambda表达式实质上是一个匿名函数,它可以被看作是一个函数式接口的实例,因此它可以作为方法参数或返回值类型,或者赋值给一个变量。Lambda表达式的实质是一个对象,它实现了一个函数式接口。当Lambda表达式被调用时,它会执行函数式接口中的抽象方法。
2.3 lambda表达式与集合的配合
Lambda表达式和集合的配合使用是Java 8中的一个重要特性,它可以大大简化集合的操作,这些集合方法参数包含Java8预定义的一些函数式接口,例如filter的参数是Predicate断言接口,forEach的参数是Consumer消费者接口。
以下的思维导图展示了lambda的常见使用方法:
使用举例:
//1.遍历操作
List<String> list = Arrays.asList("apple", "banana", "orange", "pear");
list.forEach(str -> System.out.println(str));
//2.过滤操作
List<String> list = Arrays.asList("apple", "banana", "orange", "pear");
List<String> result = list.stream().filter(str -> str.length() > 5).collect(Collectors.toList());
//3.映射操作
List<String> list = Arrays.asList("apple", "banana", "orange", "pear");
List<String> result = list.stream().map(str -> str.toUpperCase()).collect(Collectors.toList());
//4.排序操作,升序排列
List<Integer> list = Arrays.asList(5, 2, 4, 1, 3);
List<Integer> result = list.stream().sorted((a, b) -> a - b).collect(Collectors.toList());
//5.分组操作,下面的代码使用Lambda表达式将集合中的字符串按照长度进行分组:
List<String> list = Arrays.asList("apple", "banana", "orange", "pear");
Map<Integer, List<String>> result = list.stream().collect(Collectors.groupingBy(String::length));
//6.拼接操作
List<String> list = Arrays.asList("apple", "banana", "orange", "pear");
String result = list.stream().collect(Collectors.joining(","));
//7.统计操作
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
long count = list.stream().count();
int sum = list.stream().mapToInt(Integer::intValue).sum();
double average = list.stream().mapToInt(Integer::intValue).average().orElse(0);