【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 循环对集合的每一个元素进行处理;
链 和 过滤 (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 方法 以 串行的方式获取 每个人的年龄 ,最后求和;
- 【lambda】java8 lambda
- java8 Lambda
- Java8 Lambda
- Java8 Lambda
- java8 lambda
- java8-lambda
- java8-lambda
- java8 Lambda
- Java8 Lambda表达式教程
- Java8 Lambda表达式教程
- Java8: Lambda表达式语法
- java8 Lambda表达式
- Java8 Lambda表达式教程
- Java8 Lambda表达式
- java8 Lambda表达式
- Java8 Lambda表达式教程
- Java8 Lambda表达式教程
- Java8的lambda(1)
- linux3.4 和linux2.6的区别
- 一张表里,不同字段,值相同的两条记录,查询这两条记录中不同字段的语句
- 《Linux内核设计与实现》——内存管理
- 处理inline元素之间换行产生的空白
- 机器学习/深度学习测试题(一) —— 单层感知器的激活函数
- 【lambda】java8 lambda
- CGI实例--表单GET与POST示例
- 数字统计问题
- 1043. 输出PATest(20)
- maven 镜像
- 微信小程序一个坑的地方(uploadFile:fail Error: unable to verify the first certificate)
- Study Jams_ Android:gravity 和 Android:layout_gravity
- malloc(0)返回什么
- 网络接入技术及其特点