jdk8新特性 lambda表达式详解

本文主要讲到的内容有:

一- 前言

java中lambda表达式是java匿名函数的一种表达形式,只是写法和我们之前学习了解到的匿名函数有点不一样。本教程主要介绍Java SE 8才引进来的lambda表达式。

花费时间:大概1小时就可以阅读完这篇内容。

Lambda表达式是Java SE 8才引进的新特性。对于只申明一个函数的接口,它提供了一个简单和简洁的方式让程序员编写匿名函数,同时改善了Java集合框架库(collection),使得更加容易迭代、过滤一个集合,更加容易从另一个集合中提取数据。并且在多核计算机的情况下,新特性提高了运算性能。

平台需求:本文所示代码需要在jdk 8或以上平台上面运行。

二- 背景

java的匿名内部类允许我们通过简洁的方式实现一个类或者一个类的方法,它没有名字,只使用一次。比如下面的代码:

JButton testButton = new JButton("Test Button");

testButton.addActionListener(new ActionListener(){
    @Override public void actionPerformed(ActionEvent ae){
        System.out.println("Click Detected by Anon Class");
    }
});

上面的例子表示一个button增加一个监听器。而ActionListener是一个接口,并且它只有一个方法。它的定义如下所示:

package java.awt.event;
import java.util.EventListener;

public interface ActionListener extends EventListener {

    public void actionPerformed(ActionEvent e);
}

注意:上面这种接口类型有一个特点,那就是一个接口中只定义了一个方法。这一点对于lambda表达式特别重要。

三- lambda表达式的语法

一个Lambda表达式由三部分组成:

扫描二维码关注公众号,回复: 2663527 查看本文章
参数列表 箭头 Body
(int x, int y) -> x + y

body部分可以是单个表达式,也可以是一个代码块。所以body相当于匿名内部类中的方法体。如果body中只有一个表达式,比如上面所示的x+y,那么相当于return x+y,return是可以被省略的。

看下面例子:

(int x, int y) -> x + y

() -> 42

(String s) -> { System.out.println(s); }

第一个表达式有两个形参,分别是x和y,函数的作用是返回x+y的值。

第二个表达式没有参数,函数的作用是直接返回42.

第三个表达式有一个字符串类型的参数,函数的作用是打印该字符串。

了解上面的语法知识之后,我们再来看看其他一些例子:

四- Lambda程序例子

4-1 Runnable Lambda

我们可以使用Lambda表达式写一个Runnable测试程序:

/**
 * Created by liangyh on 2016-11-27.
 */
public class RunnableTest {

    public static void main(String[] args) {
        //匿名内部类
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("hello Runnable 1!");
            }
        };

        //Lambda
        Runnable r2 = ()-> System.out.println("hello Runnable 2");

        r1.run();
        r2.run();
    }
}

上面这两个例子中,Runnable类只有一个方法(这个很重要),run方法没有参数并且没有返回值。使用匿名内部类使用了五行代码,而使用lambda只需一行代码。

4-2 Comparator Lambda

下面是java.util.Comparator的例子:

enum Gender { MALE, FEMALE }

public class Person {
    private String givenName;
    private String surName;
    private int age;
    private Gender gender;
    private String eMail;
    private String phone;
    private String address;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Created by liangyh on 2016-11-27.
 */
public class ComparatorTest {
    public static void main(String[] args) {
        List<Person> personList = Person.createShortList();

        //匿名内部类
        Collections.sort(personList, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getSurName().compareTo(o2.getSurName());
            }
        });

        for(Person p: personList){
            p.printName();
        }

        //lambda 1
        Collections.sort(personList, (Person o1, Person o2)->o1.getSurName().compareTo(o2.getSurName()));

        for(Person p: personList){
            p.printName();
        }
         //lambda 2
        Collections.sort(personList, (o1, o2)->o1.getSurName().compareTo(o2.getSurName()));
        for(Person p: personList){
            p.printName();
        }
    }
}

从上面的lambda 1和lambda 2中的参数可以看到,我们传入的o1, o2可以不用指定它的类型,编译器能够自动判断。(为什么?java.lang.Comparator接口只有一个方法)

4-3 Listener Lambda

最后,我们再看一下ActionListenter的例子:

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * Created by liangyh on 2016-11-27.
 */
public class ListenerTest {
    public static void main(String[] args) {
        JButton testButton = new JButton("button");
        testButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("click button, 匿名内部类");
            }
        });

        testButton.addActionListener(event -> System.out.println("click button, lambda"));

        JFrame frame = new JFrame("test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(testButton, BorderLayout.CENTER);
        frame.pack();
        frame.setVisible(true);
    }
}

通过上面的程序我们看到,lambda表达式作为一个参数传进方法中。

五- 使用Lambda表达式重构代码

这一小节的内容是在上一小节的基础之上编写的。在这一节中将给大家展示如何使用lambda表达式改善(重构)我们的代码,使我们的代码更加简单易懂,减少重复代码。

下面我们将通过一步步编写一个小项目的形式来展示lambda表达式的优势。

这个小项目会涉及到三类人:

  • Drivers:(司机)年龄>16
  • Draftees:(士兵)年龄在18到25之间
  • Pilots:(飞行员)年龄在23在65之间

现在我们有一份名单,名单里面有着三类人的相关信息,比如姓名、年龄、手机号、邮件号码、邮件地址等等,具体的定义看一参见Person类。我们的任务是给所有的司机打电话,给所有的士兵发邮件,给所有的飞行员送邮寄。(搞笑得很:))

好,上面便是我们这个小项目简单的需求。

5-1 Person Class

下面是这三类人的定义:

enum Gender { MALE, FEMALE }

public class Person {
    private String givenName;
    private String surName;
    private int age;
    private Gender gender;
    private String eMail;
    private String phone;
    private String address;
  •  

在Person类中有一个内部类Builder,用来帮助构造初始化一个Person对象。下面是创建一组Person对象的代码片。完整的源码在本文底部以链接的形式给出

 public static List<Person> createShortList(){
        List<Person> people = new ArrayList<>();

        people.add(
                new Person.Builder()
                        .givenName("Bob")
                        .surName("Baker")
                        .age(21)
                        .gender(Gender.MALE)
                        .email("[email protected]")
                        .phoneNumber("201-121-4678")
                        .address("44 4th St, Smallville, KS 12333")
                        .build()
        );

        people.add(
                new Person.Builder()
                        .givenName("Jane")
                        .surName("Doe")
                        .age(25)
                        .gender(Gender.FEMALE)
                        .email("[email protected]")
                        .phoneNumber("202-123-4678")
                        .address("33 3rd St, Smallville, KS 12333")
                        .build()
        );

        people.add(
                new Person.Builder()
                        .givenName("John")
                        .surName("Doe")
                        .age(25)
                        .gender(Gender.MALE)
                        .email("[email protected]")
                        .phoneNumber("202-123-4678")
                        .address("33 3rd St, Smallville, KS 12333")
                        .build()
        );

        people.add(
                new Person.Builder()
                        .givenName("James")
                        .surName("Johnson")
                        .age(45)
                        .gender(Gender.MALE)
                        .email("[email protected]")
                        .phoneNumber("333-456-1233")
                        .address("201 2nd St, New York, NY 12111")
                        .build()
        );

       //。。。。。。

        return people;
    }

5-2 第一个实现(最初的实现):

根据Person类和相关的选择策略,实现给所有的司机打电话,给所有的士兵发邮件,给所有的飞行员送邮寄的功能。

import java.util.List;

/**
 * Created by liangyh on 2016-11-27.
 */
public class RobotContact {

    public void callDrivers(List<Person> plist){
        for(Person p :plist){
            if(p.getAge() >= 16){
                robotCall(p);
            }
        }
    }

    public void emailDraftees(List<Person> plist){
        for(Person p: plist){
            if (p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE){
                robotEmail(p);
            }
        }
    }

    public void  mailPilots(List<Person> plist){
        for(Person p: plist){
            if (p.getAge() >= 23 && p.getAge() <= 65){
                robotMail(p);
            }
        }
    }

    public void robotCall(Person p){
        System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
    }

    public void robotEmail(Person p){
        System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
    }

    public void robotMail(Person p){
        System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
    }
}

上面的代码有什么缺点呢?

  • 存在重复代码

    • 比如每个方法重复打印信息
    • if选择策略在每一个方法中重写。
  • 每增加一个“人群”就需要修改上面的代码,写相应的处理函数。

  • 代码不够灵活,如果修改if语句中的选择策略,就需要修改很多地方。

    所以,上面的代码的维护性很差。

5-3 重构后的方法

如何完善上面的代码呢?我们可以从判断策略入手。如果把判断策略放在单独的方法里面,代码的灵活性会好一点。

import java.util.List;

/**
 * Created by liangyh on 2016-11-27.
 */
public class RobotContact {

    public void callDrivers(List<Person> plist){
        for(Person p :plist){
            if(isDriver(p)){
                robotCall(p);
            }
        }
    }

    public void emailDraftees(List<Person> plist){
        for(Person p: plist){
            if (isDraftee(p)){
                robotEmail(p);
            }
        }
    }

    public void  mailPilots(List<Person> plist){
        for(Person p: plist){
            if (isPilot(p)){
                robotMail(p);
            }
        }
    }

    public boolean isDriver(Person p){
        return p.getAge() >= 16;
    }
    public boolean isDraftee(Person p){
        return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
    }
    public boolean isPilot(Person p){
        return p.getAge() >= 23 && p.getAge() <= 65;
    }

    public void robotCall(Person p){
        System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
    }

    public void robotEmail(Person p){
        System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
    }

    public void robotMail(Person p){
        System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
    }
}

上面的代码中,我们把判断策略提取出来分别放在单独的函数里面。这样判断条件就可以重用,当判断条件改变的时候修改起来也很方便。

但是,这样之后增加了许多(三个)单独的函数和许多代码量,有没有一种更好的方式把这三个方法合成一个呢?

5-4 匿名内部类

在lambda表达式之前,我们可以考虑使用匿名内部类。一个只有一个方法(比如test),方法的返回值为boolean的接口(比如MyTest.java)可以帮助我们解决问题。在调用test函数的时候,我们可以把判断条件传进这个test函数中。接口代码如下:

/**
 * Created by liangyh on 2016-11-27.
 */
public interface MyTest<T> {
    boolean test(T t);
}

所以RobotContact类变成下面的样子:

package test;

import test.Person;

import java.util.List;

/**
 * Created by liangyh on 2016-11-27.
 */
public class RobotContactAnon {
    //注意:这里由phoneDrivers变成了phoneContacts
    //因为call的人群范围由所传入的参数来决定。
    //所以这个函数的使用范围变广了。
    public void phoneContacts(List<Person> plist, MyTest<Person> aTest){
        for(Person p :plist){
            if(aTest.test(p)){
                robotCall(p);
            }
        }
    }

    public void emailContacts(List<Person> plist, MyTest<Person> aTest){
        for(Person p: plist){
            if(aTest.test(p)){
                robotEmail(p);
            }
        }
    }

    public void  mailContacts(List<Person> plist, MyTest<Person> aTest){
        for(Person p: plist){
            if(aTest.test(p)){
                robotMail(p);
            }
        }
    }

    public void robotCall(Person p){
        System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
    }

    public void robotEmail(Person p){
        System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
    }

    public void robotMail(Person p){
        System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
    }
}

这样子的话我们只需要三个函数就可以了。感觉特别棒,但是在调用函数的时候就有点糟糕了。下面是测试函数:

package test;

import test.Gender;
import test.Person;

import java.util.List;

/**
 * Created by liangyh on 2016-11-27.
 */
public class RobotCallTest03 {

    public static void main(String[] args) {
        List<Person> personList = Person.createShortList();
        RobotContactAnon robot = new RobotContactAnon();

        System.out.println("calling all drivers");
        robot.phoneContacts(personList,
                new MyTest<Person>() {
                    @Override
                    public boolean test(Person person) {
                        return person.getAge() >= 16;
                    }
                });

        System.out.println("emailing all draftees");
        robot.emailContacts(personList,
                new MyTest<Person>() {
                    @Override
                    public boolean test(Person p) {
                        return p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
                    }
                });

        System.out.println("mailing all pilots");
        robot.mailContacts(personList, new MyTest<Person>() {
            @Override
            public boolean test(Person p) {
                return p.getAge() >= 23 && p.getAge() <= 65;
            }
        });
    }
}

上面的代码我们使用到了匿名内部类。通过匿名内部类来重构,使得我们的逻辑代码更加简洁清晰。无论如何,现在我们的代码相比重构之前得到了很大的改善。下面我们使用lambda表达式来代替匿名内部类:

5-5 Lambda Expressions

package test;

import java.util.List;

/**
 * Created by liangyh on 2016-11-27.
 */
public class RobotCallLambdaTest03 {

    public static void main(String[] args) {
        List<Person> personList = Person.createShortList();
        RobotContactAnon robot = new RobotContactAnon();

        System.out.println("calling all drivers");
        robot.phoneContacts(personList, 
                person->person.getAge() >= 16);

        System.out.println("emailing all draftees");
        robot.emailContacts(personList,
                p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE);

        System.out.println("mailing all pilots");
        robot.mailContacts(personList, p -> p.getAge() >= 23 && p.getAge() <= 65);
    }
}

是不是比使用匿名内部类简洁好看了很多?看上面代码的时候注意不要看晕了哈,lambda表达式只有参数和body,没有方法名的,因为方法名是没有必要的。编译器自动识别参数的类型。

java.util.function

上面的代码我们使用到了一个MyTest接口,它的方法作为条件判断使用。其实在jdk 8已经为我们提供好了这样的方法,在java.util.function包中,接口名为Predicate,定义为:

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);

使用上面这个Predicate接口之后,我们的逻辑代码变成下面的了:(只需把MyTest改为Predicate)

package test;

import java.util.List;
import java.util.function.Predicate;

/**
 * Created by liangyh on 2016-11-27.
 */
public class RobotContactLambda {
    //注意:这里由phoneDrivers变成了phoneContacts
    //因为call的人群范围由所传入的参数来决定。
    //所以这个函数的使用范围变广了。
    public void phoneContacts(List<Person> plist, Predicate<Person> aTest){
        for(Person p :plist){
            if(aTest.test(p)){
                robotCall(p);
            }
        }
    }

    public void emailContacts(List<Person> plist, Predicate<Person> aTest){
        for(Person p: plist){
            if(aTest.test(p)){
                robotEmail(p);
            }
        }
    }

    public void  mailContacts(List<Person> plist, Predicate<Person> aTest){
        for(Person p: plist){
            if(aTest.test(p)){
                robotMail(p);
            }
        }
    }

    public void robotCall(Person p){
        System.out.println("Calling " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getPhone());
    }

    public void robotEmail(Person p){
        System.out.println("EMailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getEmail());
    }

    public void robotMail(Person p){
        System.out.println("Mailing " + p.getGivenName() + " " + p.getSurName() + " age " + p.getAge() + " at " + p.getAddress());
    }
}

相应的测试代码为:(其实和上一个测试代码(main函数)是一样的,只是这里把lambda表达式提取并存到一个变量里面去了。)

package test;

import java.time.Period;
import java.util.List;
import java.util.function.Predicate;

/**
 * Created by liangyh on 2016-11-27.
 */
public class RobotCallLambdaTest04 {

    public static void main(String[] args) {
        List<Person> personList = Person.createShortList();
        RobotContactLambda robot = new RobotContactLambda();

        Predicate<Person> allDrivers = person->person.getAge() >= 16;
        Predicate<Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
        Predicate<Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65;

        System.out.println("calling all drivers");
        robot.phoneContacts(personList, allDrivers);

        System.out.println("emailing all draftees");
        robot.emailContacts(personList, allDraftees);

        System.out.println("mailing all pilots");
        robot.mailContacts(personList, allPilots);
    }
}

是不是简洁了太多?

六- 新的java.util.function 包

jdk8不单单提供了Predicate接口,它还为我们提供了很多标准接口:

  • Predicate:对象的一个属性作为参数

  • Consumer:一个要执行的操作和对象作为参数

  • Function:把一个T类型的对象转换成U类型的对象

  • Supplier:为类型T实例化一个对象(比如一个工厂)

  • UnaryOperator:一元运算符。T -> T

  • BinaryOperator:二元运算符。(T, T)-> T

6-1 东方的名字书写方式和方法引用

对于前面的例子,如果能够灵活的打印Person类中的相关信息,将是一件特别棒的事情。比如我希望Person类中的名字既可以以西方的方式也可以以东方的方式排版打印。在西方,名前姓后,在东方恰好相反。

6-2 最初的实现:

public void printWesternName(){

        System.out.println("\nName: " + this.getGivenName() + " " + this.getSurName() + "\n" +
                "Age: " + this.getAge() + "  " + "Gender: " + this.getGender() + "\n" +
                "EMail: " + this.getEmail() + "\n" +
                "Phone: " + this.getPhone() + "\n" +
                "Address: " + this.getAddress());
    }

    public void printEasternName(){

        System.out.println("\nName: " + this.getSurName() + " " + this.getGivenName() + "\n" +
                "Age: " + this.getAge() + "  " + "Gender: " + this.getGender() + "\n" +
                "EMail: " + this.getEmail() + "\n" +
                "Phone: " + this.getPhone() + "\n" +
                "Address: " + this.getAddress());
    }

每一个方法打印一种。但是,如果我们希望第三种打印方式呢?那么就需要写第三个方法。所以这样的实现方式是不完美不优雅的。

6-3 Function接口

Function接口可以很好地解决这个问题。它只有一个方法(这个很关键),定义为:

public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

注意apply方法,参数是T类型的,返回值是R类型的。

public String printCustom(Function<Person, String> f){
        return f.apply(this);
}

我们在person类中定义了上述方法,用到了apply方法。下面,我们需要实现Function接口的apply方法。使用匿名内部类或者lambda表达式:

package test2;

import java.util.List;
import java.util.function.Function;

/**
 * Created by liangyh on 2016-11-27.
 */
public class NameTestNew {
    public static void main(String[] args) {
        List<Person> list = Person.createShortList();

        System.out.println("custom list");
        for(Person person: list){
            System.out.println(
                    person.printCustom(p->"Name: "+p.getGivenName()+" Email: "+p.getEmail()));
        }

        Function<Person, String> westernStyle = p -> "\nName: " + p.getGivenName() + " " + p.getSurName() + "\n" +
                "Age: " + p.getAge() + "  " + "Gender: " + p.getGender() + "\n" +
                "EMail: " + p.getEmail() + "\n" +
                "Phone: " + p.getPhone() + "\n" +
                "Address: " + p.getAddress();

        Function<Person, String> easternStyle = p -> "\nName: " + p.getSurName() + " " + p.getGivenName() + "\n" +
                "Age: " + p.getAge() + "  " + "Gender: " + p.getGender() + "\n" +
                "EMail: " + p.getEmail() + "\n" +
                "Phone: " + p.getPhone() + "\n" +
                "Address: " + p.getAddress();

        System.out.println("western ");
        for(Person person: list){
            System.out.println(person.printCustom(westernStyle));
        }

        System.out.println("eastern ");
        for (Person person: list){
            System.out.println(person.printCustom(westernStyle));
        }
    }
}

上面代码中的变量westernStyle和easternStyle,其实就是相应接口的实现类的对象的句柄或者说引用

上面程序的执行结果为:

custom list
Name: Bob Email: [email protected]
Name: Jane Email: [email protected]
Name: John Email: [email protected]
Name: James Email: [email protected]
Name: Joe Email: [email protected]
Name: Phil Email: phil.smith@examp;e.com
Name: Betty Email: [email protected]
western 

Name: Bob Baker
Age: 21  Gender: MALE
EMail: [email protected]
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333

Name: Jane Doe
Age: 25  Gender: FEMALE
EMail: [email protected]
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333

Name: John Doe
Age: 25  Gender: MALE
EMail: [email protected]
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333

Name: James Johnson
Age: 45  Gender: MALE
EMail: [email protected]
Phone: 333-456-1233
Address: 201 2nd St, New York, NY 12111

Name: Joe Bailey
Age: 67  Gender: MALE
EMail: [email protected]
Phone: 112-111-1111
Address: 111 1st St, Town, CA 11111

Name: Phil Smith
Age: 55  Gender: MALE
EMail: phil.smith@examp;e.com
Phone: 222-33-1234
Address: 22 2nd St, New Park, CO 222333

Name: Betty Jones
Age: 85  Gender: FEMALE
EMail: [email protected]
Phone: 211-33-1234
Address: 22 4th St, New Park, CO 222333
eastern 

Name: Bob Baker
Age: 21  Gender: MALE
EMail: [email protected]
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333

Name: Jane Doe
Age: 25  Gender: FEMALE
EMail: [email protected]
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333

Name: John Doe
Age: 25  Gender: MALE
EMail: [email protected]
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333

Name: James Johnson
Age: 45  Gender: MALE
EMail: [email protected]
Phone: 333-456-1233
Address: 201 2nd St, New York, NY 12111

Name: Joe Bailey
Age: 67  Gender: MALE
EMail: [email protected]
Phone: 112-111-1111
Address: 111 1st St, Town, CA 11111

Name: Phil Smith
Age: 55  Gender: MALE
EMail: phil.smith@examp;e.com
Phone: 222-33-1234
Address: 22 2nd St, New Park, CO 222333

Name: Betty Jones
Age: 85  Gender: FEMALE
EMail: [email protected]
Phone: 211-33-1234
Address: 22 4th St, New Park, CO 222333

七- Lambda表达式和java集合

上一节介绍了Function接口和lambda表达式的基本语法。这一节将介绍lambda表达式如何改善Collection框架。

现在我们的小项目中的drivers、pilots和draftees的搜索判断条件封装在SearchCriteria类中。代码如下所示:

package test2;

import test.*;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;

/**
 * Created by liangyh on 2016-11-27.
 */
public class SearchCriteria {
    private final Map<String, Predicate<Person>> searchMap = new HashMap<>();

    private SearchCriteria(){
        super();
        initSearchMap();
    }

    private void initSearchMap(){
        Predicate<test2.Person> allDrivers = person->person.getAge() >= 16;
        Predicate<test2.Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
        Predicate<test2.Person> allPilots = p -> p.getAge() >= 23 && p.getAge() <= 65;

        searchMap.put("allDrivers", allDrivers);
        searchMap.put("allDraftees", allDraftees);
        searchMap.put("allPilots", allPilots);
    }

    public Predicate<Person> getCriteria(String predicateName){
        Predicate<Person> target;
        target = searchMap.get(predicateName);
        if(target == null){
            System.out.println("search criteria not found");
            System.exit(1);
        }
        return target;
    }

    public static SearchCriteria getInstance(){
        return new SearchCriteria();
    }
}

第一个测试:(先看测试代码,后面有详细解释)

package test2;

import java.util.List;

/**
 * Created by liangyh on 2016-11-27.
 */
public class Test01ForEach {
    public static void main(String[] args) {
        List<Person> personList = Person.createShortList();

        System.out.println("\nwestern phone list");
        personList.forEach(p->p.printWesternName());

        System.out.println("\neastern phone list");
        personList.forEach(Person::printEasternName);

        System.out.println("\ncustom phone list");
        personList.forEach(p -> {
            System.out.println(p.printCustom(r-> "name: "+r.getGivenName()+" EMail:"+r.getEmail()));
        });
    }
}

上面代码中的forEach方法是从jdk8才开始出现的。这个方法的定义为:

/**
     * Performs the given action for each element of the {@code Iterable}
     * until all elements have been processed or the action throws an
     * exception.  Unless otherwise specified by the implementing class,
     * actions are performed in the order of iteration (if an iteration order
     * is specified).  Exceptions thrown by the action are relayed to the
     * caller.
     *
     * @implSpec
     * <p>The default implementation behaves as if:
     * <pre>{@code
     *     for (T t : this)
     *         action.accept(t);
     * }</pre>
     *
     * @param action The action to be performed for each element
     * @throws NullPointerException if the specified action is null
     * @since 1.8
     */
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

forEach方法调用了T.accept()方法,而accept方法定义在java.util.function包中,它表示把一个操作(或者行为)作为函数参数传进去。好,知道它的来源之后,明白了它是一个未实现的方法,所以我们可以采用匿名内部类或者lambda表达式实现它。上面的测试代码main函数就是。下面便是accept方法的定义。

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

我们可以通过上面的方式遍历所有的集合。它的基本结构和for循环类似。但是,我们这里的遍历方式提供了很多种好处:

7-1、Chaining 和 Filters

并且在jdk1.8中,在循环迭代一个集合的时候我们可以把不同的方法链接在一起。

下面的例子是先过滤再循环一个List。


import java.util.List;

/**
 * Created by liangyh on 2016-11-27.
 */
public class Test02Filter {
    public static void main(String[] args) {
        List<Person> personList = Person.createShortList();
        SearchCriteria search = SearchCriteria.getInstance();
        System.out.println("western pilot phone list");
        //两个冒号表示方法引用,method reference,
        personList.stream().filter(search.getCriteria("allPilots"))
                .forEach(Person::printWesternName);
        //或者
        /*personList.stream().filter(search.getCriteria("allPilots"))
                .forEach(p->p.printWesternName());*/

        System.out.println("\n eastern draftee phone list");
        personList.stream().filter(search.getCriteria("addDraftees"))
                .forEach(Person::printEasternName);
    }
}

第一个循环过滤条件为所有的飞行员,第二个为所有的士兵。

一部分运行结果为:

eastern draftee phone list

Name: Baker Bob
Age: 21  Gender: MALE
EMail: [email protected]
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333

Name: Doe John
Age: 25  Gender: MALE
EMail: [email protected]
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333

7-2 变懒一点

上面代码所展示的这些特性是很有用的,但是为什么在for循环已经很完美的情况下还要增加它们呢?通过把迭代移到java集合库中,使得程序员能够更好地优化他的代码。为了进一步解释,得介绍两个专有名词:

  • 懒(Laziness):在程序中,laziness指的是我们在用到某对象的时候才去初始化和执行它。在前面的例子中,最后那个循环是“懒”的,因为循环是在过滤之后的结果集合中发生的。过滤之后只剩下两个士兵了,所以遍历也就只发生在这两个士兵上,而不是list中所有的人。
  • 急(Eagerness):如果for循环未经过滤遍历了所有的list中的元素,那么就是这里说的eagerness。

通过把“循环”作为java 集合框架库的一部分(之前是通过迭代iterator的方式),使得程序员能够通过的方式优化代码。

7-3 stream 方法

在上面的例子中,细心的同学已经注意到stream方法在过滤循环之前调用。这个方法把java中的集合(Collection)作为输入,把java.util.stream.Stream接口作为输出。一个Stream代表着可以链接不同方法的一系列元素。一个链接操作(chain)只能在特定的Stream中发生一次。所调用的方法决定了这个stream是串行(默认)的还是并行的。本小节末尾会给出一个并行stream的例子。

7-4 改变和结果

前面已经提到,一个stream只能使用一次,使用之后就会被抛弃。所以,集合中的元素进行stream操作的时候不能被修改。但是,如果你想在链接操作(chain)之后能够返回刚才集合中相关元素呢?你可以把这些元素存到一个新的集合当中。具体怎么做?

package test2;

import java.util.List;
import java.util.stream.Collectors;

/**
 * Created by liangyh on 2016-11-28.
 */
public class Test03toList {

    public static void main(String[] args) {
        List<Person> personList = Person.createShortList();
        SearchCriteria searchCriteria = SearchCriteria.getInstance();

        List<Person> pilotList = personList
                .stream()
                .filter(searchCriteria.getCriteria("allPilots"))
                .collect(Collectors.toList());

        System.out.println("\n western pilot phone list");
        pilotList.forEach(Person::printWesternName);
        //或者
        //pilotList.forEach(person -> person.printWesternName());
    }
}

上面的collect方法里面的参数是Collectors工具类。Collectors工具类能够更具过滤的结果返回一个List或者Set。上面的代码展示了一个stream的结果被存到一个新的数组中然后循环打印。

7-5 使用map来计算

这里的map是指java.util.stream.Stream类中的map相关方法。

map方法经常结合过滤使用。

下面程序是计算所有飞行员的总年龄和平均年龄。

package test2;

import java.util.List;
import java.util.Optional;
import java.util.OptionalDouble;

/**
 * Created by liangyh on 2016-11-28.
 */
public class Test04Map {
    public static void main(String[] args) {
        List<Person> personList = Person.createShortList();
        SearchCriteria search = SearchCriteria.getInstance();

        System.out.println("old style");
        int sum = 0;
        int count = 0;
        for(Person p:personList){
            if(p.getAge() >= 23 && p.getAge() <= 65){
                sum += p.getAge();
                count++;
            }
        }

        long average = sum/count;
        System.out.println("total ages: "+sum);
        System.out.println("average age: "+average);

        System.out.println("\n new style");
        long totalAge = personList
                .stream()
                .filter(search.getCriteria("allPilots"))
                .mapToInt(p->p.getAge())
                .sum();

        OptionalDouble averageAge = personList
                .parallelStream()
                .filter(search.getCriteria("allPilots"))
                .mapToDouble(p -> p.getAge())
                .average();

        System.out.println("total ages: "+totalAge);
        System.out.println("average age: "+averageAge.getAsDouble());
    }
}

执行结果:

old style
total ages: 150
average age: 37

 new style
total ages: 150
average age: 37.5

最后那个循环计算平均年龄的方法中没有计算出sum就得出了平均数。

还注意到parallelStream方法用来获取并行的stream。所以那些值可以并行计算。

返回的结果也有一点不一样。

八- 总结

Lambda表达式让我们程序员的对代码的关注点发生了改变。和匿名函数相比,Lambda表达式减少了很多东西,比如接口名字和方法名,使得我们更加关注真实能用到的组件,比如参数和body里面的逻辑部分。这样做的结果便是提高代码开发效率和阅读体验。

从这边博客中你可以了解到:

  • Java匿名内部类
  • jdk se 8中lambda表达式代替匿名内部类。
  • lambda表达式的语法
  • 使用java.util.function.Predicate接口作为查询判断条件
  • java.util.function.Function接口
  • jdk8增加支持lambda表达式的新特性。

源码下载:http://download.csdn.net/detail/liangyihuai/9695471

lambda的定义 https://en.wikipedia.org/wiki/Lambda_expression

猜你喜欢

转载自blog.csdn.net/qq_34246546/article/details/81535661