入门lambda表达式(一)

475 阅读5分钟

引文

  在这篇文章中,我想介绍一下Java 8最吸引人的新特性——lambda表达式。
  先贴上lambda表达式的基本语法,有两种形式:

  • (parameters) -> expression
  • (parameters) -> {expressions}

  lambda允许把函数作为一个方法的参数传递进方法中,体现了函数式编程思想。使用lambda表达式可以使代码变得更加简洁紧凑。lambda表达式的本质只是一个所谓的“语法糖”,并没有为Java语言添加新的功能,而是对已有功能进行了封装,由编译器推断并转换为常规的代码,因此可以用更少的代码来实现同样的功能。我个人觉得,lambda表达式虽好,但不要乱用,用在合适的地方,可以有效减少代码量,使代码更加简洁,而滥用只会降低代码的可读性,使程序难以调试和维护。那么lambda表达式的应用场景是什么呢?
  网上有很多lambda表达式的例子,我看了半天,觉得无外乎就两个场景(个人浅显观点......):
  一是代替函数式接口。函数式接口简单来说就是只包含一个抽象方法的接口,比如Java标准库中的java.lang.Runnable和java.util.Comparator都是典型的函数式接口。对于函数式接口,除了可以使用Java中标准的方法来创建实现对象之外,还可以使用lambda表达式来创建实现对象,这可以在很大程度上简化代码的实现。在使用lambda表达式时,只需要提供形式参数和方法体。由于函数式接口只有一个抽象方法,所以通过lambda表达式声明的方法体肯定是这个唯一的抽象方法的实现,而且形式参数的类型可以根据方法的类型声明进行自动推断(即形式参数可以省略类型)。
  第二个场景,就是和集合配合使用。Java 8新增了两个对集合数据进行批量操作的包:java.util.function和java.util.stream。可以说,lambda表达式和stream是自Java语言添加泛型和注解以来最大的变化,lambda表达式很大程度上影响了我们在处理集合时的编码方式。

Lambda表达式基本用法

  下面,我通过几个典型的例子带大家领略lambda表达式的强悍:

使用lambda表达式实现Runnable接口

传统方式

public class runnableTest {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                System.out.println("I am Hawk.");
            }
        }).start();
    }
}

使用lambda表达式

public class runnableTest {

    public static void main(String[] args) {
        new Thread(() ->
            System.out.println("I am Hawk.")
        ).start();
    }
}

  通过这个例子,我们也可以进一步熟悉lambda表达式的声明方式:由形式参数和方法体两部分组成,中间通过“->”分隔。形式参数通常情况下不需要包含类型声明,可以进行自动推断。因为 System.out.println("I am Hawk.") 是单行语句,可以不用花括号“{}”括起来,如果方法体包含多行语句,则需要用“{}”括起来。

使用lambda表达式实现Comparator接口

  关于Comparator的机制,不在我们这次的讨论范围内,我有时间...额...大概会写。总之,当我们相比较的对象没有实现Comparable接口时,可以使用Comparator接口并实现其中的compare方法进行比较。比如说,有这样一个Penple类:

// 使用lombok注解自动生成构造器与get、set方法等
@Data
@NoArgsConstructor
@AllArgsConstructor
public class People {
    
    private String name;
    
    private Integer age;
}

  现在我想按照年龄对People对象进行排序:
传统方式

public class ComparatorTest {

    public static void main(String[] args) {
        List<People> peopleList = new ArrayList<>();
        peopleList.addAll(Arrays.asList(new People("a", 20), new People("b", 21), new People("c", 22)));
        Collections.sort(peopleList, new Comparator<People>() {
            @Override
            public int compare(People p1, People p2) {
                return p1.getAge().compareTo(p2.getAge());
            }
        });
    }
}

使用lambda表达式

// 方法一
public class ComparatorTest {

    public static void main(String[] args) {
        List<People> peopleList = new ArrayList<>();
        peopleList.addAll(Arrays.asList(new People("a", 20), new People("b", 21), new People("c", 22)));
        Collections.sort(peopleList, (p1, p2) ->
            p1.getAge().compareTo(p2.getAge()));
    }
}

// 方法二
public class ComparatorTest {

    public static void main(String[] args) {
        List<People> peopleList = new ArrayList<>();
        peopleList.addAll(Arrays.asList(new People("a", 20), new People("b", 21), new People("c", 22)));
        peopleList.sort(Comparator.comparing(People::getAge));
    }
}

  不知道大家咋想的,反正我当时知道还有方法二这种写法时是真的惊了。感觉Java 8中的集合类型在增加了lambda表达式的支持之后变得......emmm高深莫测。需要注意一点,在方法二中,还使用了Java 8的另一个新特性——方法引用,就是那两个冒号“::”(有点C++的赶脚)。方法引用可以在不调用某个方法的情况下引用一个方法,是另一种实现函数式接口的方法,可以进一步简化代码。

使用lambda表达式对列表进行迭代

传统方法

public class foreachTest {

    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        stringList.addAll(Arrays.asList("a", "b", "c"));
        for (String str : stringList) {
            System.out.println(str);
        }
    }
}

使用lambda表达式

public class foreachTest {

    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        stringList.addAll(Arrays.asList("a", "b", "c"));
        stringList.forEach(System.out::println);
    }
}

  额......先写到这里吧,因为stream部分内容较多,过几天再补充完。这次主要介绍了使用lambda表达式的第一个场景——代替函数式接口。这是一个趋势,希望大家在今后的编码中,能逐渐抛弃匿名内部类,改用lambda这种更为简洁的写法。下次我将重点介绍lambda在集合中的使用以及如何与stream进行配合。