Java 8的新特性可以帮助你:
1.使用Java 8可以减少冗长的代码,让代码更易于理解
2.通过方法引用和Stream API,代码会更加直观
使用 Java 8 重构现在代码 :
1.使用 Lambda 表达式取代匿名类
2.使用方法引用重构 Lambda 表达式
3.使用 Stream API 重构指令式的数据处理
Java 8 新特性总结:
Lambda 表达式
行为化参数(Lambda 以及方法引用)
Stream API (流) 高效率的集合操作
CompletableFuture 优化并发接口
Optional解决null指针异常
Default 接口默认方法
LocalDate 等..新的时间类
⭐️书本最末的一些总结
命令式编程(编写代码,设计实现过程,处理不同的情况(异常,NULL,循环),获取结果)
函数式编程(通过代码告诉程序,我想要什么,然后拿到结果)
⭐️ Java 8 的大方向和主要功能对现有代码的影响
方法引用就是让你根据已有的方法实现来创建Lambda表达式,当你需要使用方法引用时,目标引用放在分隔符 ::前,方法的名称放在后面,例如Apple :: getWeight就是引用了Apple类中定义的方法 getWeight,请记住,不需要括号,因为你没有实际调用这个方法,方法引用就是Lambda表达式 (Apple apple) -> apple.getWeight() 的快捷写法
⭐️对于Lambda表达式参数,尽量使用方法引用的写法(官方推荐)
//Lambda 写法
FunctionstringToInteger = (String s) -> Integer.parseInt(s);
BiPredicate, String> contains = (list, element) -> list.contains(element);
// --> 方法引用
FunctionstringToInteger2 = Integer::parseInt;
BiPredicate, String> contains2 = List::contains;
⭐️ Lambda 表达式等效的方法引用写法栗子(练习题)
lambda编程就是方法参数化:让方法接受多种行为作为参数,并在内部使用,来完成不同的行为
⭐️lambda的核心思想
3.10小结:
lambda表达式可以理解为一种匿名函数
lambda表达式可以让你简洁地传递代码
函数式接口,就是声明了一个抽象方法的接口
只有在接受函数式接口的地方才可以使用 lambda 表达式
java8自带常用函数式接口:java.util.function包,包括Predicate,Function...等
为了避免装箱操作,Predicate和Function等通用函数式接口的原始类型
⭐️Lambda表达式,就是针对面向函数式接口方法作为参数的时候将方法体作为参数输入
StreamAPI 允许以声明性方式处理数据集合,可以把它看成遍历数据集的高级迭代器。
此外流还可以透明睇并行处理,无需写任何多线程代码:
声明性---更简洁、更易读,可复合---更灵活,可并行--性能更好
流的使用一般包括三件事:
1、数据源(如集合)来执行
2、中间操作链,形成一条流的流水线
3、一个终端操作、执行流水线、并能生成结果
⭐️StreamAPI的基本用户、基本概念
集合讲的是数据,Stream流讲的是计算
Stream 所有操作都会返回另一个流,这样他们就形成一条流水线,最后通过collect,返回list结束
stream api 常用方法
anyMatch 接受 lambda,返回布尔值,用于判断集合
filter -- 接受lambda,从流中排除某些元素
map -- 接受lambda,讲元素转换为其他形式或提取信息
sorted -- 接受Comparator 函数接口
limit -- 接受Int,截断流,限制返回条数
distinct -- 不显示重复
collect -- 将流转换为其他形式,通过 toList() 转为 list
//集合是生产生,流是消费者,流只能消费一次
⭐️筛选集合、数组的好工具
stream库可以自动选择一种适合你硬件的数据表示和并行实现
而集合的迭代需要自己管理所有问题,还有和synchronized的艰苦斗争
⭐️stream迭代可以最大化利用硬件,集合就有点过时
连接stream流的操作称为中间操作,关闭流的操作称为终端操作(中间操作一般都可以合并起来,在终端操作时一次性全部处理)
⭐️stream自行选择最优的方法执行并行操作
Stream API通过allMatch、 anyMatch、 noneMatch、findFirst和findAny方法提供这样的工具
⭐️stream api对于数据的查找和匹配提供的一套API工具,和filter函数不一样,以上API工具都是提供boolean返回值用于判断,filter用于过滤集合
java.util.optional是一个容器类,用于表示一个值存在或不存在,用户避免和null相关的Bug
isPresent() 将在Optional包含值得时候返回true,否则返回false
ifPresent(Consumerblock) 会在值存在的时候执行给定的代码块
get() 会在值存在的时返回值,否则抛出一个NoSuchElement异常
orElse(T other) 会在值存在的时返回值,否则返回一个默认值
⭐️新的容器类和一些新的调用方法
用Stream流8道题解答方法:
//交易员
Trader raoul =newTrader("Raoul","Cambridge");
Trader mario =newTrader("Mario","Milan");
Trader alan =newTrader("Alan","Cambridge");
Trader brian =newTrader("Brian","Cambridge");
//交易
Listtransactions = Arrays.asList(
newTransaction(brian,2011,300),
newTransaction(raoul,2012,1000),
newTransaction(raoul,2011,400),
newTransaction(mario,2012,710),
newTransaction(mario,2012,700),
newTransaction(alan,2012,950)
);
//1.找出2011年发生的所有交易,并按交易额排序
transactions.stream().filter(t -> t.getYear() ==2011).sorted(Comparator.comparing(Transaction::getValue)).forEach(System.out::println);
//2.交易员都在哪些不同的城市工作过?distinct不显示重复数据
transactions.stream().map(t -> t.getTrader().getName() +":"+ t.getTrader().getCity()).distinct().forEach(System.out::println);
//3.查找所有来自剑桥的交易员,并且按照姓名排序
transactions.stream().filter(t -> t.getTrader().getCity().equals("Cambridge")).sorted((o1, o2) -> o1.getTrader().getName().compareTo(o2.getTrader().getName())).map(t -> t.getTrader().getName() +"="+ t.getTrader().getCity()).distinct().forEach(System.out::println);
//返回所有交易员的姓名字符创,按照字母排序
String nameString = transactions.stream() .map(t -> t.getTrader().getName()).distinct().sorted().reduce("", (n1, n2) -> n1 + n2);
//有没有交易员是在Milan工作的 ?
// 查找 // 判断
// transactions.stream().filter(t -> t.getTrader().getCity().equals("Milan")).map(t -> t.getTrader().getName()).distinct().forEach(System.out::println);
booleanisMilan = transactions.stream().anyMatch(t -> t.getTrader().getCity().equals("Milan"));
//打印在剑桥的交易员的所有交易额
transactions.stream().filter(t -> t.getTrader().getCity().equals("Cambridge")).map(t -> t.getValue()).forEach(System.out::println);
//所有交易中,交易额最高的是多少
transactions.stream().map(t -> t.getValue()).reduce(0, Integer::max);
transactions.stream().mapToInt(Transaction::getValue).max();
//所有交易中,交易额最小的是多少
transactions.stream().reduce((t1, t2) -> t1.getValue() < t2.getValue() ? t1 : t2);//通过反复比较,找出最小的交易
transactions.stream().min(Comparator.comparing(Transaction::getValue));
transactions.stream().mapToInt(Transaction::getValue).min();
⭐️使用stream流来操作集合真是得心应手,想要什么都能找到
Stream流的创建不单针对集合,还可以从值序列,数组,文件来创建流,例如:
//由字符串创建流,转为大写循环输出
Streamstream = Stream.of("java8","lambdas","a","in","action");
stream.map(String::toUpperCase).forEach(System.out::println);
//由数组创建流,统计数组合计
int[] streamArray = {2,5,7,11,22,33};
Arrays.stream(streamArray).sum();
⭐️stream api 可能改变所有java程序的写法
Streams API可以表达复杂的数据处理查询。常用的流操作总结在表5-1中
可以使用filter、distinct、skip和limit对流做筛选和切片
可以使用map和flatMap提取或转换流中的元素
可以使用findFirst和findAny方法查找流中的元素,你可以用allMatch、noneMatch和anyMatch方法让流匹配给定的谓词
这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流
你可以利用reduce方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大元素
流不仅可以从集合创建,也可从值、数组、文件以及iterate与generate等特定方法创建
⭐️第二章节 Stream流的总结,大概了解,还需要时间去练习和消化其中内容,熟悉Stream流对Java编码方式都会有很大的改变,感觉Java8的函数式编程已经完全颠覆的以前的编码方式
函数式编程相对于指令式编程(Java 7前)的一个主要优势:你只需要指出希望的结果“做什么”,而不用操心执行的步骤,“如何做”,例如针对的集合,根据对象某个属性讲集合分组,函数编程代码如下:
Transaction是交易对象,根据Currency货币针对所有交易进行分组 (开发中很常见):
Map> mapCurrency = list.stream().collect(groupingBy(Transaction :: getCurrency))
还可以自定义逻辑,把逻辑作为Lambda参数,进行分组
public enum CaloricLevel { DIET, NORMAL, FAT }
Map> dishesByCaloricLevel = menu.stream().collect( groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return
CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
} ));
要实现多级分组,可以使用双参数版本Collectors.groupingBy工厂方法创建收集器
⭐️如果不是应用比较广,自身对Java语言一直不是很有兴趣,因为语言太繁琐,很简单的功能要写很多的代码,函数式编程,让我开始对Java有些兴趣了,SQL也是函数式编程,终于开始觉得Java有点酷了
分区是分组的特殊情况,有一个布尔值作为分类函数,意味着分组Map的主键为Boolean,于是也最多可以分为2组,true是一组,false是一组
⭐️分区有点像分组的简化,对于逻辑不是很复杂的分组,可以用分区
stream可以通过对收集源调用prarllelStream方法来把集合转换为并行流,并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流,这样一来,你就可以自动把给定操作的工作负荷分配给多核处理器的所有内核,让他们都忙起来
⭐️stream 自带多线程处理方法,并且根据硬件自动优化,不需要自己实现复杂的多线程,对于CPU核心越来越多的现在,相比以前Java的单核处理效率有很大的提高
把顺序流转成并行流,从而让前面的函数归约过程(也就是求和)并行运行,对顺序流调用parallel()方法
并行编程很复杂,有时候甚至有点违反直觉,如果用的不对(比如采用了一个不易并行化的操作,如iterate),它甚至可能让程序的整体性能更差,所以在调用看似神器的 parallel 操作时,了解背后发生
(使用正确的数据结构然后使其并行工作能够保证最佳的性能)
⭐️顺序流转并行流就是调用 parallel() 方法,多线程方法也不能乱用,使用不当会影响性能
建议将所有迭代器数据处理模式处理集合的代码都转换为Stream API的方法,因为Stream API能更清晰地表达数据处理管道的意图,通过短路和延迟载入以及利用现代计算机的多核架构,我们可以对Stream进行优化
⭐️重构现有代码的建议
使用 default (默认方法)重新定义接口 ===================》
Java 8中接口方法可以包含实现类,类可以从多个接口中继承它们的默认方法(默认方法包含实现),充分利用这些功能为我们服务,保持接口的精致性和正交性,能帮助我们在现有的代码基础上最大程序的实现代码复用和行为模式
⭐️灵活利用 Java 8 接口的默认方法,可以实现多继承,最大程序的复用代码
如果一个类使用相同的函数签名从多个地方(比如另一个类或接口)继承了方法,通过三条规则可以进行判断
1.类中的方法优先级最高,类或父类中声明的方法的优先级高于任何声明为默认方法的优先级
2.如果无法依据第一条进行判断,那么子接口的优先级更高,函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体
3.最后如果还是无法判断,继承了多个接口的类必须通过显示覆盖的调用期望的方法
⭐️虽然这种冲突可能极少的发生,但是一单发生这样的状况,必须要有一套规则来确定
使用 optional 取代 null ===================》
null 带来的种种问题:
1.它是错误之源 -- NullPoninterException 是目前Java程序开发中最典型的异常
2.它使你的代码彭章 -- 它让你的代码充斥着深度嵌套的null检查,代码可读性糟糕透顶
3.它自身毫无意义 -- null自身没有任何语义,尤其是,它代表的是静态类型语言中以一种错误的方式对缺失变量值的建模
4.它破坏Java哲学 -- Java 一直试图避免让程序员意识到指针的存在,唯一的例外是:null指针
5.导致大量问题 -- null并不属于任何类型,这意味着它可以被复制给人以引用类型的变量
⭐️万恶之源 ----- null
通过类型系统让你的域模型中隐藏的知识显式地体现在你的代码中,换句话说,你永远不应该忘记语言的首要功能就是沟通,即使对程序设计语言而言也没什么不同声明方法接受一个 optional 参数,或者将结果作为 optional 类型返回,让你的同事或者未来你方法的使用者,很清楚的知道它是可以接受空值,或者可能返回一个空值
⭐️ opational 如果只是声明空值,设置默认值,那么出现空值该如何处理??
使用Optional从文件中读取属性:
⭐️
public intreadDuration(Properties properties, String name){
// 1.0 不用Optional 封装类的写法
// String value = properties.getProperty(name);
// if(value != null){
// try{
// int i = Integer.parseInt(value);
// if(i > 0){
// return i;
// }
// }catch (NumberFormatException e){
//
// }
// return 0;
// }
// optional 封装类
returnOptional
.ofNullable(properties.getProperty(name))//获取 property 然后将结果封装到 optional , optional 使用ofNullable创建
.flatMap(OptionalUtility ::stringToInt)//Lambda 将 string 转为 Integer ,
.filter(i -> i >0)//过滤 value > 0
.orElse(0);//如果为null, 就返回0
}
Java 8 之前的Date和Calendar类的缺陷和糟糕的设计,导致用户转投第三方时间库,比如joda-time,所以在Java 8提供了高质量的时间和日期支持,在java.time包中整合了很多 Joda-time 的特性,常用的包邮LocalDate、LocalTime、Instant、Duration、Period
⭐️ 改进历史问题
LocalDate类是一个不可变对象,它只提供简单的日子,并不包含当天的时间信息,也不附带任何和时区相关的信息。
⭐️Java 8 中简单的日期类
LocalTime用来表示当天时间的对象,并不包含日期信息,也不附带任何和时区相关的信息
⭐️Java 8 中简单的时间类
LocalDateTime 合并时间和日期,它是 LocalDate和LocalTime的合体,它同时表示日期和时间,但不带有市区信息
⭐️Java 8 中简单的时间和日期对象的合体类
TemporalAdjuster提供了大量对于常见的用力,日期和时间的API,例如将日期调整到下周日,下个工作日,或者本月的最后一天
⭐主要用于修改时间
DateTimeFormatter 用于解析日期时间对象,打印已急输出,对于老的java.util.DateFormat相比较,所有的DateTimeFormatter实例都是线程安全的
⭐️获取本地时间字符串可以写成:
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"))
感觉Java 8所有的特性,都变成了链式变成,或者说是函数式编程
Java 8新增的java.time.ZoneId类是老版java.util.TimeZone的替代品,新版的日期和时间API的时区的处理被极大的简化了,它的设计目标就是要让你无需为时区处理的复杂和繁琐而操心,跟其他日期和时间类一样,ZoneId类也是无法修改的
⭐️记录就好,时区这章太无聊,看的想睡觉
命令式编程:
一般通过编程实现一个系统,有两种思考方式,一种专注于如何实现,比如:首先做这个,紧接着更新那个,然后……,举个例子,如果你希望通过计算机找出列表中最昂贵的事务,通常需要执行一系列的命令
从列表中取出一个事务,将其与临时最昂贵事务进行比较,
如果事务开销更大,就将临时昂贵的事务设置为该事务,
接着从列表中取出下一个事务,并重复上述操作,
这种“如何做”风格编程非常适合经典的面向对象编程,有时我们也称之为“命令式”编程,因为它的特点是它的指令和计算机底层的汇编非常接近,比如赋值,条件分支,以及循环
⭐️作者对于现在编程模式的思考
函数式编程:
这一种方式则更加关注要做什么,比如Stream API查询一个对象,这种查询把最终实现的细节留给了函数库,我们把这种思想称之为“内部迭代”,它的巨大优势在于你的查询语句现在读起来像是问题的陈述,由于采用了这种方法,我们马上能理解它的功能,比理解一系列的命令要简洁的多,采用“函数式编程”风格由你指定规则,给出希望实现的目标,让系统来决定如何实现这个目标,它带来的好处非常明显,用这种方式编程的代码更加接近问题陈述
⭐️对于函数式编程的思考
函数式编程的世界里,如果函数,比如Comparator.comparing 能满足下面任一要求就可以被称为高阶函数(higher-order function):
接受至少一个函数作为参数
返回的结果是一个函数
⭐️comparing(e1,e2) 可以直接排序List
匿名类有着不尽如人意的特性,会对应用程序的性能带来负面的影响:
1.编译器会为每个匿名类生成一个新的.class文件,这些新生成的类文件的文件名通常以ClassName$1这种形式呈现,其中ClassName是雷明磊出现的类名字,紧跟着一个美元符号和数字,生成大量的类文件是不利的,因为每个类文件在使用之前都需要加载和验证,这会直接影响应用的启动性能
2.如果你为了实现一个比较器,使用了一百多个不同的Lambda表达式,这意味着该比较会有一百多个不同的子类型,这种情况下,JVM的运行时性能条有会变得更加困难
⭐️匿名类的负面