Lambda-Java8新特性最佳实践

一、基本概念

1.背景

Lambda是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。 Lambda 表达式(Lambda expression)可以看作是一个匿名函数,基于数学中的λ演算得名,也可称为闭包(Closure)

2.Lambda表达式的语法

基本语法: (parameters) -> expression 或 (parameters) ->{ statements; }

Lambda表达式由三部分组成:

  1. paramaters:类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号。
  2. ->:可理解为"被用于"的意思
  3. 方法体:可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不反回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不反回。
java 复制代码
// 1. 不需要参数,返回值为 2
() -> 2
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的和
(x, y) -> x + y
// 4. 接收2个int型整数,返回他们的乘积
(int x, int y) -> x * y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)

3.函数式接口

要了解Lambda表达式,首先需要了解什么是函数式接口,函数式接口定义:一个接口有且只有一个抽象方法

注意:

  1. 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
  2. 如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的。所以,从某种意义上来说,只要你保证你的接口中只有一个抽象方法,你可以不加这个注解。加上就会自动进行检测的。
    定义方式:
java 复制代码
@FunctionalInterface
interface NoParameterNoReturn {
	//注意:只能有一个方法
	void test();
}

但是这种方式也是可以的:我们知道在 jdk1.8之后接口中的方法式可以有具体实现的

java 复制代码
@FunctionalInterface
interface NoParameterNoReturn {
	void test();
	default void test2() {
		System.out.println("JDK1.8新特性,default默认方法可以有具体的实现");
	}
}

二、Lambda表达式的基本使用

我们在上面提到过,Lambda表达式本质是一个匿名函数,函数的方法是:返回值 方法名 参数列表 方法体。在,Lambda表达式中我们只需要关心:参数列表 方法体。

1.无返回值函数式接口

java 复制代码
//无返回值无参数
@FunctionalInterface
interface NoParameterNoReturn {
    void test();
}
//无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
    void test(int a);
}
//无返回值两个参数
@FunctionalInterface
interface MoreParameterNoReturn {
    void test(int a,int b);
}
public class TestDemo {
    public static void main(String[] args) {
        NoParameterNoReturn n = ()->{
            System.out.println("无参数无返回值");
        };
        n.test();

        OneParameterNoReturn o = (a)-> {
            System.out.println("无返回值一个参数"+a);
        };
        o.test(666);
        MoreParameterNoReturn m = (int a,int b)->{
            System.out.println("无返回值两个参数"+a+" "+b);
        };
        m.test(666,999);
    }
}

运行结果:

2.有返回值函数接口

java 复制代码
//有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
    int test();
}
//有返回值一个参数
@FunctionalInterface
interface OneParameterReturn {
    int test(int a);
}
//有返回值多个参数
@FunctionalInterface
interface MoreParameterReturn {
    int test(int a,int b);
}
public class TestDemo {
    public static void main(String[] args) {
        NoParameterReturn n = ()->{
            return 666;
        };
        int ret1 = n.test();
        System.out.println(ret1);
        System.out.println("================");
        OneParameterReturn o = (int a)->{
            return a;
        };
        int ret2 = o.test(999);
        System.out.println(ret2);
        System.out.println("================");
        MoreParameterReturn m = (int a,int b)-> {
            return a+b;
        };
        int ret3 = m.test(10,90);
        System.out.println(ret3);
    }
}

运行结果:

3.语法精简

Lambda表达式的语法还可以精简,显得非常有逼格,但是可读性就非常差。

  1. 参数类型可以省略,如果需要省略,每个参数的类型都要省略。
  2. 参数的小括号里面只有一个参数,那么小括号可以省略
  3. 如果方法体当中只有一句代码,那么大括号可以省略
  4. 如果方法体中只有一条语句,其是return语句,那么大括号可以省略,且去掉return关键字
    把上面的代码精简示例:
java 复制代码
	public static void main(String[] args) {
        MoreParameterNoReturn moreParameterNoReturn = (a, b)->{
            System.out.println("无返回值多个参数,省略参数类型:"+a+" "+b);
        };
        moreParameterNoReturn.test(20,30);
        OneParameterNoReturn oneParameterNoReturn = a ->{
            System.out.println("无参数一个返回值,小括号可以省略:"+ a);
        };
        oneParameterNoReturn.test(10);
        NoParameterNoReturn noParameterNoReturn = ()->System.out.println("无参数无返回值,方法体中只有 一行代码");
        noParameterNoReturn.test();
        //方法体中只有一条语句,且是return语句
        NoParameterReturn noParameterReturn = ()-> 40;
        int ret = noParameterReturn.test();
        System.out.println(ret);
    }

三、变量捕获

Lambda 表达式中存在变量捕获 ,了解了变量捕获之后,我们才能更好的理解Lambda 表达式的作用域 。Java当中的匿名类中,会存在变量捕获。

1.匿名内部类

我们在前面的博客------>内部类 中提到了匿名内部类中变量的捕获。
匿名内部类中:一定是程序在运行的过程当中没有发生改变的量

如果把捕获的变量 a在匿名内部类中修改,就会报错。

2.Lambda的变量捕获

Lambda的变量捕获,同样也是不能捕获放生改变的,如果发生改变就会报错。

java 复制代码
@FunctionalInterface
interface NoParameterNoReturn {
	void test();
} 
public static void main(String[] args) {
	int a = 10;
	NoParameterNoReturn noParameterNoReturn = ()->{
	// a = 99; error
	System.out.println("捕获变量:"+a);
	};
	noParameterNoReturn.test();
}

四、Lambda在集合当中的使用

为了能够让Lambda和Java的集合类集更好的一起使用,集合当中,也新增了部分接口,以便与Lambda表达式对接。要用Lambda遍历集合就一定要看懂源码。

1.List和forEach、sort

forEach()方法遍历集合,先得看一下源码。如果要打印元素,它需要的实现 Consumer接口,同时要实现重写accept()方法,它会把数组里的每一个元素都交给,accept()方法。

代码示例:

java 复制代码
import java.util.*;
import java.util.function.Consumer;

public class TestDemo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("bit");
        list.add("hello");
        list.add("lambda");
        list.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });
        System.out.println("=================");
        list.forEach(a-> System.out.println(a));
    }
}

运行结果:

sort 方法

java 复制代码
import java.util.function.Consumer;

public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("bit");
        list.add("hello");
        list.add("lambda");
        list.sort(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });
        System.out.println(list);
        System.out.println("=============");
        //Lambda方法
        list.sort((o1,o2)->o2.compareTo(o1));
        System.out.println(list);
}

运行结果:

2.HashMap和forEach

HashMap的forEach源码需要的是两个参数。

java 复制代码
import java.util.function.Consumer;

public static void main(String[] args) {
     HashMap<Integer, String> map = new HashMap<>();
     map.put(1, "hello");
     map.put(2, "bit");
     map.put(3, "hello");
     map.put(4, "lambda");
     map.forEach(new BiConsumer<Integer, String>(){
         @Override
         public void accept(Integer k, String v){
           System.out.println(k + "=" + v);
         }
     });
}

运行结果

改为Lambda后

java 复制代码
public static void main(String[] args) {
    HashMap<Integer, String> map = new HashMap<>();
    map.put(1, "hello");
    map.put(2, "bit");
    map.put(3, "hello");
    map.put(4, "lambda");
    map.forEach((k,v)-> System.out.println("key = "+k+" vak = "+v));
}

运行结果

java 复制代码
@Slf4j
public class LambdaUtil {
    public static void main(String[] args) {
        Apple[] appleArray = new Apple[]{
                new Apple("1", "name1", new BigDecimal("1.11"), 1),
                new Apple("2", "name2", new BigDecimal("2.22"), 1),
                new Apple("3", "name3", new BigDecimal("4.44"),1 ),
                new Apple("4", "name3", new BigDecimal("4.44"),1)
        };
        List<Apple> appleList =  Arrays.asList(appleArray);
        List<Apple> list = new ArrayList<>(Arrays.asList(appleArray));
        Apple[] array = appleList.toArray(new Apple[0]);
        //以某个属性分组
        Map<String, List<Apple>> groupBy = appleList.stream().collect(Collectors.groupingBy(Apple::getId));
        //List转Map
        Map<String, Apple> appleMap = appleList.stream().collect(Collectors.toMap(Apple::getId, a -> a,(k1, k2)->k1));
        log.info("appleMap:" + JSON.toJSONString(appleMap));
        //获取集合中的某个属性转为集合
        List<String> nameList = appleList.stream().map(Apple::getName).collect(Collectors.toList());
        log.info("nameList:" + JSON.toJSONString(nameList));
        //过滤
        List<Apple> filterList = appleList.stream().filter(a -> a.getName().equals("name3")).collect(Collectors.toList());
        //求和
        BigDecimal totalMoney = appleList.stream().map(Apple::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
        //获取集合中某个最大值的int数据
        int amount = appleList.stream().mapToInt(Apple::getAmount).max().orElse(-1);
        log.info("amount:" + amount);
        //查找流中最大值
        Optional<Apple> maxApple = appleList.stream().max(comparing(Apple::getMoney));
        /*maxApple.ifPresent(System.out::println);*/
        log.info("maxApple:" + JSON.toJSONString(maxApple));
        //查找流中最小值
        Optional<Apple> minApple = appleList.stream().min(comparing(Apple::getMoney));
        log.info("minApple:"+JSON.toJSONString(minApple));
        //根据id去重
        List<Apple> unique = appleList.stream().collect(
                collectingAndThen(
                        toCollection(() -> new TreeSet<>(comparing(Apple::getId))), ArrayList::new));
        log.info("unique 根据id去重:"+JSON.toJSONString(unique));
        //集合中的属性去重
        List<Apple> uniqueBy = appleList.stream().distinct().collect(Collectors.toList());
        log.info("uniqueBy 属性去重:"+JSON.toJSONString(uniqueBy));
        //根据集合中的某个属性进行升序重排
        List<Apple> escAppleList = appleList.stream().sorted(Comparator.comparing(Apple::getMoney)).collect(Collectors.toList());
        log.info("escAppleList升序:"+JSON.toJSONString(escAppleList));
        //根据集合中的某个属性进行降序重排
        List<Apple> descAppleList = appleList.stream().sorted(Comparator.comparing(Apple::getMoney).reversed()).collect(Collectors.toList());
        log.info("descAppleList降序:"+JSON.toJSONString(descAppleList));
        //根据集合中的某个属性过滤并获取第一个
        Apple appleFilter = appleList.stream().filter(x -> x.getAmount() == 1 || x.getAmount() == 20 || x.getAmount() == 26 || x.getAmount() == 89)
                .findFirst().orElse(null);
        log.info("appleFilter 属性过滤并获取第一个:"+JSON.toJSONString(appleFilter));
        //根据集合中的属性转换为键值对Map
        Map<String, Apple> map = appleList.stream().collect(Collectors.toMap(Apple::getId, apple -> apple));
        log.info("map 属性转换为键值对Map:"+JSON.toJSONString(map));
        //分页
        List<Apple> pageList = appleList.stream().skip(3 * (1 - 1)).limit(3).collect(Collectors.toList());
        log.info("pageList 分页:"+JSON.toJSONString(pageList));
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
class Apple {
    private String id;
    private String name;
    private BigDecimal money;
    private Integer amount;
}

总结

Lambda表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读
优点:

  1. 代码简洁,开发迅速
  2. 方便函数式编程
  3. 非常容易进行并行计算
  4. Java 引入 Lambda,改善了集合操作
    缺点:
  5. 代码可读性变差
  6. 在非并行计算中,很多计算未必有传统的 for 性能要高
  7. 不容易进行调试
相关推荐
Abladol-aj6 分钟前
并发和并行的基础知识
java·linux·windows
清水白石0086 分钟前
从一个“支付状态不一致“的bug,看大型分布式系统的“隐藏杀机“
java·数据库·bug
吾日三省吾码6 小时前
JVM 性能调优
java
弗拉唐7 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi777 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
少说多做3437 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀7 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
蓝黑20208 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深8 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++