Java8 : 新特性

写在前面

对于 Lambda 表达式、Stream 流的好奇源于一道算法题的惊艳解法

题目

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。 由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。

如果不存在则输出0。

我的解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
//val 代表当前确定的最小值
int val = 0;
//count 表示当前对应的最小值 出现/抵消 剩下的次数
int count = 0;
//遍历数组
for(int i = 0;i<array.length;i++){
//若 count 为0 则需要
if(count == 0){
val = array[i];
count++;
}else{
if(val == array[i]){
count++;
}else{
count--;
}
}
}

count = 0;
for(int i = 0;i<array.length;i++){
if(array[i] == val){
count++;
}
}
if(count > array.length / 2)return val;
else return 0;
}
}

三行代码

1
2
3
4
5
public int MoreThanHalfNum_Solution(int [] array) {
Arrays.sort(array);
int i=array[array.length/2];
return IntStream.of(array).filter(k->k==i).count()>array.length/2?i:0;
}

从以上的代码可以看出来,Java8 新特性所展现出来的简洁性与优雅性令人印象深刻。

关于新特性

相信很多同学在刷算法题的时候都会遇到排序的问题,特别是对 ArrayList 的排序,如果在题目没有明确要考察排序算法的情况下大多数同学都会用到 Collections.sort() 函数,一般情况下只需要直接把 ArrayList 放进去就可以了,但是在复杂情况下是需要自行定义 Compartor 接口的,而在这时使用新特性可大大简化冗余的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 例如:
* 对于 ArrayList<ArrayList<Integer>> 对内层 ArrayList size进行排序
* “茴”字的三种写法 / 孔乙己梗
**/

//普通写法
Collections.sort(list, new Comparator<ArrayList<Integer>>() {
@Override
public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
return o1.size() - o2.size();
}
});

//Comparator 比较器进阶写法
Comparator.comparingInt(ArrayList::size)

//Lambda 表达式写法
Collections.sort(result,(o1,o2)->o2.size() - o1.size());

关于 Lambda 表达式

  • Lambda表达式专门针对只有一个方法的接口。
  • 不细展开,面向日常使用的有如下用法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* 
* 以下是替代匿名类的用法
*
**/
//Comparator 比较器的 Lambda 表达式写法
Collections.sort(list,(o1,o2)-> * );
//*处填写比较的属性 例如o2.size() - o1.size()

//线程的 Lambda 表达式写法
Thread t = new Thread(() -> * );
//*处填写原本Run()函数要执行的函数体 例如System.out.println("...")

/*
* 以下是以流水线的方式处理数据的用法(会在稍后 Stream 中解释)
*
**/
List<Integer> list = Arrays.asList(4,7,6,3,2,1,7,8,8,5,10);
//过滤出偶数列表 [4,6,8,8,10]
List<Integer> evens = list.stream().filter(i -> i % 2 == 0).collect(Collectors.toList());

/*
* 以下是 foreach 的 Lambda 表达式写法
*
**/
for (String s : strs) {
System.out.println(s);
}
//简化
strs.stream().forEach(s -> System.out.println(s));
//还能简化
strs.stream().forEach(System.out::println);
//继续简化 hhh
strs.forEach(System.out::println);
/*
* 以上涉及到了 Lambda 表达式的方法引用
* objectName::instanceMethod 对象::实例方法 将lambda的参数当做方法的参数使用
* ClassName::staticMethod 类::静态方法 将lambda的参数当做方法的参数使用
* ClassName::instanceMethod 类::实例方法 将lambda的第一个参数当做方法的调用者,其他的参数作为方法的参数。
**/

关于 Stream 流

获取流

  • List 集合类可以直接用 list.stream() 获取流。
  • Arrays 数组可以使用 Arrays.stream(array) 获取流。

中间操作

filter(lambda)
将结果为 false 的元素过滤掉
map(lambda)
转换元素的值
flatMap(fun)
多对多转换元素的值
limit(n)
保留前 n 个元素
skip(n)
跳过前 n 个元素
distinct()
去重元素
sorted()
Comparable 元素的流排序
sorted(lambda)
将流元素排序

终结操作

约简操作

max(lambda)
最大值
min(lambda)
最小值
count()
统计数量
findFirst()
返回第一个元素
findAny()
返回任意元素
anyMatch(Predicate)
任意元素匹配时返回 true
allMatch(Predicate)
所有元素匹配时返回 true
noneMatch(Predicate)
没有元素匹配时返回 true

收集操作

iterator()
迭代器
forEach(fun)
遍历元素作为 fun 方法入参
toArray()
转化为数组
toArray(T[] :: new)
转换为指定类型后转化为数组
collect(Collector)
使用 Collector 收集器收集元素

收集器

Collectors.toList()
Collectors.toSet()
Collectors.toMap(fun1, fun2)

总结

1
2
3
4
5
6
7
public int MoreThanHalfNum_Solution(int [] array) {
//排序
Arrays.sort(array);
//找出数组中间元素 因为目标是大于数组长度一半的元素 所以排序后 若存在此元素 数组中间元素一定为该数
int i=array[array.length/2];
return IntStream.of(array).filter(k->k==i).count()>array.length/2?i:0;
}

回到开篇的三行代码,分析最后一行,可以看出此处使用 IntStream.of(array) 获取流,换成Arrays.stream(array) 也可以,然后 filter(k->k==i).count() 找出所有等于数组中间元素的数并统计个数,最后是一个三目运算符,累计个数 > 一半元素为 true 则输出该元素 i ,否则输出 0

搜索了很久,没有找到其他的使用 Stream 做的算法题,可能这种方法对算法题并没有什么提升吧 hhh。如果同学们发现有使用 Stream 做起来很方便的题目也可以发到我的邮箱一起交流 `ericwongmoon@outlook.com` 。