学习笔记之——Java 8
导言:Java语言随着Java1.0发布以来,不断更新迭代,而Java 8中新功能是自Java1.0发布以来,Java发生的最大的变化。它提供了强大的新语汇和新设计模式,学习它能让你编写更清楚,更简洁的代码。
简单说完为什么学习Java 8后,那我们就来开始到真正的学习中吧。
首先,Java 8有哪些新特性呢?
1. Lambda 表达式
2. 函数式接口
3. 方法引用与构造器引用
4. Stream API
5. 接口中的默认方法与静态方法
6. 新时间日期API
7. 其他新特性
其中,本文只讨论比较重要的三个特性即:
Lambda表达式
函数式接口
Stream API
行为参数化:
在学习Lambda表达式之前,先了解一个简单的概念:行为参数化
什么是行为参数化?
行为参数化就是可以帮助你处理频繁变更的需求的一种软件开发模式。一言以蔽之,它意味着拿出一个代
码块,把它准备好却不去执行它。这个代码块以后可以被你程序的其他部分调用,这意味着你可以推迟这
块代码的执行
这是参考Java 8实战的概念分析,听起来似乎有点拗口,没有什么是举栗子解决不了滴,如果有的话,那就举两个[doge]
比如你妈妈有一天需要你去帮她买超市买两瓶酱油,于是她给了你一系列的指令:先下楼,去楼下忘左拐500米的小巷的CSDN超市,去超市二楼买XXX品牌的酱油,好了,这个指令你知道怎么做了,可是第二天,你妈妈需要你去超市买另一个牌子的酱油了,又或者是以后去别的超市买酱油,更恐怖的来了,有一天你妈妈不让你买酱油了,而是做一件完全不一样的事,那么,如果这样的话,交给计算机,你要编写几个函数,太多辣,根本数不过来(家庭弟位显现出来)。可是,你有没有想过,只需要一个函数,然后将行为作为参数传入该函数,再去执行返回自己想要的结果,大大节省了工作量。
可能我们还是没能很好理解行为参数化带来的好处,那么我们不妨看看Java 8实战(图灵程序设计图书)的栗子叭(需要PDF电子图书的小伙伴可以私信我领取哦)
作为乙方的农民伯伯和甲方的买家,为了应对甲方的需求,农民伯伯请求你来编写程序筛选甲方需要的苹果。
Day1
甲方:今天我需要绿色的苹果
你很高兴也很容易地编写出下面的程序:
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>(); //累积苹果的列表
for(Apple apple: inventory){
if( "green".equals(apple.getColor() ) { //仅仅选出绿苹果
result.add(apple);
}
}
return result;
}
又或者是更灵活多变的程序:可以匹配任意颜色(通过传参)
public static List<Apple> filterApplesByColor(List<Apple> inventory,
String color) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory){
if ( apple.getColor().equals(color) ) {
result.add(apple);
}
}
return result;
}
//测试方法
List<Apple> greenApples = filterApplesByColor(inventory, "green");
List<Apple> redApples = filterApplesByColor(inventory, "red");
Day2——Day5
甲方:我不仅需要红苹果,现在也需要比150g重的大苹果了!
—接下来的每天甲方提出了更多要求…(像极了甲方爸爸)
你:我把每个能想到的属性都加上去呗,于是乎有了:
public static List<Apple> filterApples(List<Apple> inventory, String color,
int weight, boolean flag) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory){
if ( (flag && apple.getColor().equals(color)) ||
(!flag && apple.getWeight() > weight) ){ //十分笨拙的选择颜色或重量的方式
result.add(apple);
}
}
return result;
}
//测试代码:
List<Apple> greenApples = filterApples(inventory, "green", 0, true);
List<Apple> heavyApples = filterApples(inventory, "", 150, false);
那么许多问题接踵而来:客户端代码冗余糟糕可读性差!那么怎么解决呢?
★行为参数化!
我只传给你一个苹果先,先不告诉你要怎么筛选,等到了你需要告诉我的时候我再去根据需求筛选,岂不美哉!
定义选择标准的接口:
public interface ApplePredicate{
boolean test (Apple apple);
}
实现接口中的方法:
public class AppleHeavyWeightPredicate implements ApplePredicate{ //仅仅选出重的苹果
public boolean test(Apple apple){
return apple.getWeight() > 150;
}
}
public class AppleGreenColorPredicate implements ApplePredicate{ //仅仅选出绿苹果
public boolean test(Apple apple){
return "green".equals(apple.getColor());
}
}
注意:上述案例仍然没能完成行为参数化
到这里按理来讲应该都设计完毕了,可是这样做后期维护成本高,每每添加一种筛选都需要新增一个类/方法
于是,你可能会想,我能不能将接口和实体传入实现类,根据抽象条件筛选呢?
public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate p){
List<Apple> result = new ArrayList<>();
for(Apple apple: inventory){
if(p.test(apple)){ //谓词对象封装了测试苹果的条件
result.add(apple);
}
}
return result;
}
引用一下原文的话
这里值得停下来小小地庆祝一下。这段代码比我们第一次尝试的时候灵活多了,读起来、用起来也更容易!现在你可以创建不同的ApplePredicate对象,并将它们传递给filterApples方法。免费的灵活
性!比如,如果农民让你找出所有重量超过150克的红苹果,你只需要创建一个类来实现
ApplePredicate就行了。你的代码现在足够灵活,可以应对任何涉及苹果属性的需求变更了:
public class AppleRedAndHeavyPredicate implements ApplePredicate{
public boolean test(Apple apple){
return "red".equals(apple.getColor())&& apple.getWeight() > 150;
}
}
List<Apple> redAndHeavyApples =
filterApples(inventory, new AppleRedAndHeavyPredicate());
你已经做成了一件很酷的事:filterApples方法的行为取决于你通过ApplePredicate对象传递的代
码。换句话说,你把filterApples方法的行为参数化了!
如果到这里仍然能理解这些做法,那真的很棒!不过呢,学习才刚刚起步,我们需要再往下深究
通过上面的代码尝试,你或许发现每次都要编写实现类实现接口,而有些东西我只要用一次,那么,怎么做?
匿名内部类oeds(偶尔的神)!
还是上面的代码,我们做第五次优化:
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
//直接内联参数化filterapples方法的
public boolean test(Apple apple){
return "red".equals(apple.getColor());
}
});
不过我第一次看匿名类看的真的是懵懵懵,它的行为不过是将ApplePredicate函数的实现写在内部,也没有了对象…
需要再去寻找参考巩固理解
我们引入最后一个优化:Lambda表达式:便于我们的初步进入Java8超级重要的一个部分的学习
你有没有想过,上面较难理解的匿名类可以简简单单通过一句话描述透彻,如果有,那就是Lambda表达式(yyds)。话不多说,直接上代码:
List<Apple> result =
filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));
也许看得一脸懵逼,没关系,在后面简单讲述Lambda表达式后,你或许会觉得这清新脱俗.
Lambda表达式
你可以先了解的:
可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。这个定义够大的,让我们慢慢道来:
匿名——不知道类的名字
函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。
Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
简洁——无需像匿名类那样写很多模板代码
①Lambda基本语法:
三大部分:两苹果直接的重量比较
->:操作符,区分左部分与右部分
左部分:参数列表(传入需要操作的参数)
右部分:Lambda体,也是执行代码块
②在哪里以及如何使用Lambda:
我们暂且仅介绍一个比较重要的:那就是在函数式接口编程时应用Lambda
函数式接口:仅仅定义一个抽象方法的接口,通常在任意函数式接口上添加@FunctionalInterface注解标明这是一个函数式接口
我们这介绍四个重要且已经定义好的函数式接口:
消费性接口Consumer;好比消费者去游乐场玩,带着钱去空着手出,都消费啦
供给型接口Supplier: 这个也好理解,比如部分公司老总,啥也不用做,但因为有“供给型接口”,啥也没传进去,但是就是能返回“钱”/(ㄒoㄒ)/~~(当然啦是很少一部分人,我们只需好好努力【doge】)
函数型接口Function:这个就比较符合现实,一分耕耘一分收获,也比较好理解
断言型接口:Predicate:传入一个参数,它会返回一个布尔值
还有其它的函数式接口,我也先一股脑儿放这啦
如果你说,哎呀,记不住好麻烦,没关系!我们可以自定义函数式接口呢!
方法不难,定义想要的接口(只能有一个抽象方法),加上@FunctionalInterface注解就可以啦,这里贴个小示例:
@FunctionalInterface
public interface MyFunction2<T, R> { //T、R为参数类型
public R getValue(T t1, T t2);
}
③到现在为止也介绍了许多内容了,然而,实践才是检验真理的唯一标准,不如我们康康项目中如何应用Lambda表达式叭
项目需求:
设计一个程序,这个程序在接下来的一段时间中的需求更迭会特别快,主要是读取文档文件中的字符串,可能一次读一行,一次读两行,或者返回使用最频繁的词,你会怎么设计?
First:考虑到需求不断变更,维护工作量大,于是记得行为参数化
你需要一种方法把行为传递给processFile,以便它可以利用BufferedReader执行不同的行为。而谈及传递行为,Lambda表达式是一个非常不错的选择~
Second:定义所需要的函数式接口:
既然你知道可能会用及Lambda,量身定做所需要的接口又是非常之重要的。
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader br)throws IOException;
}
Third:执行你所需要执行的行为:注意,这里并没有完成行为,而是将完成的动作交给Lambda体待程序真正需要运行时方执行
//第三步,执行一个行为,将行为(接口)作为参数
public static String processFile(BufferedReaderProcessor p) throws IOException{
try (BufferedReader br=
new BufferedReader(new FileReader("D:\\课件\\java8\\source\\java8-day02\\src\\com\\atguigu\\Lambda1\\data.txt"))){
return p.process(br);
}
}
Fourth:传递Lambda体
//第四步:传递lambda
@Test
public void test1() throws IOException {
//读一行
String oneLine=
processFile((BufferedReader br) -> br.readLine());
System.out.println(oneLine);
}
@Test
public void test2() throws IOException{
//读两行
String twoLine=
processFile((BufferedReader br) -> br.readLine()+br.readLine());
System.out.println(twoLine);
}
到这里,你有没有更为清晰呢?尝试理解并熟识这个步骤哦~
④简单讲讲类型检查和类型推断:
Lambda表达式的参数类型的检查和推断都可以由编译器推断而来,你需要知道:
1)关于类型检查
2)关于类型推断:
请注意,有时候显式写出类型更易读,有时候去掉它们更易读。没有什么法则说哪种更好;对于如何让代码更易读,程序员必须做出自己的选择
Comparator<Apple> c =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); //没有类型推断
Comparator<Apple> c =
(a1, a2) -> a1.getWeight().compareTo(a2.getWeight()); //有类型推断
⑤方法引用
为什么使用方法引用?
方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。在一些情况下,比起使用Lambda表达式,它们似乎更易读,感觉也更自然
举两三个栗子我们一起理解一下叭
1)打印参数x:
(x) -> {System.out.println(x)}; 等价于 System.out::println
2)开方:
BinaryOperator< Double> bo=(x,y) -> Math.pow(x,y)
等价于
BinaryOperator< Double> bo=Math::pow
是不是看起来精简易懂了许多呢?
我们来最后划上一个句号叭:
案例:
用不同的排序策略给苹果排序
步骤:
- 传递代码
- 使用匿名类
- 使用Lambda表达式
- 使用方法引用
传递代码:
//Java 8的API已经为你提供了一个List可用的sort方法,你不用自己去实现它
void sort(Comparator<? super E> c)
public class AppleComparator implements Comparator<Apple> {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
}
inventory.sort(new AppleComparator());
使用匿名类:(免去繁琐的创建对象调用过程,实现即编即调)
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
使用Lambda表达式:(简化代码,只取实用的那一部分)
inventory.sort((Apple a1, Apple a2)
-> a1.getWeight().compareTo(a2.getWeight())
);
使用方法引用:(方法引用就是替代那些转发参数的Lambda表达式的语法糖,你可以用方法引用让你的代码更简洁)
inventory.sort(comparing(Apple::getWeight));
到这里,Lambda表达式都已经大致介绍完成啦,还有更深入的知识我们以后有时间再慢慢探讨,或者是自己参考文献学习哦。那我们步入最后一个大纲的学习!冲冲冲
Stream API
①先知:
学习Stream API 前,我们不妨先了解几个概念:
1)流:
Stream(流)到底是什么呢?
*简单概括:流就是数据渠道,用于操作数据源(集合、数组等)所产生的元素序列,“集合讲数据,流讲计算”
2)API:大佬替小白编写好的封装函数(通常我们只需要知道如何使用即可,不必了解底层)
Java API主要以类的形式呈现
详细可参考作者:lph-China的:博客
3)Stream API:
使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。
4)关于流的一个知识:只能消费(遍历)一次
List<String> title = Arrays.asList("Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println); ←─打印标题中的每个单词
s.forEach(System.out::println); ←─java.lang.IllegalStateException:流已被操作或关闭
②Stream的三大步骤:
1)创建Stream:
一个数据源,获取一个流
2)中间操作:
一个中间操作链,对数据源的数据进行处理
3)终止操作:’
一个终止操作,执行中间操作链,并产生结果【没有终止操作中间操作也不会得以运行】
创建流
Java 8中Collection接口被拓展,提供了两个获取流的方法:
- default Stream < E > stream():返回一个顺序流
- default Stream < E> parallelStream():返回一个并行流
何为顺序流而又何为并行流呢?
顺序流:
简单理解为按照任务一步步往下完成,直至总任务完成
并行流:
可以理解为将任务拆解成多个小任务,由多人同时合作完成一个大任务。
并行流所用框架即为:Fork/Join框架:(引用博主:Bug_stack图片)
创建流:(了解即可)
1)由值创建流:
使用静态方法Stream.of,通过显示创建一个流,它可以接受任意数量的参数。
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
2)通过数组创建流:
你可以使用静态方法Arrays.stream从数组创建一个流。它接受一个数组作为参数。例如,你可以将一个原始类型int的数组转换成一个IntStream
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum(); //41
3)创建无限流:
可以使用静态方法Stream.iterate()【迭代】或者Stream.generate()【生成】创建无限流
实例:通过无限流创建来模拟斐波那契数列的运算
//使用StreamAPI生成斐波那契数列
@Test
public void test(){
Stream.iterate(new int[]{0,1},
t->new int[]{t[1],t[0]+t[1]})
.limit(20) //限制输出20个
.forEach(t->System.out.println("("+t[0]+","+t[1]+")"));
}
运行结果:
中间操作
1.筛选与切片
学习前面的知识后,你应该对filter()方法有了一定的理解,即接受一个布尔值为参数,返回相对应的流.
.filter(): 筛选一个流
.distinct(): 筛选流的基础上去除重复元素
.limit(): 截断流,像上述斐波那契数列的截断,若没有这步,它会无限一直运行下去…应该就要换新机了
.skip(): 跳过元素
2.映射
有时候呢,只需要返回我们想要的结果,比如,教务系统查找某学期挂科3门以上的同学的姓名,而不是要该同学的全部信息,那么使用映射可以做到这种效果哦.
.map(Function f)
.flatMap(Function f)
关于map:是比较常规的操作
//给定一个单词列表,返回另一个列表,显示每个单词有几个字母
List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(toList());
关于flatMap:流的扁平化
给你一组单词列表,让你返回另一个不重复的且含有单词列表中所有字符的集合
你也许会想到以下解法:
//寻找单词列表中所有字符,不重复!
List<String> list= Arrays.asList("Hello","World");
list.stream()
.map((words) -> words.split(""))
.distinct()
.collect(toList());
//测试方法
for (String s:list) {
System.out.println(s);
}
可小白我验证之后却得出这样的结果:
这是为什么呢?
原来这里map返回的流类型是Stream<String[]>类型的,而这样的话split函数无法用于它,我们真正想要的是Stream< String >类型的,简单来讲就是我们操作的类型都不正确,原本字符串可以用split进行分割,但是给你n个字符串组成的集合,而你只能在集合中“动手”,自然就操作不了啦(个人鄙见,欢迎读者纠错)
理清了为什么,那需要怎么做呢?Java给我们提供了一个很棒的解决方案:
.flatMap()
我们再更改一下上面的代码:
List<String> words=Arrays.asList("Hello","World");
List<String> stream= words.stream()
.map(w->w.split("")).flatMap(Arrays::stream)
.distinct().collect(Collectors.toList());
for (String s:stream) {
System.out.println(s);
}
结果测试:
3.排序
在映射出结果集后,有些情境下往往需要我们排序比较,比如排序东京奥运会的奖牌榜,先按金牌块数比较,然后是银牌,最后是铜牌…
我们依旧以案例来理解掌握排序的奥义:
1.将员工表中的工资按从低到高自然排序
2.将员工表中的员工信息先按年龄进行排序,若年龄相同再按名字排序
/***
sorted()——自然排序
sorted(Comparator com)——定制排序
*/
@Test
public void test2(){
emps.stream()
.map(Employee::getSalary)
.sorted()
.forEach(System.out::println);
System.out.println("------------------------------------");
emps.stream()
.sorted((x, y) -> {
if(x.getAge() == y.getAge()){
return x.getName().compareTo(y.getName());
}else{
return Integer.compare(x.getAge(), y.getAge());
}
}).forEach(System.out::println);
}
终止操作
流的终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,包括List、Integer、Void
1.查找与匹配
这里小编就从尚硅谷课件中摘取常用的几个方法,大家理解下过了就行儿
2.收集
常见的收集方法
好啦,到这里Java 8的三个比较重要的新特性就大概“浑水摸鱼”地学习完了,需要学习资料的私信我领取即可,后期我也会尝试出框架、算法类的学习日志,大家一起借鉴学习叭,奥力给!
【注】:
1.本文有任何错漏处都欢迎评论区或私信积极指正
2.本文参考《Java 8实战》及尚硅谷《Java 8》学习资料,以及其他博主的引用皆以说明,如有侵权请联系,谢谢!