');}

Java8

Author Avatar
Rico 10月 17, 2019

在很多编程语言里(例如JavaScript),都可以实现函数式的编程,也就是函数可以作为变量去灵活使用,但是java一直都不可以,之前很多都使用一些匿名内部类这种丑的亚批的代码。java8之后算是可以使用伪函数式编程,其实也是应用了单方法接口去实现。并且设计出了lambda语法

函数式编程

lamda

lambda之前实现thread

  1. Thread thread = new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. System.out.println("xxx");
  5. }
  6. });

在lambda实现thread

  1. Thread thread = new Thread(()-> System.out.println("xx"));

函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

其实上面的runnable接口就是一个函数式接口

Java8 添加了一个新的特性Function,顾名思义这一定是一个函数式的操作。我们知道Java8的最大特性就是函数式接口。所有标注了@FunctionalInterface注解的接口都是函数式接口,具体来说,所有标注了该注解的接口都将能用在lambda表达式上。(函数式接口可以被隐式转换为 lambda 表达式)

函数式接口可以被隐式转换为 lambda 表达式。

Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。

标注了@FunctionalInterface的接口有很多,但此篇我们主要讲Function,了解了Function其他的操作也就很容易理解了。

我们也可以自己实现一个

Lambda表达式是如何在java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。

我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。

  1. @FunctionalInterface
  2. interface Converter<F, T> {
  3. T convert(F from);//如果写两个方法就会报错
  4. }
  1. public class MyTest {
  2. public static void main(String[] args) {
  3. Converter<String, Integer> converter = (from) -> Integer.valueOf(from);//如果是多行就用大括号括起来 使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):
  4. Integer converted = converter.convert("123");
  5. System.out.println(converted); // 123
  6. }
  7. }

需要注意如果没有@FunctionalInterface,上面的代码也是对的。

Stream

foreach

  1. //构建个User对象的list,后面都会使用到 User(id,name,age)
  2. List<User> userList = Lists.newArrayList();
  3. userList.add(new User(1, "aaa", 18));
  4. userList.add(new User(2, "bbb", 21));
  5. userList.add(new User(3, "ccc", 25));
  6. //使用stream.foreach 依次输出
  7. userList.stream().forEach(user->{
  8. System.out.println(user.getName());
  9. });

filter

filter可以按照你的要求进行过滤,非常方便

  1. userList.stream().filter(user -> user.getAge() > 18).forEach(user -> {
  2. //输出age大于18的user的name
  3. System.out.println(user.getName());
  4. });

filter中也可以是一个大括号的语法段或者是方法,返回true/false即可

  1. List list = list.stream().map(xxDO::getID).filter(xxx::contains).collect(Collectors.toList());

Map映射

中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。

  1. //字符串集合转成大写形式,并按序输出
  2. stringCollection
  3. .stream()
  4. .map(String::toUpperCase)
  5. .sorted((a, b) -> b.compareTo(a))
  6. .forEach(System.out::println);
  7. // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
  1. List<Staff> staff = Arrays.asList(
  2. new Staff("mkyong", 30, new BigDecimal(10000)),
  3. new Staff("jack", 27, new BigDecimal(20000)),
  4. new Staff("lawrence", 33, new BigDecimal(30000))
  5. );
  6. //将对象集合中对象的某个字段生成一个集合
  7. List<String> collect = staff.stream().map(x -> x.getName()).collect(Collectors.toList());
  8. System.out.println(collect); //[mkyong, jack, lawrence]
  1. List<Integer> num = Arrays.asList(1,2,3,4,5);
  2. //依次对集合中的对象进行操作
  3. List<Integer> collect1 = num.stream().map(n -> n * 2).collect(Collectors.toList());
  4. System.out.println(collect1); //[2, 4, 6, 8, 10]
  1. //将对象集合生成key为id,value为对象的映射map
  2. userMap.putAll(users.stream().collect(Collectors.toMap(e ->
  3. e.getId().toString(), view -> view, (k1, k2) -> k2)));
  4. //[{"id":1,"name":"张三"},{"id":2,"name":"李四"}] -- > {1:{"id":1,"name":"张三"},2:{"id":2,"name":"李四"}}

  1. //将do集合的元素挨个转成bo然后形成一个新的集合
  2. userBOS = userDOS.stream().map(USERConvert::convertDO2BO).collect(Collectors.toList());
  1. List<Long> userids = bos.stream().map(UserBO::getId).collect(Collectors.toList());

collect

toMap

collect可以实现各种集合之间按照一定自定义规则的转换,最为典型的就是list转换map的例子

  1. Map<String, User> map = userList.stream().collect(Collectors.toMap(User::getName, pojo -> pojo, (oldVal, newVal) -> newVal));

解释下toMap的3个参数
第一个参数是用哪个字段作为key
第二个参数是map的value类型,可以取原来的对象,也可以是原来对象的某个字段 比如pojo->pojo改为 User::getAge 就是value只收集user的age字段
第三个参数(k1, k2) -> k1)是可选的,在key冲突的时候丢弃其中一个,如果没有这个参数会直接报错

当toMap中没有用合并函数时,出现key重复时,会抛出异常 : Exception in thread “main” java.lang.IllegalStateException: Duplicate key aa

当使用合并函数时,可通过Labmda表达式,对重复值进行处理

toList

Collectors.toList():转换成List集合。/ Collectors.toSet():转换成set集合。

  1. System.out.println(Stream.of("a", "b", "c","d").collect(Collectors.toSet()));

Collectors.toCollection(TreeSet::new):转换成特定的set集合。

  1. TreeSet<String> treeSet = Stream.of("a", "c", "b", "a").collect(Collectors.toCollection(TreeSet::new));
  2. System.out.println(treeSet);

拼接字符串

  1. System.out.println(Stream.of("1", "3", "3", "2").collect(Collectors.joining(",")));

collectingAndThen

Collectors.collectingAndThen(Collectors.toList(), x -> x.size()):先执行collect操作后再执行第二个参数的表达式。这里是先塞到集合,再得出集合长度。

  1. Integer integer = Stream.of("1", "2", "3").collect(Collectors.collectingAndThen(Collectors.toList(), x -> x.size()));

collect+map

collect+map抽取pojo对象的某个字段

  1. List<String> names = userList.stream().map(User::getName).collect(Collectors.toList());

match

Match 匹配

Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。

  1. boolean anyStartsWithA =
  2. stringCollection
  3. .stream()
  4. .anyMatch((s) -> s.startsWith("a"));
  5. System.out.println(anyStartsWithA); // true
  6. boolean allStartsWithA =
  7. stringCollection
  8. .stream()
  9. .allMatch((s) -> s.startsWith("a"));
  10. System.out.println(allStartsWithA); // false
  11. boolean noneStartsWithZ =
  12. stringCollection
  13. .stream()
  14. .noneMatch((s) -> s.startsWith("z"));
  15. System.out.println(noneStartsWithZ); // true

Collectors

Collectors类中提供的方法

总结一下,就是以下几类方法:

转换成集合:toList(),toSet(),toMap(),toCollection()

将集合拆分拼接成字符串:joining()

求最大值、最小值、求和、平均值 :maxBy(),minBy(),summingInt(),averagingDouble()

对集合分组:groupingBy(),partitioningBy()

对数据进行映射:mapping()

将流数据转换成集合

  1. //转换成list集合
  2. List<String> stringList = strings.stream().collect(Collectors.toList());
  3. //转换成Set集合
  4. Set<String> stringSet = strings.stream().collect(Collectors.toSet());
  5. //转换成Map集合
  6. Map<String,Object> stringObjectMap = strings.stream()
  7. .collect(Collectors.toMap(k -> k, v -> v ));
  8. System.out.println(stringList);
  9. System.out.println(stringSet);
  10. System.out.println(stringObjectMap);
  11. /*=================打印结果=================
  12. [ab, s, bc, cd, abcd, sd, jkl]
  13. [ab, bc, cd, sd, s, jkl, abcd]
  14. {sd=sd, cd=cd, bc=bc, ab=ab, s=s, jkl=jkl, abcd=abcd}
  15. */

将集合拆分拼接成字符串

  1. //joining
  2. String str1 = strings.stream().collect(Collectors.joining("--"));
  3. //collectingAndThen
  4. String str2 = strings.stream().collect(Collectors.collectingAndThen(
  5. Collectors.joining("--"), s1 -> s1 += ",then" //在第一个joining操作的结果基础上再进行一次操作
  6. ));
  7. System.out.println(str1);
  8. System.out.println(str2);
  9. /*=================打印结果=================
  10. ab--s--bc--cd--abcd--sd--jkl
  11. ab--s--bc--cd--abcd--sd--jkl,then
  12. */

求最大值、最小值、求和、平均值

  1. List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
  2. //最大值
  3. Integer maxValue = list.stream().collect(Collectors.collectingAndThen(
  4. //maxBy需要Comparator.comparingInt来确定排序规则
  5. Collectors.maxBy(Comparator.comparingInt(a -> a)), Optional::get
  6. ));
  7. //最小值
  8. Integer minValue = list.stream().collect(Collectors.collectingAndThen(
  9. //minBy需要Comparator.comparingInt来确定排序规则
  10. Collectors.minBy(Comparator.comparingInt(a -> a)), Optional::get
  11. ));
  12. //求和
  13. Integer sumValue = list.stream().collect(Collectors.summingInt(i -> i));
  14. //平均值
  15. Double avgValue = list.stream().collect(Collectors.averagingDouble(i -> i));
  16. System.out.println("列表中最大的数 : " + maxValue);
  17. System.out.println("列表中最小的数 : " + minValue);
  18. System.out.println("所有数之和 : " + sumValue);
  19. System.out.println("平均数 : " + avgValue);
  20. /*=================打印结果=================
  21. 列表中最大的数 : 5
  22. 列表中最小的数 : 1
  23. 所有数之和 : 15
  24. 平均数 : 3.0
  25. */

虽然这样也可以,但是明显IntSummaryStatistics要更灵活点

对集合分组

  1. Map<Integer, List<String>> map = strings.stream()
  2. //根据字符串长度分组(同理,对对象可以通过某个属性分组)
  3. .collect(Collectors.groupingBy(String::length));
  4. Map<Boolean, List<String>> map2 = strings.stream()
  5. //根据字符串是否大于2分组
  6. .collect(Collectors.groupingBy(s -> s.length() > 2));
  7. System.out.println(map);
  8. System.out.println(map2);
  9. /*=================打印结果=================
  10. {1=[s], 2=[ab, bc, cd, sd], 3=[jkl], 4=[abcd]}
  11. {false=[ab, s, bc, cd, sd], true=[abcd, jkl]}
  12. */

.对数据进行映射

  1. String str = strings.stream().collect(Collectors.mapping(
  2. //先对集合中的每一个元素进行映射操作
  3. s -> s += ",mapping",
  4. //再对映射的结果使用Collectors操作
  5. Collectors.collectingAndThen(Collectors.joining(";"), s -> s += "=====then" )
  6. ));
  7. System.out.println(str);
  8. //=================打印结果=================
  9. //ab,mapping;s,mapping;bc,mapping;cd,mapping;abcd,mapping;sd,mapping;jkl,mapping=====then

Optional

今天再看阿里的Java开发手册,里面异常处理第10条提到这样一个建议。

【推荐】防止 NPE ,是程序员的基本修养,注意 NPE 产生的场景:
1 ) 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
反例: public int f() { return Integer 对象}, 如果为 null ,自动解箱抛 NPE 。
2 ) 数据库的查询结果可能为 null 。
3 ) 集合里的元素即使 isNotEmpty ,取出的数据元素也可能为 null 。
4 ) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE 。
5 ) 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。
6 ) 级联调用 obj . getA() . getB() . getC(); 一连串调用,易产生 NPE 。
正例:使用 JDK8 的 Optional 类来防止 NPE 问题。

里面的正确示例提示我们用Java8的Optional类来防止NPE的问题。

那我们今天就看看这个Optional类吧

我们可以看到Optional总共也就10+个方法,其中有三个static方法。并且Optional的构造方法是private,不能new出来。

所以我们一般用这三个static来获取Optional的对象。

序号 方法 & 描述
1 static Optional empty()返回空的 Optional 实例。
2 boolean equals(Object obj)判断其他对象是否等于 Optional。
3 Optional<T> filter(Predicate<? super <T> predicate)如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。
4 <U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional
5 T get()如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
6 int hashCode()返回存在值的哈希码,如果值不存在 返回 0。
7 void ifPresent(Consumer<? super T> consumer)如果值存在则使用该值调用 consumer , 否则不做任何事情。
8 boolean isPresent()如果值存在则方法会返回true,否则返回 false。
9 <U>Optional<U> map(Function<? super T,? extends U> mapper)如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。
10 static Optional of(T value)返回一个指定非null值的Optional。
11 static Optional ofNullable(T value)如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
12 T orElse(T other)如果存在该值,返回值, 否则返回 other。
13 T orElseGet(Supplier<? extends T> other)如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。
14 <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常
15 String toString()返回一个Optional的非空字符串,用来调试

of/ofNullable源码

  1. public static <T> Optional<T> of(T value) {
  2. return new Optional<>(value);
  3. }
  4. public static <T> Optional<T> ofNullable(T value) {
  5. return value == null ? empty() : of(value);
  6. }

很明显 of 对null对象没有做任何处理,ofNullable才做了处理。所以当我们不知道传入的对象是否为null的时候,我们应该选择用 ofNullable来做处理。

  1. public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
  2. Objects.requireNonNull(mapper);
  3. if (!isPresent())
  4. return empty();
  5. else {
  6. return Optional.ofNullable(mapper.apply(value));
  7. }
  8. }

如果我们想获取Object里面的值的话,我们就需要用到这个map

  1. public class OptionalTest {
  2. public static void main(String[] args) {
  3. Person person = new Person("zhangsan", 18);
  4. String name = getName(person);
  5. System.out.println(name);
  6. }
  7. private static String getName(Person person) {
  8. if (Objects.isNull(person)) {
  9. return "unKnown";
  10. }
  11. return person.getName();
  12. }
  13. }

我们看上面的这个例子。

我们有一个函数 getName 作用是获取Person对象的名字。但我并不知道这个Person是否为Null。

所以我要进行一个判断,判断Person是否为空,在做决定。

但如果我们使用Optional类的话,我们可以这样写

  1. public class OptionalTest {
  2. public static void main(String[] args) {
  3. Person person = new Person("zhangsan", 18);
  4. String name = getName(person);
  5. System.out.println(name);
  6. }
  7. private static String getName(Person person) {
  8. String name = Optional.ofNullable(person).map(x -> x.getName())
  9. .orElse("unKnown");
  10. return name;
  11. }
  12. }
  1. Optional.ofNullable(user).map(USERDO::getName).orElse("")//如果有就获取name否则返回''
  1. Optional.ofNullable(param.getUserId()).isPresent()//是否不为空
  1. Optional.ofNullable(list).filter(CollectionUtils::isNotEmpty).map(r -> r.get(0)).orElse(null);//获取第一个

如果传入的为空,它会自动new一个 Optional t = (Optional) EMPTY;

有效的处理到了null的问题,而且还非常的简洁。

Predicate和Consumer

在这些stream的各种操作组合骚包操作下,有时候直接写一个lambda显得有点丑,特别是这个lambda方法体内东西很多的时候,这个时候就可以使用这2个内置的函数先行定义lambda方法体,这样就非常灵活骚包,而且还可以分开写

  1. Predicate<User> predicate = p -> p.getName().startsWith("s");
  2. Consumer<User> consumer = p -> {
  3. System.out.println(p.getName());
  4. System.out.println(p.getAge());
  5. };
  6. userList.stream().filter(predicate).forEach(consumer);

Predicate 接口

Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非):

  1. Predicate<String> predicate = (s) -> s.length() > 0;
  2. predicate.test("foo"); // true
  3. predicate.negate().test("foo"); // false
  4. Predicate<Boolean> nonNull = Objects::nonNull;
  5. Predicate<Boolean> isNull = Objects::isNull;
  6. Predicate<String> isEmpty = String::isEmpty;
  7. Predicate<String> isNotEmpty = isEmpty.negate();

Function 接口

Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen):

  1. Function<String, Integer> toInteger = Integer::valueOf;
  2. Function<String, String> backToString = toInteger.andThen(String::valueOf);
  3. backToString.apply("123"); // "123"

Consumer 接口

Consumer 接口表示执行在单个参数上的操作。

  1. Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
  2. greeter.accept(new Person("Luke", "Skywalker"));

实现原理

其实stream和parallelStream都是通过fork/join框架去执行的,大概是做了这么几件事

  • 使用自带的ForkJoinPool.commonPool()这个公共线程池去执行任务
  • 然后这些任务会被拆分成几个小任务进行对应的流式处理
  • 处理完成后类似future这种方式得到所有结果集再进行合并

总结

lambda加stream的组合简化我们开发的复杂度,并且让代码更加简洁

参考

https://www.cnblogs.com/Deters/p/11137532.html

https://www.jianshu.com/p/0bf8fe0f153b

This blog is under a CC BY-NC-SA 3.0 Unported License
本文链接:http://hogwartsrico.github.io/2019/10/17/Java8/