Lamda的基本使用
forEach的使用
Random random = new Random(); random.ints().limit(10).forEach(System.out::println);
但是forEach并没有返回值,所以有时候在对集合进行循环,使用map()函数更为方便:
final List<String> friends = Arrays.asList("Brian", "Nate", "Neal", "Raju", "Sara", "Scott"); friends.stream().map(name -> name.toUpperCase()).forEach(name -> System.out.print(name + " ")); System.out.println();
老式遍历和新式遍历的区别:
java8之前的增强for这种方式的迭代是使用Iterator接口来实现的,调用了它的hasNext和next方法。 这两种方式都属于外部迭代器,它们把如何做和想做什么揉到了一起。我们显式的控制迭代,告诉它从哪开始到哪结束;第二个版本则在底层通过Iterator的方法来做这些。显式的操作下,还可以用break和continue语句来控制迭代。 第二个版本比第一个少了点东西。如果我们不打算修改集合的某个元素的话,它的方式比第一个要好。不过这两种方式都是命令式的,在现在的Java中应该摒弃这种方式。 改成函数式原因有这几个:
- for循环本身是串行的,很难进行并行化。
- 这样的循环是非多态的;所得即所求。我们直接把集合传给for循环,而不是在集合上调用一个方法(支持多态)来执行特定的操作。
- 从设计层面来说,这样 写的代码违反了“Tell,Don't Ask”的原则 。我们请求执行一次迭代,而不是把迭代留给底层库来执行。
是时候从老的命令式编程转换到更优雅的内部迭代器的函数式编程了。使用内部迭代器后我们把很多具体操作都扔给了底层方法库来执行,你可以更专注于具体的业务需求。底层的函数会负责进行迭代的。我们先用一个内部迭代器来枚举一下名字列表。
Iterable接口在JDK8中得到加强,它有一个专门的名字叫forEach,它接收一个Comsumer类型的参数。如名字所说,Consumer的实例正是通过它的accept方法消费传递给它的对象的。
这个forEach方法是一个高阶函数,它接收一个lambda表达式或者代码块,来对列表中的元素进行操作。在每次调用的时候 ,集合中的元素会绑定到name这个变量上。底层库托管了lambda表达式调用的活。它可以决定延迟表达式的执行,如果合适的话还可以进行并行计算。内部迭代器的版本更为简洁。而且,使用它的话我们可以更专注每个元素的处理操作,而不是怎么去遍历——这可是声明式的。
不过这个版本还有缺陷。一旦forEach方法开始执行了,不像别的两个版本,我们没法跳出这个迭代。(当然有别的方法能搞定这个)。
在这个例子里,Java编译器通过上下文分析,知道name的类型是String。它查看被调用方法forEach的签名,然后分析参数里的这个函数式接口。接着它会分析这个接口里的抽象方法,查看参数的个数及类型。即便这个lambda表达式接收多个参数,我们也一样能进行类型推导,不过这样的话所有参数都不能带参数类型;在lambda表达式中,参数类型要么全不写,要写的话就得全写。
Java编译器对单个参数的lambda表达式会进行特殊处理:如果你想进行类型推导的话,参数两边的括号可以省略掉。
这里有一点小警告:进行类型推导的参数不是final类型的。在前面显式声明类型例子中,我们同时也把参数标记为final的。这样能防止你在lambda表达式中修改参数的值。通常来说,修改参数的值是个坏习惯,这样容易引起BUG,因此标记成final是个好习惯。不幸的是,如果我们想使用类型推导的话,我们就得自己遵守规则不要修改参数,因为编译器可不再为我们保驾护航了。
fiter过滤器的使用
示例:
List<Person> javaProgrammers = new ArrayList<Person>() { { add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000)); add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500)); add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800)); add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600)); add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200)); add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900)); } };
- 单个条件过滤
String s = comRepayInfo.getLoanOrderId() + String.valueOf(repayNum1); List<CompensatVO> collect = temporaryList.stream().filter((CompensatVO vo1) -> (vo1.getLoanOrderId()+vo1.getRepayNum()).equals(s)).collect(Collectors.toList());
- 多条件重过滤
// 定义 filters Predicate<Person> ageFilter = (p) -> (p.getAge() > 25); Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400); Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender())); System.out.println("下面是年龄大于 24岁且月薪在$1,400以上的女PHP程序员:"); phpProgrammers.stream() .filter(ageFilter) .filter(salaryFilter) .filter(genderFilter) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
System.out.println("最前面的3个女性 Java programmers:"); javaProgrammers.stream() .filter(genderFilter) .limit(3) .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
System.out.println("根据 name 排序,并显示前5个 Java programmers:"); List<Person> sortedJavaProgrammers = javaProgrammers .stream() .sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName()))) .limit(5) .collect(toList());
- 选择某个字段最大的和最小的值
使用这种方法不需要先排序
System.out.println("工资最低的 Java programmer:"); Person pers = javaProgrammers .stream() .min((p1, p2) -> (p1.getSalary() - p2.getSalary())) .get(); System.out.printf("Name: %s %s; Salary: $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary()) System.out.println("工资最高的 Java programmer:"); Person person = javaProgrammers .stream() .max((p, p2) -> (p.getSalary() - p2.getSalary())) .get(); System.out.printf("Name: %s %s; Salary: $%,d.", person.getFirstName(), person.getLastName(), person.getSalary())
List排序:
- 升序排序:
1.Collections.sort(dataList,(p1,p2)->p1.getInstallmentNo().compareTo(p2.getInstallmentNo()));//dataList是需要排序的集合 2.Collections.sort(dataList,Comparator.comparing(RepayPlanDTO::getInstallmentNo));//dataList是需要排序的集合 3.dataList.stream.sorted(Comparator.comparing((Apple a) -> a.getWeight())).collect(Collectors.toList());
//或者等价于
dataList.stream.sorted(Comparator.comparing(Apple::getWeight)).collect(Collectors.toList());//dataList是需要排序的集合
4.userList.stream.sort((String s1, String s2) -> (s1.length() - s2.length()));//根据名称的长度进行排序
- 降序排序
1.Collections.sort(dataList,(p1,p2)->p2.getInstallmentNo().compareTo(p1.getInstallmentNo())); 2.dataList.stream.sorted(Comparator.comparing(Apple::getWeight).reversed()).collect(Collectors.toList());
- 多参数排序
1.dataList.sort(Comparator.comparing(RepayPlanDetailFileVO::getLoanOrderId).thenComparing(RepayPlanDetailFileVO::getRepayNum));
list转map的操作
示例:
List<User> list =new ArrayList<>(); User user1=new User("fgg",2); User user2=new User("ghj",1); User user3=new User("dft",4); User user4=new User("abc",3); User user5=new User("ads",5); list.add(user1); list.add(user2); list.add(user3); list.add(user4); list.add(user5);
- 用年龄作为map的key,名字作为map的value:
Map<Integer, User> collect3 = collect2.stream().collect(Collectors.toMap(User::getAge, User:getName));
- 用年龄作为map的key,user对象作为map的value:
Map<Integer, User> collect3 = collect2.stream().collect(Collectors.toMap(User::getAge, user -> user));
Map<Integer, User> collect5 = collect2.stream().collect(Collectors.toMap(User::getAge, Function.identity()));这两种用法是等价的
- 但是如果做为key的字段有重复的上面的写法就不行了,在执行程序时就会报错,但我们可以用下面的方式来解决:
Map<Integer, User> collect3 = collect2.stream().collect(Collectors.toMap(User::getAge, user -> user,(key1, key2) -> key2));//这种写法只是在出现两个一样的key值时,暴力的用后面一个key的value值覆盖前面相同的key的value值
Map<Integer, List<User>> collect5 = collect2.stream().collect(Collectors.groupingBy(User::getAge));//这种写法是当遇到相同的key值时,会自动的将相同key值的value值组装成一个集合
Map<Integer, User> collect3 = collect2.stream().collect(Collectors.toMap(User::getAge, user -> user,(key1, key2) -> key2,LinkedHashMap::new));//这种写法是我们可以执行特定的map类型来接受我们的结果
- 使用map()函数进行转换
System.out.println("将 PHP programmers 的 first name 拼接成字符串:"); String phpDevelopers = phpProgrammers .stream() .map(Person::getFirstName) .collect(joining(" ; ")); // 在进一步的操作中可以作为标记(token) System.out.println("将 Java programmers 的 first name 存放到 Set:"); Set<String> javaDevFirstName = javaProgrammers.stream().map(Person::getFirstName).collect(Collectors.toSet());
System.out.println("将 Java programmers 的 first name 存放到 TreeSet:"); TreeSet<String> javaDevLastName = javaProgrammers .stream() .map(Person::getLastName) .collect(toCollection(TreeSet::new));
Map排序:
Map<String ,Integer> map=new HashMap<>(); LinkedHashMap<String, Integer> collect = map.entrySet().stream().sorted(Map.Entry.<String, Integer>comparingByValue()).collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue(), (k, v) -> k, LinkedHashMap::new));
public Map<String, Integer> sortedByKeys(Map<String, Integer> map) { Map<String, Integer> result = new LinkedHashMap<>(); map.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEachOrdered(x -> result.put(x.getKey(), x.getValue())); return result; }
- comparingByKey() //利用key值进行排序,但要求key值类型需要实现Comparable接口。
- comparingByValue() //利用value值进行排序,但要求key值类型需要实现Comparable接口。
- comparingByKey(Comparator) //利用key值进行排序,但key值并没有实现Comparable接口,需要传入一个Comparator比较器。
- comparingByValue(Comparator) //利用value值进行排序,但value值并没有实现Comparable接口,需要传入一个Comparator比较器。
Map合并
示例:
Map<String, Employee> map1 = new HashMap<>();
Map<String, Employee> map2 = new HashMap<>();
Employee employee1 = new Employee(1L, "Henry"); Employee employee2 = new Employee(22L, "Annie"); Employee employee3 = new Employee(8L, "John"); map1.put(employee1.getName(), employee1); map1.put(employee2.getName(), employee2); map1.put(employee3.getName(), employee3); Employee employee4 = new Employee(2L, "George"); Employee employee5 = new Employee(3L, "Henry"); map2.put(employee4.getName(), employee4); map2.put(employee5.getName(), employee5);
Map<String, Employee> map3 = new HashMap<>(map1);
- 使用Merge将map2合并到map3中:
Java8为 java.util.Map接口新增了merge()函数。merge() 函数的作用是: 如果给定的key之前没设置value 或者value为null, 则将给定的value关联到这个key上.否则,通过给定的remaping函数计算的结果来替换其value。如果remapping函数的计算结果为null,将解除此结果。First, let’s construct a new HashMap by copying all the entries from the map1:
map2.forEach( (key, value) -> map3.merge(key, value, (v1, v2) -> new Employee(v1.getId(),v2.getName())) );
其中(v1, v2) -> new Employee(v1.getId(),v2.getName())是当出现key相同时的合并策略,示例中的key值出现相同的情况时的合并策略是:id来自map3而name来自map2.
- 使用Stream.concat()进行合并:
Java8的Stream API 也为解决该问题提供了较好的解决方案。
Map<String, Employee> result=Stream.concat(map1.entrySet().stream(), map2.entrySet().stream()).collect( Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,(value1, value2) -> new Employee(value2.getId(), value1.getName())));;
- 使用Stream.of()进行合并:
通过Stream.of()方法不需要借助其他stream就可以实现map的合并。
Map<String, Employee> map3 = Stream.of(map1, map2).flatMap(map -> map.entrySet().stream()).collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue,(v1, v2) -> new Employee(v1.getId(), v2.getName())));
- 使用Simple Streaming进行合并:
借助stream的管道操作来实现map合并
Map<String, Employee> map3 = map2.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue,(v1, v2) -> new Employee(v1.getId(), v2.getName()),() -> new HashMap<>(map1)));
- 使用StreamEx进行合并:
我们还可以使Stream API 的增强库
Map<String, Employee> map3 = EntryStream.of(map1).append(EntryStream.of(map2)).toMap((e1, e2) -> e1);
注意:(e1, e2) -> e1 表达式来处理重复key的问题,如果没有该表达式依然会报IllegalStateException异常.
Predicate和Function的使用
目前,已经出现了两种类型的函数式接口(Functional Interface)。它们分别是filter方法使用的Predicate和map方法使用的Function。
在上面我们讲filter的多条件过滤时,我们把过滤的条件单独拿了出来,可以方便方法内的其他filter复用,但是如果我们在进行某种过滤时只是传的值不通过滤条件是相同的,遇到这种情况我们是否需要再增加个过滤条件呢,很显然要是这样做了重复代码就太多了,我们可以如下来做:
第一种做法:
public static Predicate<Integer> checkAge(final Integer letter) { return (p) -> (p.getAge() >letter);
}
第二种方式:
final Function<Integer, Predicate<Integer>> checkAgeResult= (Integer letter) -> { Predicate<Integer> checkAge = (Integer age) -> age>letter; return checkAge; };
或者
final Function<Integer, Predicate<Integer>> checkAgeResult = (Integer letter) -> (Integer age) -> age>letter;
或者
final Function<Integer, Predicate<Integer>> checkAgeResult = letter -> age -> age>letter;
这三者是等价的
使用示例:
final Function<Integer, Predicate<User>> checkAge = letter -> age -> age.getAge()>letter; final Function<String, Predicate<User>> checkName = letter -> name -> name.getName().contains(letter); List<User> abc = list.stream().filter(checkAge.apply(2).and(checkName.apply("abc"))).collect(Collectors.toList());
注意:在使用的时候Function<Integer,President<User>>中的User对应的就是最终集合中的类型,而Integer对应的是参数“letter”的数据类型
计算
mapToInt方法得到的是一个Stream类型的子类型IntStream的实例,与IntStream类似,还有LongStream和DoubleStream类型,用于简化相关的操作。而通常和这些方法联合使用的除了sum()方法还有max(),min(),average()等一系列方法用来实现常用的归约。
reduce方法的工作原理,可以这样概括:在对一个集合中的元素按照顺序进行两两操作时,根据某种策略来得到一个结果,得到的结果将作为一个元素参与到下一次操作中,最终这个集合会被归约成为一个结果。这个结果也就是reduce方法的返回值。
- 计算支付金额的总和,可以使用并行Stream提高效率
System.out.println("计算付给 Java programmers 的所有money:"); int totalSalary = javaProgrammers .parallelStream() .mapToInt(p -> p.getSalary()) .sum();
- 获取某个数据的统计数据
//计算 count, min, max, sum, and average for numbers List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); IntSummaryStatistics stats = numbers .stream() .mapToInt((x) -> x) .summaryStatistics(); System.out.println("List中最大的数字 : " + stats.getMax()); System.out.println("List中最小的数字 : " + stats.getMin()); System.out.println("所有数字的总和 : " + stats.getSum()); System.out.println("所有数字的平均值 : " + stats.getAverage());
- reduce归约
final Optional<String> aLongName = friends.stream() .reduce((name1, name2) -> name1.length() >= name2.length() ? name1 : name2); aLongName.ifPresent(name -> System.out.println(String.format("A longest name: %s", name)));
第一次执行两两操作时,name1和name2代表的是集合中的第一个和第二个元素,当第一个元素的长度大于等于第二个元素时,将第一个元素保留下来,否则保留第二个元素。 第二次执行两两操作时,name1代表的是上一次操作中被保留下来的拥有较长长度的元素,name2代表的是第三个元素。 以此类推...最后得到的结果就是集合中第一个拥有最长长度的元素了。
实际上,reduce方法接受的Lambda表达式的行为被抽象成了BinaryOperator接口:
@FunctionalInterface public interface BinaryOperator<T> extends BiFunction<T,T,T> { // others... } @FunctionalInterface public interface BiFunction<T, U, R> { /** * Applies this function to the given arguments. * * @param t the first function argument * @param u the second function argument * @return the function result */ R apply(T t, U u); // others... }
源码也反映了BinaryOperator和另一个函数式接口BiFunction之间的关系,当BiFunction接口中接受的三个参数类型一致时,也就成为了一个BinaryOperator接口。因此,前者实际上是后者的一个特例。
另外需要注意的几点:
- reduce方法返回的对象类型时Optional,这是因为待操作的集合可能是空的。
- 当集合只有一个元素时,reduce会立即将该元素作为实际结果以Optional类型返回,不会调用传入的Lambda表达式。
- reduce方法是会按照集合的顺序对其元素进行两两操作的,可以额外传入一个值作为“基础值”或者“默认值”,那么在第一次进行两两操作时,第一个操作对象就是这个额外传入的值,第二个操作对象是集合中的第一个元素。
比如,以下代码为reduce方法传入了默认值:
final String steveOrLonger = friends.stream() .reduce("Steve", (name1, name2) -> name1.length() >= name2.length() ? name1 : name2);
- 复制不同的值
// 用所有不同的数字创建一个正方形列表 List<Integer> numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4); List<Integer> distinct = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList()); System.out.printf("Original List : %s, Square Without duplicates : %s %n", numbers, distinct);
- 元素链接
// 将字符串换成大写并用逗号链接起来 List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.","Canada"); String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", ")); 或者
String result=names.stream().map(String::toUpperCase).collect(Collectors.joining(", "));
可见collect方法并不自己完成归约操作,它会将归约操作委托给一个具体的Collector,而Collectors类型则是一个工具类,其中定义了许多常见的归约操作,比如上述的joining Collector
学习链接:
java8中Comparable与Comparator的区别
java8新特性 lambda Stream map(函数式编程)