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:
##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
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
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
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
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
interface Predicate<t> { boolean test(T t); }
Predicate
interface Predicate<person> { boolean test(Person t); }
在这个参数化类中有一个与CheckPerson.boolean test(Person p)方法的参数和返回值都一致的方法。因此就可以同如下方法所演示的一样使用Predicate
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表达式
再来看一下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
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
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));
分析下如上代码的执行过程,整理如下表:
表中的filter、map和forEach操作都是聚集操作。聚集操作处理的元素来自Stream,而非是直接从集合中获取(就是因为这示例程序中调用的第一个方法是stream())。Stream是一个数据序列。和集合不同,Stream并没有用特定的结构存储数据。相反的,Stream从一个特定的源获取数据,比如从集合获取数据,通过一个pipeline。pipeline是一个Stream操作序列,在这个例子中就是filter-map-forEach。此外,聚集操作通常采用lambda表达式作为参数,这也给了我们许多自定义的空间。
更多Simple use case of lambda expression in java相关文章请关注PHP中文网!