Lambda
Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构
为什么用lambda
1、使用Lambda表达式可以使代码变的更加紧凑,例如在Java中实现一个线程,只输出一个字符串Hello World!,我们的代码如下所示:
public static void main(String[] args) throws Exception {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
}).start();
}
使用Lambda表达式之后代码变成如下形式:
public static void main(String[] args) throws Exception {
new Thread(() -> System.out.println("Hello World!")).start();
}
Streams
就是JAVA8提供给我们的对于元素集合统一、快速、并行操作的一种方式。它能充分运用多核的优势,以及配合lambda表达式、链式结构对集合等进行许多有用的操作
作用:
提供了一种操作
大数据接口,让数据操作更容易和更快
使用stream,我们能够对collection的元素进行过滤、映射、排序、去重等许多操作。
中间方法和终点方法:
它具有过滤、映射以及减少遍历数等方法,这些方法分两种:中间方法和终端方法,
“流”抽象天生就该是持续的,中间方法永远返回的是Stream,因此如果我们要获取最终结果的话,
必须使用终点操作才能收集流产生的最终结果。区分这两个方法是看他的返回值,
如果是Stream则是中间方法,否则是终点方法
1、 过滤
filter():
月薪超过10000美元员工
List<Person> personList = new ArrayList<Person>()
personList .stream()
.filter((p) -> (p.getSalary() > 10000))
.forEach((p) -> System.out.println(p.getName());
//统计数量
Long rel = personList.stream()
.filter(p -> p.getAge() > 18)
.count();
自定义过滤器
Predicate<Person> ageFilter = (p) -> (p.getAge() > 25);
Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 10000);
年龄大于25岁,月薪10K以上
personList .stream()
.filter(ageFilter)
.filter(genderFilter)
.forEach((p) -> System.out.println(p.getName()));
2、limit限制获取个数
获取3个大于25岁的人
personList .stream()
.filter(genderFilter)
.limit(3)
.forEach((p) -> System.out.println(p.getName()));
3、排序
List排序
根据薪资升序排列
personList.stream()
.sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) )
.collect( Collectors.toList() );
根据年龄升序排列
personList.stream()
.sorted(Comparator.comparing(Person::getAge))
.collect(Collectors.toList());
降序排序
personList.stream()
.sorted(Comparator.comparing(Person::getAge).reversed())
.collect(Collectors.toList());
//map排序
Map<String, Integer> map = new HashMap();
map.put("349", 12);
map.put("329", 13);
map.put("6", 14);
map.put("399", 15);
map.put("99", 16);
map = map.entrySet()
stream()
.sorted(Map.Entry.<String, Integer>comparingByKey())
.collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue(),
(k, v) -> k, LinkedHashMap::new));
comparingByKey()根据键排序,comparingByValue 根据值排序
对最低和最高的薪水感兴趣,比排序后选择第一个/最后一个 更快的是min和max方法
Person pers = personList
.stream()
.min((p1, p2) -> (p1.getSalary() - p2.getSalary()))
.get()
Person pers = personList
.stream()
.max((p1, p2) -> (p1.getSalary() - p2.getSalary()))
.get()
4、计算合
(1)
personList
.stream()
.map(Person::getSalary)
.reduce(BigDecimal.ZERO, BigDecimal::add);
(2)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce((x, y) -> x + y).get();
(3)
personList
.parallelStream()
.mapToInt(p -> p.getSalary()) //mapToDouble()、mapToLong()
.sum();
问题:并行流
(4)
//reduce使用 acc中间结果(会使用第一个元素的地址)、bb是Stream的元素
会修改person对象中的值 不推荐
personList.stream()
.reduce((acc, bb) -> {
acc.setAge(acc.getAge() + bb.getAge());
acc.setSalary(acc.getSalary() + bb.getSalary());
return acc;
}).get();
(5)
//方式二 初始化一个对象,中间结果使用此地址
Person person0 = new Person();
Person person= personList
.stream()
.reduce(person0, (person1, person2) -> {
person1.setSalary(person1.getSalary() + person2.getSalary());
person1.setAge(person1.getAge() + person2.getAge());
return person1;
});
(6)
//将集合分类放入map中并求和
Map<String, Integer> studentScoreMap = new HashMap<>();
List<StudentScore> studentScoreList = buildATestList();
//转换
studentScoreList.forEach(studentScore -> studentScoreMap.merge(
studentScore.getStuName(),
studentScore.getScore(),
Integer::sum));
5、去重:distinct()
List<int> numbers = Arrays.asList(1, 2, 3, 4, 5, 5, 5, 5, 6, 7);
List<int> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
distinct()方法并不能设置条件. 解决方案如下:
首先定义一个过滤器:
public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return object -> seen.putIfAbsent(keyExtractor.apply(object), Boolean.TRUE) == null;
}
再去重
List<Person> distinctUsers =personList.stream()
.distinct(distinctByKey(Person::getName))
.collect(Collectors.toList());
personList
.stream().collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getName))), ArrayList::new));
多字段去重
personList= personList.stream().collect(
Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(
o -> o.getName() + ";" + o.getAge()))), ArrayList::new));
6、collect()
(1)list转map
这个方式出现键值重复时会报错
Map<Integer, String> map =personList.stream().collect(Collectors.toMap(Person::getAge,Person::getName))
key重复时报错的解决,将第二次出现的值覆盖第一个
Map<Integer, String> map2 =personList
.stream()
.collect(Collectors.toMap(Person::getAge,Person::getName,(k2,k1)->k1));
出现重复时将俩者拼接返回
Map<Integer, String> map2 =personList
.stream()
.collect(Collectors.toMap(Person::getAge,Person::getName,(k2,k1)->k1+","+k2));
出现重复时加入一个集合中
Map<Integer, List<String>> map = personList.stream().collect(Collectors.toMap(Person :: getAge,
// 此时的value 为集合,方便重复时操作
s -> {
List<String> studentNameList = new ArrayList<>();
studentNameList.add(s.getName());
return studentNameList;
},
// 重复时将现在的值全部加入到之前的值内
(List<String> value1, List<String> value2) -> {
value1.addAll(value2);
return value1;
}
));
结果示例:
{20岁:[张三, 李四], 25岁:[王二, 麻子]}
抽取指定key,value为对象
Map<Integer, Person> map =personList.stream().collect(Collectors.toMap(Person::getId,person->person))
(2)分组
根据id分组
Map<Long, List<Person>> groupByMap = personList.stream().collect(Collectors.groupingBy(Person::getId));
分组并统计
Map<Integer, Long> collect =persionList.stream().collect(Collectors.groupingBy(Person::getAge, counting()))
Map<Integer, Long> curriculum = articleList.stream()
.collect(Collectors.groupingBy(Article::getZhuanlanId, Collectors.summingLong(article->articleMap.getOrDefault(article.getId(), 0L))));
分组并取指定属性
Map<Integer, List<String>> part = personList
.stream()
.collect(Collectors.groupingBy(Person::getAge,Collectors.mapping(Person::getName, toList())));
分区是分组的特殊情况。 它最多可以分为两组,如id大于2和不大于2的
Map<Boolean, List<Person>> part = personList
.stream()
.collect(Collectors.partitioningBy(x->x.getAge()>2));
分组并排序
Map<String, List<Person>> collectMap = personList.stream()
.sorted(Comparator.comparing(Person::getSex))
.collect(Collectors.groupingBy(Person::getAge,TreeMap::new,Collectors.toList()));
groupingBy和partitioningBy的区别
我们先来看官方源码说明
public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier) {
return groupingBy(classifier, toList());
}
groupingBy的函数参数为Function然后他的返回值也是Map,但是他的key是泛型,那么这个分组就会将数据分组成多个key的形式。
我们再来看下partitioningBy
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
return partitioningBy(predicate, toList());
}
可以看出函数的参数一个Predicate接口,那么这个接口的返回值是boolean类型的,也只能是boolean类型,然后他的返回值是Map的key是boolean类型,也就是这个函数的返回值只能将数据分为两组也就是ture和false两组数据。
(3)joining
name用逗号相连
String join = personList.stream().map(Person::getName).collect(Collectors.joining(","));
性能问题
有些细心的同学可能会有这样的疑问:在对于一个Stream进行多次转换操作,每次都对Stream的每个元素进行转换,而且是执行多次,这样时间复杂度就是一个for循环里把所有操作都做掉的N(转换的次数)倍啊。其实不是这样的,转换操作都是lazy的,多个转换操作只会在汇聚操作(见下节)的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在汇聚操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。