lambda表达式是Java 8 中的一个很重要的新特性,它容许将行为传到函数中。在Java 8 之前,如果我们想要把行为传到函数中,仅有的选择就是匿名内部类。但Java 8 发布以后,lambda表达式将大量替代匿名内部类的使用,简化代码,突出匿名内部类中最重要的逻辑代码。下面以Runable
接口说明这一点:
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
使用lambda表达式简化:
Runnable runnable1 = () -> System.out.println(Thread.currentThread().getName());
一、lambda表达式简介
lambda表达式是一个匿名函数,一种没有声明的方法,没有修饰符,返回值声明和名称。Java中的lambda表达式通常使用语法是(parameters) -> (expression)
或(parameters) ->{ statements; }
,例如:
// 接收两个int型参数,返回它们的值
(int a, int b) -> { return a + b; }
// 无参数
() -> System.out.println("Hello World");
// 接收一个String型字符串,打印字符串
(String s) -> { System.out.println(s); }
// 无参数 返回 42
() -> 42
// 无参数,返回3.1415
() -> { return 3.1415 };
lambda表达式的结构说明:
- Lambda 表达式可以具有零个,一个或多个参数;
- 可以显式声明参数的类型,也可以由编译器自动从上下文推断参数的类型。例如 (int a) 与刚才相同 (a);
- 参数用小括号括起来,用逗号分隔。例如 (a, b) 或 (int a, int b) 或 (String a, int b, float c);
- 空括号用于表示一组空的参数。例如 () -> 42;
- 当有且仅有一个参数时,如果不显式指明类型,则不必使用小括号。例如 a -> return a*a;
- Lambda 表达式的正文可以包含零条,一条或多条语句;
- 如果 Lambda 表达式的正文只有一条语句,则大括号可不用写,且表达式的返回值类型要与匿名函数的返回类型相同;
- 如果 Lambda 表达式的正文有一条以上的语句必须包含在大括号(代码块)中,且表达式的返回值类型要与匿名函数的返回类型相同。
二、lambda表达式示例
1. 使用Java 8 lambda表达式进行事件处理
java 8 之前:
JButton show = new JButton("Show");
show.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Hello world");
}
});
java 8 :
show.addActionListener((e) -> {
System.out.println("Hello world");
});
2. 使用Java 8 lambda表达式进行列表迭代
java 8 之前:
List<String> items = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
for (String item : items) {
System.out.println(item);
}
java 8 :
items.forEach(item -> System.out.println(item));
java 8 之前:
Map<String,String> map = new HashMap <>();
map.put("1","a");
map.put("2","b");
map.put("3","c");
for(String key : map.keySet()){
System.out.println(map.get(key));
}
java 8 :
map.forEach((k,v)->System.out.println(map.get(k)));
3. 使用lambda表达式和函数式接口Predicate进行逻辑操作
Java 8 添加了一个 java.util.function
的包。它包含了很多类,用来支持Java的函数式编程。其中一个便是Predicate,使用 java.util.function.Predicate
函数式接口以及lambda表达式,可以向API方法添加逻辑,用更少的代码支持更多的动态行为。
public static void main(String[] arg){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
System.out.print("输出所有数字:");
evaluate(list, (n) -> true);
System.out.print("不输出:");
evaluate(list, (n) -> false);
System.out.print("输出偶数:");
evaluate(list, (n) -> n % 2 == 0);
System.out.print("输出奇数:");
evaluate(list, (n) -> n % 2 == 1);
System.out.print("输出大于 5 的数字:");
evaluate(list, (n) -> n > 5);
}
public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
// for (Integer n : list) {
// if (predicate.test(n)) {
// System.out.print(n + " ");
// }
// }
list.stream().filter(name -> predicate.test(name)).forEach(name -> System.out.println(name+" "));
}
4. 多个Predicate合并使用
java.util.function.Predicate
允许将两个或更多的 Predicate 合成一个。它提供类似于逻辑操作符AND和OR的方法,名字叫做and()、or()和xor(),用于将传入 filter() 方法的条件合并起来。例如,要得到所有以J开始,长度为四个字母的语言,可以定义两个独立的 Predicate 示例分别表示每一个条件,然后用 Predicate.and()
方法将它们合并起来,如下所示:
List<String> list = Arrays.asList("Java", "C", "C++", "Ruby", "C#", "Go");
Predicate<String> startsWithJ = (n) -> n.startsWith("J");
Predicate<String> fourLetterLong = (n) -> n.length() == 4;
list.stream()
.filter(startsWithJ.and(fourLetterLong))
.forEach((n) -> System.out.print("nName, which starts with 'J' and four letter long is : " + n));
5. Java 8中使用lambda表达式的Map和Reduce
java.util.stream.Stream
接口 和 Lambda 表达式一样,都是 Java 8 新引入的。所有 Stream 的操作必须以 Lambda 表达式为参数。Stream 接口中带有大量有用的方法,比如 map() 的作用就是将 input Stream 的每个元素映射成output Stream 的另外一个元素。
下面的例子,我们将 Lambda 表达式 x -> x*x
传递给map()
方法,将其应用于流的所有元素。之后,我们使用forEach
打印列表的所有元素:
java 8 之前:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
int x = n * n;
System.out.println(x);
}
java 8 :
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
下面的示例中,我们给定一个列表,然后求列表中每个元素的平方和。这个例子中,我们使用了 reduce() 方法,这个方法的主要作用是把 Stream 元素组合起来:
java 8 之前:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
int x = n * n;
sum = sum + x;
}
System.out.println(sum);
java 8 :
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);
6. 通过过滤创建一个String列表
过滤是Java开发者在大规模集合上的一个常用操作,而现在使用lambda表达式和流API过滤大规模数据集合是惊人的简单。流提供了一个 filter() 方法,接受一个 Predicate 对象,即可以传入一个lambda表达式作为过滤逻辑。下面的例子是用lambda表达式过滤Java集合,将帮助理解。
List<String> strList = Arrays.asList("Java", "C", "C++", "Ruby", "C#", "Go");
// 创建一个字符串列表,每个字符串长度大于2
List<String> filtered = strList.stream().filter(x -> x.length()> 2).collect(Collectors.toList());
System.out.printf("Original List : %s, filtered list : %s %n", strList, filtered);
7. 去重操作
本例展示了如何利用流的 distinct() 方法来对集合进行去重:
List<Integer> numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4);
List<Integer> distinct = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
System.out.printf("Original List : %s, Square Without duplicates : %s %n", numbers, distinct);
8. 计算集合元素的最大值、最小值、总和以及平均值
IntStream、LongStream 和 DoubleStream 等流的类中,有个非常有用的方法叫做 summaryStatistics()
。可以返回 IntSummaryStatistics、LongSummaryStatistics 或者 DoubleSummaryStatistic s,描述流中元素的各种摘要数据。在本例中,我们用这个方法来计算列表的最大值和最小值。它也有 getSum() 和 getAverage() 方法来获得列表的所有元素的总和及平均值。
//获取数字的个数、最小值、最大值、总和以及平均值
List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Highest prime number in List : " + stats.getMax());
System.out.println("Lowest prime number in List : " + stats.getMin());
System.out.println("Sum of all prime numbers : " + stats.getSum());
System.out.println("Average of all prime numbers : " + stats.getAverage());