Home  >  Article  >  Java  >  Simple use case of lambda expression in java

Simple use case of lambda expression in java

高洛峰
高洛峰Original
2017-01-23 15:08:301527browse

My views on lambda expressions in Java are quite complicated:

One of my thoughts is this: lambda expressions reduce the reading experience of Java programs. Java programs have never been outstanding for their expressiveness. On the contrary, one of the factors that makes Java popular is its safety and conservatism - even beginners can write robust and easy-to-maintain code as long as they pay attention. Lambda expressions have relatively higher requirements for developers, which makes maintenance more difficult.

Another way I think: As a coder, it is necessary to learn and accept the new features of the language. If you give up its expressive advantages just because of its poor reading experience, then some people will find it difficult to understand even the trinocular expression. Language is also developing, and those who can’t keep up will be left behind voluntarily.

I don’t want to be left behind. But if I have to make a choice, my decision is still relatively conservative: there is no need to use lambda in the Java language - it makes many people in the current Java circle not used to it, and will cause an increase in labor costs. If you like it very much, you can consider using scala.

No matter what, I started to try to master Lambda. After all, some of the codes maintained at work use Lambda (believe me, I will gradually remove it). The tutorials I studied are related tutorials on the Oracla-Java official website.

——————————

Suppose you are currently creating a social networking application. One feature is that administrators can perform certain actions on members who meet specified criteria, such as sending messages. The following table details this use case:

Simple use case of lambda expression in java

##Use the following Person class to represent member information in the social network:

public class Person {
  
  public enum Sex {
    MALE, FEMALE
  }
  
  String name;
  LocalDate birthday;
  Sex gender;
  String emailAddress;
  
  public int getAge() {
    // ...
  }
  
  public void printPerson() {
    // ...
  }
}

Assume that all members are stored in a List instance.

In this section, we start with a very simple method, and then try to implement it using local classes and anonymous classes. In the end, we will gradually experience the power and efficiency of Lambda expressions. The complete code can be found here.

Option 1: Create methods one by one to find members who meet the specified criteria


This is the simplest and roughest solution to implement the case mentioned above: just create several methods , each method checks a criterion (such as age or gender). The following piece of code verifies that the age is greater than a specified value:

public static void printPersonsOlderThan(List<person> roster, int age) {
  for (Person p : roster) {
    if (p.getAge() >= age) {
      p.printPerson();
    }
  }
}

This is a very fragile solution, and it is very likely that the application will not run due to a small update. If we add new member variables to the Person class or change the algorithm for measuring age in the standard, we will need to rewrite a lot of code to adapt to this change. Furthermore, the restrictions here are too rigid. For example, what should we do if we want to print members whose age is younger than a certain specified value? Add a new method printPersonsYoungerThan? This is obviously a stupid method.

Option 2: Create a more general method


The following method is more adaptable than printPersonsOlderThan; this method prints member information within the specified age group:

public static void printPersonsWithinAgeRange(
    List<person> roster, int low, int high) {
  for (Person p : roster) {
    if (low <= p.getAge() && p.getAge() < high) {
      p.printPerson();
    }
  }
}

At this time, I have a new idea: What if we want to print member information of a specified gender, or member information that both meets the specified gender and is within the specified age group? What if we tweaked the Person class and added attributes like friendliness and location. Although writing this method is more general than printPersonsYoungerThan, writing a method for every possible query will also lead to brittle code. It is better to separate the standard checking code into a new class.

Option 3: Implement standard checking in a partial class


The following method prints member information that meets the search criteria:

public static void printPersons(List<person> roster, CheckPerson tester) {
  for (Person p : roster) {
    if (tester.test(p)) {
      p.printPerson();
    }
  }
}

A CheckPerso object tester is used in the program to verify each instance in the List parameter roter. If tester.test() returns true, the printPerson() method will be executed. In order to set the retrieval criteria, you need to implement the CheckPerson interface.

The following class implements CheckPerson and provides a specific implementation of the test method. The test method in this class filters the information of members who meet the requirements for military service in the United States: that is, the gender is male and the age is between 18 and 25 years old.

class CheckPersonEligibleForSelectiveService implements CheckPerson {
  public boolean test(Person p) {
    return p.gender == Person.Sex.MALE &&
        p.getAge() >= 18 &&
        p.getAge() <= 25;
  }
}

To use this class, you need to create an instance and trigger the printPersons method:

printPersons(
    roster, new CheckPersonEligibleForSelectiveService());

Now the code looks less fragile — —We don’t need to rewrite the code because of changes in the Person class structure. But there is still extra code: a newly defined interface and an inner class defined for each search criterion in the application.

Because CheckPersonEligibleForSelectiveService implements an interface, you can use anonymous classes without having to define an inner class for each standard.

Option 4: Use anonymous classes to implement standard checks


One parameter in the printPersons method called below is an anonymous class. The function of this anonymous class is the same as the CheckPersonEligibleForSelectiveService class in Solution 3: both filter members whose gender is male and who are between 18 and 25 years old.

printPersons(
    roster,
    new CheckPerson() {
      public boolean test(Person p) {
        return p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25;
      }
    }
);

这个方案减少了编码量,因为不再需要为每个要执行的检索方案创建新类。不过这样做仍让人有些不舒服:尽管CheckPerson接口只有一个方法,实现的匿名类仍是有些冗长笨重。此时可以使用Lambda表达式替换匿名类,下面会说下如何使用Lambda表达式替换匿名类。

方案五:使用Lambda表达式实现标准检查

CheckPerson接口是一个函数式接口。所谓的函数式接口就是指任何只包含一个抽象方法的接口。(一个函数式接口中也可以有多个default方法或静态方法)。既然函数式接口中只有一个抽象方法,那么在实现这个函数式接口的方法的时候可以省略掉方法的方法名。为了实现这个想法,可以使用Lambda表达式替换匿名类表达式。在下面重写的printPersons方法中,相关的代码做了高亮处理:

printPersons(
    roster,
    (Person p) -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

在这里还可以使用一个标准的函数接口来替换CheckPerson接口,从而进一步简化代码。

方案六:在Lambda表达式中使用标准函数式接口

再来看一下CheckPerson接口:

interface CheckPerson {
  boolean test(Person p);
}

这是一个非常简单的接口。因为只有一个抽象方法,所以它也是一个函数式接口。这个抽象方法只接受一个参数并返回一个boolean值。这个抽象接口太过简单了,以至于我们会考虑是否有必要在应用中定义一个这样的接口。此时可以考虑使用JDK定义的标准函数式接口,可以在java.util.function包下找到这些接口。

在这个例子中,我们就可以使用Predicate接口来替换CheckPerson。在这个接口中有一个boolean test(T t)方法:

interface Predicate<t> {
  boolean test(T t);
}

Predicate接口是一个泛型接口。泛型类(或者是泛型接口)使用一对尖括号()指定了一个或多个类型参数。在这个接口中只有一个类型参数。在使用具体类声明或实例化一个泛型类时,就会获得一个参数化类。比如说参数化类Predicate就是这样的:

interface Predicate<person> {
  boolean test(Person t);
}

在这个参数化类中有一个与CheckPerson.boolean test(Person p)方法的参数和返回值都一致的方法。因此就可以同如下方法所演示的一样使用Predicate接口来替换CheckPerson接口:

public static void printPersonsWithPredicate(
    List<person> roster, Predicate<person> tester) {
  for (Person p : roster) {
    if (tester.test(p)) {
      p.printPerson();
    }
  }
}

然后使用下面的代码就可以像方案三中一样筛选适龄服兵役的会员了:

printPersonsWithPredicate(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

有没有注意到,这里使用Predicate作为参数类型时并没有显式指定参数类型。这里并不是唯一适用lambda表达式的地方,下面的方案会介绍更多lambda表达式的用法。

方案七:在整个应用中使用lambda表达式

再来看一下printPersonsWithPredicate 方法,考虑是否可以在这里使用lambda表达式:

public static void printPersonsWithPredicate(
    List<person> roster, Predicate<person> tester) {
  for (Person p : roster) {
    if (tester.test(p)) {
      p.printPerson();
    }
  }
}

在这个方法中使用Predicate实例tester检查了roster中的每个Person实例。如果Person实例符合tester中定义的检查标准,将会触发Person实例的printPerson方法。

除了触发printPerson方法,满足tester标准的Person实例还可以执行其他的方法。可以考虑使用lambda表达式指定要执行的方法(私以为这个特性很好,解决了java中方法不能作为对象传递的问题)。现在需要一个类似printPerson方法的lambda表达式——一个只需要一个参数且返回为void的lambda表达式。记住一点:要使用lambda表达式,需要先实现一个函数式接口。在这个例子中需要一个函数式接口,其中只包含一个抽象方法,这个抽象方法有个类型为Person的参数,且返回为void。可以看一下JDK提供的标准函数式接口Consumer,它有一个抽象方法void accept(T t)正好满足这个要求。在下面的代码中使用一个Consumer的实例调用accept方法替换了p.printPerson():

public static void processPersons(
    List<person> roster,
    Predicate<person> tester,
    Consumer<person> block) {
  for (Person p : roster) {
    if (tester.test(p)) {
      block.accept(p);
    }
  }
}

对应这里,可以使用如下代码筛选适龄服兵役的会员:

processPersons(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.printPerson()
);

如果我们想做的事情不只是打印会员信息,而是更多的事情,比如验证会员身份、获取会员联系方式等等。此时,我们需要一个有返回值方法的函数式接口。JDK的标准函数式接口Function就有一个这样的方法R apply(T t)。下面的方法从参数mapper中获取数据,并在这些数据上执行参数block指定的行为:

public static void processPersonsWithFunction(
    List<person> roster,
    Predicate<person> tester,
    Function<person  , string> mapper,
    Consumer<string> block) {
  for (Person p : roster) {
    if (tester.test(p)) {
      String data = mapper.apply(p);
      block.accept(data);
    }
  }
}

下面的代码获取了roster中适龄服兵役的所有会员的邮箱信息并打印出来:

processPersonsWithFunction(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);

方案八:多使用泛型

再来回顾一下processPersonsWithFunction方法。下面是这个方法的泛型版本,新方法在参数类型上要求更为宽容:

public static <x  , Y> void processElements(
    Iterable<x> source,
    Predicate<x> tester,
    Function<x  , Y> mapper,
    Consumer<y> block) {
  for (X p : source) {
    if (tester.test(p)) {
      Y data = mapper.apply(p);
      block.accept(data);
    }
  }
}

 

要打印适龄服兵役的会员信息可以像下面这样调用processElements方法:

processElements(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);

在方法的调用过程中执行了如下行为:

从一个集合中获取对象信息,在这个例子里是从集合实例roster中获取Person对象信息。
过滤能够匹配Predicate实例tester的对象。在这个例子里,Predicate对象是一个lambda表达式,它指定了过滤适龄服兵役的条件。

将过滤后的对象交给一个Function对象mapper处理,mapper会为这个对象匹配一个值。在这个例子中Function对象mapper是一个lambda表达式,它返回了每个会员的邮箱地址。

由Consumer对象block为mapper匹配的值指定一个行为。在这个例子里,Consumer对象是一个lambda表达式,它的作用是打印一个字符串,也就是Function实例mapper返回的会员邮件地址。

方案九:使用将lambda表达式作为参数的聚集操作

下面的代码中使用了聚集操作来打印roster集合中适龄服兵役会员的邮件地址:

roster.stream()
    .filter(
        p -> p.getGender() == Person.Sex.MALE
            && p.getAge() >= 18
            && p.getAge() <= 25)
    .map(p -> p.getEmailAddress())
    .forEach(email -> System.out.println(email));

分析下如上代码的执行过程,整理如下表:

Simple use case of lambda expression in java

表中的filter、map和forEach操作都是聚集操作。聚集操作处理的元素来自Stream,而非是直接从集合中获取(就是因为这示例程序中调用的第一个方法是stream())。Stream是一个数据序列。和集合不同,Stream并没有用特定的结构存储数据。相反的,Stream从一个特定的源获取数据,比如从集合获取数据,通过一个pipeline。pipeline是一个Stream操作序列,在这个例子中就是filter-map-forEach。此外,聚集操作通常采用lambda表达式作为参数,这也给了我们许多自定义的空间。

更多Simple use case of lambda expression in java相关文章请关注PHP中文网!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn