【lambda】java8 lambda

来源:互联网 发布:如何优化英语教学 编辑:程序博客网 时间:2024/06/10 06:11

今天面试的时候,贵公司竟然已经都在全部使用java 8 了,学习一下,省得成为门槛!

详情参看 oralce 官网的 : Java SE 8:Lambda Quick Start

1. 介绍

Lambda 表达式是 Java 8 的新特性,通过使用一个表达式来代表一个方法接口。同时Lambda 表达式 使 从 集合中迭代数据 和 取数据 更简单。


2. 背景

匿名内部类(Anonymous Inner Class)

在 Java 中,匿名内部类 提供了 一种类的实现方式,这种类的实现方法很方便,随时需要随时都可以进行不同的实现,而不需要单独再去写一个实现类。比如 Swing 或者 JavaFX 中的事件监听机制代码:

        JButton button = new JButton("Test Button");        button.addActionListener(new ActionListener() {            @Override            public void actionPerformed(ActionEvent e) {                System.out.println("Click detected by anon class");            }        });
这种实现方法方便阅读,但是 代码不够优雅:因为 为了定义一个方法需要太多的代码;


函数式接口(Functional Interfaces)

上面提到的 ActionListener 接口 在 Jdk 8 中是如下定义的:

package java.awt.event;import java.util.EventListener;/** * @author Carl Quinn * @since 1.1 */public interface ActionListener extends EventListener {    /**     * Invoked when an action occurs.     */    public void actionPerformed(ActionEvent e);}

这个接口的特点 : 接口仅仅包含一个 方法,遵循这种模式的接口 在 Java 8 中 被称为 函数式接口(functional interface)

Note:这种类型的接口 在之前被 称为 单一抽象方法类型 SAM(Single Abstract Method);

在 Java 中 通过 匿名内部类 来使用函数式接口 是一个 很常见的模式,除了 EventListener 类,像 Runnable ,Comparator 接口 也是以相似的方式被使用;因此 通过 lambda 表达式的使用 来促使 函数式接口的改变。


Lambda 表达式语法

Lambda 表达式 通过 将 五行代码 转变为 一条单独的声明 来 解决 匿名内部类 臃肿的内码;

Lambda 表达式 由三部分组成:

      ★  参数列表  : (int x,int y)

      ★  剪头标记   :  ->

      ★  body 体     :  x + y

body 可以是 单个表达式,也可以是一个语句块;在表达式中,body 已经经过简单的计算并且返回;在语句块中,body 像 一个方法体一样被计算 并且 返回语句 返回 对 匿名方法 的 调用者的控制;在顶层使用 break 和 continue 关键字 是非法的 ,但是 在 循环中是允许的 ;如果 body  返回一个结果,每个控制路径必须 返回 或者 抛出一个异常;

一些简单的栗子:

(int x, int y) -> x + y() -> 42(String s) -> { System.out.println(s); }

第一个栗子: 有两个Integer 类型的参数,x 和 y;使用表达式的形式 返回  x+y;

第二个栗子:无参数;使用表达式的形式返回 一个 Integer 值 42;

第三个栗子:有一个字符串参数,s;使用语句块的形式将字符串打印到控制台,并且无返回值;


3.Lambda 栗子

Runnable Lambda:使用语句块的形式,将五行代码转变为一条语句;

package com.ycit.lambda;/** * Created by xlch on 2017/3/2. */public class RunnableTest {    public static void main(String [] args) {        Runnable runnable = new Runnable() {            @Override            public void run() {                System.out.println("normal Anonymous Inner class");            }        };        Runnable runnable1 = () -> System.out.println("Lambda Expression");        runnable.run(); //输出 normal Anonymous Inner class        runnable1.run();//输出 Lambda Expression    }}

Comparator 栗子

根据 Person 的 name 排序:

package com.ycit.bean;import java.util.ArrayList;import java.util.List;/** * Created by xlch on 2017/3/3. */public class Person {    private String name;    private int age;    public Person() {    }    public Person(String name, int age) {        this.name = name;        this.age = age;    }    //getter setter ignore    public static List<Person> createList() {        Person p1 = new Person("bob",21);        Person p2 = new Person("john",25);        Person p3 = new Person("jack",23);        Person p4 = new Person("amy",27);//        List<Person> persons = ImmutableList.of(p1, p2, p3, p4);不可变的list        List<Person> persons = new ArrayList<>();        persons.add(p1);        persons.add(p2);        persons.add(p3);        persons.add(p4);        return persons;    }}

ComparatorTest

package com.ycit.lambda;import com.ycit.bean.Person;import java.util.Collections;import java.util.Comparator;import java.util.List;/** * Created by xlch on 2017/3/3. */public class ComparatorTest {    public static void main(String[] args) {        List<Person> persons = Person.createList();        Collections.sort(persons, new Comparator<Person>() {            @Override            public int compare(Person o1, Person o2) {                return o1.getName().compareTo(o2.getName());            }        });        System.out.println("===normal sorted asc name===");        for (Person person:persons) {            System.out.println(person.getName());        }        System.out.println("=====lambda sorted desc name=======");        Collections.sort(persons,(Person p1, Person p2) -> p2.getName().compareTo(p1.getName()));        for (Person person:persons) {            System.out.println(person.getName());        }        System.out.println("=====lambda sorted asc name=======");        Collections.sort(persons, (p1, p2) -> p1.getName().compareTo(p2.getName()));        for (Person person:persons) {            System.out.println(person.getName());        }    }}

第二种 Lambda 表达式 省略了 参数的 类型声明,Lambda 表达式 支持 目标类型(target typing),目标类型是 根据 使用的上下文来推断对象类型;因为我们是把结果分配给了 用泛型定义的 Comparator. 编译器可以推断出这两个参数类型为 Person;


ActionListener 栗子:

package com.ycit.lambda;import javax.swing.*;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;/** * Created by xlch on 2017/3/1. */public class InnerClass {    public static void main(String[] args) {        JButton button = new JButton("Test Button");        button.addActionListener(new ActionListener() {            @Override            public void actionPerformed(ActionEvent e) {                System.out.println("Click detected by anon class");            }        });        button.addActionListener(e -> System.out.println("Click detected by lambda expression"));        // Swing stuff        JFrame frame = new JFrame("Listener Test");        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);        frame.add(button, BorderLayout.CENTER);        frame.pack();        frame.setVisible(true);    }}

Lambda 表达式 是作为参数被传递, Target Typing 被 用于以下场景:

   ★ 变量声明

   ★ assignment

   ★返回语句

   ★数组初始化

   ★ 方法或者构造函数参数

   ★ Lambda 表达式的 body

   ★ 条件表达式

   ★ 强制转换表达式

  

4.使用 Lambda 表达式 改进代码

Lambda 表达式 应该为 DRY(Don`t Repeat Yourself)原则提供更好的支持,并且使代码 更简单,更可读;

一个常见的查询案例

从一个人员列表中找出:

           适合做司机的人(年龄大于16岁)

           可以应征入伍的人(年龄大于18岁小于25岁的男性)

           有资格做飞行员的人(年龄在23-65 之间的人)

然后通过不同的方式通知这些人(打电话,发邮件,传真等等);

初次尝试

你可能会写出如下类似的代码方法供外部使用(或者多个业务集中在一个方法中):

package com.ycit.lambda;/** * Created by xlch on 2017/3/4. */import com.ycit.bean.Gender;import com.ycit.bean.Person;import java.util.List;/** * * Drivers: Persons over the age of 16 * Draftees: Male persons between the ages of 18 and 25 * Pilots (specifically commercial pilots): Persons between the ages of 23 and 65 */public class QueryCase {    public void callDriver(List<Person> persons) {        for (Person person:persons) {            if (person.getAge() >= 16){                call(person);            }        }    }    public void emailDraftees(List<Person> persons) {        for (Person p:persons) {            if (p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE){                email(p);            }        }    }    public void mailPilots(List<Person> persons) {        for (Person p:persons) {            if (p.getAge() >= 23 && p.getAge() <= 65){                mail(p);            }        }    }    /**-----------通讯方式----------------**/    public void call(Person person) {        System.out.println("call :" +person.getName());    }    public void mail(Person person) {        System.out.println("mail:" + person.getName());    }    public void email(Person person) {        System.out.println("email:" + person.getName());    }}

从代码的方法名定义中(callDriver,emailDraftees,mailPilots)我们可以看出,这个方法描述了正在发生的行为的类别 ;查询条件被明确地和 动作调用绑定到一起;导致了代码不够灵活


以上设计的代码有以下不好的地方:

    ★ 违背了  DRY 原则 : 每个方法重复的循环 ;每个方法中 选择方法都需要重写;

    ★ 每个使用案例都需要实现大量的方法;

    ★ 代码不灵活。如果更改了搜索条件,则需要更新一些代码。因此,代码是不可维护的。

方法重构

从查询条件 重构开始 :如果 判断条件 被分离到单独的方法中,那么这就算是一个改进:

package com.ycit.lambda;/** * Created by xlch on 2017/3/4. */import com.ycit.bean.Gender;import com.ycit.bean.Person;import java.util.List;/** * * Drivers: Persons over the age of 16 * Draftees: Male persons between the ages of 18 and 25 * Pilots (specifically commercial pilots): Persons between the ages of 23 and 65 */public class QueryCase {    public void callDriver(List<Person> persons) {        for (Person person:persons) {            if (isDriver(person)){                call(person);            }        }    }    public void emailDraftees(List<Person> persons) {        for (Person p:persons) {            if (isDraftee(p)){                email(p);            }        }    }    public void mailPilots(List<Person> persons) {        for (Person p:persons) {            if (isPilot(p)){                mail(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 call(Person person) {        System.out.println("call :" +person.getName());    }    public void mail(Person person) {        System.out.println("mail:" + person.getName());    }    public void email(Person person) {        System.out.println("email:" + person.getName());    }}

查询条件被封装在单独的方法中,相对之前的代码有所提升。查询条件可以重复利用,并且可以在整个类中反复变化。
但是 对于每个使用案例,仍然需要大量的重复代码 和 一个单独的方法。那么有没有更好的方法传递查询条件给方法呢?

匿名类

在 Lambda 表达式之前,使用匿名内部类是一种选择。

例如,一个接口中包含一个 返回 boolean 的 test 方法(函数式接口),查询条件可以在方法被调用的时候传递,像这样:

package com.ycit.lambda;/** * Created by xlch on 2017/3/5. */public interface MyTest<T> {    boolean test(T t);}

使用该接口

package com.ycit.lambda;import com.ycit.bean.Person;import java.util.List;/** * Created by xlch on 2017/3/5. */public class QueryCaseWithInnerClass {    public void callContacts(List<Person> persons, MyTest<Person> myTest) {        for (Person person:persons) {            if (myTest.test(person)){                call(person);            }        }    }    public void emailContacts(List<Person> persons, MyTest<Person> myTest) {        for (Person p:persons) {            if (myTest.test(p)){                email(p);            }        }    }    public void mailContacts(List<Person> persons, MyTest<Person> myTest) {        for (Person p:persons) {            if (myTest.test(p)){                mail(p);            }        }    }    /**-----------通讯方式----------------**/    public void call(Person person) {        System.out.println("call :" +person.getName());    }    public void mail(Person person) {        System.out.println("mail:" + person.getName());    }    public void email(Person person) {        System.out.println("email:" + person.getName());    }}


看上去很好,但是当以上方法被调用时,代码就有点丑陋了:

package com.ycit.lambda;import com.ycit.bean.Gender;import com.ycit.bean.Person;import java.util.List;/** * Created by xlch on 2017/3/5. */public class UseInnerClassTet {    public static void main(String [] args) {        List<Person> persons = Person.createList();        QueryCaseWithInnerClass innerClass = new QueryCaseWithInnerClass();        innerClass.callContacts(persons, new MyTest<Person>() {            @Override            public boolean test(Person person) {                return person.getAge() >=16;            }        });        innerClass.emailContacts(persons, new MyTest<Person>() {            @Override            public boolean test(Person p) {                return  p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;            }        });        innerClass.mailContacts(persons, new MyTest<Person>() {            @Override            public boolean test(Person p) {                return p.getAge() >= 23 && p.getAge() <= 65;            }        });    }}



这是一个练习 "垂直问题"(vertical problem)的一个很好的栗子;代码有点难阅读,而且我们必须要为每一个使用案例写自定义的查询条件


使用 Lambda 表达式刚刚好

在之前的栗子中, 通过 MyTest 函数式接口将 匿名类传递给方法。然而,在 Java SE8 中不需要写那样的接口,它提供了含有大量标准 函数式接口的  java.util.function 包。

在这个案例中,Predicate 接口符合我们的需求。

其中部分源码:

package java.util.function;import java.util.Objects;@FunctionalInterfacepublic interface Predicate<T> {    boolean test(T t);    //....}

修改后

package com.ycit.lambda;import com.ycit.bean.Person;import java.util.List;import java.util.function.Predicate;/** * Created by xlch on 2017/3/5. */public class QueryCaseWithFunction {    public void callContacts(List<Person> persons, Predicate<Person> predicate) {        for (Person person:persons) {            if (predicate.test(person)){                call(person);            }        }    }    public void emailContacts(List<Person> persons, Predicate<Person> predicate) {        for (Person p:persons) {            if (predicate.test(p)){                email(p);            }        }    }    public void mailContacts(List<Person> persons, Predicate<Person> predicate) {        for (Person p:persons) {            if (predicate.test(p)){                mail(p);            }        }    }    /**-----------通讯方式----------------**/    public void call(Person person) {        System.out.println("call :" +person.getName());    }    public void mail(Person person) {        System.out.println("mail:" + person.getName());    }    public void email(Person person) {        System.out.println("email:" + person.getName());    }}

垂直问题的解决

Lambda 表达式解决了垂直问题, 让 Lambda 表达式得到了简单的重用,

package com.ycit.lambda;import com.ycit.bean.Gender;import com.ycit.bean.Person;import java.util.List;import java.util.function.Predicate;/** * Created by xlch on 2017/3/5. */public class UseLambdaTest {    public static void main(String[]args) {        List<Person> persons = Person.createList();        QueryCaseWithFunction robo = new QueryCaseWithFunction();        Predicate<Person> drivers = person -> person.getAge() >= 16;        Predicate<Person> draftees = person -> person.getAge() >= 18 && person.getAge() <= 25 && person.getGender() == Gender.MALE;        Predicate<Person> pilots = person -> person.getAge() >= 23 && person.getAge() <= 65;        robo.callContacts(persons, drivers);        robo.callContacts(persons, draftees);        robo.callContacts(persons, pilots);        robo.emailContacts(persons, draftees);        robo.mailContacts(persons, pilots);    }}

为每一个分组都创建了一个 Predicate,你可以传递任意一个分组到 联系方式的方法中。


5.java.util.function 包

Java SE8 中提供了大量函数式接口:

       ★  Predicate :对象作为参数传递的一个属性;代表一个参数的 predicate (相当于返回 boolean 值的 Function 接口);

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


       ★  Consumer:对象作为参数传递的将要被执行的一个动作;代表这样一种操作:接收一个单独的输入参数,不返回任何结果

public interface Consumer<T> {    void accept(T t);}


       ★  Function: 把 T 转为 U ;代表这样一种函数:接收一个参数,返回另一个参数

public interface Function<T, R> {    R apply(T t);}


       ★  Supplier: 提供 T 的一个实例(例如一个工厂):代表 结果的 提供者

public interface Supplier<T> {    T get();}


       ★  UnaryOperator: 从 T 到 T 的一个一元操作

public interface UnaryOperator<T> extends Function<T, T> {    static <T> UnaryOperator<T> identity() {        return t -> t;    }}


       ★  BinaryOperator:从(T,T)到 T 的 一个二元操作;

@FunctionalInterfacepublic interface BinaryOperator<T> extends BiFunction<T,T,T> {    public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {        Objects.requireNonNull(comparator);        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;    }    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {        Objects.requireNonNull(comparator);        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;    }}



另外,这些接口中的大多数 都有 原型版本,这将是一个很好的开始


东方风格的名字 和 相关方法

由于东方和西方的文化差异,在名字的写法上有差别,

东方人是把 姓写在前面,名写在后面;西方人是把名写在前面,姓写在后面;

没有使用 Lambda 表达式 的老方法

Person.java

    public void printWesternName() {        System.out.println("western name:" + this.getGivenName() + " " + this.getSurName());    }    public void printEasternName() {        System.out.println("eastern name:" + this.getSurName() + " " + this.getGivenName());    }


Function 函数式接口

Function 接口可以解决这个问题,仅仅有一个 抽象方法:将 T 转为 R

package java.util.function;import java.util.Objects;@FunctionalInterfacepublic interface Function<T, R> {    R apply(T t);    //....}

使用 Lambda 表达式解决:

package com.ycit.lambda;import com.ycit.bean.Person;import java.util.List;import java.util.function.Function;/** * Created by xlch on 2017/3/5. */public class FunctionTest {    public static void main(String [] args) {        // ① 传统的方法        System.out.println("==========传统解决方法");        List<Person> persons = Person.createList();        for (Person person:persons) {            person.printEasternName();            person.printWesternName();        }        //② 自定义        System.out.println("=========自定义");        for (Person person:persons) {            System.out.println(                    person.printCustom(p -> "name:" + p.getGivenName())            );        }        //③ 提前定义 Lambda 表达式        Function<Person,String> westernNames = p -> "western name:" + p.getGivenName() + " " + p.getSurName();        Function<Person,String> easternNames = p -> "eastern name:" + p.getSurName() + " " + p.getGivenName();        System.out.println("============print western name");        for (Person person:persons) {            System.out.println(                    person.printCustom(westernNames)            );        }        System.out.println("============print eastern name");        for (Person person:persons) {            System.out.println(                    person.printCustom(easternNames)            );        }    }}

6.Lambda 表达式 和 集合


循环(Looping)

第一个新特色是 对任何的 集合类 都可以使用的新方法 : forEach

栗子:

        List<Person> persons = Person.createList();        persons.forEach(p -> p.printEasternName());        persons.forEach(Person :: printEasternName);        persons.forEach(p -> System.out.println(p.printCustom(e -> "giveName is " + e.getGivenName())));

第一个 forEach 是 标准化 的Lambda 表达式的 使用;

第二个 展示了 一种方法引用(method reference),对类中已经存在的方法执行操作,这个语法可以用来代替 正常的 Lambda 表达式语法;

forEach 方法的顶层为  java.lang.Iterable 接口中的方法,源码:

    default void forEach(Consumer<? super T> action) {        Objects.requireNonNull(action);        for (T t : this) {            action.accept(t);        }    }

底层使用了 for 循环对集合的每一个元素进行处理;

参数为 Consumer 接口(函数式接口),该接口 代表了 一类操作 :接收一个参数,无返回值;

链 和 过滤 (Chaining and Filters)

对集合 先过滤再 循环:

        System.out.println("=====filter");        List<Person> persons = Person.createList();        persons.stream().filter(p -> p.getAge() >= 30).forEach(p -> System.out.println(p.getAge()));

惰性

对于已经有一个 极好的 for 循环 ,为什么还给集合增加这些方法呢?

通过将迭代特征移动到类库中,这允许 Java 开发者 做更多的代码优化。为了进一步的解释,需要定义两个术语:

    ★ Laziness:在编程中,惰性是指 当你需要去处理对象时,只处理你想要处理的对象,对于上面的 先过滤再循环就是一种 lazy

    ★ Eagerness:对 列表中的每一个对象都执行操作的代码被称为 " eager",例如 加强版的 for 循环;


stream 方法

java.util.Collection  中的 stream 方法源码:

    default Stream<E> stream() {        return StreamSupport.stream(spliterator(), false);    }

该方法输入一个 Collection  ,返回 一个 java.util.stream.Stream 接口 作为输出。

一个 Stream  代表 可以链接各种方法的 有序元素。默认情况下, 元素一旦被消费掉,那么将不再可用;因此,一连串的操作在指定的 Stream 上只能出现一次;而且,一个 Stream 的 串行(serial,默认)和并行(parallel)取决于被调用的方法;


修改 和 结果

一个 Stream 在使用过后会被处理掉。因此,集合中的元素不可以通过 Steam 改变;

但是你可以在一连串的操作之后将结果返回,保存为另一个新的集合。

        System.out.println("=====Make a new list after filtering.");        List<Person> oldAges = persons.stream().filter(p -> p.getAge() >= 80).collect(Collectors.toList());        oldAges.forEach(p -> System.out.println(p.printCustom(e -> "age is " + e.getAge())));

Stream 的 collect 方法  传入 Collectors 类 ,Collectors 类 能够根据 Stream 的结果 返回 List 或者 Set


使用 map 计算

        System.out.println("===计算total average");        long totalAge = persons.stream().filter(p -> p.getAge() >= 30).mapToInt(p -> p.getAge()).sum();        System.out.println("totalAge ==" + totalAge);        OptionalDouble average = persons.stream().filter(p -> p.getAge() >= 30).mapToDouble(p -> p.getAge()).average();        System.out.println("average age ==" + average.getAsDouble());

计算 总和:使用 map 方法 以 串行的方式获取 每个人的年龄 ,最后求和;




0 0
原创粉丝点击