好程序员Java 教程分享 Java8.0 新特性之 Lambda 表达式: Java 8 已经发布很久了,很多报道表明 Java 8 是一次重大的版本升级。本篇文章,主要给大家介绍的是 lambda 表达式。
Lambda 表达式
Lambda 表达式(也称为闭包)是 Java 8 中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。
很多JVM 平台上的语言( Groovy 、 Scala 等)从诞生之日就支持 Lambda 表达式,但是 Java 开发者没有选择,只能使用匿名内部类代替 Lambda 表达式。
Lambda 的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。而 lambda 表达式的使用需要和函数式接口结合。
1. 函数式接口
1.1. 概念
函数式接口在Java 中是指:有且仅有一个抽象方法的接口。 函数式接口,即适用于函数式编程场景的接口。而 Java 中的函数式编程体现就是 Lambda ,所以函数式接口就是可 以适用于 Lambda 使用的接口。只有确保接口中有且仅有一个抽象方法, Java 中的 Lambda 才能顺利地进行推导。 备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的 for-each 语法,其实 底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲, Java 中的 Lambda 可以被当做是匿名内部 类的“语法糖”,但是二者在原理上是不同的。
1.2, 格式
只要确保接口中有且仅有一个抽象方法即可:
1. 修饰符 interface 接口名称 {
2. public abstract 返回值类型 方法名称 ( 可选参数信息 );
3. }
1.3 @FunctionalInterface 注解
与 @Override 注解的作用类似, Java 8 中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上:
1. @FunctionalInterface
2. public interface MyFunctionalInterface {
3. void myMethod();
4. }
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注 意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样.
2. 函数式接口的使用
2.1 函数式接口作为参数 , 方法不带参数
1. // 定义函数式接口
2. public interface MyInterface{
3.
4. public abstract void show();
5.
6. }
7.
8. // 使用 ( 匿名内部类对象 / 函数式 )
9. public class Demo01 {
10.
11. public static void main(String[] args) {
12. method01(new MyInterface01() {
13.
14. @Override
15. public void show() {
16. System.out.println(” 你好 , 函数式接口 “);
17. }
18. });
19. // 函数式
20. method01(() -> {
21. System.out.println(” 你好 , 函数式接口 “);
22. });
23. // 函数式简写 ( 如果方法体中只有一句代码 )
24. method01(() -> System.out.println(” 你好 , 函数式接口 “));
25. }
26.
27. public static void method01(MyInterface01 inter) {
28. inter.show();
29. }
30.
31. }
函数式接口的优势
函数式接口比匿名内部类对象产生更少的字节码对象, 提升 java 执行效率 .
2.2, 函数式接口作为参数 , 方法带参数
1. // 定义函数式接口
2. public interface MyInterface02 {
3.
4. public abstract void show(String msg1, String msg2);
5.
6. }
7.
8. // 使用函数式接口
9. public static void main(String[] args) {
10. // 匿名内部类对象
11. method01(new MyInterface02() {
12.
13. @Override
14. public void show(String msg1, String msg2) {
15. System.out.println(msg1 + msg2);
16. }
17. });
18. // 函数式完整
19. method01((String msg1, String msg2) -> {
20. System.out.println(msg1 + msg2);
21. });
22. // 函数式简写
23. method01((msg1, msg2) -> System.out.println(msg1 + msg2));
24.
25. }
26.
27. public static void method01(MyInterface02 inter) {
28. inter.show(“hello”, ” 函数式 “);
29. }
2.3, 函数式接口作为返回值 , 方法不带参数
1. // 定义函数式接口
2. public interface MyInterface02 {
3.
4. public abstract void show(String msg1, String msg2);
5.
6. }
7. public static void main(String[] args) {
8.
9. getInter1().show(” 你好 “, “ 函数式 “);
10. getInter2().show(” 你好 “, “ 函数式 “);
11.
12. }
13.
14. // 函数式完整
15. public static MyInterface02 getInter1() {
16.
17. return (String msg1, String msg2) -> {
18. System.out.println(msg1 + msg2);
19. };
20. }
21.
22. // 函数式简写
23. public static MyInterface02 getInter2() {
24.
25. return (msg1, msg2) -> System.out.println(msg1 + msg2);
26. }
3. 函数式编程应用场景
3.1, 概念
在兼顾面向对象特性的基础上,Java 语言通过 Lambda 表达式使用函数式接口 , 就叫做函数式编程
3.2, 使用 lambada 作为参数
如果抛开实现原理不说,Java 中的 Lambda 表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数 式接口类型,那么就可以使用 Lambda 表达式进行替代。
1. public class Demo04Runnable{
2. private static void startThread(Runnable task){
3. new Thread(task).start();
4. }
5. public static void main(String[] args) {
6. startThread(() ‐ >System.out.println(“ 线程执行 “));
7. }
8. }
3.3, 使用函数式接口作为返回值
如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda 表达式。
1. public class Demo06Comparator {
2.
3. private static Comparator<Integer> getComparator(){
4. return (num1,num2) ‐ > num1 – num2;
5. }
6.
7. public static void main(String[] args) {
8. Integer[] array = {3,2,1};
9. Arrays.sort(array, getComparator());
10. // 遍历数组
11. }
12. }
3.4, 函数式接口的方法有返回值
1. public static void main(String[] args) {
2.
3. showMsg(new MyInterface03() {
4.
5. @Override
6. public String getMsg() {
7. return “hello functional interface”;
8. }
9. });
10.
11. // lambada 表达式
12. showMsg(() -> {
13.
14. return “hello1 functional interface”;
15. });
16.
17. // lambda 表达式简写
18. showMsg(() -> “hello1 functional interface”);
19.
20. }
21.
22. public static void showMsg(MyInterface03 inter) {
23. String msg = inter.getMsg();
24. System.out.println(msg);
25. }
4. 常用函数式接口 (Supplier 接口 )
JDK 提供了大量常用的函数式接口以丰富 Lambda 的典型使用场景,它们主要在 java.util.function 包中被提供。 下面是简单的几个接口及使用示例。
4.1,Supplier 接口
java.util.function.Supplier<T> 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的 Lambda 表达式需要“对外提供”一个符合泛型类型的对象数据
4.2, 基本使用
1. private static String getString(Supplier<String> function ){
2. return function.get();
3. }
4. public static void main(String[] args){
5. String msgA=”Hello”;
6. String msgB=”World”;
7. System.out.println(getString(()->msgA+msgB));
8. }
4.2 ,综合案例
需求: 使用 Supplier 接口作为方法参数类型,通过 Lambda 表达式求出 int 数组中的最大值。提示:接口的泛型请使用 java.lang.Integer 类。
1. public static void main(String[] args) {
2. Integer max = getMax(()->{
3. Integer[] nums = {1,2,3,4};
4. int max2 = nums[0];
5. for (Integer num : nums) {
6. if(max2 < num){
7. max2 = num;
8. }
9. }
10. return max2;
11. });
12. System.out.println(max);
13. }
14.
15. public static Integer getMax(Supplier<Integer> supplier){
16. return supplier.get();
17. }
5. 常用函数式接口 (Consumer 接口 )
5.1,Consumer 接口
java.util.function.Consumer<T> 接口则正好与 Supplier 接口相反,它不是生产一个数据,而是消费一个数据, 其数据类型由泛型决定
5.2,accept 方法
Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。基本使用如:
1. public static void main(String[] args) {
2. consumeString((msg)->System.out.println(msg));
3. }
4.
5.
6. public static void consumeString(Consumer<String> consumer){
7. consumer.accept(“hello”);
8. }
5.3, andThen 方法
如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作, 然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的 default 方法 andThen
1. default Consumer<T> andThen(Consumer<? super T> after) {
2. Objects.requireNonNull(after);
3. return (T t) -> { accept(t); after.accept(t); };
4. }
注: java.util.Objects 的 requireNonNull 静态方法将会在参数为 null 时主动抛出 NullPointerException 异常。这省去了重复编写 if 语句和抛出空指针异常的麻烦。
需求:先打印大写HELLO, 再打印小写 hello
1. public static void main(String[] args) {
2. consumeString((msg) -> System.out.println(msg.toUpperCase()),
3. (msg) -> System.out.println(msg.toLowerCase()));
4. }
5.
6. public static void consumeString(Consumer<String> consumer1, Consumer<String> consumer2) {
7. consumer1.andThen(consumer2).accept(“hello”);
8. }
6. 常用函数式接口 (Predicate 接口 )
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean 值结果。这时可以使用 java.util.function.Predicate<T> 接口
6.1, test 方法
Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断的场景
1. public enum SingleClass06 {
2. INSTANCE;
3. }
6.2 ,基本使用
1. public static void main(String[] args) {
2. System.out.println(predicateTest((msg) -> msg.length() > 3, “hello”));
3. }
4.
5.
6. public static boolean predicateTest(Predicate<String> predicate,String msg){
7. return predicate.test(msg);
8.
9. }
7. 总结
在本文中, 我们学会了使用 lambda 表达式的不同方式 , 同时也学习了 java8.0 开始自带的一些常用函数式接口。