JDK8新特性
参考菜鸟教程:https://www.runoob.com/java/java8-new-features.html
1. 接口默认方法
Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法;而且允许定义static方法,使用的时候直接类名.方法名即可。
package com.zd.bx.test; public interface Interface1 { /** * 抽象方法1 * * @param str */ void method1(String str); /** * 静态方法 * * @param str */ public static void method2(String str) { System.out.println(str); } default void log(String str) { System.out.println("I1 logging::" + str); } }
2. Lambda 表达式
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。
语法如下:
(parameters) -> expression
或
(parameters) ->{ statements; }
1. 以下是lambda表达式的重要特征:
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
例如:
package com.zd.bx; public class MyTest { public static void main(String args[]) { MyTest tester = new MyTest(); // 类型声明 MathOperation addition = (int a, int b) -> a + b; // 不用类型声明 MathOperation subtraction = (a, b) -> a - b; // 大括号中的返回语句 MathOperation multiplication = (int a, int b) -> { return a * b; }; // 没有大括号及返回语句 MathOperation division = (int a, int b) -> a / b; System.out.println("10 + 5 = " + tester.operate(10, 5, addition)); System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction)); System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication)); System.out.println("10 / 5 = " + tester.operate(10, 5, division)); // 不用括号 GreetingService greetService1 = message -> System.out.println("Hello " + message); // 用括号 GreetingService greetService2 = (message) -> System.out.println("Hello " + message); greetService1.sayMessage("Runoob"); greetService2.sayMessage("Google"); } interface MathOperation { int operation(int a, int b); } interface GreetingService { void sayMessage(String message); } private int operate(int a, int b, MathOperation mathOperation) { return mathOperation.operation(a, b); } }
结果:
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google
2. 变量作用域:
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
例如:
package com.zd.bx; public class MyTest { public static void main(String args[]) { String salutation = "Hello! "; GreetingService greetService1 = message -> { System.out.println(salutation + message); // 不允许修改引用,salutation隐式的被声明为final // salutation = ""; }; greetService1.sayMessage("Runoob"); } interface GreetingService { void sayMessage(String message); } }
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
3.方法引用
方法引用通过方法的名字来指向一个方法。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 :: 。
例如:
package com.zd.bx; import java.util.Arrays; import java.util.List; import java.util.function.Supplier; public class Car { // Supplier是jdk1.8的接口,这里和lamda一起使用了 public static Car create(final Supplier<Car> supplier) { return supplier.get(); } public static void collide(final Car car) { System.out.println("Collided " + car.toString()); } public void follow(final Car another) { System.out.println("Following the " + another.toString()); } public void repair() { System.out.println("Repaired " + this.toString()); } public static void main(String[] args) { System.out.println("===1==="); // 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new final Car car = Car.create(Car::new); final List<Car> cars = Arrays.asList(car); System.out.println("===2==="); // 静态方法引用:它的语法是Class::static_method cars.forEach(Car::collide); System.out.println("===3==="); // 特定类的任意对象的方法引用:它的语法是Class::method cars.forEach(Car::repair); System.out.println("===4==="); // 特定对象的方法引用:它的语法是instance::method final Car police = Car.create(Car::new); cars.forEach(police::follow); } }
结果:
===1===
===2===
Collided com.zd.bx.Car@3e3abc88
===3===
Repaired com.zd.bx.Car@3e3abc88
===4===
Following the com.zd.bx.Car@3e3abc88
补充:方法引用可以理解为lambda表达式的快捷写法,它比lambda表达式更加的简洁,可读性更高.有更好的重用性.如果实现比较简单,一句话就可以实现,复用的地方又不多推荐使用lambda表达式,否则应该使用方法引用.
(1)方法引用的分类如下:
(2)举一个简单的例子,我们需要搜集list中所有用户的username: map 用于映射每个元素到对应的结果
第一版可能就是for循环往新的集合里面加
第二版可能就是:
List<User> list = Lists.newArrayList(user, user2, user3, user4); List<Object> collect = list.stream().map(new Function<User, Object>() { @Override public Object apply(User user) { return user.getUsername(); } }).collect(Collectors.toList()); System.out.println(collect);
第三版:
List<User> list = Lists.newArrayList(user, user2, user3, user4); List<Object> collect = list.stream().map((userTmp) -> { return userTmp.getUsername(); }).collect(Collectors.toList()); System.out.println(collect);
第四版:
List<User> list = Lists.newArrayList(user, user2, user3, user4); List<Object> collect = list.stream().map(userTmp -> { return userTmp.getUsername(); }).collect(Collectors.toList()); System.out.println(collect);
第五版: 使用对象方法引用。这种方法的使用规则是:抽象方法的第一个参数类型刚好是实例方法的类型,抽象方法剩余的参数恰好可以当做实例方法的参数。
List<User> list = Lists.newArrayList(user, user2, user3, user4); List<Object> collect = list.stream().map(User::getUsername).collect(Collectors.toList()); System.out.println(collect);
这个就精简多了,可以参照第四版lambda表达式进行理解。只有一个参数且第一个参数是实例方法的参数调用者,后面的参数(为空)是实例方法的参数,满足上面条件可以用对象方法的引用。
4.函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。
如定义了一个函数式接口如下:
package com.zd.bx; @FunctionalInterface public interface GreetingService { void sayMessage(String message); default int method2() { return 2; } }
那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):
GreetingService greetService1 = message -> System.out.println("Hello " + message);
greetService1.sayMessage("234");
函数式接口可以对现有的函数友好地支持 lambda。
1.JDK8 之前已有的函数式接口
java.lang.Runnable、java.util.concurrent.Callable、java.util.Comparator等。如:
@FunctionalInterface public interface Runnable { public abstract void run(); }
2.JDK8提供的函数式接口
JDK8提供了好几个函数式接口,其中有用的是如下几个:
1.Consumer<T> 代表了接受一个输入参数并且无返回的操作
package java.util.function; import java.util.Objects; @FunctionalInterface public interface Consumer<T> { void accept(T t); default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
2. Supplier<T> 无参数,返回一个结果。 代表了接受一个输入参数并且无返回的操作
package java.util.function; @FunctionalInterface public interface Supplier<T> { T get(); }
3. Predicate<T> 接受一个输入参数,返回一个布尔值结果。
package java.util.function; import java.util.Objects; @FunctionalInterface public interface Predicate<T> { boolean test(T t); default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } default Predicate<T> negate() { return (t) -> !test(t); } default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } }
4. Function<T,R> 接受一个输入参数,返回一个结果。
package java.util.function; import java.util.Objects; @FunctionalInterface public interface Function<T, R> { R apply(T t); default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } static <T> Function<T, T> identity() { return t -> t; } }
例如:
package com.zd.bx; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; import lombok.Data; @Data public class Car { public static void main(String args[]) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); // Predicate<Integer> predicate = n -> true // n 是一个参数传递到 Predicate 接口的 test 方法 // n 如果存在则 test 方法返回 true System.out.println("输出所有数据:"); // 传递参数 n eval(list, n -> true); // Predicate<Integer> predicate1 = n -> n%2 == 0 // n 是一个参数传递到 Predicate 接口的 test 方法 // 如果 n%2 为 0 test 方法返回 true System.out.println("输出所有偶数:"); eval(list, n -> n % 2 == 0); // Predicate<Integer> predicate2 = n -> n > 3 // n 是一个参数传递到 Predicate 接口的 test 方法 // 如果 n 大于 3 test 方法返回 true System.out.println("输出大于 3 的所有数字:"); eval(list, n -> n > 3); } public static void eval(List<Integer> list, Predicate<Integer> predicate) { for (Integer n : list) { if (predicate.test(n)) { System.out.println(n + " "); } } } }
结果:
输出所有数据: 1 2 3 4 5 6 7 8 9 输出所有偶数: 2 4 6 8 输出大于 3 的所有数字: 4 5 6 7 8 9
5. Stream
添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
(1)Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
(2)内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
流分类:大体分为中间操作和终止操作。中间操作返回stream, 终止操作就是结束流处理。 stream 采用惰性求值,惰性求值就是终止没有调用的情况下,中间操作不会执行。
中间操作: 分为有状态和无状态。无状态代表当前操作和其他元素操作前后无关系; 有状态代表当前结果需要依赖其他元素。
终止操作: 终止操作又分为短路操作和非短路操作。短路操作就是不需要等待所有结果计算完,我们只需要得到一个或者任何一个结果就可以了; 非短路操作就是需要等待所有结果执行完。
1.生成流
集合接口有两个方法来生成流:
stream() − 为集合创建串行流。
parallelStream() − 为集合创建并行流。
例如:
// 集合 Stream<Integer> stream = Lists.newArrayList(1, 2).stream(); Stream<Integer> stream2 = Lists.newArrayList(1, 2).parallelStream(); // 数组 IntStream stream3 = Arrays.stream(new int[]{1, 2, 3}); // 数字流 IntStream intStream = IntStream.of(1, 2, 3); IntStream intStream2 = IntStream.rangeClosed(1, 3); // 1, 2, 3 俊包含 // 使用random 创建无限流 (随机选取十个) IntStream limit = new Random().ints().limit(10); Random random = new Random(); Stream<Integer> limit1 = Stream.generate(new Supplier<Integer>() { @Override public Integer get() { return random.nextInt(); } }).limit(10);
更全的生成流的方法
package org.example; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @Slf4j public class PlainTest { public static void main(String[] args) throws Exception { // 创建流以及求和 Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7); Integer result = integerStream.reduce(0, Integer::sum); System.out.println(result); // 数组创建流以及求和 IntStream stream = Arrays.stream(new int[]{1, 2, 3, 4, 5, 6, 7}); // int sum = stream.sum(); // System.out.println(sum); // IntStream 转换为 Stream Stream<Integer> integerStream1 = stream.mapToObj(Integer::valueOf); // Stream.iterate()生成无限流 (第一个参数可以理解为种子) List<Integer> collect = Stream.iterate(2, x -> x + 1).limit(7).collect(Collectors.toList()); System.out.println(collect); // 使用Stream.generare()方法生成流 // generate()和iterate()有些不一样,前者需要一个Supplier来创建一个无线流,按照你的需求来创建,而后者则根据你给的种子和迭代器来创建 Stream<String> limit = Stream.generate(UUID.randomUUID()::toString).limit(2); limit.collect(Collectors.toList()).forEach(System.out::println); // 生成范围流, rangeClosed 表示闭区间, range 表示开区间 IntStream intStream = IntStream.rangeClosed(1, 3);//生成1->3的int流 // int sum = intStream.sum(); // System.out.println(sum); // 转为Stream, boxed 等价于 mapToObj(Integer::valueOf); Stream<Integer> boxed = intStream.boxed(); List<Integer> collect1 = boxed.collect(Collectors.toList()); System.out.println(collect1); } }
2. forEach / peek
Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。只不过forEach 是终止操作,peek 是中间操作。
以下代码片段使用 forEach 输出了10个随机数:
Random random = new Random(); random.ints().limit(10).forEach(System.out::println);
peek 如下:
Random random = new Random(); random.ints().limit(10).peek(System.out::println).count();
补充:foreach中跳出本次循环与结束循环
foreach中跳出本次循环使用return,类似于continue关键字;如果想结束循环,可以用try、catch + throw异常实现。
List<String> strings = new ArrayList<>(); strings.add("1"); strings.add("2"); strings.add("3"); strings.forEach(str -> { if ("2".equals(str)) { System.out.println("这是2"); return; } System.out.println(str); });
结果:
1
这是2
3
补充:foreach遍历map的方式
Map<Long, Long[]> userIdDeptIds = new LinkedHashMap<>(); userIdDeptIds.put(1L, new Long[] { 1L, 11L }); userIdDeptIds.put(2L, new Long[] { 2L, 22L }); userIdDeptIds.forEach((userId, departmentIds) -> { System.out.println("=============="); System.out.println(userId); System.out.println(ToStringBuilder.reflectionToString(departmentIds, ToStringStyle.NO_CLASS_NAME_STYLE)); });
结果:
==============
1
[{1,11}]
==============
2
[{2,22}]
补充:distinct() 用于元素去重
package estest; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class ESTest { public static void main(String[] args) { // 元素去重 String[] a = {"1", "2", "3", "3", "4"}; // 获取到stream对象 Stream<String> stream = Arrays.stream(a); // 获取到去重后的元素保存到集合中 List<String> collect = stream.distinct().collect(Collectors.toList()); // 集合转数组 String[] strings = collect.toArray(new String[collect.size()]); System.out.println(Arrays.toString(strings)); } }
结果:
[1, 2, 3, 4]
补充: count 用于计数,是个终结方法
ArrayList<String> strings = Lists.newArrayList("1", "2", "3"); long count = strings.stream().count(); System.out.println(count);
3. map
(1) 解释: 将流中的元素映射到另外一个流中 (把流中的每一个值,使用所提供的函数执行一遍,一一对应。得到元素个数相同的流。)
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
参数解释:(简单的说入参就是上一个流的元素,返回值就是你要转换为的新stream的类型)
返回由将给定函数应用于该流元素的结果组成的流。 这是一个中间操作。 参数: 映射器-适用于每个元素的无干扰、无状态功能 类型参数: 新流的元素类型 返参:新流
(2)例如: 用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); // 获取对应的平方数 List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList()); System.out.println(squaresList);
结果:
[9, 4, 49, 25]
(3) 补充:
另外提供了几个mapToInt, mapToLong 等api, 返回不同的stream
IntStream mapToInt(ToIntFunction<? super T> mapper); LongStream mapToLong(ToLongFunction<? super T> mapper); DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
补充: flatMap, 可以理解为将stream 扁平映射。
流中的每个元素都走一遍Function, 且返回值是stream。
1. 源码
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
参数解释:我的理解是假如你的集合流中包含子集合,那么使用flatMap
可以返回该子集合的集合流, 然后进行合并或者其他操作
2. 测试
(1) 测试1
private static void reactorTest() { User user = new User(Lists.newArrayList("语文书", "数学书")); User user2 = new User(Lists.newArrayList("英语书", "数学书")); List<User> users = Lists.newArrayList(user, user2); List<String> collect = users.stream().flatMap(tmp -> tmp.getBooks().stream()).collect(Collectors.toList()); System.out.println(collect); } @AllArgsConstructor @Data private static class User { private List<String> books; }
结果:
[语文书, 数学书, 英语书, 数学书]
(2) 测试2
List<List<String>> strs = Lists.newArrayList(Lists.newArrayList("1", "2"), Lists.newArrayList("3", "4")); List<String> collect = strs.stream().flatMap(Collection::stream).collect(Collectors.toList()); System.out.println(collect.size()); System.out.println(collect);
结果:
4
[1, 2, 3, 4]
3. 补充
stream 也提供了一些 flatMapToInt、 flatMapToLong、flatMapToDouble 方法
IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
4. filter
filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl"); // 获取空字符串的数量 long count = strings.stream().filter(string -> string.isEmpty()).count(); System.out.println(count);
结果:
2
5. limit、skip
limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:
Random random = new Random(); random.ints().limit(10).forEach(System.out::println);
skip 用于跳过指定数量的流:
ArrayList<String> strings = Lists.newArrayList("1", "2", "3"); long count = strings.stream().skip(2).count(); System.out.println(count); // 1
6. sorted
sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:
Random random = new Random(); random.ints().limit(10).sorted().forEach(System.out::println);
7.并行(parallel)流
parallelStream 是流并行处理程序的代替方法。
1. 默认使用的是使用ForkJoin 线程池处理(ForkJoinPool.commonPool)。
IntStream.range(1, 20).parallel().forEach(num -> { System.out.println(Thread.currentThread().getName() + "\t" + num); });
结果:
ForkJoinPool.commonPool-worker-11 3 ForkJoinPool.commonPool-worker-11 4 ForkJoinPool.commonPool-worker-7 14 ForkJoinPool.commonPool-worker-7 10 ForkJoinPool.commonPool-worker-7 1 ForkJoinPool.commonPool-worker-7 5 ForkJoinPool.commonPool-worker-7 16 ForkJoinPool.commonPool-worker-7 15 ForkJoinPool.commonPool-worker-7 19 ForkJoinPool.commonPool-worker-7 18 ForkJoinPool.commonPool-worker-13 2 ForkJoinPool.commonPool-worker-9 11 ForkJoinPool.commonPool-worker-15 13 main 12 ForkJoinPool.commonPool-worker-3 6 ForkJoinPool.commonPool-worker-5 17 ForkJoinPool.commonPool-worker-11 8 ForkJoinPool.commonPool-worker-11 9 ForkJoinPool.commonPool-worker-11 7
2. 如果多次调用并行、串行以最后一次为准
IntStream.range(1, 3).parallel().peek(num -> { System.out.println(Thread.currentThread().getName() + "\t" + num); }).sequential().forEach(num -> { System.out.println(Thread.currentThread().getName() + "\t" + num); });
结果:
main 1 main 1 main 2 main 2
3. 可以使用自定义的线程池: 可以看到使用的是自己的线程池进行的forkJoin
// 使用自己的ForkJoinPool ForkJoinPool forkJoinPool = new ForkJoinPool(2); forkJoinPool.submit(() -> IntStream.range(1, 6).parallel().peek(num -> { System.out.println(Thread.currentThread().getName() + "\t" + num); } ).count()); forkJoinPool.shutdown(); synchronized (forkJoinPool) { forkJoinPool.wait(); }
结果:
ForkJoinPool-1-worker-0 2 ForkJoinPool-1-worker-1 3 ForkJoinPool-1-worker-0 1 ForkJoinPool-1-worker-1 5 ForkJoinPool-1-worker-0 4
8. collector 收集器
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println("筛选列表: " + filtered); String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("合并字符串: " + mergedString);
结果:
筛选列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
其他常用操作如下:
public static void main(String[] args) { ArrayList<User> users = Lists.newArrayList(new User(18, "男", 1), new User(18, "男", 1), new User(19, "女", 2), new User(18, "男", 2), new User(18, "男", 2) ); // 获取所有人的年龄 List<Integer> collect = users.stream().map(User::getAge). collect(Collectors.toList()); System.out.println("年龄: " + collect); TreeSet<Integer> collect1 = users.stream().map(User::getAge). collect(Collectors.toCollection(TreeSet::new)); System.out.println("年龄2: " + collect1); // 统计汇总信息 IntSummaryStatistics collect2 = users.stream().collect(Collectors.summarizingInt(User::getAge)); System.out.println("统计信息: " + collect2); // 分块 (一个特殊的分组信息) Map<Boolean, List<User>> groups = users.stream().collect(Collectors.partitioningBy(user -> user.getSex().equals("男"))); MapUtils.verbosePrint(System.out, "分块信息", groups); // 分组 Map<Integer, List<User>> collect3 = users.stream().collect(Collectors.groupingBy(User::getGroup)); MapUtils.verbosePrint(System.out, "班级分组信息", collect3); Map<Integer, Long> collect4 = users.stream().collect(Collectors.groupingBy(User::getGroup, Collectors.counting())); MapUtils.verbosePrint(System.out, "班级分组信息汇总人数", collect4); } @Data @AllArgsConstructor public static class User { private int age; private String sex; private int group; }
结果:
年龄: [18, 18, 19, 18, 18] 年龄2: [18, 19] 统计信息: IntSummaryStatistics{count=5, sum=91, min=18, average=18.200000, max=19} 分块信息 = { false = [PlainTest2.User(age=19, sex=女, group=2)] true = [PlainTest2.User(age=18, sex=男, group=1), PlainTest2.User(age=18, sex=男, group=1), PlainTest2.User(age=18, sex=男, group=2), PlainTest2.User(age=18, sex=男, group=2)] } 班级分组信息 = { 1 = [PlainTest2.User(age=18, sex=男, group=1), PlainTest2.User(age=18, sex=男, group=1)] 2 = [PlainTest2.User(age=19, sex=女, group=2), PlainTest2.User(age=18, sex=男, group=2), PlainTest2.User(age=18, sex=男, group=2)] } 班级分组信息汇总人数 = { 1 = 2 2 = 3 }
9. 统计
一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("列表中最大的数 : " + stats.getMax()); System.out.println("列表中最小的数 : " + stats.getMin()); System.out.println("所有数之和 : " + stats.getSum()); System.out.println("平均数 : " + stats.getAverage());
10. 判断匹配的方法: 都是终结方法
allMatch()、anyMatch()、nonMatch()
ArrayList<String> strings = Lists.newArrayList("1", "2", "3", "5", "1"); boolean b = strings.stream().allMatch(str -> "1".equals(str)); boolean c = strings.stream().anyMatch(str -> "1".equals(str)); boolean d = strings.stream().noneMatch(str -> "1".equals(str)); System.out.println(b); // f System.out.println(c); // t System.out.println(d); // f
11. max、min 返回最大最小, 是一个终结方法
ArrayList<Integer> strings = Lists.newArrayList(1, 2, 3, 4);
System.out.println(strings.stream().max(Comparator.comparing(Integer::valueOf)).get());
源码如下:
Optional<T> max(Comparator<? super T> comparator);
12. reduce 用于归纳元素
Optional<T> reduce(BinaryOperator<T> accumulator);
T reduce(T identity, BinaryOperator<T> accumulator);
identity 可以理解为默认值
测试:
ArrayList<Integer> strings = Lists.newArrayList(1, 2, 3, 4); System.out.println(strings.stream().reduce((o1, o2) -> { return o1 + o2; }).get());
13. IntStream 用法
private static void reactorTest() { IntSummaryStatistics intSummaryStatistics = IntStream.range(11, 16).summaryStatistics(); System.out.println(intSummaryStatistics.getCount()); System.out.println(intSummaryStatistics.getSum()); int sum = IntStream.range(11, 16).sum(); System.out.println(sum); List<String> collect = IntStream.range(11, 16).mapToObj(i -> { return i + "_val"; }).collect(Collectors.toList()); System.out.println(collect); }
结果:
5
65
65
[11_val, 12_val, 13_val, 14_val, 15_val]
补充: stream 原理如下
1.所有的操作都是链式调用,一个元素只迭代一次。其原理是拿每一个元素依次调用中间操作。
2.每一个中间操作都返回一个新的流,流里面有一个属性: sourceStage, 指向同一个地方head(第一个stream)
head->nextStage->nextStage->...->null
3.有状态操作会截断无状态,单独处理。相当于有状态操作是一个分水岭,前后都是一串stream 按上面规则跑。
4.并行环境下,有状态操作的中间操作不一定能并行操作
5.parallel、sequetial也是中间操作,只不过他们不会创建流,只是修改head的并行标志属性 parallel
测试1:(验证上面1, 2 ,3)
Stream<Integer> integerStream = Lists.newArrayList(1, 2).stream() // 第一个无状态操作 .filter(key -> { System.out.println("1" + "\t" + key); return true; }) // 第二个无状态操作 .filter(key -> { System.out.println("2" + "\t" + key); return true; }) // 第一个有状态操作 .sorted((k1, k2) -> { System.out.println("有状态排序:" + k1); return k1 - k2; }) // 第三个无状态操作 .filter(key -> { System.out.println("3" + "\t" + key); return true; }) // 第四个无状态操作 .filter(key -> { System.out.println("4" + "\t" + key); return true; }); long count = (long) integerStream.count();
结果:
1 1
2 1
1 2
2 2
有状态排序:2
3 1
4 1
3 2
4 2
debug 查看 integerStream 如下:
测试2:(验证上面4, 5)
Stream<Integer> integerStream = Lists.newArrayList(1, 2, 3).stream() // 第一个无状态操作 .filter(key -> { System.out.println(Thread.currentThread().getName() + "\t" + key); return true; }) // 第一个有状态操作 .sorted((k1, k2) -> { System.out.println(Thread.currentThread().getName() + " 有状态排序:" + k1); return k1 - k2; }) // 第四个无状态操作 .filter(key -> { System.out.println(Thread.currentThread().getName() + "\t" + key); return true; }).parallel(); long count = (long) integerStream.count();
结果:
main 2 ForkJoinPool.commonPool-worker-1 1 ForkJoinPool.commonPool-worker-2 3 main 有状态排序:2 main 有状态排序:3 main 2 ForkJoinPool.commonPool-worker-2 1 ForkJoinPool.commonPool-worker-1 3
debug 查看 integerStream:
补充: findFirst、findAny 返回Optional 对象
public static void main(String[] args) { // findFirst、findAny 返回 Optional /** * findFirst:此方法返回流中的第一个元素(按照流的遍历顺序)。在串行流中,它简单地返回遇到的第一个元素。在并行流中,它的行为也相对可预测,通常会返回处理结果中的第一个元素。 * findAny:此方法返回流中的任意一个元素(按照流的遍历顺序)。在串行流中,它通常也返回遇到的第一个元素,与 findFirst 的行为相似。但在并行流中,findAny 的行为可能会有所不同,因为它可能会返回最先处理完的元素,这样可以提高性能,因为它允许多个线程同时搜索元素。 */ List<Integer> integers = Lists.newArrayList(1, 2, 3, 2, 1); Optional<Integer> first1 = integers.stream().findFirst(); System.out.println(first1.get()); System.out.println(integers.stream().filter(integer -> integer == 2).findFirst().orElse(null)); // 找不到,返回的是 java.util.Optional.EMPTY, 空的Optional 对象 System.out.println(integers.stream().filter(integer -> integer == 4).findFirst().orElse(null)); // findAny System.out.println(integers.stream().filter(integer -> integer == 3).findAny().orElse(null)); /** * 1 * 2 * null * 3 */ }
6.Optional 类
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。
Optional提供很多有用的方法,这样我们就不用显式进行空值检测。Optional 类的引入很好的解决空指针异常。
例如:
package com.zd.bx; import java.util.Optional; import lombok.Data; @Data public class Car { public static void main(String args[]) { Car java8Tester = new Car(); Integer value1 = null; Integer value2 = new Integer(10); // Optional.ofNullable - 允许传递为 null 参数 Optional<Integer> a = Optional.ofNullable(value1); // Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException Optional<Integer> b = Optional.of(value2); System.out.println(java8Tester.sum(a, b)); } public Integer sum(Optional<Integer> a, Optional<Integer> b) { // Optional.isPresent - 判断值是否存在 System.out.println("第一个参数值存在: " + a.isPresent()); System.out.println("第二个参数值存在: " + b.isPresent()); // Optional.orElse - 如果值存在,返回它,否则返回默认值 Integer value1 = a.orElse(new Integer(0)); // Optional.get - 获取值,值需要存在 Integer value2 = b.get(); return value1 + value2; } }
结果:
第一个参数值存在: false
第二个参数值存在: true
10
7. 日期时间 API
在之前的API中,日期时间API有下列问题:
(1)非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的
(2)设计上:java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
(1)Local(本地) − 简化了日期时间的处理,没有时区的问题。
package com.zd.bx; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; import lombok.Data; @Data public class Car { public static void main(String args[]) { Car java8tester = new Car(); java8tester.testLocalDateTime(); } public void testLocalDateTime() { // 获取当前的日期时间 LocalDateTime currentTime = LocalDateTime.now(); System.out.println("当前时间: " + currentTime); LocalDate date1 = currentTime.toLocalDate(); System.out.println("date1: " + date1); Month month = currentTime.getMonth(); int day = currentTime.getDayOfMonth(); int seconds = currentTime.getSecond(); System.out.println("月: " + month + ", 日: " + day + ", 秒: " + seconds); LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012); System.out.println("date2: " + date2); // 12 december 2014 LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12); System.out.println("date3: " + date3); // 22 小时 15 分钟 LocalTime date4 = LocalTime.of(22, 15); System.out.println("date4: " + date4); // 解析字符串 LocalTime date5 = LocalTime.parse("20:15:30"); System.out.println("date5: " + date5); } }
结果:
当前时间: 2020-07-16T22:58:21.163
date1: 2020-07-16
月: JULY, 日: 16, 秒: 21
date2: 2012-07-10T22:58:21.163
date3: 2014-12-12
date4: 22:15
date5: 20:15:30
(2)Zoned(时区) − 通过制定的时区处理日期时间。
package com.zd.bx; import java.time.ZoneId; import java.time.ZonedDateTime; import lombok.Data; @Data public class Car { public static void main(String args[]) { Car java8tester = new Car(); java8tester.testZonedDateTime(); } public void testZonedDateTime() { // 获取当前时间日期 ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]"); System.out.println("date1: " + date1); ZoneId id = ZoneId.of("Europe/Paris"); System.out.println("ZoneId: " + id); ZoneId currentZone = ZoneId.systemDefault(); System.out.println("当期时区: " + currentZone); } }
8. Base64
在Java 8中,Base64编码已经成为Java类库的标准。Java 8 内置了 Base64 编码的编码器和解码器。
例如:
package com.zd.bx; import java.io.UnsupportedEncodingException; import java.util.Base64; import java.util.UUID; public class Car { public static void main(String args[]) { try { // 使用基本编码 String base64encodedString = Base64.getEncoder().encodeToString("runoob?java8".getBytes("utf-8")); System.out.println("Base64 编码字符串 (基本) :" + base64encodedString); // 解码 byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString); System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8")); base64encodedString = Base64.getUrlEncoder().encodeToString("runoob?java8".getBytes("utf-8")); System.out.println("Base64 编码字符串 (URL) :" + base64encodedString); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 10; ++i) { stringBuilder.append(UUID.randomUUID().toString()); } byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8"); String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes); System.out.println("Base64 编码字符串 (MIME) :" + mimeEncodedString); } catch (UnsupportedEncodingException e) { System.out.println("Error :" + e.getMessage()); } } }
补充:Java8根据某个属性分组的快速方法,转为map结构
public static void main(String[] args) throws ExecutionException, InterruptedException { User user = new User("u1", 1); User user2 = new User("u2", 2); User user3 = new User("u3", 3); User user4 = new User("u4", 1); User user5 = new User("u5", 5); User user6 = new User("u6", 5); List<User> users = new ArrayList<>(); users.add(user); users.add(user2); users.add(user3); users.add(user4); users.add(user5); users.add(user6); users.add(user6); // 根据年龄分组转为map Map<Integer, List<User>> collect = users.stream().collect(Collectors.groupingBy(User::getAge)); System.out.println(collect); // 搜集数据的username 属性 List<String> collect1 = users.stream().map(User::getUsername).collect(Collectors.toList()); System.out.println(collect1); // 过滤,去重,然后收集username, 并且用空格拼接 String collect2 = users.stream().filter((userTmp) -> userTmp.getAge() > 3).distinct().map(User::getUsername).collect(Collectors.joining(" ")); System.out.println(collect2); }
结果:
{1=[User{username='u1', age=1}, User{username='u4', age=1}], 2=[User{username='u2', age=2}], 3=[User{username='u3', age=3}], 5=[User{username='u5', age=5}, User{username='u6', age=5}, User{username='u6', age=5}]}
[u1, u2, u3, u4, u5, u6, u6]
u5 u6
补充:Collectors.toMap 使用
1. Collectors.toMap 如果key 值为null 会报NPE 异常, 如果key 值重复,需要自己指定保留的value 值, 如果不指定也会报错。
List<User> users = Lists.newArrayList(new User("zs", "张三", "男"), new User("zs2", "张三", "男"), new User(null, null, "男"), new User("zs3", "张三3", "男")); // key 能为空,value 不允许为空。 如果重复不做处理会报错,所以Collectors.toMap 第三个参数指定key 重复之后保留的数据 Map<String, String> collect = users.stream().filter((user) -> user.getUsername() != null).collect(Collectors.toMap(User::getFullname, User::getUsername, (v1, v2) -> { System.out.println("v1: " + v1); System.out.println("v2: " + v2); return v1; })); System.out.println(collect);
结果:
v1: zs
v2: zs2
{张三=zs, 张三3=zs3}
2. 比如有个用户集合,我们想将其按id 分组:
public static void main(String[] args) throws NoSuchFieldException { User user = new User(); user.setId(1); user.setUsername("username" + 1); User user2 = new User(); user2.setId(2); user2.setUsername("username" + 2); ArrayList<User> users = Lists.newArrayList(user, user2); Map<Integer, User> collect = users.stream().collect(Collectors.toMap(User::getId, Function.identity())); System.out.println(collect); } @Data private static class User { private Integer id; private String username; }
结果:
{1=PlainTest.User(id=1, username=username1), 2=PlainTest.User(id=2, username=username2)}
补充: .collect 实现简单解读
1. .collect 源码解读
/** * Performs a <a href="package-summary.html#MutableReduction">mutable * reduction</a> operation on the elements of this stream using a * {@code Collector}. A {@code Collector} * encapsulates the functions used as arguments to * {@link #collect(Supplier, BiConsumer, BiConsumer)}, allowing for reuse of * collection strategies and composition of collect operations such as * multiple-level grouping or partitioning. * * <p>If the stream is parallel, and the {@code Collector} * is {@link Collector.Characteristics#CONCURRENT concurrent}, and * either the stream is unordered or the collector is * {@link Collector.Characteristics#UNORDERED unordered}, * then a concurrent reduction will be performed (see {@link Collector} for * details on concurrent reduction.) * * <p>This is a <a href="package-summary.html#StreamOps">terminal * operation</a>. * * <p>When executed in parallel, multiple intermediate results may be * instantiated, populated, and merged so as to maintain isolation of * mutable data structures. Therefore, even when executed in parallel * with non-thread-safe data structures (such as {@code ArrayList}), no * additional synchronization is needed for a parallel reduction. * * @apiNote * The following will accumulate strings into an ArrayList: * <pre>{@code * List<String> asList = stringStream.collect(Collectors.toList()); * }</pre> * * <p>The following will classify {@code Person} objects by city: * <pre>{@code * Map<String, List<Person>> peopleByCity * = personStream.collect(Collectors.groupingBy(Person::getCity)); * }</pre> * * <p>The following will classify {@code Person} objects by state and city, * cascading two {@code Collector}s together: * <pre>{@code * Map<String, Map<String, List<Person>>> peopleByStateAndCity * = personStream.collect(Collectors.groupingBy(Person::getState, * Collectors.groupingBy(Person::getCity))); * }</pre> * * @param <R> the type of the result * @param <A> the intermediate accumulation type of the {@code Collector} * @param collector the {@code Collector} describing the reduction * @return the result of the reduction * @see #collect(Supplier, BiConsumer, BiConsumer) * @see Collectors */ <R, A> R collect(Collector<? super T, A, R> collector);
可以看到传入的是一个Collector,是一个泛型,泛型的T,A,R 分别表示:
T : 要处理的元素的类型
A : 累加器的类型
R : 返回结果的类型
2. java.util.stream.Collector 源码如下:
public interface Collector<T, A, R> { /** * A function that creates and returns a new mutable result container. * * @return a function which returns a new, mutable result container */ Supplier<A> supplier(); /** * A function that folds a value into a mutable result container. * * @return a function which folds a value into a mutable result container */ BiConsumer<A, T> accumulator(); /** * A function that accepts two partial results and merges them. The * combiner function may fold state from one argument into the other and * return that, or may return a new result container. * * @return a function which combines two partial results into a combined * result */ BinaryOperator<A> combiner(); /** * Perform the final transformation from the intermediate accumulation type * {@code A} to the final result type {@code R}. * * <p>If the characteristic {@code IDENTITY_TRANSFORM} is * set, this function may be presumed to be an identity transform with an * unchecked cast from {@code A} to {@code R}. * * @return a function which transforms the intermediate result to the final * result */ Function<A, R> finisher(); /** * Returns a {@code Set} of {@code Collector.Characteristics} indicating * the characteristics of this Collector. This set should be immutable. * * @return an immutable set of collector characteristics */ Set<Characteristics> characteristics(); /** * Returns a new {@code Collector} described by the given {@code supplier}, * {@code accumulator}, and {@code combiner} functions. The resulting * {@code Collector} has the {@code Collector.Characteristics.IDENTITY_FINISH} * characteristic. * * @param supplier The supplier function for the new collector * @param accumulator The accumulator function for the new collector * @param combiner The combiner function for the new collector * @param characteristics The collector characteristics for the new * collector * @param <T> The type of input elements for the new collector * @param <R> The type of intermediate accumulation result, and final result, * for the new collector * @throws NullPointerException if any argument is null * @return the new {@code Collector} */ public static<T, R> Collector<T, R, R> of(Supplier<R> supplier, BiConsumer<R, T> accumulator, BinaryOperator<R> combiner, Characteristics... characteristics) { Objects.requireNonNull(supplier); Objects.requireNonNull(accumulator); Objects.requireNonNull(combiner); Objects.requireNonNull(characteristics); Set<Characteristics> cs = (characteristics.length == 0) ? Collectors.CH_ID : Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH, characteristics)); return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, cs); } /** * Returns a new {@code Collector} described by the given {@code supplier}, * {@code accumulator}, {@code combiner}, and {@code finisher} functions. * * @param supplier The supplier function for the new collector * @param accumulator The accumulator function for the new collector * @param combiner The combiner function for the new collector * @param finisher The finisher function for the new collector * @param characteristics The collector characteristics for the new * collector * @param <T> The type of input elements for the new collector * @param <A> The intermediate accumulation type of the new collector * @param <R> The final result type of the new collector * @throws NullPointerException if any argument is null * @return the new {@code Collector} */ public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier, BiConsumer<A, T> accumulator, BinaryOperator<A> combiner, Function<A, R> finisher, Characteristics... characteristics) { Objects.requireNonNull(supplier); Objects.requireNonNull(accumulator); Objects.requireNonNull(combiner); Objects.requireNonNull(finisher); Objects.requireNonNull(characteristics); Set<Characteristics> cs = Collectors.CH_NOID; if (characteristics.length > 0) { cs = EnumSet.noneOf(Characteristics.class); Collections.addAll(cs, characteristics); cs = Collections.unmodifiableSet(cs); } return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, finisher, cs); } /** * Characteristics indicating properties of a {@code Collector}, which can * be used to optimize reduction implementations. */ enum Characteristics { /** * Indicates that this collector is <em>concurrent</em>, meaning that * the result container can support the accumulator function being * called concurrently with the same result container from multiple * threads. * * <p>If a {@code CONCURRENT} collector is not also {@code UNORDERED}, * then it should only be evaluated concurrently if applied to an * unordered data source. */ CONCURRENT, /** * Indicates that the collection operation does not commit to preserving * the encounter order of input elements. (This might be true if the * result container has no intrinsic order, such as a {@link Set}.) */ UNORDERED, /** * Indicates that the finisher function is the identity function and * can be elided. If set, it must be the case that an unchecked cast * from A to R will succeed. */ IDENTITY_FINISH } }
可以看到,内部包含:
supplier:提供一个新的集合,用于存放元素
accumulator:返回一个BiConsumer累加器,也就是元素和我们的集合进行的操作。 需要做的就是元素放入集合。
combiner:返回一个BinaryOperator,用于并行流执行时合并两个累加器list到一个累加器list中
finisher: 返回一个Function,用于完成时,将累加器的结果A转换成想要的结果R,不需要转换时可以使用 t -> t;表达式表示;
characteristics:它会返回一个不可变的Characteristics集合,用于定义收集器的行为;
1、UNORDERED:归约结果不受流中项目的遍历和累积顺序的影响,即流归约结果是无序的,可以并行执行归约。 2、CONCURRENT:accumulator函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为UNORDERED,那它仅在用于无序数据源时才可以并行归约。 3、IDENTITY_FINISH:这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用作归约过程的最终结果。这也意味着,将累加器A不加检查地转换为结果R是安全的,即不需要定义finisher,或定义为 t -> t 。
2. 自己实现Collectors.toList
import lombok.AllArgsConstructor; import lombok.Data; import java.util.*; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; public class PlainTest { public static void main(String[] args) { List<User> userList = new ArrayList<>(); userList.add(new User("zs", 2)); userList.add(new User("zs", 4)); userList.add(new User("ls", 2)); userList.add(new User("ls", 23)); // List<String> collect = userList.stream().map(User::getName).collect(Collectors.toList()); List<String> collect1 = userList.stream().map(User::getName).collect(new Collector<String, List<String>, List<String>>() { @Override public Supplier<List<String>> supplier() { System.out.println(1); return ArrayList::new; } @Override public BiConsumer<List<String>, String> accumulator() { System.out.println(2); return new BiConsumer<List<String>, String>() { @Override public void accept(List<String> strings, String s) { System.out.println(21); strings.add(s); } }; // return List::add; } /** * * @return */ @Override public BinaryOperator<List<String>> combiner() { System.out.println(3); return new BinaryOperator<List<String>>() { @Override public List<String> apply(List<String> left, List<String> right) { System.out.println(4); left.addAll(right); return left; } }; } @Override public Function<List<String>, List<String>> finisher() { System.out.println(5); return new Function<List<String>, List<String>>() { @Override public List<String> apply(List<String> strings) { System.out.println(6); return strings; } }; } @Override public Set<Characteristics> characteristics() { System.out.println(7); return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH)); } }); System.out.println(collect1); } @Data @AllArgsConstructor public static class User { private String name; private int age; } }
结果:
1
2
3
7
21
21
21
21
7
[zs, zs, ls, ls]
换成parallelStream() 再次查看:
7
1
2
3
7
21
21
21
21
4
4
4
7
[zs, zs, ls, ls]
3. 精简下
List<String> collect1 = userList.parallelStream().map(User::getName).collect(new Collector<String, List<String>, List<String>>() { @Override public Supplier<List<String>> supplier() { return ArrayList::new; } @Override public BiConsumer<List<String>, String> accumulator() { // return (a, b) -> a.add(b); return List::add; } /** * * @return */ @Override public BinaryOperator<List<String>> combiner() { return (left, right) -> { left.addAll(right); return left; }; } @Override public Function<List<String>, List<String>> finisher() { return t -> t; } @Override public Set<Characteristics> characteristics() { return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH)); } }); System.out.println(collect1); }
4. 方法二:
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
简单实现:
List<User> userList = new ArrayList<>(); userList.add(new User("zs", 2)); userList.add(new User("zs", 4)); userList.add(new User("ls", 2)); userList.add(new User("ls", 23)); // List<String> collect = userList.stream().map(User::getName).collect(Collectors.toList()); List<String> collect1 = userList.parallelStream().map(User::getName) .collect(ArrayList::new, List::add, List::addAll ); System.out.println(collect1);
结果:
[zs, zs, ls, ls]
5. 自己实现 Collectors.groupBying(User::getName)
List<User> userList = new ArrayList<>(); userList.add(new User("zs", 2)); userList.add(new User("zs", 4)); userList.add(new User("ls", 2)); userList.add(new User("ls", 23)); HashMap<String, List<User>> collect = userList.stream(). collect(HashMap::new, new BiConsumer<HashMap<String, List<User>>, User>() { @Override public void accept(HashMap<String, List<User>> objectObjectHashMap, User user) { List<User> users = objectObjectHashMap.get(user.getName()); if (users == null) { users = new ArrayList<>(); objectObjectHashMap.put(user.getName(), users); } users.add(user); } }, HashMap::putAll ); System.out.println(collect);
结果:
{ls=[PlainTest.User(name=ls, age=2), PlainTest.User(name=ls, age=23)], zs=[PlainTest.User(name=zs, age=2), PlainTest.User(name=zs, age=4)]}
6. toList 和 groupBying 源码查看
public static <T> Collector<T, ?, List<T>> toList() { return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add, (left, right) -> { left.addAll(right); return left; }, CH_ID); } public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream) { Supplier<A> downstreamSupplier = downstream.supplier(); BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator(); BiConsumer<Map<K, A>, T> accumulator = (m, t) -> { K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key"); A container = m.computeIfAbsent(key, k -> downstreamSupplier.get()); downstreamAccumulator.accept(container, t); }; BinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner()); @SuppressWarnings("unchecked") Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory; if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID); } else { @SuppressWarnings("unchecked") Function<A, A> downstreamFinisher = (Function<A, A>) downstream.finisher(); Function<Map<K, A>, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") M castResult = (M) intermediate; return castResult; }; return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID); } } public static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) { BiConsumer<M, T> accumulator = (map, element) -> map.merge(keyMapper.apply(element), valueMapper.apply(element), mergeFunction); return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID); }
另外groupingBy 还有一个参数、一个参数的重载。
public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier) { return groupingBy(classifier, toList()); } public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream) { return groupingBy(classifier, HashMap::new, downstream); }
比如实现自动转为TreeMap
Map<String, List<Map<String, Object>>> collect = datas.stream().collect(Collectors.groupingBy(tmp -> MapUtils.getString(tmp, DATE_TIME, ""), TreeMap::new, Collectors.toList()));
7. 自己实现 按某个字段分组然后计数存到map 中
package qz; import com.beust.jcommander.internal.Lists; import lombok.extern.slf4j.Slf4j; import qz.bean.User; import java.util.*; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; import java.util.stream.Collectors; /** * PlainTest * * @author qlq * @date 2024/4/18 10:48 */ @Slf4j public class PlainTest { public static void main(String[] args) { User user = new User("qlq", "123456", "1"); User user2 = new User("qlq1", "123456", "2"); User user3 = new User("qlq", "123456", "1"); List<User> users = Lists.newArrayList(user, user2, user3); Map<String, List<User>> collect = users.stream().collect(Collectors.groupingBy(User::getClassNum)); System.out.println(collect); // users 按 classNum 分组,统计每个组的个数。 结果收集到map 中 Map<String, Long> collect1 = users.stream().collect(Collectors.groupingBy(User::getClassNum, Collectors.counting())); System.out.println(collect1); // 自己实现 /** * 泛型对应的顺序如下 * * T : 要处理的元素的类型 * A : 累加器的类型 * R : 返回结果的类型 */ Map<String, Integer> collect2 = users.parallelStream().collect(new Collector<User, Map<String, Long>, Map<String, Integer>>() { /** * 返回一个新的集合, 用于处理初期存储数据 * @return */ @Override public Supplier<Map<String, Long>> supplier() { PrintTool.printTimeAndThread("supplier", "1"); return HashMap::new; } /** * biConsumer, 遍历操作, 也就是遍历集合做的操作 * @return */ @Override public BiConsumer<Map<String, Long>, User> accumulator() { PrintTool.printTimeAndThread("accumulator", "2"); return new BiConsumer<Map<String, Long>, User>() { @Override public void accept(Map<String, Long> stringLongMap, User user) { PrintTool.printTimeAndThread("accumulator", "21"); if (stringLongMap.containsKey(user.getClassNum())) { stringLongMap.put(user.getClassNum(), stringLongMap.get(user.getClassNum()) + 1); } else { stringLongMap.put(user.getClassNum(), 1L); } } }; } /** * 合并操作, parallelStream 操作下的操作 * @return */ @Override public BinaryOperator<Map<String, Long>> combiner() { PrintTool.printTimeAndThread("accumulator", "3"); return new BinaryOperator<Map<String, Long>>() { @Override public Map<String, Long> apply(Map<String, Long> stringLongHashMap, Map<String, Long> stringLongHashMap2) { PrintTool.printTimeAndThread("accumulator", "31"); // 创建一个新Map用于存放合并后的结果 Map<String, Long> mergedMap = new HashMap<>(stringLongHashMap); // 遍历第二个Map的所有键值对 for (Map.Entry<String, Long> entry : stringLongHashMap2.entrySet()) { String key = entry.getKey(); long value = entry.getValue(); // 如果第一个Map中存在相同的键,则将对应值累加到新Map中 if (mergedMap.containsKey(key)) { mergedMap.put(key, mergedMap.get(key) + value); } else { // 否则直接将键值对添加到新Map中 mergedMap.put(key, value); } } return mergedMap; } }; } /** * 转换结果处理器。 A -> R * @return */ @Override public Function<Map<String, Long>, Map<String, Integer>> finisher() { PrintTool.printTimeAndThread("accumulator", "4"); return new Function<Map<String, Long>, Map<String, Integer>>() { @Override public Map<String, Integer> apply(Map<String, Long> stringLongHashMap) { PrintTool.printTimeAndThread("accumulator", "41"); HashMap<String, Integer> stringIntegerHashMap = new HashMap<>(); stringLongHashMap.forEach((key, value) -> { stringIntegerHashMap.put(key, Integer.valueOf(value.toString())); }); return stringIntegerHashMap; } }; } /** * 1、UNORDERED:归约结果不受流中项目的遍历和累积顺序的影响,即流归约结果是无序的,可以并行执行归约。 * * 2、CONCURRENT:accumulator函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为UNORDERED,那它仅在用于无序数据源时才可以并行归约。 * * 3、IDENTITY_FINISH:这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用作归约过程的最终结果。这也意味着,将累加器A不加检查地转换为结果R是安全的,即不需要定义finisher,或定义为 t -> t 。 * @return */ @Override public Set<Characteristics> characteristics() { PrintTool.printTimeAndThread("accumulator", "51"); return Collections.unmodifiableSet(EnumSet.of(Characteristics.CONCURRENT)); } }); PrintTool.printTimeAndThread("collect2", collect2.toString()); /** * 精简之后的 * <R> R collect(Supplier<R> supplier, * BiConsumer<R, ? super T> accumulator, * BiConsumer<R, R> combiner); */ Map<Object, Long> collect3 = users.parallelStream().collect(HashMap::new, (map, tmpUser) -> { // 遍历map, 根据classNum 计数 Long count = map.getOrDefault(tmpUser.getClassNum(), 0L); map.put(tmpUser.getClassNum(), count + 1); }, Map::putAll); PrintTool.printTimeAndThread("collect3", collect3.toString()); } }
补充: jdk8 map 提供的方法
1. compute: 对 hashMap 中指定 key 的值进行重新计算。
package org.example; import java.util.HashMap; import java.util.function.BiFunction; public class PlainTest { public static void main(String[] args) { // compute: 对 hashMap 中指定 key 的值进行重新计算。 // 如果 key 对应的 value 不存在,则返回该 null,如果存在,则返回通过 remappingFunction 重新计算后的值 //创建一个 HashMap HashMap<String, Integer> prices = new HashMap<>(); // 往HashMap中添加映射项 prices.put("Shoes", 200); prices.put("Bag", 300); prices.put("Pant", 150); PrintUtils.print("HashMap: " + prices); MyBiFunction myBiFunction = new MyBiFunction(); // 重新计算鞋子打了10%折扣后的值 Integer newPrice = prices.compute("Shoes", myBiFunction); PrintUtils.print("Discounted Price of Shoes: " + newPrice); // 输出更新后的HashMap PrintUtils.print("Updated HashMap: " + prices); Integer tshirtPrice = prices.compute("tshirt", myBiFunction); PrintUtils.print("Discounted Price of tshirtPrice: " + tshirtPrice); // 输出更新后的HashMap PrintUtils.print("Updated HashMap: " + prices); Integer tshirt2Price = prices.compute("tshirt2", myBiFunction); PrintUtils.print("Discounted Price of tshirt2Price: " + tshirt2Price); // 输出更新后的HashMap PrintUtils.print("Updated HashMap: " + prices); Integer bag = prices.compute("Bag", myBiFunction); PrintUtils.print("Discounted Price of bag: " + bag); // 输出更新后的HashMap PrintUtils.print("Updated HashMap: " + prices); } public static class MyBiFunction implements BiFunction<String, Integer, Integer> { @Override public Integer apply(String key, Integer value) { PrintUtils.print("key: %s, value: %s", key, value); if (value != null && !"Bag".equals(key)) { return value - value * 10 / 100; } if ("tshirt".equals(key)) { return 20; } return null; } } }
结果:
threadName main, str: HashMap: {Pant=150, Bag=300, Shoes=200} threadName main, str: key: Shoes, value: 200 threadName main, str: Discounted Price of Shoes: 180 threadName main, str: Updated HashMap: {Pant=150, Bag=300, Shoes=180} threadName main, str: key: tshirt, value: null threadName main, str: Discounted Price of tshirtPrice: 20 threadName main, str: Updated HashMap: {Pant=150, tshirt=20, Bag=300, Shoes=180} threadName main, str: key: tshirt2, value: null threadName main, str: Discounted Price of tshirt2Price: null threadName main, str: Updated HashMap: {Pant=150, tshirt=20, Bag=300, Shoes=180} threadName main, str: key: Bag, value: 300 threadName main, str: Discounted Price of bag: null threadName main, str: Updated HashMap: {Pant=150, tshirt=20, Shoes=180}
注意:
1. 如果map 中不存在对应的key,传到java.util.function.BiFunction#apply 的是null
2. 如果java.util.function.BiFunction#apply 返回的是null,则原map 将删除对应的key; 否则是替换对应的值
2.
package org.example; import java.util.HashMap; import java.util.function.Function; public class PlainTest { public static void main(String[] args) { // compute: 对 hashMap 中指定 key 的值进行重新计算。 // 如果 key 对应的 value 不存在,则返回该 null,如果存在,则返回通过 remappingFunction 重新计算后的值 //创建一个 HashMap HashMap<String, Integer> prices = new HashMap<>(); // 往HashMap中添加映射项 prices.put("Shoes", 200); prices.put("Bag", 300); prices.put("Pant", 150); PrintUtils.print("HashMap: " + prices); MyFunction myFunction = new MyFunction(); // 重新计算鞋子打了10%折扣后的值 Integer shoes = prices.computeIfAbsent("Shoes", myFunction); PrintUtils.print("shoes: " + shoes); PrintUtils.print("prices: " + prices); Integer tshirt = prices.computeIfAbsent("tshirt", myFunction); PrintUtils.print("tshirt: " + tshirt); PrintUtils.print("prices: " + prices); Integer Shoes2 = prices.computeIfAbsent("Shoes2", myFunction); PrintUtils.print("Shoes2: " + Shoes2); PrintUtils.print("prices: " + prices); } public static class MyFunction implements Function<String, Integer> { @Override public Integer apply(String key) { PrintUtils.print("key: %s", key); if ("tshirt".equals(key)) { return null; } // 返回一个默认值 return 299; } } }
结果:
threadName main, str: HashMap: {Pant=150, Bag=300, Shoes=200} threadName main, str: shoes: 200 threadName main, str: prices: {Pant=150, Bag=300, Shoes=200} threadName main, str: key: tshirt threadName main, str: tshirt: null threadName main, str: prices: {Pant=150, Bag=300, Shoes=200} threadName main, str: key: Shoes2 threadName main, str: Shoes2: 299 threadName main, str: prices: {Pant=150, Shoes2=299, Bag=300, Shoes=200}
常用场景:
# map 初始化一些元素,有则返回、无则创建 Map<String, List<String>> result = new HashMap<>(); result.computeIfAbsent(key, (k) -> new ArrayList<>()).add(str);
3.
package org.example; import java.util.HashMap; import java.util.function.BiFunction; public class PlainTest { public static void main(String[] args) { // compute: 对 hashMap 中指定 key 的值进行重新计算。 // 如果 key 对应的 value 不存在,则返回该 null,如果存在,则返回通过 remappingFunction 重新计算后的值 //创建一个 HashMap HashMap<String, Integer> prices = new HashMap<>(); // 往HashMap中添加映射项 prices.put("Shoes", 200); prices.put("Bag", null); prices.put("Pant", 150); PrintUtils.print("HashMap: " + prices); MyBiFunction myBiFunction = new MyBiFunction(); // 重新计算鞋子打了10%折扣后的值 Integer newPrice = prices.computeIfPresent("Shoes", myBiFunction); PrintUtils.print("Discounted Price of Shoes: " + newPrice); PrintUtils.print("Updated HashMap: " + prices); Integer tshirtPrice = prices.computeIfPresent("tshirt", myBiFunction); PrintUtils.print("Discounted Price of tshirtPrice: " + tshirtPrice); PrintUtils.print("Updated HashMap: " + prices); Integer tshirt2Price = prices.computeIfPresent("tshirt2", myBiFunction); PrintUtils.print("Discounted Price of tshirt2Price: " + tshirt2Price); PrintUtils.print("Updated HashMap: " + prices); Integer bag = prices.computeIfPresent("Bag", myBiFunction); PrintUtils.print("Discounted Price of bag: " + bag); // 输出更新后的HashMap PrintUtils.print("Updated HashMap: " + prices); } public static class MyBiFunction implements BiFunction<String, Integer, Integer> { @Override public Integer apply(String key, Integer value) { PrintUtils.print("key: %s, value: %s", key, value); if (value != null && !"Bag".equals(key)) { return value - value * 10 / 100; } if ("tshirt".equals(key)) { return 20; } return null; } } }
结果:
threadName main, str: HashMap: {Pant=150, Bag=null, Shoes=200} threadName main, str: key: Shoes, value: 200 threadName main, str: Discounted Price of Shoes: 180 threadName main, str: Updated HashMap: {Pant=150, Bag=null, Shoes=180} threadName main, str: Discounted Price of tshirtPrice: null threadName main, str: Updated HashMap: {Pant=150, Bag=null, Shoes=180} threadName main, str: Discounted Price of tshirt2Price: null threadName main, str: Updated HashMap: {Pant=150, Bag=null, Shoes=180} threadName main, str: Discounted Price of bag: null threadName main, str: Updated HashMap: {Pant=150, Bag=null, Shoes=180}
补充:stream 直接转数组
String[] strings = Lists.newArrayList("1", "2", "3").stream().toArray(String[]::new);
补充: 关于collector null 问题
package qz; import com.beust.jcommander.internal.Lists; import lombok.extern.slf4j.Slf4j; import qz.bean.User; import java.util.List; import java.util.Set; import java.util.stream.Collectors; /** * PlainTest * * @author qlq */ @Slf4j public class PlainTest { public static void main(String[] args) { List<User> users = Lists.newArrayList(new User("1", "1", null), new User("01", "1", "1"), new User("2", "2", null), new User("3", "3", "3")); // key 不允许为空。 也就是分组的key 不能为空 // Map<String, List<User>> collect = users.stream().collect(Collectors.groupingBy(User::getClassNum)); // System.out.println(collect); // key 允许为空, value 不允许为空 // Map<String, String> collect2 = users.stream().collect(Collectors.toMap(User::getClassNum, User::getName)); // System.out.println(collect2); // 允许为空 Set<String> collect1 = users.stream().map(User::getClassNum).collect(Collectors.toSet()); System.out.println(collect1); // 允许为空 List<String> collect3 = users.stream().map(User::getClassNum).collect(Collectors.toList()); System.out.println(collect3); } }