文章目录
- 1 第一章 为什么关心Java
- 2 第二章 行为参数化
- 3 第三章 Lambda表达式
- 4 第四章 引入流
- 5 第五章 使用流
- 6 第六章 用流收集数据
- 7 第七章 并行流
- 8 第八章 重构、测试、调试
- 9 第九章 默认方法
- 10 第十章 用Optional取代null
- 11 第十一章 CompletableFuture:组合式异步编程
- 12 第十二章 新的日期和时间API
- 12.1 相关类说明
1 第一章 为什么关心Java
《Java 8 实战》提到了函数式编程、流、默认方法等,大概了解书中所讲美容
2 第二章 行为参数化
将行为抽象出来,而不是将行为和被操作者在写在同一个函数里
如:筛选苹果,以颜色
1.值传递,行为写在函数里
public static List<Apple> filter(List<Apple> inventory){
for(Apple apple:inventory){
if(apple.getColor().equal("green")){
添加到list
}
}
return 添加的list
}
2.值传递,颜色放在参数上
public static List<apple> filter(List<Apple> inventory,String color){}
当需求发生变化,颜色或者其他参数作为筛选条件,编写的代码将会变得丑陋且麻烦
3.行为参数化
//定义行为接口
public interface ApplePredicate{
boolean action(Apple apple);
}
//根据各种筛选要求实现各种类,需要哪种筛选条件就使用哪种类
public class AppleWeightPredicate implements APplePredicate{
public booelan action(Apple apple){
return apple.getWeight()>100;
}
}
public class AppleColorPredicate implements APplePredicate{
public booelan action(Apple apple){
return apple.getColor().equals("green");
}
}
public staic List<Apple> filter(List<Apple> inventory,ApplePredicate p){
for(Apple apple:inventory){
if(p.action(apple)){
return 添加list
}
}
return 添加的list
}
缺点:很啰嗦,需要声明可能只需要实例化一次的类
4.匿名类
List<Apple> redApples = filterApples(inventory,new ApplePredicate(){
public boolean action(Apple apple){
return "red".equals(apple.getColor());
}
})
只需要额外创建接口,每次在传入参数时可以自定义代码
缺点:读困难,代码占用地方大
5.Lambda表达式
List<Apple> result=filterApples(inventory,(Apple apple)->"red".equals(apple.getColor))
lambda表达式代替了
pulic class 匿名类 implements ApplePredicate{
public boolean action(Apple apple){
return "red".equals(apple.getColor);
}
}
ApplePredicate p = new 匿名类();
lambda表达式根据原本参数继承接口,实现接口中的函数,并生成一个对象,产生和传参数对象一样的效果。
6.将类型抽象化
public interface Predicate<T>{
boolean action(T t);
}
public static<T> List<T> filter(List<T> list,Predicate<T>p){
for(T e:list){
if(p.action(e)){
e添加到list1
}
}
return list1
}
三个例子:List.sort排序Comparator,Runnable执行代码,GUI事件处理
3 第三章 Lambda表达式
3.1 基本语法
(param) -> expression 表达式;
(param) -> { statements;} 语句
如果使用return,必须在花括号里面使用
花括号里面必须是语句,不可以是表达式,比如"asdfasf",但可以是return “dafsdf”
3.2 函数式接口和函数描述符
函数式接口:只定义一个 抽象方法的接口。
函数描述符:函数式接口的抽象方法叫函数描述符
函数式接口的签名根据其抽象方法描述
3.2.1 可以使用@FunctionalInterface标注函数式接口
如:
java.util.Comparator用于排序;
public interface Comparator<T>{
int compare(T o1,T o2);
}
签名为(T o1,T o2)->int
java.lang.Runnable 线程;
public interface Runnable{
void run();
}
签名为()->void
接口可以有默认方法(类没有对方法进行实现时,主体为方法提供默认实现的方法),只要接口只定义一个抽象方法,它仍然是函数式接口
3.2.2 其中,java8在java.util.function引入了几个函数式接口
三个泛型函数式接口:Predicate,Consumer,Function<T,R>
//谓语
@FunctionalInterface
public interface Predicate<T>{
boolean test(T t);
}
(T t)->boolean;
@FunctionalInterface
public interface Consumer<T>{
void accept(T t);
}
(T t)->void
//可以用于函数,可以用于将输入对象的信息映射到输出
@FunctinalInterface
public interface Function<T,R>{
R apply(T t);
}
(T t)->R
3.2.3 为了装箱、拆箱带来的额外的版本
泛型T只能绑定到引用类型,java有将原始类型转化为引用类型的机制:装箱(boxing)。引用类型转化为原始类型:拆箱(unboxing)
装箱将原始类型包裹起来,保存到堆里,需要更多的内存,搜索被包裹的原始值
java8为原始类型函数式接口带来了专门版本,如IntPredicate
public interface IntPredicate{
boolean test(int t);
}
IntPredicate evenNumbers = (int i)->i%2==0;
evenNumbers.test(100) //此时未装箱
3.3 环绕执行模式
3.3.1 定义接口
@FunctionalInterface
public interface BufferedReaderProcessor{
String process(BufferedReader b) throws IOException;
}
3.3.2 定义函数
public static String processFile(BufferedReaderProcessor p) throws IOException{
//带资源的try不需要显示关闭资源
try(BufferedReader br = new BufferedReader(new FileReader(path))){
return p.process(br);
}
}
3.3.3 将行为传入参数
1.lambda表达式传入
String line=processFile((BufferedReader br)->br.readLine());
2.方法引用
String line=processFile(BufferedReader::readLine);
3.3.4 异常
任何函数式接口不允许抛出受检异常
抛出异常的方法:
定义自己的函数式接口,声明受检异常。
将Lambda包在try/catch块中
@FunctionalInterface
public interface BufferedReaderProcessor{
String process(BufferedReader b) throws IOException;
}
或者
Function<BufferedReader,String>f=(BufferedReader b)->{
try{
return b.readLine();
}catch(IOException e){
throw new RuntimeException(e);
}
}
3.3.5 类型检查
- 查看目标类型
- 查看接口函数
- 查看接收参数和返回值
特殊的兼容规则
如果Lambda主题是语句表达式,他将会和返回void的函数描述符兼容。
//Consumer返回void,可以兼容函数返回的boolean
Consumer<String>b=s->list.add(s);
3.3.6 类型判断
java编译器从上下文推断函数式配合lambda表达式,推断lambda签名,了解lambda的参数类型,省去标注参数类型
Comparator<Apple> c = (a1,a2)->a1.getWeight().compareTo(a2.getWeight());
List<Apple>greenApples=filter(inventory,a->"green".equals(a.getColor()));
3.3.7 类变量,实例变量,局部变量
变量 | 定义位置 | 初始化值 | 生命周期 | 作用范围 | 内存位置 |
---|---|---|---|---|---|
类变量 | 类中方法外static关键字声明 | 有默认初始化值 | 第一次访问创建,程序结束销毁 | 整个类 | 静态存储区 |
实例变量 | 类中方法外 | 有默认初始化值 | 生命周期同其对象 | 整个类 | 堆内存 |
局部变量 | 方法内 | 不赋值不能用 | 方法被调用存在,结束而消失 | 方法有用 | 栈内存 |
变量加载顺序
静态代码块(只加载一次)->构造方法(创建实例时加载一次)->静态方法(调用会执行)
3.3.8 lambda使用局部变量
Lambda允许使用外层作用域中定义的变量,成为捕获lambda
int portNumber = 111;
Runnable r= ()->System.out.println(portNumber);
当lambda捕获局部变量时,其必须声明为final或者事实final。
1.实例变量保存在堆上,局部变量保存在栈上。若lambda直接访问局部变量且在线程中使用,则该线程可能在该变量回收后访问该变量
lambda表达式最终被处理为额外的线程执行
final类型局部变量在lambda表达式中是局部变量的拷贝
因此要让java访问自由局部变量时,实际访问副本,不访问原始变量
2.不鼓励使用改变外部变量的典型命名式编程模式
3.3.9 闭包
函数的实例,无限访问该函数的非本地变量,可以作为参数传递给另一个函数,可以访问和修改作用域之外的变量
3.3.10 方法引用
方法引用是根据已有的方法实现来创建lambda表达式
- 指向静态方法的方法引用Integer::parseInt
- 指向任意类型实例方法的方法引用Apple::getWeight
- 指向现有对象的实例方法的方法引用apple::getColor,此时调用的是外部的对象的方法
3.3.11 构造方法引用
ClassName::new
首先理解Supplier接口 (供应商)
@FuctionalInterface
public interface Supplier<T>{
T get();
}
Consumer接口(消费者)
@FunctionalInterface
public interface Consumer<T>{
//(T)->void
void accept(T t);
//先执行调用者方法再执行传入参数方法。
default Consumer<T> andThen(Consumer<? super T> after){
Objects.requireNonNull(after);
return (T t)->{accept(t);after.accept(t);}
}
}
public class ConsumerTest {
public static void main(String[] args) {
Consumer<Integer> consumer = (x) -> {
int num = x * 2;
System.out.println(num);
};
Consumer<Integer> consumer1 = (x) -> {
int num = x * 3;
System.out.println(num);
};
consumer.andThen(consumer1).accept(10);
}
}
//输出 20 30
根据水果名字和重量创建该水果并返回。
不将构造函数实例化却能引用它
//全都初始化一次
static Map<String,Function<Integer,Fruit>>map=new HashMap<>();
static {
map.put("apple",Apple::new);
map.put("origin",Origin::new);
//etc...
}
public static Fruit getFruit(String fruit,Integer weight){
return map.get(fruit.toLowerCase()) //根据名字获取水果的构造函数
.apply(weight); //调用构造函数,生成该重量的水果。
}
3.3.12 Comparable接口,Comparator接口
- Comparable接口
public interface Comparable<T> {
int compareTo(T var1);
}
接口通过x.compareTo(y)比较x和y的大小
- x<y返回负数
- x=y返回0
- x>y返回正数
- var1为null,抛出空指针异常
实现该接口的类的数组或List可以调用Arrays.sort()和Collections.sort()排序,实现该接口时也要指定具体类型
public class Student implements Comparable<Student>{
int age;
@Overrdie
public int compareTo(Student student){
return this.age-student.age; //正序
}
}
- Comparator接口
public static void testComparator() {
Function<String, Box> function = Box::new;
List<Box> list = new ArrayList<>();
list.add(function.apply("orange"));
list.add(function.apply("apple"));
list.add(function.apply("banana"));
//comparing静态辅助方法,接受Function,逆序也可以在comparing后面添加reversed()方法
list.sort(Comparator.comparing(Box::getName,Comparator.reverseOrder()));
System.out.println(list.stream().map(Box::getName).collect(toList()));
//String 实现了Comparable接口,所以可以使用compareTo
list.sort((Box b1,Box b2)->b1.getName().compareTo(b2.getName()));
System.out.println(list.stream().map(Box::getName).collect(toList()));
}
//输出
//[orange, banana, apple]
//[apple, banana, orange]
List.sort的接口为
default void sort(Comparator<? super E> c)
3.3.13 比较器复合,谓词复合,函数复合
- 比较器复合
sort(comparing(Apple::getWeight) //按照方法排序
.reversed() //逆序
.thenComparing(Apple::getColor)) //上面的相同时按照这个方法排序
- 谓词复合
Predicate<T>接口下可以使用negate,and,or代表非、与、或;
a.or(b).and(c)代表(a||b)&&c
- 函数复合
Function接口有两个默认方法andThen和compose,都会返回Function的一个实例
Function<Integer,Integer>f=x->x+1;
Function<Integer,Integer>g=x->x*2;
//相当于g(f(x))先执行f(x)函数后将结果作为g(y)的参数
Function<Integer,Integer>h=f.andThen(g);
//相当于f(g(x))先执行g(x)
Function<Integer,Integer>r=f.compose(g);
4 第四章 引入流
4.1 流简介
4.1.1 流的好处
- 代码声明方式写的,不说明如何具体实现操作
- 链式操作,流水线操作,清晰可读
- 并行处理透明,自动优化
4.1.2 一些概念
Java 8的集合支持stream方法生成流,也被称为”从支持数据处理操作的源生成的元素序列“
- 元素序列:集合的目的在于访问和存储元素,流的目的在于表达计算。元素序列代表了数据流
- 源:集合、数组、或者其他输入输出资源都可以作为源。有序集合生成流会保留原有顺序
- 流水线:流操作可能返回一个流,多个操作因此可以连接起来形成打到流水线
- 内部迭代:不像使用迭代器的显式迭代,流的迭代操作在内部
- 常用操作:
- filter:从流中过滤某些元素
- map:将流中的元素转化为另一种元素
- limit:截断流,限制结果数量
- collect:将流转化为其他形式
流是按需获取,集合是急切创建。
流只能遍历一次,遍历完后这个流就被消费掉了,无法再用这个流,只能从源再次获取
List<Dish>类型的 dishs;
dishs.stream()
.filter(d->d.getCalories>300)
.map(Dish::getName)
.limit(3)
.collect(toList());
//结果是从菜单列表里获取前三个卡路里大于300的菜的名字
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0IvZfbhZ-1610341324700)(C:\Users\dreambyday\AppData\Roaming\Typora\typora-user-images\image-20200827193617929.png)]
- 中间操作会返回一个流,如filter,map,limit,sorted,distinct等
- 终端操作会将流水线生成结果,如collect,forEach,count等
5 第五章 使用流
5.1 谓词筛选filter(Predicate<? super T> var1)
接受返回boolean的函数作为参数
Stream<T> filter(Predicate<? super T> var1);
5.2 筛选不重复distinct()
5.3 截断流limit(n)
返回前n个元素的流
limit用在可以用在有序流和无序流上,无序流上将不会排序
5.4 跳过元素skip(n)
返回扔掉前n个元素的流
5.5 映射
5.5.1 map(Function<? super T, ? extends R> var1)
<R> Stream<R> map(Function<? super T, ? extends R> var1);
接受一个函数,应用到每个元素上,将其映射为新的元素。
5.5.2 扁平化流flatMap(Function<? super T, ? extends Stream<? extends R>> var1)
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> var1);
例:将[“hello”,“world”]变成[“h”,“e”,“l”,“o”,“w”,“r”,“d”]
1.先看map版本,可能会写成这样
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A4o44W6r-1610341324703)(C:\Users\dreambyday\AppData\Roaming\Typora\typora-user-images\image-20200827212528937.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1WqOD3YP-1610341324704)(C:\Users\dreambyday\AppData\Roaming\Typora\typora-user-images\image-20200827212552032.png)]
map实际返回的是Stream<String[]>类型,而我们需要的是Stream类型的
2.使用map再次将字符数组映射为流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6PRtGzWi-1610341324707)(C:\Users\dreambyday\AppData\Roaming\Typora\typora-user-images\image-20200827213149736.png)]
发现映射后流的类型变成了Stream<Stream>,这也是不对的。
3.使用flatMap将各个数组映射为流的内容
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wN0H5SDN-1610341324709)(C:\Users\dreambyday\AppData\Roaming\Typora\typora-user-images\image-20200827213515199.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pYC4bbRy-1610341324710)(C:\Users\dreambyday\AppData\Roaming\Typora\typora-user-images\image-20200827213528251.png)]
flatMap方法会将流中每个值换成另外的流,然后将所有的流连接起来。
5.6 查找和匹配
5.6.1 检查谓词是否至少匹配一个元素
anyMatch(T->boolean)
5.6.2 检查谓词是否匹配所有元素
allMatch(T->boolean)
5.6.3 检查谓词是否全都不匹配
noneMatch(T->boolean)
5.6.4 Optional 容器类
代表一个值存在或不存在,防止终端流获取结果为空
- isPresent() 将在Optional包含值返回true
- isPresent(Consumer block)会在值存在时执行给定代码块,接受T类型参数,返回void
- T get() 值存在时返回值,否则抛出异常
- T orElse(T other)值存在时返回值,否则返回默认值
menu.stream()
.filter(Dish::isVegetarian)
.findAny() //返回Optional<Dish>
.ifPresent(d->System.out.println(d.getName()));//包含值就打印,否则不做任何事
5.6.5 查找任意元素
finaAny()找到任意结果返回,返回Optional,并行流中限制少
5.6.6 查找第一个元素
findFirst()找到第一个,并行限制多
5.7 规约
BiFunction接口,接收两个参数,返回一个结果
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T var1, U var2);
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (t, u) -> {
return after.apply(this.apply(t, u));
};
}
}
继承接口,有两个默认函数
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> {
return comparator.compare(a, b) <= 0 ? a : b;
};
}
static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> {
return comparator.compare(a, b) >= 0 ? a : b;
};
}
}
//第一个参数为初始值,第二个参数为操作,接受两个参数返回一个值
T reduce(T var1, BinaryOperator<T> var2);
//无初始值,用Optional接收结果
Optional<T> reduce(BinaryOperator<T> var1);
<U> U reduce(U var1, BiFunction<U, ? super T, U> var2, BinaryOperator<U> var3);
内部运行流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a8gAKBQC-1610341324712)(C:\Users\dreambyday\AppData\Roaming\Typora\typora-user-images\image-20200827225750067.png)]
相对于逐步迭代求和,reduce的迭代由内部实现,可以并行执行。迭代式求和需要更新共享变量sum,不容易并行化
- 有状态:
- reduce,sum,max等只需要一个int或者double,内部状态有界,很小
- sort,distinct在排序和删除时要知道之前的历史,排序要求所有元素放到缓冲区才能给输出,内部状态较大,无界,流较大时可能有问题
- 无状态:map,filter等
5.8 数值流
先看代码
int calories=menu.stream().map(Dish::getCalories).reduce(0,Integer::sum);
这样在使用reduce的时可能需要装箱拆箱的成本
5.8.1 原始类型特化
IntStream,DoubleStream,LongStream分别将流中的元素转化为int,long,double避免暗含的装箱成本,还有对数值流求和的sum等函数
- 映射到数值流
menu.stream()
.mapToInt(Dish::getCalories) //返回IntStream
.sum(); //返回int
- 转换回对象流
menu.stream().mapToInt(Dish::getCalories).boxed();//将IntStream转换为Stream
- 默认值OptionalInt
OptionalInt maxCalories=menu.stream().mapToInt(Dish::getCalories).max();
防止流中没有元素
5.8.2 数值范围
IntStraem和LongStream有range和rangeClose方法
range生成左闭右开
rangeClose生成左右闭合
IntStream.rangeClosed(1,100).filter(n->n%2==0);
生成一个1到100间所有偶数的流
5.9 构建流
5.9.1 由值构建流
Stream<String> stream=Stream.of("aadf","dfddf","dfa");
Stream<String> stream=Stream.empty();
5.9.2 由数组构建流
int[]numbers={2,1,3,4,6};
int sum=Arrays.stream(numbers) //IntStream
.sum();
5.9.3 由文件生成流
try(Stream<String>lines=Files.lines(Paths.get("data.txt"),Charset.defaultCharset())){
uniqueWords=lines.flatMap(line->Arrays.stream(line.split(" ")))
.distinct()
.count();
}catch(IOException e){
}
File.lines生成流,每个元素是文件中的一行
5.9.4 函数生成无限流
- Stream.iterate迭代
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return (t) -> {
return t;
};
}
}
static <T> Stream<T> iterate(final T seed, final UnaryOperator<T> f);
static <T> Stream<T> iterate(final T seed, final Predicate<? super T> hasNext, final UnaryOperator<T> next);
Stream.iterate(0,n->n+2) //接受初值0,每次生成的元素为前一个元素加2
.limit(10)
.forEach(System.out::println);
- Stream.generate 生成
接受一个Supplier类型
Stream.generate(Math::random);
生成无限流,每个元素为0到1间的随机双精度数
6 第六章 用流收集数据
6.1 收集器
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();
public static<T, R> Collector<T, R, R> of(Supplier<R> supplier,
BiConsumer<R, T> accumulator,
BinaryOperator<R> combiner,
Characteristics... characteristics) {
Objects.requireNonNull(supplier);
Objects.requireNonNull(accumulator);
Objects.requireNonNull(combiner);
Objects.requireNonNull(characteristics);
Set<Characteristics> cs = (characteristics.length == 0)
? Collectors.CH_ID
: Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH,
characteristics));
return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, cs);
}
public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A, R> finisher,
Characteristics... characteristics) {
Objects.requireNonNull(supplier);
Objects.requireNonNull(accumulator);
Objects.requireNonNull(combiner);
Objects.requireNonNull(finisher);
Objects.requireNonNull(characteristics);
Set<Characteristics> cs = Collectors.CH_NOID;
if (characteristics.length > 0) {
cs = EnumSet.noneOf(Characteristics.class);
Collections.addAll(cs, characteristics);
cs = Collections.unmodifiableSet(cs);
}
return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, finisher, cs);
}
enum Characteristics {
CONCURRENT,
UNORDERED,
IDENTITY_FINISH
}
<R, A> R collect(Collector<? super T, A, R> collector);
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
6.2 Collectors工厂类-汇总和归约
- toList()
- count()
- maxBy(Comparator<? super T> comparator)
- minBy
- summingInt(ToIntFunction<? super T> mapper) 求和
- summingLong
- summingDouble
- joining(String str) 将流中每个对象用toString方法用StringBuilder将所有字符串连接起来,可以用str将其分割开
- reducing工厂方法是所有这些特殊情况的一般化
6.3 Collectors.reducing工厂方法介绍
例:
int totalCalories=menu.stream().collect(reducing(0,Dish::getCalories,(i,j)->i+j));
接收三个参数
- 第一个:操作起始值
- 第二个:转化函数 接收T类型返回U类型
- 第三个:BinaryOperator,2->1
public static <T, U>
Collector<T, ?, U> reducing(U identity,
Function<? super T, ? extends U> mapper,
BinaryOperator<U> op) {
return new CollectorImpl<>(
boxSupplier(identity),
(a, t) -> { a[0] = op.apply(a[0], mapper.apply(t)); },
(a, b) -> { a[0] = op.apply(a[0], b[0]); return a; },
a -> a[0], CH_NOID);
}
public static <T> Collector<T, ?, T>
reducing(T identity, BinaryOperator<T> op) {
return new CollectorImpl<>(
boxSupplier(identity),
(a, t) -> { a[0] = op.apply(a[0], t); },
(a, b) -> { a[0] = op.apply(a[0], b[0]); return a; },
a -> a[0],
CH_NOID);
}
//一个表达式,接收两个参数,返回
public static <T> Collector<T, ?, Optional<T>>
reducing(BinaryOperator<T> op) {
class OptionalBox implements Consumer<T> {
T value = null;
boolean present = false;
@Override
public void accept(T t) {
if (present) {
value = op.apply(value, t);
}
else {
value = t;
present = true;
}
}
}
6.4 Stream.reduce
T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
Stream.collect(reducing())和Stream.reduce()两种方法通常会获得相同的结果
例如可以用reduce方式实现collect(toList())
Stream<Integer> stream=Arrays.asList(1,2,3,4,4).stream();
List<Integer>numbers=stream.reduce(
new ArrayList<Integer>(), //初始值
(List<Integer> l ,Integer e)->{ //初始值传入l,流中元素传入e,返回的结果再次作为初始值
l.add(e);
return l;
}
)
但reduce方法的本意是将两个值结合起来生成新值,应该是不可变的归约。
collect方法的设计是为了改变容器,积累出需要的结果。
上面的方法滥用reduce方法,在原地改变了累加器的List,造成这个归约不能并行工作,多线程并发修改同一个数据结构破坏list
collect适用于可变容器的归约。
6.5 分组
public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream);
//接收Function分类,Supplier
public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy (Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream);
//接收一个Function
public static <T, K> Collector<T, ?, Map<K, List<T>>>groupingBy(Function<? super T, ? extends K> classifier);
Map<String, List<Apple>> collect = list.stream().collect(Collectors.groupingBy(Apple::getColor));
//{red=[Apple(color=red, weight=4), Apple(color=red, weight=6)], green=[Apple(color=green, weight=5)]}
向groupingBy传递了Function,结果是一个map,Function接口返回的结果就是分组的依据
多级分组
BiFunction<String,Integer,Apple> biFunction=Apple::new;
List<Apple>list=new ArrayList<>();
list.add(biFunction.apply("red",4));
list.add(biFunction.apply("green",5));
list.add(biFunction.apply("red",6));
list.add(biFunction.apply("green",10));
list.add(biFunction.apply("green",0));
System.out.println(list.stream().collect(groupingBy(Apple::getColor, groupingBy(apple -> {
if (apple.getWeight() > 5) {
return "weight";
} else {
return "light";
}
}))));
使用子组收集数据
groupingBy的第二个收集器可以是任何类型
list.stream().collect(groupingBy(Apple::getColor, mapping(apple->{
if(apple.getWeight()<5){
return "light";
}else{
return "weight";
}
},toSet())));
可以用来给对象打标签
6.6 分区partitioningBy
分区是分组的特殊情况,由谓词作为分类函数,返回boolean
分区可以返回true和false两套元素列表
6.7 收集器接口Collector
public interface Collector<T, A, R> { //T是要收集的项目的泛型,A 是累加器的类型,R是收集到对象的类型
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();
}
6.7.1 建立新的结果容器supplier
supplier方法返回空的Supplier,无参,用于创建空的累加器实例
样例:
public Supplier<List<T>>supplier(){
return ()->new ArrayList<T>();
}
6.7.2 将方法添加到结果容器accumulator
accumulator方法返回执行归约操作的函数。当流遍历到第n个元素,函数执行两个参数:保存归约结果的累加器和第n个元素本身,结果返回void
样例:
public BiConsumer<List<T>,T>accumulator(){
return (list,item)->list.add(item);
}
6.7.3 对结果容器应用最终转换:finisher
finisher方法返回积累过程最后调用的函数,将累加器对象转换为整个集合操作的最终结果
6.7.4 合并两个结果容器:combiner方法
combiner方法会返回供归约操作使用的函数,定义对流的各个子部分进行并行处理时,各个子部分归约所得累加器如何合并。
- 原始流以递归方式拆分为子流,知道判断不需要继续拆分(跟内核相关)
- 所有子流可以并行处理
- 使用combiner方法合并,返回结果
6.7.5 characteristics方法
返回一个不可变的Characteristics集合,定义了收集器行为,关于流是否可以归约,可以使用哪些优化
包含三个枚举:
- UNORDERED 归约结果不受流中项目的遍历和积累顺序的影响
- CONCURRENT accumulator函数可以从多个线程同时调用,且收集器可以并行归约流。如果收集器没有标为UNORDERED,那它仅在使用无序数据源才可以并行归约
- IDENTITY_FINISH 完成器方法返回函数是恒等函数,可以跳过,累加器A不加检查地转换为结果R是安全的
7 第七章 并行流
7.1 流转化
parallel()顺序流转化为并行流
sequential() 并行流编程顺序流
7.2 考虑并行流还是顺序流的因素
- 装箱
- limit、findFirst等依赖元素顺序的操作影响并行流性能
- 操作流水线的成本,元素通过流水线成本*处理元素的个数大致为总成本,通过流水线成本高要考虑并行流
- 小的数据量,并行处理的好处可能抵不上并行化造成的开销
- 数据结构是否易于分解
- 终端合并代价大小
7.3 分支、合并框架
工作窃取
7.4 可分迭代器Spliterator
8 第八章 重构、测试、调试
9 第九章 默认方法
为了解决开发者在接口中添加新的方法导致使用者不得不对其进行实现的问题。
接口开发者在添加新的方法时可以提供一个默认的实现方法。
public interface Sized{
int size();
//默认方法,default声明,是非抽象方法
default boolean isEmpty(){
reaturn size() == 0;
}
}
静态方法可以存在于接口内部,可以将工具辅助类的静态方法转移到接口内部。
9.1 抽象类和抽象接口
- 一个类只能继承一个抽象类,一个类可以实现多个接口
- 一个抽象类可以有实例变量,接口不可以有实例变量
9.2 默认方法的使用模式
9.2.1 可选方法
在接口中为不常用的方法提供默认实现,实体类就不需要显示提供方法
9.2.2 行为的多继承
9.3 解决冲突的规则
9.3.1 解决问题的三条规则
- 类中的方法优先级最高,类或者父类中声明方法优先级高于声明为默认方法的优先级
- 第一条无法判断,函数签名相同时,选择拥有最具体实现的默认方法的接口,若B继承A,则B比A具体
- 第二条无法判断,继承多个接口的类必须通过显示覆盖和调用期望的方法,显示选择哪个默认方法
9.3.2 三条规则的样例
public interface A{
default void hello(){
sout("A");
}
}
public interface B{
default void hello(){
sout("B");
}
}
public class C implements B,A{
public static void main(String args[]){
new C().hello();
}
}
上面的代码输出为B。C类无hello的实现,第一条规则无法判断。B继承A,B比A具体,符合第二条,所以输出B
- 继承的类没有覆盖接口中的方法
public class D implements A{}
public class C extends D implements B,A{
public static void main(String args[]){
new C().hello();
}
}
上面的代码还是输出B。虽然D类实现了A,拥有了A的默认方法,但没有覆盖hello方法。规则2说若父类没有对应方法,则应该选择最具体的接口中实现的方法。若D中对接口A的hello方法进行覆盖,则会调用D中的hello方法
- 继承了抽象类
public abstract class D implements A{
public abstract void hello();
}
虽然A有默认方法,但是其他类继承D类时还是要提供hello方法。
- 无法判断实现的两个接口
public interface A{
void hello(){
sout("A");
}
}
public interface B{
void hello(){
sout("B");
}
}
当C实现A和B的接口时
public class C implements B,A{}
编译器会因为无法判断你选择那个方法报错。之恩给你显式决定调用哪个方法
public class C implements B,A{
void hello(){
//显式调用B中的方法
B.super.hello();
}
}
- 几乎一样的函数签名,如Integer和Number的返回值
public interface A{
default Number getNumber(){
return 10;
}
}
public interface B{
default Integer getNumber(){
return 42;
}
}
public class C implements B , A{
public static void main(String args[]){
sout(new C().getNumber());
}
}
因为C无法判断哪个更加具体,无法编译通过。
- 菱形继承
public interface A{
default void hello(){
sout("A");
}
}
public interface B extends A{}
public interface C extends A{}
public class D implements B,C{
public static void main(String args[]){
new D().hello();
}
}
代码输出为A(虽然我觉得按照之前的规则要冲突)
- B中添加hello的默认方法,按照规则2,B比A更具体,调用B中的默认方法
- B、C都添加hello默认方法,会冲突,需要显式指定
- C中添加抽象的hello方法(不是默认),C的抽象方法比B继承A的默认方法有更高优先级,C更具体,但D需要为hello显式添加实现。
10 第十章 用Optional取代null
将对象包装,当对象内的属性多层包裹对象,获取其值时要多层判断空指针,既不美观,又不可读,写起来麻烦,Optional可以解决这个问题
可以当做只有一个元素的流来理解
10.1 Optional设计初始未将其考虑为作为类的字段,未实现Serializable接口,序列化时可能出现错误
建议使用函数获取:
public class Person{
private Car car;
public Optional<Car> getCarAsOptional(){
reaturn Optional.ofNullable(car);
}
}
10.2 常用方法:
- Optional.empty() 创建空Optional
- Optional.of(car) 根据非空值创建Optional,传入空指针会抛出NullPointerException
- Optional.ofNullable(car) 可接受null的Optional
- Optional.map 映射,Optional内的对象传给map中的函数,返回值
- Optional.flatMap 接受一个函数作文参数,函数返回值是另一个流,方法应用到每个元素,生成新的流的流,最后会用流的内容替换每个流(也就是扁平化)
10.3 读取变量:
- get() 直接返回封装的变量,但null值时会抛出异常
- orElse(T other) 对象不包含值时提供默认值
- orElseGet(Supplier<? extends T> other) Optional为空时调用(限制较大)
- orElseThrow(Supplier<? extends X>exceptionSupplier) Optional的对象为空时抛出异常,可以定制期望的抛出类型
- ifPresent(Consumer<? super T>) Optional不为空时将对象作为参数传入接口
- isPresent() 是否为空
10.4 不解包组合两个Optional对象
现有两个类型的变量被Optional包裹,将两个对象取出来操作。如Person和Car ,Optional person
Optional<T> result = person.flatMap( //扁平化,即使operations返回Optional类型也能不被Optional包裹两层
p->car.map( //person的内容
c->operations(p,c)));
10.5 filter过滤
同Stream
11 第十一章 CompletableFuture:组合式异步编程
12 第十二章 新的日期和时间API
12.1 相关类说明
- Instant 时间戳
- Duration 持续时间、时间差
- LocalDate 只包含日期 2020-02-02
- LocalTime 只包含时间 10:32:10
- LocalDateTime 包含时间和日期
- Peroid 时间段
- ZoneOffset 时区偏移量
- ZonedDateTime 带时区的日期时间
- Clock 时钟,获取当前时间戳
- java.time.format.DateTimeFormatter 时间格式化类
12.2 为什么用新的API
旧API:
- 易用性差,年份从1900年开始,月份从0开始
- 输出结果不直观
- 格式化时线程不安全
12.3 LocalDate,LocalTime,LocalDateTime
12.3.1 创建LocalDate,LocalTime对象并读取
LocalDate date = LocalDate.of(2014,3,18); //2014-03-18
int year = date.getYear(); //2014
Month month = date.getMonth(); //MARCH
int day = date.getDayOfMonth(); //18
DayOfWeek dow = date.getDayOfWeek(); //TUESDAY
int len = date.lengthOfMonth(); //31
boolean leap = date.isLeapYear(); //false
LocalDate today = LocalDate.now(); //当前日期
/**********************/
LocalTime time = LocalTIme.of(13,45,20); //13:45:20
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();
//直接解析字符串
LocalDate date = LocalDate.parse("2014-03-06");
LocalTime time = LocalTime.parse("13:45:20");
12.3.2 使用TemporalField读取LocalDate
int year = date.get(ChronoField.YEAR);
int month = date.get(ChronoField.MONTH_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);
12.3.3 合并时间日期
LocalDateTime dt = LocalDateTime.of(,,,,,);//六个参数
LocalDateTime dt = LocalDateTime.of(date,time);
LocalDateTime dt = date.atTime(,,); //三个时间参数
LocalDateTime dt = date.atTime(time);
LocalDateTime dt = time.atDate(date);
//时间日期转化为时间或日期
LocalDate date = dt.toLocalDate();
LocalTIme time = dt.toLocalTime();
12.4 机器的日期和时间格式
以Unix元年时间(UTC时区1970年1月1日午夜)开始所经历的秒数开始计算
Instant.ofEpochSecond(秒,纳秒)
12.5 Duration或Period
12.6 操纵、解析、格式化日期
12.6.1 直接操作
LocalDate date1 = LocalDate.of(2014,3,18);
LocalDate date2 = date1.withYear(2001);
LocalDate date3 = date1.withDayOfMonth(25); //改变天
LocalDate date4 = date1.with(ChronoField.MONTH_OF_YEAR,9); //改变月
12.6.2 相对操作
LocalDate dt = LocalDate.of(2014,3,18);
LocalDate dt1 = dt.plusWeeks(1); //加一周
LocalDate dt2 = dt.minusYears(3); //减三年
LocalDate dt3 = dt.plus(6,ChronoUnit.MONTHS); //加六个月
12.6.3 TemporalAdjuster 更加灵活地 操纵日期
12.6.4 格式化
DateTimeFormatter.ofPattern("dd/MM/yyyy")
12.7 处理不同的时区和历法
ZoneID
ZonedDateTime