Java8 lambda表达式概念以及使用lambda操作list集合

一、Lambda 表达式基本概况

(1)Lambda 表达式,也可称为闭包,它是推动Java 8发布的最重要新特性;Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中);使用 Lambda 表达式可以使代码变的更加简洁紧凑。

语法如下:

(parameters) -> expression
或
(parameters) ->{ statements; }

(2)以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指明表达式返回了一个数值。

(3)使用 Lambda 表达式需要了解以下几点:

  • Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。
  • Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
  • 将匿名内部类简写成lambda表达式的依据:
    • 能够使用Lambda的依据是必须有相应的函数接口(函数接口,是指内部有抽象方法的接口)。实际上Lambda的类型就是对应函数接口的类型。
    • Lambda表达式另一个依据是类型推断机制,在上下文信息足够的情况下,编译器可以推断出参数表的类型,而不需要显式指明。注意,Java是强类型语言,每个变量和对象都必须有明确的类型。
  • 如果使用了匿名内部类,编译过后将会产生一个以$符号结尾的class字节码文件,而使用lambda则不会产生这样的class字节码文件,只会产生一个class文件,这是因为jvm将Lambda表达式封装成了主类的一个私有方法,并通过invokedynamic指令进行调用。

(4)变量作用域

  • lambda 表达式只能引用标记了 final的外层局部变量,这就是说不能在lambda内部修改定义在域外的局部变量,否则会报编译错误。
  • lambda 表达式内的局部变量可以不用声明为final,但是必须不可被后面的代码修改(即隐性的具有final 的语义)。
  • 在Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

二、Lambda and Collections

我们先从最熟悉的Java集合框架(Java Collections Framework, JCF)开始说起。

为引入Lambda表达式,Java8新增了java.util.funcion包,里面包含常用的函数接口,这是Lambda表达式的基础,Java集合框架也新增部分接口,以便与Lambda表达式对接。

首先回顾一下Java集合框架的接口继承结构:

上图中绿色标注的接口类,表示在Java8中加入了新的接口方法,当然由于继承关系,他们相应的子类也都会继承这些新方法。下表详细列举了这些方法。

接口名 Java8新加入的方法
Collection removeIf()、spliterator()、 stream()、 parallelStream() 、forEach()
List replaceAll() 、sort()
Map getOrDefault()、 forEach()、 replaceAll()、 putIfAbsent() 、remove()、 replace() 、computeIfAbsent() 、computeIfPresent()、 compute()、 merge()

这些新加入的方法大部分要用到java.util.function包下的接口,这意味着这些方法大部分都跟Lambda表达式相关。

Collection中的新方法

(1)forEach()方法 以list为例说明forEach()方法。

该方法的签名为void forEach(Consumer<? super E> action)作用是对容器中的每个元素执行action指定的动作,其中Consumer是个函数接口,里面只有一个待实现方法void accept(T t)

需求:假设有一个字符串列表,需要打印出其中所有长度大于3的字符串。

List<String> list = new ArrayList<>(Arrays.asList("i", "love", "liangliang"));
// 使用增强for循环实现
for (String s : list) {
    if (s.length() > 3) {
        System.out.println("使用增强for循环实现:" + s);
    }
}

// 使用匿名内部类实现Comsumer接口
list.forEach(new Consumer<String>() {
    @Override
    public void accept(String s) {
        if (s.length() > 3) {
            System.out.println("使用Consumer内部类实现:" + s);
        }
    }
});
// 上述代码调用forEach()方法,并使用匿名内部类实现Comsumer接口。

// lambda写法
list.forEach(s -> {
    if (s.length() > 3) {
        System.out.println("使用lambda表达式实现:" + s);
    }
});
// 上述代码给forEach()方法传入一个Lambda表达式,我们不需要知道accept()方法,也不需要知道Consumer接口,类型推导帮我们做了一切。

提示:在idea中,如果使用了匿名内部类实现,idea会提示将它转换成lambda表达式实现,因此如果暂时忘记了如何写lambda表达式可以利用这一特性快速转成lambda表达式。


(2)removeIf() 以list为例说明removeIf()方法。

该方法签名为boolean removeIf(Predicate<? super E> filter),作用是删除容器中所有满足filter指定条件的元素,其中Predicate是一个函数接口,里面只有一个待实现方法boolean test(T t)

需求:假设有一个字符串列表,需要删除其中所有长度大于3的字符串。

List<String> list = new ArrayList<>(Arrays.asList("i", "love", "liangliang"));

// 我们知道如果需要在迭代过程中对容器进行删除操作必须使用迭代器,否则会抛出ConcurrentModificationException,所以迭代器实现如下
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    if (iterator.next().length() > 3) {
        iterator.remove();
    }
}
// 遍历出来,使用lambda表达式
list.forEach(System.out::println);

// 使用removeIf()方法,并使用匿名内部类实现Precicate接口
list.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.length() > 3;
    }
});
list.forEach(System.out::println);

// 使用lambda表达式实现 删除长度大于3的元素
list.removeIf(str -> str.length()>3);
list.forEach(System.out::println);

不得不说idea真的太智能了,提示检测功能简直了,不用说了今后对于idea的提示建议90%我都接受,太智能了。


(3)replaceAll() 以list为例说明replaceAll()方法。

该方法签名为void replaceAll(UnaryOperator<E> operator)作用是对每个元素执行operator指定的操作,并用操作结果来替换原来的元素。其中UnaryOperator是一个函数接口,里面只有一个待实现函数T apply(T t)

需求:假设有一个字符串列表,将其中所有长度大于3的元素转换成大写,其余元素不变。

List<String> list = new ArrayList<>(Arrays.asList("i", "love", "liangliang"));

// Java7及之前的实现方式,采用for循环实现
for (int i = 0; i < list.size(); i++) {
    String str = list.get(i);
    if (str.length() > 3) {
        list.set(i, str.toUpperCase());
    }
}
list.forEach(System.out::println);

// 调用replaceAll()方法,并使用匿名内部类实现UnaryOperator接口
list.replaceAll(new UnaryOperator<String>() {
    @Override
    public String apply(String s) {
        if (s.length() > 3) {
            return s.toUpperCase();
        }
        return s;
    }
});
list.forEach(System.out::println);

// 使用Lambda表达式实现
list.replaceAll(s -> {
    if (s.length() > 3) {
        return s.toUpperCase();
    } else {
        return s;
    }
});
list.forEach(System.out::println);

(4)sort() 以list为例说明sort()方法。

该方法定义在List接口中,方法签名为void sort(Comparator<? super E> c)该方法根据c指定的比较规则对容器元素进行排序Comparator接口我们并不陌生,其中有一个方法int compare(T o1, T o2)需要实现,显然该接口是个函数接口。

需求:假设有一个字符串列表,按照字符串长度增序对元素排序。

List<String> list = new ArrayList<>(Arrays.asList("love", "liangliang", "i"));

// 按元素长度进行排序
// 由于Java7以及之前sort()方法在Collections工具类中,所以代码要这样写:
list.sort(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        // 默认是升序排序
        return o1.length() - o2.length();
    }
});
// 输出排序后的结果
list.forEach(System.out::println);

// 现在可以直接使用List.sort()方法,结合Lambda表达式实现对list元素排序
list.sort((str1, str2) -> str1.length() - str2.length());
// 还可以进一步简化成如下进行排序
list.sort(Comparator.comparingInt(String::length));
// 输出排序后的结果
list.forEach(System.out::println);

这里的::表示引用,因为如果只是按照默认的或者参数只有一个的情况下idea会提示你进行优化,用::引用来进行替换就行了。

参考博文:
(1)https://objcoding.com/2019/03/04/lambda/ (非常详细,值得仔细阅读)
(2)https://www.runoob.com/java/java8-lambda-expressions.html

原文地址:https://www.cnblogs.com/jasonboren/p/13731705.html