今天面试的时候,贵公司竟然已经都在全部使用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
★ 条件表达式
★ 强制转换表达式
最佳实践建议:
尽量保证 lambda 表达式的精简:
- 避免 lambda 体 中过多的代码块,可以通过调用方法来实现;
@Test
public void practise() {
Function<String, String> function = this::buildStr;
// Function<String, String> function1 = param -> buildStr(param); // 和此等价
String result = add(" Message ", function);
System.out.println(result);
}
public String buildStr(String param) {
//many code
return " something " + param;
}
public String add(String str, Function<String, String> function) {
return function.apply(str);
}
- 避免指定 参数类型 ,可以直接省略:
像这样:
(a, b) -> a.toLowerCase() + b.toLowerCase();
而不是这样:
(String a, String b) -> a.toLowerCase() + b.toLowerCase();
- 避免返回语句 和 括号:
像这样:
a -> a.toLowerCase();
而不是这样:
a -> {return a.toLowerCase();};
避免单个参数时使用括号:
像这样:
a -> a.toLowerCase();
而不是这样:
(a) -> a.toLowerCase();
- 使用方法引用
lambda 表达式:
a -> a.toLowerCase();
方法引用:
String::toLowerCase;
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;
@FunctionalInterface
public 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 的 一个二元操作;
@FunctionalInterface
public 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;
@FunctionalInterface
public 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 方法
stream 简介图:
Stream 定义:
- 支持 串行 和 并行 聚合操作的 元素序列。
java.util.Collection 中的 stream 方法源码:
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
一个 Stream 代表 可以链接各种方法的 有序元素。默认情况下, 元素一旦被消费掉,那么将不再可用;因此,一连串的操作在指定的 Stream 上只能出现一次;而且,一个 Stream 的 串行(serial,默认)和并行(parallel)取决于被调用的方法;
除了对象引用的 Stream , 还提供了 基本数据类型的 Stream: IntStream,LongStream,DoubleStream.
修改 和 结果
一个 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 方法 以 串行的方式获取 每个人的年龄 ,最后求和;