在 Java 版本的历次更新迭代中,Java8 是一个特殊的存在,与以往的版本 升级不同。我们对 Java8 似乎抱有更大的期待,因为它是 Java5 之后最重要的一次升级 ,提供了十多个新特性,其中 Lambda 表达式是 Java8 新特性中最重要的一个。
Lambda 表达式允许开发者将函数作为参数传给某个方法,即支持 函数式编程 ,这并不是一种新技术,很多基于 JVM 的语言如 Groovy 和 Scala 都支持函数式编程,Java 官方直到 Java8 才引入函数式编程机制。 在 Java8 诞生之前,开发者更多的关注点在于对象的属性,这也是面向对象编程思想的核心,即对数据进行抽象,而函数式编程则是对行为进行抽象,是面向函数进行编程。
Java8 通过引入 Lambda 表达式来支持函数式编程,Lambda 表达式允许我们将一个函数作为参数进行传递,一个函数定义了一个行为,语法如下所示。
(参数) -> (方法体)
我们首先通过一个例子来直观理解什么是 Lambda 表达式: 启动一个线程,不使用 Lambda 表达式的代码如下所示。
new Thread(new Runnable() { @Override public void run() { System.out.println("do it...""); } }).start();
使用 Lambda 表达式的代码如下所示。
new Thread(()-> System.out.println("do it...")).start();
new Thread() 中传递的就是一个函数,它定义了线程的具体任务,即行为。通过对比可以得出结论,使用 Lambda 表达式可以替代传统的匿名类开发方式,不需要创建匿名类即可完成业务逻辑的代码编写,开发效率更高。
Lambda 表达式由 3 部分组成:1、参数。2、->。3、函数主体。
这里需要注意,能够使用 Lambda 表达式的必须是一个函数接口,函数接口是指该接口中只包含一个方法,如 Runnable 接口。
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
如果一个接口中包含超过两个方法,则不能使用 Lambda 表达式,接下来我们介绍几种常用的函数接口类型。
1、没有参数、没有返回值的函数接口
public interface MyInterface { public void test(); }
具体使用如下所示,定义一个 test 方法,将函数接口作为参数传入。
public class Test { public static void main(String[] args) { Test test = new Test(); //不使用Lambda表达式 test.test(new MyInterface() { @Override public void test() { System.out.println("123"); } }); //使用Lambda表达式 test.test(()-> System.out.println("333")); } public void test(MyInterface myInterface){ myInterface.test(); } }
运行结果如下图所示。
2、带参数的函数接口
public interface MyInterface { public void test(int x); }
具体使用如下所示。
public class Test { public static void main(String[] args) { Test test = new Test(); test.test((int x)-> System.out.println("传入的参数是:"+x)); } public void test(MyInterface myInterface){ int x = 10; myInterface.test(x); } }
运行结果如下图所示。
参数的数据类型也可以不声明,根据上下文自动获取,如下所示。
test.test((int x)-> System.out.println("传入的参数是:"+x)); 等于 test.test((x)-> System.out.println("传入的参数是:"+x));
3、有返回值的函数接口
public interface MyInterface { public boolean test(); }
具体使用如下所示。
public class Test { public static void main(String[] args) { Test test = new Test(); test.test(()-> true); } public void test(MyInterface myInterface){ System.out.println(myInterface.test()); } }
运行结果如下图所示。
4、有参数、有返回值的函数接口
public interface MyInterface { public int test(int x,int y); }
具体使用如下所示。
public class Test { public static void main(String[] args) { Test test = new Test(); test.test((int x,int y)-> x+y); } public void test(MyInterface myInterface){ int x = 10; int y = 20; System.out.println("10和20之和是:"+myInterface.test(x,y)); } }
运行结果如下图所示。
如果函数接口的方法体只有一条语句,可省略 {},如下所示。
test.test((int x,int y)-> x+y);
如果函数接口的方法体包含多条语句,需要在 {} 中添加相关语句,如下所示。
test.test((int x,int y)-> { System.out.println("参数是:"+x+","+y); return x+y; });
搞清楚了 Lambda 表达式的基本使用,接下来我们介绍几种实际开发中常用的 Lambda 表达式案例。
Lambda 表达式实际案例
1、替代匿名类
不使用 Lambda 表达式的代码如下所示。
new Thread(new Runnable() { @Override public void run() { System.out.println("do it..."); } }).start();
使用 Lambda 表达式的代码如下所。
new Thread(()-> System.out.println("do it...")).start();
2、遍历集合
不使用 Lambda 表达式的代码如下所示。
List<String> list = Arrays.asList("Hello","World","Java"); for(String str:list){ System.out.println(str); }
使用 Lambda 表达式的代码如下所。
List<String> list = Arrays.asList("Hello","World","Java");
list. forEach (str-> System.out.println(str));
双冒号 :: 表示方法引用,可以引用其他方法。
List<String> list = Arrays.asList("Hello","World","Java"); list.forEach(System.out::println);
运行结果如下图所示。
Java8 针对数据处理提供了 Stream API,让开发者能够以声明的方式来处理数据,Stream 对数据的处理类似于 SQL 语句查询数据库,将数据集合抽象成一种流,提供传输流的管道,并且可以在管道的节点上添加处理,如过滤、排序等,常用方法如下所示。
3、filter 过滤
filter() 方法是 Stream 提供的对数据进行过滤的 API,需要结合 Lambda 表达式来处理,比如过滤出目标集合中长度大于等于 5 的 字符串 ,具体操作如下所示。
List<String> list = Arrays.asList("Hello","World","Java"); list.stream().filter(str->str.length()>=5).forEach(str-> System.out.println(str));
运行结果如下图所示。
4、Predicate 多条件过滤
如果需要通过多个条件对集合进行过滤,可以使用 Predicate 来处理,Predicate 可以定义具体的过滤条件,调用多次 filter() 方法,通过传入不同的 Predicate 对象来进行过滤,具体操作如下所示。
List<String> list = Arrays.asList("Hello","World","Java"); Predicate<String> predicate1 = str->str.length()>=5; Predicate<String> predicate2 = str->str.startsWith("H"); list.stream() .filter(predicate1) .filter(predicate2) .forEach(str-> System.out.println(str));
运行结果如下图所示。
除了上述方法,也可以调用 Predicate 对象的 and() 方法,对多个 Predicate 对象进行且运算,或者用 or() 进行或运算,如下所示。
List<String> list = Arrays.asList("Hello","World","Java"); Predicate<String> predicate1 = str->str.length()>=5; Predicate<String> predicate2 = str->str.startsWith("H"); list.stream() .filter(predicate1.and(predicate2)) .forEach(str-> System.out.println(str));
运行结果如下图所示。
同时也可将 Predicate 作为参数传递给目标方法,具体操作如下所示。
public class Test { public static void main(String[] args) { List<String> list = Arrays.asList("Hello","World","Java"); //输出以J开头的字符串 test(list,(str)->str.startsWith("J")); //输出以a结尾的字符串 test(list,(str)->str.endsWith("a")); //输出长度大于等于5的字符串 test(list,(str)->str.length()>=5); //输出全部字符串 test(list,(str)->true); //不输出 test(list,(str)->false); } public static void test(List<String> list, Predicate<String> predicate){ for(String str:list){ if(predicate.test(str)){ System.out.println(str); } } } }
运行结果如下图所示。
5、limit 截取
使用 limit() 方法可以对数据集合进行截取,原理与 SQL 语句的 limit 一致,具体操作如下所示。
List<String> list = Arrays.asList("Hello","World","Java"); list.stream() .limit(2) .forEach(str-> System.out.println(str));
运行结果如下图所示。
limit() 也可以结合 filter() 来使用,如下所示。
List<String> list = Arrays.asList("Hello","World","Java"); list.stream() .filter(str->str.length()>=5) .limit(1) .forEach(str-> System.out.println(str));
运行结果如下图所示。
6、sorted 排序
使用 sorted() 方法可以对目标集合的数据进行排序,如下所示。
List<Integer> list = Arrays.asList(1,6,2,3,5,4); list.stream() .sorted() .forEach(num-> System.out.println(num));
运行结果如下图所示。
默认是升序排列,可通过添加 Comparator.reverseOrder() 进行降序排列,如下所示。
List<Integer> list = Arrays.asList(1,6,2,3,5,4); list.stream() .sorted(Comparator.reverseOrder()) .forEach(num-> System.out.println(num));
运行结果如下图所示。
7、max 返回集合最大值,min 返回集合最小值
需要注意的是 max() 和 min() 的返回值是 Optional 类型,Optional 也是 Java8 提供的新特性,Optional 类是一个可以为 null 的容器对象,需要调用 get() 方法取出容器内的数据,具体操作如下所示。
List<Integer> list = Arrays.asList(1,6,2,3,5,4); System.out.println(list.stream().max(Integer::compareTo).get());
运行结果如下图所示。
List<Integer> list = Arrays.asList(1,6,2,3,5,4); System.out.println(list.stream().min(Integer::compareTo).get());
运行结果如下图所示。
8、map 对集合中元素进行特定操作
如集合中的每个元素 +10 之后输出,具体操作如下所示。
List<Integer> list = Arrays.asList(1,6,2,3,5,4); list.stream() .map(num->num+10) .forEach(num-> System.out.println(num));
运行结果如下图所示。
9、reduce 对集合中元素进行特定操作
reduce() 和 map() 一样,都可以对集合中元素进行操作,区别在于 reduce() 是将所有元素按照传入的逻辑进行处理,并将结果合并成一个值返回,如返回集合所有元素之和,操作如下所示。
List<Integer> list = Arrays.asList(1,6,2,3,5,4); System.out.println(list.stream().reduce((sum,num)->sum+num).get());
需要注意的是 reduce() 的返回值是 Optional 类型,需要调用 get() 方法取出容器内的数据,运行结果如下图所示。
10、collection 基于目标集合的元素生成新集合
从目标集合中取出所有的奇数生成一个新的集合,具体操作如下所示。
List<Integer> list = Arrays.asList(1,6,2,3,5,4); List<Integer> list2 = list.stream() .filter(num->num%2!=0) .collect(Collectors.toList()); list2.forEach(num-> System.out.println(num));
运行结果如下图所示。
关注微信公众号「Java大联盟」,关注即可获取海量学习干货,同时还有不定期送书,键盘,鼠标等粉丝福利。