// ()表示没有参数
Runnable noArguments = () -> System.out.println("hello");
// Lambda 表达式主体可以是一个表达式,或一个代码段,使用大括号{};
// 参数类型可以不用指定,由javac根据上下文自动进行类型推断
ActionListener oneArgument = event -> {
System.out.println("button clicked");
System.out.println("hello");
}
// 有且只有一个参数,可以省略();后接表达式,可省略{}
ActionListener oneArgument = event -> System.out.println("button clicked");
// 多个参数
BinaryOperator<Long> add = (x, y) -> x + y;
// 多个参数并指定参数类型
BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
如果匿名内部类 需要引用它所在方法中的变量,则需要将该变量声明为final,即最终变量;在java8中,虽然放开了这个限制,但是变量必须是事实上的最终变量,也就是说变量不可以多次赋值,否则会编译报错【idea直接提示错误】,如下代码会提示错误
String name = "phenix";
name = "phenix";
Stream.of("1","2","3").forEach(e -> {
System.out.println(name);
});
编译报错信息:
java: 从lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量
函数接口是只有一个抽象方法的接口, 用作 Lambda 表达式的类型。
Java中重要的函数接口
| 接口 | 参数 | 返回类型 | 示例 |
|---|---|---|---|
| Predicate | T | boolean | 这张唱片已经发行了吗 |
| Consumer | T | void | 输出一个值 |
| Function<T,R> | T | R | 获得 Artist 对象的名字 |
| Supplier | None | T | 工厂方法 |
| UnaryOperator | T | T | 逻辑非( !) |
| BinaryOperator | (T, T) | T | 求两个数的乘积( *) |
函数接口的图示:
参数类型-->接口名称-->返回值类型
@Test
public void test004() {
Predicate<Integer> atLeast5 = x -> x > 5;
System.out.println(atLeast5.test(1));
System.out.println(atLeast5.test(10));
Function<Double, Double> calculator = x -> x + x;
System.out.println(calculator.apply(5d));
IntPred atLeast1 = x -> x > 5;
System.out.println(atLeast1.test(1));
System.out.println(atLeast1.test(10));
}
某些情况下, 用户需要手动指明类型, 建议大家根据自己或项目组的习惯, 采用让代码最便于阅读的方法。
Lambda 表达式中的类型推断, 实际上是 Java 7 就引入的目标类型推断的扩展。 比如Java 7 中的菱形操作符, 它可使 javac 推断出泛型参数的类型。
Map<String, Integer> oldWordCounts = new HashMap<String, Integer>();
// 可以简写为:
Map<String, Integer> diamondWordCounts = new HashMap<>();
我们为变量 oldWordCounts明确指定了泛型的类型, 而变量 diamondWordCounts则使用了菱形操作符。 不用明确声明泛型类型, 编译器就可以自己推断出来, 这就是它的神奇之处!
如果将构造函数直接传递给一个方法, 也可根据方法签名来推断类型。 如下代码中我们传入了 HashMap, 根据方法签名已经可以推断出泛型的类型。
useHashmap(new HashMap<>());
...
private void useHashmap(Map<String, String> values);
Java 7 中程序员可省略构造函数中的泛型类型, Java 8 更进一步, 程序员可省略 Lambda 表达式中的所有参数类型。 再强调一次, 这并不是魔法, javac 根据 Lambda 表达式上下文信息就能推断出参数的正确类型。 程序依然要经过类型检查来保证运行的安全性, 但不用再显式声明类型罢了。 这就是所谓的类型推断。
举例1:
// 通过lambda表达式的主体推断出返回boolean值
Predicate<Integer> atLeast5 = x -> x > 5;
// 输出false
System.out.println(atLeast5.test(1));
// 输出true
System.out.println(atLeast5.test(10));
Lambda表达式实现了 Predicate 接口, 因此它的单一参数被推断为 Integer 类型。 javac 还可检查Lambda 表达式的返回值是不是 boolean, 这正是 Predicate 方法的返回类型。
举例2:
BinaryOperator。 该接口接受两个参数, 返回一个值, 参数和返回值的类型均相同。 实例中所用的类型是 Long。
BinaryOperator<Long> addLongs = (x, y) -> x + y;
如果,我们去掉,即“BinaryOperator addLongs = (x, y) -> x + y”则编译器报错:Operator '+' cannot be applied to 'java.lang.Object', 'java.lang.Object',因为javac无法推断出参数类型,只能认为是Object对象。
1、请查看 Function 函数的源码,回答问题
public interface Function<T,R> {
R apply(T t);
}
a. 请画出该函数接口的图示
T --> Function --> R
b. 若要编写一个计算器程序, 你会使用该接口表示什么样的 Lambda 表达式?
Function<Double, Double> calculator = x -> x + x;
Function<Double, Double> calculator = x -> x - x;
Function<Double, Double> calculator = x -> x * x;
Function<Double, Double> calculator = x -> x / x;
System.out.println(calculator.apply(5d));
c. 下列哪些 Lambda 表达式有效实现了
Function<Long,Long> ?
1). x -> x + 1; [ok]
2). (x, y) -> x + 1;
3). x -> x == 1;
2、ThreadLocal Lambda 表达式。 Java 有一个 ThreadLocal 类, 作为容器保存了当前线程里局部变量的值。 Java 8 为该类新加了一个工厂方法, 接受一个 Lambda 表达式, 并产生一个新的 ThreadLocal 对象, 而不用使用继承, 语法上更加简洁。
a. 在 Javadoc 或集成开发环境( IDE) 里找出该方法。
/**
* 当前余额
*/
private ThreadLocal<Integer> balance = ThreadLocal.withInitial(() -> 1000);
public class NotSafeBank {
/**
* 当前余额
*/
private int balance = 1000;
/**
* 存款
*
* @param money 存款金额
*/
public void deposit(int money) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " -> 当前账户余额为:" + this.balance);
this.balance += money;
System.out.println(threadName + " -> 存入 " + money + " 后,当前账户余额为:" + this.balance);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class SafeBank {
/**
* 当前余额
*/
private ThreadLocal<Integer> balance = ThreadLocal.withInitial(() -> 1000);
/**
* 存款
*
* @param money 存款金额
*/
public void deposit(int money) {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " -> 当前账户余额为:" + this.balance.get());
this.balance.set(this.balance.get() + money);
System.out.println(threadName + " -> 存入 " + money + " 后,当前账户余额为:" + this.balance.get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Test
public void test005() {
SafeBank bank = new SafeBank();
Thread thread1 = new Thread(() -> bank.deposit(200), "user1");
Thread thread2 = new Thread(() -> bank.deposit(500), "user2");
Thread thread3 = new Thread(() -> bank.deposit(1000), "user3");
thread1.start();
thread2.start();
thread3.start();
}
/*
user1 -> 当前账户余额为:1000
user1 -> 存入 200 后,当前账户余额为:1200
user2 -> 当前账户余额为:1000
user2 -> 存入 500 后,当前账户余额为:1500
user3 -> 当前账户余额为:1000
user3 -> 存入 1000 后,当前账户余额为:2000
*/
@Test
public void test006() {
NotSafeBank bank = new NotSafeBank();
Thread thread1 = new Thread(() -> bank.deposit(200), "user1");
Thread thread2 = new Thread(() -> bank.deposit(500), "user2");
Thread thread3 = new Thread(() -> bank.deposit(1000), "user3");
thread1.start();
thread2.start();
thread3.start();
}
/*
不安全的变量bank,导致不同用户使用了同一个账户
user1 -> 当前账户余额为:1000
user1 -> 存入 200 后,当前账户余额为:1200
user2 -> 当前账户余额为:1200
user2 -> 存入 500 后,当前账户余额为:1700
user3 -> 当前账户余额为:1700
user3 -> 存入 1000 后,当前账户余额为:2700
*/
b. DateFormatter 类是非线程安全的。 使用构造函数创建一个线程安全的 DateFormatter对象, 并输出日期, 如“ 01-Jan-1970”。
3、类型推断规则。 下面是将 Lambda 表达式作为参数传递给函数的一些例子。 javac 能正确推断出 Lambda 表达式中参数的类型吗? 换句话说, 程序能编译吗?
a.
Runnable helloWorld = () -> System.out.println("hello world");
b. 使用 Lambda 表达式实现 ActionListener 接口:
JButton button = new JButton();
button.addActionListener(event ->
System.out.println(event.getActionCommand()));
c. 以如下方式重载 check 方法后, 还能正确推断出 check(x -> x > 5) 的类型吗?
interface IntPred {
boolean test(Integer value);
}
boolean check(Predicate<Integer> predicate);
boolean check(IntPred predicate);
IntPred atLeast1 = x -> x > 5;
System.out.println(atLeast1.test(1));
System.out.println(atLeast1.test(10));
首先,让我们看一个例子:使用for语句计算来自London的艺术家人数
int count = 0;
for (Artist artist : allArtists) {
if (artist.isFrom("London")) {
count++;
}
}
从上述代码可以看出,for语句使用了固定的模板代码:for xxxx {};这样的方式,代码量多了,同时也不能流畅的表达程序的逻辑。单个for语句可能还好理解,但是多个for嵌套的话,则很难一眼看出程序表达的意思。for语句背后的原理,其实是使用了迭代器iterator。使用Iterator对象来控制整个迭代过程,这就是外部迭代。上述的for语句可以修改为iterator的,如下:
int count = 0;
Iterator<Artist> iterator = allArtists.iterator();
while(iterator.hasNext()) {
Artist artist = iterator.next();
if(artist.isFrom("London")) {
count++;
}
}
图示外部迭代:

外部迭代的缺点:很难进行如流一般的抽象操作;本质来讲是一种串行化的操作。
内部迭代需要实现Stream接口,如stream()方法返回的就是一个Stream接口。查找来自London的艺术家数量的功能内部迭代实现如下:
long count = allArtists.stream().filter(artist -> artist.isFrom("London")).count();
内部迭代少了for语句那样的模板代码,同时,从抽象上通过filter过滤出需要的数据,使用count进行数量统计。这样的代码,可读性更高,一眼就可以看出程序的逻辑。
图示内部迭代:

Stream 是用函数式编程方式在集合类上进行复杂操作的工具。
为了找出来自伦敦的艺术家, 需要对 Stream 对象进行过滤: filter。 过滤在这里是指“ 只保留通过某项测试的对象”。 测试由一个函数完成, 根据艺术家是否来自伦敦, 该函数返回 true 或者 false。 由于 Stream API 的函数式编程风格, 我们并没有改变集合的内容, 而是描述出 Stream 里的内容。 count() 方法计算给
定 Stream 里包含多少个对象。 返回的 Stream 对象不是一个新集合, 而是创建新集合的配方。【诡异的翻译】
像filter 这样只描述 Stream, 最终不产生新集合的方法叫作惰性求值方法; 而像 count 这样最终会从 Stream 产生值的方法叫作及早求值方法(判断一个操作是惰性求值还是及早求值很简单: 只需看它的返回值。 如果返回值是 Stream,那么是惰性求值; 如果返回值是另一个值或为空, 那么就是及早求值。 )整个过程和建造者模式有共通之处。 建造者模式使用一系列操作设置属性和配置, 最后调用一个 build 方法, 这时, 对象才被真正创建。
由于是惰性求值,如下代码,并不会打印出任何信息:
allArtists.stream().filter(artist -> {
System.out.println(artist.getName());
return artist.isFrom("London");
});
要打印出艺术家姓名,那么需要再加一个终止操作的流:
long count = allArtists.stream().filter(artist -> {
System.out.println(artist.getName());
return artist.isFrom("London");
}).count();
collect(tolist())方法由stream里的值生成一个列表,是一个及早求值操作。
// demo1: 将一串字符转换为List集合
List<String> collected = Stream.of("a", "b", "c").collect(Collectors.toList());
assertEquals(Arrays.asList("a", "b", "c"), collected);
// demo2: 对象Person,有cardNo,name,age,gender等属性
@Getter
@Sette
@Builder
public class Person() {
// 身份证
private String cardNo;
private String name;
private Integer age;
private String gender;
}
// 将Person集合persons转化为Map<String,Person>对象,key为cardNo,value为Person对象。
List<Person> persons = new ArrayList(1);
persons.add(Person.builder().cardNo("xxxxx").name("xxxxx").build());
// Function.identity()返回当前输入参数,这里就是Person对象
persons.stream().collect(Collectors.toMap(Person::getCardNo, Function.identity()));
如果有一个函数可以将一种类型的值转换成另外一种类型, map 操作就可以使用该函数, 将一个流中的值转换成一个新的流 。Lambda 表达式必须是 Function 接口的一个实例 。map方法为惰性求值。
// demo1: 小写转换为大写
List<String> collected = Stream.of("a", "b", "hello").map(e->e.toUppperCase()).collect(Collectors.toList());
assertEquals(Arrays.asList("a", "b", "hello"), collected);
filter用于过滤数据,他接受一个返回true或false的接口函数,即我们前面提到过的Predicate 。结果为true的数据会被保存下来。filter方法为惰性求值。
// 过滤首字母为数字的字符串
List<String> beginningWithNumbers = Stream.of("a", "1abc", "abc1").filter(value -> isDigit(value.charAt(0))).collect(toList());
assertEquals(Arrays.asList("1abc"), beginningWithNumbers);
flatMap 方法可用 Stream 替换值, 然后将多个 Stream 连接成一个 Stream。flatMap 方法的相关函数接口和 map 方法的一样, 都是 Function 接口, 只是方法的返回值限定为 Stream 类型罢了 。
// 将两个list合并为一个新的list
List<Integer> together = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4))
.flatMap(numbers -> numbers.stream())
.collect(Collectors.toList());
System.out.println(together);
Stream 上常用的操作之一是求最大值和最小值。 Stream API 中的 max 和 min 操作足以解决这一问题。
// 获取薪水最高和最低的职员
@Test
public void test009() {
List<Employee> empList = Arrays.asList(
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg", 300000.0)
);
Employee maxSalaryEmp = empList.stream().max(Comparator.comparing(Employee::getSalary)).get();
System.out.println(maxSalaryEmp);
Employee minSalaryEmp = empList.stream().min(Comparator.comparing(Employee::getSalary)).get();
System.out.println(minSalaryEmp);
}
/* 输出
Employee(id=3, name=Mark Zuckerberg, salary=300000.0)
Employee(id=1, name=Jeff Bezos, salary=100000.0)
*/
reduce 操作可以实现从一组值中生成一个值。 在上述例子中用到的 count、 min 和 max 方法, 因为常用而被纳入标准库中。 事实上, 这些方法都是 reduce 操作。 reduce方法的函数接口为BinaryOperator 。
@Test
public void test010() {
int sum = Stream.of(1, 2, 3).reduce(4, Integer::sum);
System.out.println(sum);
}
// 输出:10
// 展开reduce操作
@Test
public void test011() {
BinaryOperator<Integer> accumulator = (acc, element) -> acc + element;
int count = accumulator.apply(accumulator.apply(accumulator.apply(4, 1), 2), 3);
System.out.println(count);
}
// 输出:10
// 等效于如下代码:
int acc = 4;
for(Integer element : Arrays.asList(1, 2, 3)) {
acc += element;
}
System.out.println(acc);
peek 方法让你能查看每个值, 同时能继续操作流。
@Test
public void test002() {
Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg", 300000.0)
};
List<Employee> empList = Arrays.asList(arrayOfEmps);
empList.stream()
.peek(e -> e.salaryIncrement(10))
.peek(System.out::println)
.collect(Collectors.toList());
assertThat(empList, contains(
hasProperty("salary", equalTo(110000.0)),
hasProperty("salary", equalTo(220000.0)),
hasProperty("salary", equalTo(330000.0))
));
}
