蕉为什么会越来越绿?
对于我个人而言,看到同事用两三行解决了我十几行代码干的事情,而我却看不怎么懂他的代码的时候,恼羞成怒中带着焦虑和羡慕嫉妒恨。开个玩笑,同事关系还是要维持好的,恨是不存在的,但确实会焦虑,所以也引发了对我对自身的一些思考,java8也已经不是新的东西了,但我不会,而且看到同事代码的第一反应居然是天呐,可读性好差0.0。
我不知道大家遇到这样的情况会不会有和我一样的反应,但确实思考过后,剩下的是焦虑和愧疚,开发行业的技术更新换代的很快,我想,java也一样,他也是需要往更深的方向发展,期间可能会有语法上面的变化,所以,为了解决问题,我把JAVA8的这些新特性也学一下。以下是我的学习过程,发布上来也方便自己查阅。
1 lambda表达式
长什么样?
长这样:() - > 5
反正我第一次看见是懵逼的。。。
什么意思?
语法:
(param1,param2,param3 ...,paramN)- > { //代码块; }
- 首先我们知道lambda表达式,表达的是接口函数
- 箭头左侧是函数的逗号分隔的形式参数列表
- 箭头右侧是函数体代码
示例:
public class Demo{
//抽象功能接口
interface Printer{
void print(String val);
}
//通过参数传递功能接口
public void printSomething(String val,Printer p){
printer.print(val);
}
//原来的操作
public static void main(String[] args){
Demo d = new Demo();
Printer p = new Printer(){
@Override
public void print(String val){
System.out.println(val);
}
};
d.printSomething("啦啦啦", p);
}
//使用lambda语法
public static void main(String[] args){
Demo d = new Demo();
Printer p = (String val) -> {System.out.println(val);};
d.printSomething("啦啦啦", p);
}
}
lambda表达式可以让我们的代码更加简洁。
甚至,还可以再简化
//示例
Printer p = (String val) -> {System.out.println(val);};
//去掉参数类型
Printer p = (val) -> {System.out.println(val);};
//去掉参数括号
Printer p = val -> {System.out.println(val);};
//去掉函数体花括号
Printer p = val -> System.out.println(val);
- 即使没有在箭头的左侧指定参数的类型,编译器也会从接口方法的形式参数中推断出其类型
- 当只有一个参数的时候,我们完全可以省略参数的括号
- 当函数体只有一行的时候,我们完全可以省略函数体花括号
不带参数(可以用空括号代替):
() -> System.out.println("啦啦啦");
最终改造代码为:
public static void main(String[] args){
Demo d = new Demo();
demo.printSomething("啦啦啦", p -> System.out.println(p));
}
两行代码就搞定了。
2 Java Stream API
是什么?做什么用的?
Stream 可以提高我们处理集合类数据的效率。他就像一个流水线,在这条流水线上有很多工作人员来操作他的数据,A处理完流到B,B处理完流到C。。。在没有学这个之前,我们处理集合类型的数据通常通过for循环,一层一层,一次一次,Stream 会显的更加简洁,方便。
操作步骤:
- 把产品放到流水线上(转化为流),称为源操作
- 对产品进行操作,例如包上包装袋(过滤,数据转换等等),称为中间操作
- 流水线到底了,给他拿纸箱装起来(转换为其他类型返回),称为终端操作
工作人员:Filter(过滤)、Map(映射)、sort(排序),distinct(去重)等等
示例:
List<String> strs = ArrayList.asList("one","two","three","four","five");
// Stream操作
List<String> newStrs = strs.stream()
.filter(s -> s.startsWith("t"))
.map(String::toUpperCase)
.sorted()
.collect(toList());
System.out.println(newStrs);
- 使用
stream()
将集合类型数据转化为流水线。 - filter() 工作人员过滤出 “t” 开头的数据,剩下的不要,拿到流水线外。
- map() 工作人员将剩下的每个数据转化为大写。
- sorted() 工作人员给剩下的数据排序。
- collect() 工作人员使用函数toList() 将剩下的数据转换为一个新的List并返回。
所以,输出结果应该是 : [ TWO , THREE ]
2.1 各类型转化为流
数组使用Stream.of() 方法
集合使用集合类对象的stream()方法
文本文件使用Files.lines()方法
示例:
//数组
String[] arr = {"one","two","three","four","five"};
Stream<String> arrStream = Stream.of(arr);
Stream<String> arrStream = Stream.of("one","two","three","four","five");
//集合类
List<String> list = ArrayList.asList("one","two","three","four","five");
Stream<String> listStream = list.stream();
//文本文件
Stream<String> txtStream = Files.lines(Paths.get("demo.txt"));
2.2 filter
作用?
在上面草率的示例中可以看出来,filter这个工作人员的职责是过滤出符合要求的数据。
详细示例(应用到操作对象):
实体类 Employee.java (使用了lombok)
@Data
@AllArgsConstructor
public class Employee{
private Integer id;
private Integer age;
private String sex; //性别
private String firstName;
private String lastName;
}
测试类 StreamFilterTest.java
public class StreamFilterTest{
//新建10个员工对象(后面的示例也基本都会用到这10个员工)
public static void main(String[] args){
Employee e1 = new Employee(1,23,"M","Rick","Beethovan");
Employee e2 = new Employee(2,13,"F","Martina","Hengis");
Employee e3 = new Employee(3,43,"M","Ricky","Martin");
Employee e4 = new Employee(4,26,"M","Jon","Lowman");
Employee e5 = new Employee(5,19,"F","Cristine","Maria");
Employee e6 = new Employee(6,15,"M","David","Feezor");
Employee e7 = new Employee(7,68,"F","Melissa","Roy");
Employee e8 = new Employee(8,79,"M","Alex","Gussin");
Employee e9 = new Employee(9,15,"F","Neetu","Singh");
Employee e10 = new Employee(10,45,"M","Naveen","Jain");
List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
List<Employee> filterEs = employees.stream()
.filter(e -> e.getAge() > 70 && "M".equals(e.getSex()))
.collect(Collectors.toList());
System.out.println(filterEs);
}
}
filter传入的lambda表达式的意思是筛选出年龄大于70且性别为男性的员工。
所以,输出:[Employee(id=8, age=79, gender=M, firstName=Alex, lastName=Gussin)]
filter 入参类型为 Predicate<? super T>,所以如果复用较多的话,可以抽象出来
例如:寻找年龄大于70和性别为男这两个条件
public static Predicate<Employee> ageMore70 = x -> x.getAge() > 70;
public static Predicate<Employee> sexM = x -> "M".equals(x.getSex());
filter中的参数可以如SQL的where一般只有组合
// and 寻找年龄大于70 且 性别为男的员工
.filter(Empolyee.ageMore70 and Empolyee.sexM)
// or 寻找年龄大于70 或者 性别为男的员工
.filter(Empolyee.ageMore70) or Empolyee.sexM)
// negate(相反) 寻找性别不为男的员工 0.0
.filter(Empolyee.sexM.negate())
2.2 map
作用?
对流水线上的每一个数据进行转换操作。
示例:(将所有数据转换为大写)
List<String> strs = Stream.of("a","b","c","d")
.map(String::toUperCase)
.collect(Collectors.toList());
// 等价于
List<String> strs = Stream.of("a","b","c","d")
.map(s -> s.toUpperCase())
.collect(Collectors.toList());
// 复杂点 类型转换
List<Integer> ins = Stream.of("a","b","c","d")
.map(String::length)
.collect(Collectors.toList());
System.out.println(ins); // 输出 1,1,1,1
// 也可以这样
Stream.of("a","b","c","d")
.mapToInt(String::length) //除了mapToInt 还有maoToLong,mapToDouble等等
.forEach(System.out::println);
// 再复杂点 对象类型转换
// 还是前面那10个员工对象,过年了,大家都长了一岁,且性别互换
List<Employee> eys = employees.stream
.peek(e -> { //peek()是一种特殊的map,当没有返回值,或者参数就是返回值的时候可以用peek()
e.setAge(e.getAge() + 1);
e.setSex("M".equals(e.getSex)?"F":"M")
}).collect(Collectors.toList());
System.out.println(eys);
// 除此之外 还有flatMap可以用于操作子流水线
List<String> words = Arrays.asList("hello", "word");
words.stream()
.flatMap(w -> Arrays.stream(w.split(""))) //[h,e,l,l,o,w,o,r,l,d]
.forEach(System.out::println);
2.3 distinct , limit , skip
怎么三个一起来了?太草率了吧!这些都是做什么的?
字面意思。。。
- distinct作用是去重,调用的是equals方法做比较,如果需要自定义,可以重写equals方法。
- limit传入一个整数n,截取[0 , n] 个数据。
- skip传入一个整数n, 跳过n个数据后截取到最后。
示例:
// limit
Stream.of("a","b","c","d").limit(2).forEach(System.out::println); //输出 a,b
// skip
Stream.of("a","b","c","d").skip(2).forEach(System.out::println); //输出 c,d
// distinct
Stream.of("a","b","b","d").distinct(2).forEach(System.out::println); //输出 a,b,d
2.4 sort
作用?
排序,就是这么个作用,可以幻想一个业务场景,就比如前面的10个员工对象,我现在要按照年龄大小排序,用sort的话4行代码就可以搞定了。
先看字符串的排序:
// london是小写的奥
List<String> cities = Arrays.asList(
"Milan",
"london",
"San Francisco",
"Tokyo",
"New Delhi"
);
System.out.println(cities);
//[Milan, london, San Francisco, Tokyo, New Delhi]
cities.sort(String.CASE_INSENSITIVE_ORDER);
System.out.println(cities);
//[london, Milan, New Delhi, San Francisco, Tokyo]
cities.sort(Comparator.naturalOrder());
System.out.println(cities);
//[Milan, New Delhi, San Francisco, Tokyo, london]
- String.CASE_INSENSITIVE_ORDER 大小写不敏感
- Comparator.naturalOrder() 自然排序
整数型排序:
List<Integer> numbers = Arrays.asList(6, 2, 1, 4, 9);
System.out.println(numbers); //[6, 2, 1, 4, 9]
numbers.sort(Comparator.naturalOrder()); //自然排序
System.out.println(numbers); //[1, 2, 4, 6, 9]
numbers.sort(Comparator.reverseOrder()); //倒序排序
System.out.println(numbers); //[9, 6, 4, 2, 1]
- Comparator.reverseOrder() 降序排列
按对象的某个属性进行排列:
// 还是前面那10个员工 按年龄排序
List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
employees.sort(Comparator.comparing(Employee::getAge)); //升序
employees.forEach(System.out::println);
employees.sort(Comparator.comparing(Employee::getAge).reversed()); //降序
也可以使用Comparator链进行组合排序
// 先按年龄降序,再按性别升序 都是倒序的话 把reversed()加在最后
employees.sort(
Comparator.comparing(Employee::getAge).reversed()
.thenComparing(Employee::getSex)
);
employees.forEach(System.out::println);
自定义Comparator排序规则
/**
* 使用匿名类的方式实现Comparator的唯一抽象方法compare
* 小于返回-1 等于返回0 大于返回1
*/
//java8之前的写法
employees.sort(new Comparator<Employee>() {
@Override
public int compare(Employee em1, Employee em2){
if(em1.getAge() == em2.getAge()){
return 0;
}
return em1.getAge() - em2.getAge() > 0 ? -1:1;
}
});
//lambda表达式
employees.sort((em1,em2) -> {
if(em1.getAge() == em2.getAge()){
return 0;
}
return em1.getAge() - em2.getAge() > 0 ? 1:-1;
});
2.5 匹配和查找元素
日常业务中,我们可能会有以下逻辑:
- 是否包含某一个“匹配规则”的元素
- 是否所有的元素都符合某一个“匹配规则”
- 是否所有元素都不符合某一个“匹配规则”
- 查找第一个符合“匹配规则”的元素
- 查找任意一个符合“匹配规则”的元素
如果用for循环写,条件也多,人容易裂开。。。
如果使用Stream:
// 对 又是前面那10个员工
// 是否包含有超过70岁的员工
// 之前抽象出来的谓词逻辑
boolean isHaveMore70 = employees.stream().anyMatch(Employee.ageMore70);
// 使用lambda表达式
boolean isHaveMore70 = employees.stream().anyMatch(e -> e.getAge() > 70);
-
anyMatch() 是否包含某一个“匹配规则”的元素
-
allMatch() 是否所有的元素都符合某一个“匹配规则”
-
noneMatch() 是否所有元素都不符合某一个“匹配规则”
-
findFirst() 查找第一个符合“匹配规则”的元素
写法:
Optional<Employee> employeeOptional = employees.stream().filter(e -> e.getAge() > 40).findFirst(); System.out.println(employeeOptional.get());
-
findAny() 查找任意一个符合“匹配规则”的元素 和findFirst用法相同
-
- 关于Optional
- Optional类代表一个值存在或者不存在。在java8中引入,这样就不用返回null了。
- isPresent() 将在 Optional 包含值的时候返回 true , 否则返回 false 。
- ifPresent(Consumer block) 会在值存在的时候执行给定的代码块。我们在第3章
介绍了 Consumer 函数式接口;它让你传递一个接收 T 类型参数,并返回 void 的Lambda
表达式。 - T get() 会在值存在时返回值,否则?出一个 NoSuchElement 异常。
- T orElse(T other) 会在值存在时返回值,否则返回一个默认值。
2.6 归约操作 reduce
介绍一下?
reduce函数有三个参数:
-
Identity标识:一个元素,它是归约操作的初始值,如果流为空,则为默认结果。
-
Accumulator累加器:具有两个参数的函数:归约运算的部分结果和流的下一个元素。
-
Combiner合并器(可选):当归约并行化时,或当累加器参数的类型与累加器实现的类型不匹配时,用于合并归约操作的部分结果的函数。
-
阶段累加结果作为累加器的第一个参数
-
集合遍历元素作为累加器的第二个参数
Integer类型
reduce初始值为0,累加器可以是lambda表达式,也可以是方法引用。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
int result = numbers
.stream()
.reduce(0, (subtotal, element) -> subtotal + element);
System.out.println(result); //21
int result = numbers
.stream()
.reduce(0, Integer::sum);
System.out.println(result); //21
String类型
不仅可以归约Integer类型,只要累加器参数类型能够匹配,可以对任何类型的集合进行归约计算。
List<String> letters = Arrays.asList("a", "b", "c", "d", "e");
String result = letters
.stream()
.reduce("", (partialString, element) -> partialString + element);
System.out.println(result); //abcde
String result = letters
.stream()
.reduce("", String::concat);
System.out.println(result); //ancde
对象类型
计算所有员工的年龄总和
Integer total = employees.stream().map(Employee::getAge).reduce(0,Integer::sum);
System.out.println(total); //346
- 先由map将员工对象类型转换为年龄(Integer)
- 再对Integer进行归约
2.7 终端操作
回想一下
前面的Stream的示例尾端,出现过两种:
一个是forEach,字面意思也就是循环遍历的;
另一个是collect,这个就是收集的意思。
示例:
// 收集到Set
.collect(Collectors.toSet());
// 收集到List
.collect(Collectors.toList());
// 通用收集
.collect(Collectors.toCollection(LinkedList::new));
.collect(Collectors.toCollection(PriorityQueue::new));
// 收集到数组
.toArray(String[]::new);
//收集到Map
.distinct()
.collect(Collectors.toMap(
Function.identity(), //元素输入就是输出,作为key
s -> (int) s.chars().distinct().count()// 输入元素的不同的字母个数,作为value
));
// 分组收集
.collect(Collectors.groupingBy(
s -> s.charAt(0) , //根据元素首字母分组,相同的在一组
// counting() // 加上这一行代码可以实现分组统计
));
2.8 其他的一些可能会用到的方法
boolean containsTwo = IntStream.of(1, 2, 3).anyMatch(i -> i == 2);
// 判断管道中是否包含2,结果是: true
long nrOfAnimals = Stream.of(
"Monkey", "Lion", "Giraffe", "Lemur"
).count();
// 管道中元素数据总计结果nrOfAnimals: 4
int sum = IntStream.of(1, 2, 3).sum();
// 管道中元素数据累加结果sum: 6
OptionalDouble average = IntStream.of(1, 2, 3).average();
//管道中元素数据平均值average: OptionalDouble[2.0]
int max = IntStream.of(1, 2, 3).max().orElse(0);
//管道中元素数据最大值max: 3
IntSummaryStatistics statistics = IntStream.of(1, 2, 3).summaryStatistics();
// 全面的统计结果statistics: IntSummaryStatistics{count=3, sum=6, min=1, average=2.000000, max=3}
以上,就是我当前阶段对于java8的一些认识,应该算是初步掌握了lambda表达式及Stream的基础用法,像Stream的话以上的示例全部都是针对串行,他其实还有并行的操作,根据大佬们的研究,Stream的并行效率是远高于之前的for循环的,但是由于时间的关系,我当前阶段就先学到这吧。
其实这篇文章更像是我看这本书的笔记吧,示例基本都是一样的,本文的内容可以算是我对这本书的一个精简以及一些自己的认识,当然也非常推荐大家可以去看看,内容很多且在持续更新,膜拜字母哥,感谢字母哥大佬。