@[笔记, Java8, Java]
[TOC]
第一章 Java 8的更新
-
流处理
流是一系列数据项,一次只生成一项。
通过 API来传递代码
-
并行与共享的可变数据
对传给流方法的行为写法,必须能够同时对不同的输入安全地执行,这就意味着,代码不能访问共享的可变数据。
第二章 通过行为参数化传递代码
行为参数化,指让一个方法接受多种行为(或策略)作为参数,并在内部使用,来完成不同的行为。
处理行为参数化的问题,灵活性上定义接口实现类、使用匿名类、使用 Lambda 差不多。但是简洁程度上 Lambda 胜。
第三章 Lambda 表达式
Lambda 简介
Lambda 表达式为可传递的匿名函数的一种方式,它没有名称,但是它有参数列表、函数主题、返回类型,还有一个可以抛出的异常列表。
它可以被赋给一个变量,或者传递给一个接受函数式接口作为参数的方法。但是需要校验 参数列表 和 返回类型 是否跟该变量、函数式接口需求的一样。
Java7的表达
Comparator<Apple> byWeight = new Comparator<Apple>(){
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
};
Lambda 表达式
Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Lambda的基本表达式为
(parameters)-> expression
或
(parameters) -> {statements;}
如
(Integer i) -> return "Alan" + i; //无效
(Integer i) -> { return "Alan" + i;} //有效
(String s) -> {"Ironman";} // 无效
(String s) -> "Ironman"; // 有效
怎样使用 Lambda
-
函数式接口
简单的说,就是只定义了一个 抽象方法 的接口。因为现在接口有 默认方法,即在类没有对方法进行实现时,其主体为方法提供默认实现的方式。
即使一个接口有很多的 默认方法 ,只要有一个 抽象方法,仍然是一个函数式接口。(继承来的抽象方法也认为是本接口的抽象方法。 )如:
public interface Comparator<T>{ int compare( T o1, To2 ); } public interface Runnable{ void run(); }
函数描述符
函数式接口的抽象方法的签名基本上就是需匹配的 Lambda 表达式的签名。我们将这种抽象方法叫做 函数描述符 。@FunctionalInterface 可以用来标记一个函数式接口,若本接口不是,编译器会报错。非强制。
Lambda 类型检查
-
类型检查过程
a) 通过被传递的函数声明或者变量来找到类型
b) 找到该类型的抽象方法 (故,一个 Lambda 表达式不能赋值给一个 Object 对象,因 Object 不是一个函数式接口)
c) 找到该抽象方法的参数和返回值,进行类型检查。(对 void 类型有兼容。即一个有返回的 Lambda 表达式,可以赋值给一个返回 Void的函数式接口实例。)
d) 若本 Lambda 表达式会抛出异常,需跟抽象方法的 throws 匹配。 类型推断
Comparator<Apple> b1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
// b2同 b1的效果相同,编译器在这里会自动去匹配 a1和 a2的实际类型
Comparator<Apple> b2 = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
- 使用变量
a) Lambda 可以没有限制地使用实例变量和静态变量
b) Lambda 使用的局部变量,必须是 final 的,或者实际上 final 的。
究其本质原因,实际上就是静态变量和实例变量是在堆上的,而局部变量是在栈里的。
当拥有该变量的栈和 Lambda 所属于的栈为两个线程中时,会有线程安全问题。
方法引用
Lambda 表达式,是通过声明参数、方法体(针对参数列表的方法调用)、返回值的方式来声明一个方法。
而方法引用,就是避免参数调用其声明方法,而是直接指明类内方法的方式来创建 Lambda 表达式的方法。当需要使用一个方法引用时,目标引用放在分隔符前,方法名称放在后面。如:Apple::getWeight。(注意: 此处不应该带(),因为这里是一个对函数声明的引用,而不是对函数本身的调用。)
方法引用的普通类型
- 指向静态方法的方法引用, 如: Integer::parseInteger
- 指向实例方法的方法引用, 如: String::length
- 指向先用对象的实例方法的方法引用, 如:parameter::Function
构造方法引用
使用类名和关键字new的方法引用。
注意:方法引用是要赋值给一个函数式接口声明,同样需要进行返回值和类型检查。那么该接口声明的签名需跟当前类的某一个构造函数一直。
实例如下
// Supplier 的签名 () -> T
// 对应 Apple 的默认构造函数
// 等价于
// Supplier<Apple> c1 = () ->new Apple();
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
// Function 的签名 (T) -> R
// 对应 Apple 的构造函数 Apple(int)
// 等价于
// Function<Integer, Apple> c2 = (Integer weight) -> new Apple(weight);
Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c2.apply(100);
// BitFunction 的签名 (T, E) -> R
// 对应 Apple 的构造函数 Apple(String, int)
// 等价于
// BitFunction<String, Integer, Apple> c3 = (String color, Integer weight) -> new Apple( color, weight);
BitFunction<String, Integer, Apple> c3 = Apple::new;
Apple a3 = c3.apply("green", 100);
Lambda 带来的变化
以对一个 List 排序的例子,来汇总同样做 sort的几种做法。
void sort(Comparator<? super E> c)
- 代码传递
// 构造一个 Comparator 的实现类,并且实例化后传给 sort 方法。
public class AppleComparaotr implements Comparator<Apple>{
public int compare(Apple a1, Apple a2 )
{
return a1.getWeight().compareTo(a2.getWeight());
}
}
// 那么排序时
listInstance.sort( new AppleComparator() );
- 匿名类
listInstance.sort( new Comparator<Apple>{
public int compare( Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
}
);
- Lambda
import static java.util.Comparator.comparing;
// 普通 Lambda
listInstance.sort( (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()) );
// 因为 Java 编译器有根据上下文判断 Lambda 参数类型的方法,那么实际上可简写为
listInstance.sort( (a1, a2) -> a1.getWeight().compareTo(a2.getWeight()) );
// 使用现有接口 Comparator,可进一步简化为
listInstance.sort( Comparator.comparing( (Apple a) -> a.getWeight()) );
// 或者
listInstance.sort( Comparator.comparing( (a) -> a.getWeight()) )
- 方法引用
import static java.util.Comparator.comparing;
listInstance.sort( comparing(Apple::getWeight) );
复合 Lambda 表达式的有用方法
Java 8的几个接口(如 Comparator,Function, Predicate)都提供了将 Lambda 进行关联的方法。
- Compparator.reversed(), thenComparring();
- Predicate.negate(), Predicate.and(), Predicate.or();
- Function.andThen(), Function.compose();
第四章 流
流允许你以生命方式处理数据集合,通过查询语句来表达,而不是临时编写一个实现。它们可以被看做遍历数据集合的迭代器。此外,它们还可以透明地并行处理。
流可以进行筛选、切片、查找、匹配、映射、归约。
流与集合
流与集合的差别基本上就在于什么时候进行计算。
集合是内存中的一个数据结构,它包含了这个数据结构中的所有数据,每一个元素都是计算出结果后放入集合中。
流式一个固定的数据结构(不能增加或者删除),其元素都是按需计算的。就像是一个延迟创建的集合,只有消费者要求的时候才会计算值。
- 流跟迭代器一样,只能遍历一次。遍历完后,就说这个流已经被消费掉了。
Stream<Dish> s = menu.stream();
s.forEach(System.out::println); // 正常执行
s.forEach(System.out::println); // 报错 stream has already been operated upon or closed
- 对于集合的迭代操作,是外部迭代。而 Stream 类库使用内部迭代,开发者不需要关心迭代的具体实施方法,而只需要给出函数说明要做什么。
外部迭代
List<String> names = new ArrayList<String>();
// 外部迭代一
for( Dish d : menu ){
names.add(d.getName());
}
// 外部迭代二
Iterator<String> iterator = menu.iterator();
while( iterator.hasNext() )
{
Dish d = iterator.next();
names.add(d.getName());
}
内部迭代
List<String> names = menu.stream()
.map(Dish::getName)
.collect(toList());
流操作
- 中间操作
可以连接起来的流操作被称为中间操作,它们通常返回另外一个流。在碰到一个终端操作前,中间操作实际上并不会执行任何数据处理。
- 终端操作
关闭流的操作被称作终端操作,其返回结果可能是任何不是流的值。
第五章 使用流
筛选和切片
TODO
- [ ]Scala 和 Groovy
- [ ]访客模式
- [ ]策略模式
- [ ]构建器模式
- [ ]Java FX API,一种现代的 Java UI 平台
- [ ]Java7中的 try 语句,表示不需要显式的关闭资源。P41
- [ ]Java7中的菱形表达式,用法。
- [ ]java.util.Comparator.comparing
- [ ]Guava, Apache Commons Collections, LambdaJ