行为参数化(Lambda 以及方法引用)
简洁灵活代码的演进
让我们先来看下对apple根据重量排序的不同写法
public class AppleSortTest {
private static List<Apple> inventory = new ArrayList<>();
static {
inventory.add(new Apple(9,"qing"));
inventory.add(new Apple(4,"red"));
inventory.add(new Apple(7,"red"));
}
/**
* 传递代码 想排序需要继承Comparator重写一个类,尽管我只写了很少
* 的代码;而当我需要排序的属性变了后,我又需要重新写一个类
*/
@Test
public void transCodeSort() {
inventory.sort(new AppleComparator());
}
/**
* 传递匿名类 我没有为一个单独的排序创建类了,可是依然有和我
* 排序无关的代码类名方法名
*/
@Test
public void transAnonymousClassSort(){
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
}
/**
* 使用lambda表达式进行排序 我们没有了和排序无关的代码
*/
@Test
public void useLambda(){
inventory.sort((Apple a1, Apple a2)
-> a1.getWeight().compareTo(a2.getWeight())
);
}
/**
* 使用方法引用+lambda表达式 方法引用就是替代那些转发参数的Lambda表达式的语法糖;
* 方法引用让你的代码更简洁
* 重复使用现有的方法实现并直接传递它们
*/
@Test
public void useMethodQuote(){
inventory.sort(Comparator.comparing(Apple::getWeight));
}
}
通过上面这个例子很明显可以看出使用lambda表达式和方法引用,可以使我们代码变得更简洁、灵活;他们的进阶情况如下图:

函数式编程简介
- 一种使用函数进行编程的方式
-
函数式:如果一个方法既不修改它内嵌类的状态,也不修改其他对象的状态,使用return返回所有的计算结果,那么我们称其为纯粹的或者无副作用的
没有副作用函数.png
-
函数式编程:程序有一定的副作用,不过该副作用不会为其他的调用者感知,是否我们能假设这种副作用不存在呢?调用者不需要知道,或者完全不在意这些副作用,因为这对它完全没有影响
带有副作用的函数.png
- 将方法等概念作为一等值可以扩充程序员的工具库,从而让编程变得更容易
lambda表达式
概述
- Lambda表达式可以理解为一种匿名函数:它没有名称,但有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常的列表
- 什么时候以及在哪里可以使用Lambda表达式
- 只有在接受函数式接口的地方才可以使用Lambda表达式;它们可以从赋值的上下文、方法调用的上下文(参数和返回值),以及类型转换的上下文中获得目标类型
- Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例
函数式接口
- 只定义一个抽象方法的接口
- 无论是否加@FunctionalInterface
- 可以多加default方法
常用的函数式接口
- Predicate
- 谓词:在计算机语言的环境下,谓词是指条件表达式的求值返回真或假的过程
- Consumer
- 消费者 访问类型T的对象,并对其执行某些操作,没有返回
- Supplier
- 供应商 无参数的提供一个类型T的对象
- Function
- 方便使用的范本 它接受一个泛型T的对象,并返回一个泛型R的对象
类型检查

类型推断
- Java编译器会从上下文(目标类型)推断出用什么函数式接口
- 可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来得到接口
- 编译器了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型
类型推断.png
- 没有哪一种更好的说法,选用哪一种取决于程序员自己,比如有的人觉得第二种更好,而有的人觉得第二种会让人困惑,因为不能一眼看出参数类型
编译
- lambda表达式编译会使用invokedynamic避免创建额外的类
- invokedynamic会在首次执行时链接代码
捕获变量
-
Lambda表达式可以没有限制地捕获实例变量和静态 ;但局部变量必须显式声明为final,或事实上是final;看个例子
public class CaptureVarTest { public List<String> jio=new ArrayList<>(); /** * lambda 对final对象的引用 */ @Test public void lambdaCaptureVar() { int i = 0; Apple apple = new Apple(2, "pork"); List<Apple> apples = new ArrayList<>(); List<String> jio1=new ArrayList<>(); System.out.println(checkFunctionInterface(() -> { // jio = null; //不报错 // String ji=String.valueOf(i); //不报错 // i+=1; //编译报错 // apple.setWeight(2); //不报错 // apples.add(apple); //不报错 // apple = null; //编译报错 // apples = new ArrayList<>(); //编译报错 return "123"; })); // apple = null; //lambda里面原先对重量赋值不报错的,这行放开后,对重量赋值会报错 } /** * 传递的是一个函数式接口 */ private String checkFunctionInterface(OneMethodInterface one) { return one.onlyOne(); } }
- lambda表达式捕获的值是通过复制对象
- 当为值对象时,改变值,lambda表达式里面和外面无法互相同步
- 当为引用对象,拥有的是地址,当地址改变时,lambda表达式里面和外面无法互相同步
- 而实例变量和静态变量在堆上,拥有实例的地址,实例变量改变时总是可以同步的;静态变量总是同步的
- 函数式编程应尽量降低状态的副作用
- lambda表达式捕获的值是通过复制对象
流
概述

- 从支持数据处理操作(如filter、map、reduce、find、match、sort等)的源生成的元素序列(目的在于表达计算)
- 以声明性方式(首先做这个,紧接着更新那个,然后......)处理数据集合
- 只能遍历一次
常用操作
-
中间操作:返回Stream,可以接着执行
-
终端操作:从流水线产生结果,返回不是stream的值
- 中间操作和终端操作也体现了流的延迟性,只有遇到终端操作,流才会真正执行;下面代码会先输出分隔符
@Test public void streamDelayExecute() { Stream stream = Dish.menu.stream() .filter(d -> { System.out.println("filter execute"); return d.getCalories() < 400; }) .limit(1); System.out.println("分割符"); stream.collect(Collectors.toList()); }

Collector收集器
groupingBy简化代码例子
/**
* 将交易信息根据货币分组;java8以前的写法
*/
@Test
public void groupImperatively() {
Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();
for (Transaction transaction : transactions) {
Currency currency = transaction.getCurrency();
List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);
if (transactionsForCurrency == null) {
transactionsForCurrency = new ArrayList<>();
transactionsByCurrencies.put(currency, transactionsForCurrency);
}
transactionsForCurrency.add(transaction);
}
System.out.println(transactionsByCurrencies);
}
/**
* 将交易信息根据货币分组;使用收集器Collector
*/
@Test
public void groupFunctionally() {
Map<Currency, List<Transaction>> transactionsByCurrencies = transactions.stream()
.collect(groupingBy(Transaction::getCurrency));
System.out.println(transactionsByCurrencies);
}
接口方法
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
Function<A, R> finisher();
BinaryOperator<A> combiner();
Set<Characteristics> characteristics();
}
-
规约过程
顺序规约过程的逻辑步骤.png
- combiner 并行的时候使用
- characteristics
- UNORDERED:归约结果不受流中项目的遍历和累积顺序的影响
- CONCURRENT:收集器可以并行归约流;如果收集器没有标为UNORDERED,那它仅在用于无序数据源时才可以并行归约
- IDENTITY_FINISH:完成器方法返回的函数是一个恒等函数;累加器A不加检查地转换为结果R是安全的
并发流
-
并行处理一个流,而无需在代码中显式使用和协调不同的线程
-
使用的是fork/join;避免了java7中继承RecursiveTask那一套
-
使用parallel、sequential切换顺序流和并行流,但只有最后一个生效
stream.parallel() .filter(...) .sequential() .map(...) .parallel() .reduce();//这几行代码执行的就是并行流
-
Spliterator做流的切分
- trySplit() 把一些元素划出去分给第二个Spliterator

Optional
- 对存在或缺失的变量值进行建模;更好的应对null对象
- 使用Optional能帮助你设计更好的API,用户只需要阅读方法签名,就能了解该方法是否接受一个Optional类型的值
- Optional类支持多种方法,比如map、flatMap、filter,它们在概念上与Stream类中对应的方法十分相似
/**
* 会正常输出ko2
*/
@Test
public void showOrElse() {
Optional<String> optional = Optional.ofNullable(null);
System.out.println(optional.filter(str -> str.contains("kol"))
.map(str -> str + "")
.orElse("ko2"));
}
默认方法
- 兼容的方式改进API
- 相同方法签名带来的继承问题
- 1.类中的方法优先级最高
- 2.子接口的优先级更高
- 3.显式覆盖和调用期望的方法
网友评论