简单好用的lambda与Stream

lambda表达式是java8的新特性,使java在编程中更简单,代码更简洁可读,避免了冗长的内部类、匿名类,下面通过栗子看看lambda有哪些应用吧*^-^*

ForEach 与 lambda

1
2
3
4
5
6
7
8
9
10
11
12
13
Map<String, Integer> items = new HashMap<>();
items.put("A", 10);
items.put("B", 20);
items.put("C", 30);
items.put("D", 40);
// 原来的写法
for (Map.Entry<String, Integer> entry : items.entrySet()) {
System.out.println("item : " + entry.getKey() + "count : " + entry.getValue());
}
// lambda
items.forEach((k, v)->{
System.out.println("item : " + k + "count : " + v);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
for(String item : list){
System.out.println(item);
}
// lambda 参数只有一个可以省略小括号,后面的表达式只有一句可以省略{},return和分号
list.forEach(x->System.out.println(x));
// 上一句的简写
list.forEach(System.out::println);

匿名内部类 与 lambda

1
2
3
4
5
6
7
8
9
Thread t = new Thread(new Runnable() {
public void run() {
System.out.println("How a u...");
}
});
t.start();
// lamda
Thread t1 = new Thread(()->System.out.println("I'm fine...thx"));
t1.start();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String[] arr = {"a", "b", "c"};
List<String> list = Arrays.asList(arr);
// 匿名内部类
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
// lambda
Collections.sort(list, (s1, s2)->{
return s1.compareTo(s2);
});
// 当表达式只有一句时 return 可以省略
Collections.sort(list, (s1, s2)->s1.compareTo(s2));

lambda语法

一般语法

( 参数列表 )->{ 表达式语句 }

(Type parameter, Type parameter,...) -> {statements...}

单参数

parameter->{statements}

当 lambda 表达式的参数个数只有一个,可以省略小括号

单语句

parameter->statement

当 lambda 表达式只有一条语句时,可以省略大括号,return 和结尾的分号

方法引用

objectName::instanceMethodClassName::staticMethod

把 lambda 表达式的参数直接当成 instanceMethod 或 staticMethod 的参数,比如System.out::println等同于x->System.out.println(x)Math::max等同于(x,y)->Math::max(x,y)

ClassName::instanceMethodx->x.instanceMethod()的简化,eg: Student::getName等同于x->x.getName()instanceMethod是一个无参的函数

访问外部变量

只能访问外部变量,不能在 lambda 内部修改,编译器将隐式将外部变量当成 final 处理,匿名函数和 lambda 函数内对外部变量的访问是一样的,都是只能访问 final 修饰的,没有 final 修饰视为 final 修饰,不可改变值。

Stream 与 lambda

流式处理Stream 是java8的新特性,大大简化了对集合的操作,本节只看顺序流式处理,请先看看下面的栗子*^-^*

找出集合中的偶数

1
2
3
4
5
6
7
8
9
10
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = new ArrayList<>();
for (final int n : nums) {
if (n % 2 == 0) {
evens.add(n);
}
}
// stream
evens = nums.stream().filter(num->num%2 == 0).collect(Collectors.toList());
evens.forEach(System.out::println);

上面的例子中,通过 stream() 将集合转成流,filter() 对流进行过滤,筛选后的结果由收集器 collect() 对结果进行封装处理

stream

中间操作

我们先定义一个学生实体类,用来说明中间操作有什么应用

1
2
3
4
5
6
7
8
9
public class Student {
private int id;
private String name;
private int age;
private int score;
private String school;
private String document;
//... Getters,Setters,Constructor等省略
}
1
2
3
4
5
6
7
8
9
List<Student> students = new ArrayList<Student>() {
{
add(new Student(2013201319, "zcl", 18, 100, "HEU", "computer"));
add(new Student(2013201320, "zjm", 20, 90, "HEU", "computer"));
add(new Student(2013201321, "tyf", 19, 95, "HEU", "software"));
add(new Student(2013201322, "zzh", 22, 97, "HIT", "computer"));
add(new Student(2013201323, "ggy", 21, 89, "HIT", "software"));
}
};

过滤

filter

过滤姓名以‘z’开头的

1
2
List<Student> z_list = students.stream().filter(stu->stu.getName()
.charAt(0) == 'z').collect(Collectors.toList());
distinct

找出偶数后去掉重复的

1
2
List<Integer> evens = nums.stream().filter(num->num%2==0)
.distinct().collect(Collectors.toList());
sort&limit

找出成绩排名前三的同学

1
2
List<Student> scoreList = this.students.stream().sorted((s1, s2)->
s2.getScore() - s1.getScore()).limit(3).collect(Collectors.toList());
skip

找出成绩排名第三名以后的同学

1
2
List<Student> scoreList = this.students.stream().sorted((s1, s2)->
s2.getScore() - s1.getScore()).skip(3).collect(Collectors.toList());

映射

当我们只想得到一个类的某一个属性值

map

想筛选出所有成绩大于80分的同学的名字,总分和平均分 (mapToInt, mapToDouble)

1
2
3
4
List<String> names = this.students.stream().filter(s->s.getScore()>80)
.map(Student::getName).collect(Collectors.toList());
int sum = this.students.stream().mapToInt(Student::getScore).sum();
OptionalDouble average_score = this.students.stream().mapToDouble(Student::getScore).average();
flagMap

现在想求几个字符串中出现的字符,比如String[] strs = {"java", "is", "easy", "to", "use"};,用方法一,先将集合中的每个字符串分解成字符数组,在 distinct 操作是在字符数组之间找的,不是以字符为单位;方法二,flagMap 将一个流中每个值都转成一个个流,然后再将这些流扁平化成为一个流,这样可以达到目的

1
2
3
4
5
6
7
8
9
10
11
12
// 方法一
Arrays.stream(strs).map(s->s.split(""))
.distinct().collect(Collectors.toList()).forEach(l->{
Arrays.asList(l).forEach(s -> System.out.print(s + " "));
System.out.println();
});
// 方法二
Arrays.stream(strs).map(s->s.split(""))
.flatMap(Arrays::stream).distinct().sorted().
collect(Collectors.toList()).forEach(l->{
Arrays.asList(l).forEach(s-> System.out.print(s + " "));
});

终端操作

查找
  • allMatch:检测是否全部满足指定的参数行为,如果全部满则返回 true,否则返回 false
  • anyMatch:检测是否存在满足条件
  • noneMatch:是否不存在
  • findFirst:找第一
  • findAny:找任意一个(顺序流式处理中,findFirst 和 findAny 返回的结果是一样的)
归约

reduce方法

1
2
3
4
5
6
7
8
9
10
11
12
// 非reduce写法
int totalAge = this.students.stream()
.filter(s->s.getAge()>18)
.mapToInt(Student::getAge).sum();
// 规约操作
int totalAge1 = this.students.stream().filter(s->s.getAge()>18)
.mapToInt(Student::getAge).reduce(0, (a, b)->a + b);
int totalAge2 = this.students.stream().filter(s->s.getAge()>18)
.map(Student::getAge).reduce(0, Integer::sum);
Optional<Integer> totalAge3 = this.students.stream()
.filter(s->s.getAge()>18)
.map(Student::getAge).reduce(Integer::sum);
收集

经过中间操作处理后结果的封装,比如collect(Collectors.toList()),类似的还有 toSet``toMap,这些方法都来自于 java.util.stream.Collectors这个收集器

下面的栗子是相同功能的不同写法(“回”字的不同写法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 及格学生总数
long a = this.students.stream().filter(s->s.getScore()>60).collect(Collectors.counting());
long b = this.students.stream().filter(s->s.getScore()>60).count();
// 最高分,最低分
Optional<Student> maxScore = this.students.stream()
.collect(Collectors.maxBy((s1, s2)->s1.getScore()-s2.getScore()));
Optional<Student> maxScore = this.students.stream()
.collect(Collectors.maxBy(Comparator.comparing(Student::getScore)));
Optional<Student> maxScore = this.students.stream()
.collect(Collectors.minBy(Comparator.comparing(Student::getScore)));
// 总分和平均分
int totalScore = this.students.stream().collect(Collectors.summingInt(Student::getScore));
double averageScore = this.students.stream()
.collect(Collectors.averagingInt(Student::getScore));
// 一次性统计得到元素个数、总和、均值、最大值、最小值(这封装的也是够了)
IntSummaryStatistics statistics = this.students.stream()
.collect(Collectors.summarizingInt(Student::getScore));
// 字符串拼接
String names = this.students.stream().map(Student::getName)
.collect(Collectors.joining());
String names1 = this.students.stream().map(Student::getName)
.collect(Collectors.joining(", ")); // 用", "分隔名字
分组
1
2
3
4
5
6
7
// 按学校对集合分组
Map<String, List<Student>> groups = this.students.stream().collect(Collectors.groupingBy(Student::getSchool));
groups.forEach((k, v)->{
System.out.println(k);
v.forEach(x->System.out.print(x.getName() + " "));
System.out.println();
});

groupingBy 接收一个分类器Function<? super T, ? extends K> classifier,可以一级或多级分类,如:

1
2
3
Map<String, Map<String, List<Student>>> group = this.students.stream().collect(
Collectors.groupingBy(Student::getSchool,
Collectors.groupingBy(Student::getDocument)));

groupingBy 的第二个参数可以传递任意 Collector 类型,如 Collectors.groupingByCollectors.counting,如果不添加第二个参数,默认是Collectors.toList

1
2
3
// 集合中一个学校的人数,按学校分组后统计组内人数
Map<String, Long> groups = this.students.stream().collect(
Collectors.groupingBy(Student::getSchool, Collectors.counting()));
区分

区分是分组的一种特殊情况,将集合内容分为 true, flase 两类,比如求男女人数;下面是区分的两种写法,用 groupingBy 和 partitioningBy 都能实现

1
2
3
4
5
// 求集合中学生是HEU 和不是 HEU 的人数(列表)
Map<Boolean, Long> groups = this.students.stream().collect(
Collectors.groupingBy(x->x.getSchool().equals("HEU"), Collectors.counting()));
Map<Boolean, List<Student>> partition = this.students.stream().collect(
Collectors.partitioningBy(stu->"HEU".equals(stu.getSchool())));

并行流式数据处理

充分利用计算机的CPU内核数,并发处理的思想。将 stream() 替换成 parallelStream()即可,但会涉及多线程安全问题,当然效率提高的很多。

参考文献

java8 新特性 lamda和Stream

Java8 新特性之流式数据处理

您的支持鼓励我继续创作!