Java8特性大全

一、序言
Java8 是一个里程碑式的版本,凭借如下新特性,让人对其赞不绝口。

Lambda 表达式给代码构建带来了全新的风格和能力;
Steam API 丰富了集合操作,拓展了集合的能力;
新日期时间 API 千呼万唤始出来;
随着对 Java8 新特性理解的深入,会被 Lambda 表达式(包含方法引用)、流式运算的美所迷恋,不由惊叹框架设计的美。

二、方法引用
Lambda 表达式是匿名函数,可以理解为一段可以用参数传递的代码(代码像数据一样传递)。Lambda 表达式的使用需要有函数式接口的支持。

方法引用是对特殊 Lambda 表达式的一种简化写法,当 Lambda 体中只调用一个方法,此方法满足函数式接口规范,此时可以使用::方法引用语法。

从语法表现力角度来讲,方法引用 > Lambda表达式 > 匿名内部类,方法引用是高阶版的 Lambda 表达式,语言表达更为简洁,强烈推荐使用。

方法引用表达式无需显示声明被调用方法的参数,根据上下文自动注入。方法引用能够提高 Lambda 表达式语言的优雅性,代码更加简洁。下面以Comparator排序为例讲述如何借助方法引用构建优雅的代码。

(一)方法引用与排序

1、普通数据类型

普通数据类型相对较容易理解。

// 正向排序(方法引用)
Stream.of(11, 15, 11, 12).sorted(Integer::compareTo).forEach(System.out::println);
// 正向排序
Stream.of(11, 15, 11, 12).sorted(Comparator.naturalOrder()).forEach(System.out::println);
// 逆向排序
Stream.of(11, 15, 11, 12).sorted(Comparator.reverseOrder()).forEach(System.out::println);

2、对象数据类型

(1)数据完好

数据完好有两重含义,一是对象本身不为空;二是待比较对象的属性值不为空,以此为前提进行排序操作。

// 对集合按照年龄排序(正序排列)
Collections.sort(userList, Comparator.comparingInt(XUser::getAge));
// 对集合按照年龄排序(逆序排列)
Collections.sort(userList, Comparator.comparingInt(XUser::getAge).reversed());

此示例是以Integer类型展开的,同理Double类型、Long类型等数值类型处理方式相同。其中Comparator是排序过程中重要的类。

(2)数据缺失

数据缺失的含义是对象本身为空或者待比较对象属性为空,如果不进行处理,上述排序会出现空指针异常。

最常见的处理方式是通过流式运算中filter方法,过滤掉空指针数据,然后按照上述策略排序。

userList.stream().filter(e->e.getAge()!=null).collect(Collectors.toList());
1
3、字符串处理

少数开发者在构建实体类时,String类型遍地开花,在需要运算或者排序的场景下,String 的缺陷逐渐暴露出来。下面讲述字符串数值类型排序问题,即不修改数据类型的前提下完成期望的操作。

实体类

public class SUser {
    private Integer userId;
    private String UserName;
    // 本应该是Double类型,错误的使用为String类型
    private String score;
}

正序、逆序排序

// 对集合按照年龄排序(正序排列)
Collections.sort(userList, Comparator.comparingDouble(e -> new Double(e.getScore())));
1
2
数据类型转换排序时,使用 JDK 内置的 API 并不流畅,推荐使用commons-collection4包中的排序工具类。了解更多,请移步查看ComparatorUtils。

// 对集合按照年龄排序(逆序排列)
Collections.sort(userList, ComparatorUtils.reversedComparator(Comparator.comparingDouble(e -> new Double(e.getScore()))));
1
2
小结:通过以排序为例,实现 Comparator 接口、Lambda 表达式、方法引用三种方式相比较,代码可读性逐步提高。
(二)排序器

内置的排序器可以完成大多数场景的排序需求,当排序需求更加精细化时,适时引入第三方框架是比较好的选择。

1、单列排序

单列排序包含正序和逆序。

// 正序
Comparator<Person> comparator = Comparator.comparing(XUser::getUserName);
// 逆序
Comparator<Person> comparator = Comparator.comparing(XUser::getUserName).reversed();

2、多列排序

多列排序是指当待比较的元素有相等的值时,如何进行下一步排序。

// 默认多列均是正序排序
Comparator<XUser> comparator = Comparator.comparing(XUser::getUserName)
    .thenComparing(XUser::getScore);
1
2
3
// 自定义正逆序
Comparator<XUser> comparator = Comparator.comparing(XUser::getUserName,Comparator.reverseOrder())
    .thenComparing(XUser::getScore,Comparator.reverseOrder());

三、Steam API
流的操作包含如下三个部分:创建流、中间流、关闭流,筛选、去重、映射、排序属于流的中间操作,收集属于终止操作。Stream是流操作的基础关键类。

(一)创建流

(1)通过集合创建流

// 通过集合创建流
List<String> lists = new ArrayList<>();
lists.stream();
1
2
3
(2)通过数组创建流

// 通过数组创建流
String[] strings = new String[5];
Stream.of(strings);
1
2
3
应用较多的是通过集合创建流,然后经过中间操作,最后终止回集合。

(二)中间操作

1、筛选(filter)

筛选是指从(集合)流中筛选满足条件的子集,通过 Lambda 表达式生产型接口来实现。

// 通过断言型接口实现元素的过滤
stream.filter(x->x.getSalary()>10);
1
2
非空过滤

非空过滤包含两层内容:一是当前对象是否为空或者非空;二是当前对象的某属性是否为空或者非空。

筛选非空对象,语法stream.filter(Objects::nonNull)做非空断言。

// 非空断言
java.util.function.Predicate<Boolean> nonNull = Objects::nonNull;
1
2
查看Objects类了解更详细信息。

2、去重(distinct)

去重是指将(集合)流中重复的元素去除,通过 hashcode 和 equals 函数来判断是否是重复元素。去重操作实现了类似于 HashSet 的运算,对于对象元素流去重,需要重写 hashcode 和 equals 方法。

如果流中泛型对象使用 Lombok 插件,使用@Data注解默认重写了 hashcode 和 equals 方法,字段相同并且属性相同,则对象相等。更多内容可查看Lombok 使用手册

stream.distinct();
1
3、映射(map)

取出流中元素的某一列,然后配合收集以形成新的集合。

stream.map(x->x.getEmpId());
1
filter和map操作通常结合使用,取出流中某行某列的数据,建议先行后列的方式定位。

Optional<MainExportModel> model = data.stream().filter(e -> e.getResId().equals(resId)).findFirst();
if (model.isPresent()) {
    String itemName = model.get().getItemName();
    String itemType = model.get().getItemType();
    return new MainExportVo(itemId, itemName);
}

4、排序(sorted)

传统的Collectors类中的排序支持 List 实现类中的一部分排序,使用 stream 排序,能够覆盖所有的 List 实现类。

// 按照默认字典顺序排序
stream.sorted();
// 按照工资大小排序
stream.sorted((x,y)->Integer.compare(x.getSalary(),y.getSalary()));
1
2
3
4
(1)函数式接口排序

基于 Comparator 类中函数式方法,能够更加优雅的实现对象流的排序。

// 正向排序(默认)
pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getPeriod));
// 逆向排序
pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getPeriod).reversed());
1
2
3
4
(2)LocalDate 和 LocalDateTime 排序

新日期接口相比就接口,使用体验更加,因此越来越多的被应用,基于日期排序是常见的操作。

// 准备测试数据
Stream<DateModel> stream = Stream.of(new DateModel(LocalDate.of(2020, 1, 1)), new DateModel(LocalDate.of(2019, 1, 1)), new DateModel(LocalDate.of(2021, 1, 1)));
1
2
正序、逆序排序

// 正向排序(默认)
stream.sorted(Comparator.comparing(DateModel::getLocalDate)).forEach(System.out::println);
// 逆向排序
stream.sorted(Comparator.comparing(DateModel::getLocalDate).reversed()).forEach(System.out::println);

5、规约(reduce)

对流中的元素按照一定的策略计算。终止操作的底层逻辑都是由 reduce 实现的。

(三)终止操作

收集(collect)将流中的中间(计算)结果存储到集合中,方便后续进一步使用。为了方便对收集操作的理解,方便读者掌握收集操作,将收集分为普通收集和高级收集。

1、普通收集

(1)收集为List

默认返回的类型为ArrayList,可通过Collectors.toCollection(LinkedList::new)显示指明使用其它数据结构作为返回值容器。

List<String> collect = stream.collect(Collectors.toList());
1
由集合创建流的收集需注意:仅仅修改流字段中的内容,没有返回新类型,如下操作直接修改原始集合,无需处理返回值。

// 直接修改原始集合
userVos.stream().map(e -> e.setDeptName(hashMap.get(e.getDeptId()))).collect(Collectors.toList());
1
2
(2)收集为Set

默认返回类型为HashSet,可通过Collectors.toCollection(TreeSet::new)显示指明使用其它数据结构作为返回值容器。

Set<String> collect = stream.collect(Collectors.toSet());
1
2、高级收集

(1)收集为Map

默认返回类型为HashMap,可通过Collectors.toCollection(LinkedHashMap::new)显示指明使用其它数据结构作为返回值容器。

收集为Map的应用场景更为强大,下面对这个场景进行详细介绍。希望返回结果中能够建立ID与NAME之间的匹配关系,最常见的场景是通过ID批量到数据库查询NAME,返回后再将原数据集中的ID替换成NAME。

ID 到 NAME 映射

@Data
public class ItemEntity {
    private Integer itemId;
    private String itemName;
}

准备集合数据,此部分通常是从数据库查询的数据

// 模拟从数据库中查询批量的数据
List<ItemEntity> entityList = Stream.of(new ItemEntity(1,"A"), new ItemEntity(2,"B"), new ItemEntity(3,"C")).collect(Collectors.toList());
1
2
将集合数据转化成 ID 与 NAME 的 Map

// 将集合数据转化成ID与NAME的Map
Map<Integer, String> hashMap = entityList.stream().collect(Collectors.toMap(ItemEntity::getItemId, ItemEntity::getItemName));
1
2
ID与Object类映射

@Data
public class ItemEntity {
    private Integer itemId;
    private String itemName;
    private Boolean status;
}

将集合数据转化成 ID 与实体类的 Map

// 将集合数据转化成ID与实体类的Map
Map<Integer, ItemEntity> hashMap = entityList.stream().collect(Collectors.toMap(ItemEntity::getItemId, e -> e));
1
2
其中Collectors类中的toMap参数是函数式接口参数,能够自定义返回值。

public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper) {
    return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

(2)分组收集

流的分组收集操作在内存层次模拟了数据库层面的group by操作,下面演示流的分组操作。Collectors类提供了各种层次的分组操作支撑。

流的分组能力对应数据库中的聚合函数,目前大部分能在数据库中操作的聚合函数,都能在流中找到相应的能力。

// 默认使用List作为分组后承载容器
Map<Integer, List<XUser>> hashMap = xUsers.stream().collect(Collectors.groupingBy(XUser::getDeptId));

// 显示指明使用List作为分组后承载容器
Map<Integer, List<XUser>> hashMap = xUsers.stream().collect(Collectors.groupingBy(XUser::getDeptId, Collectors.toList()));

映射后再分组

Map<Integer, List<String>> hashMap = xUsers.stream().collect(Collectors.groupingBy(XUser::getDeptId,Collectors.mapping(XUser::getUserName,Collectors.toList())));
 



原文链接:https://blog.csdn.net/m0_63836794/article/details/122573854

【Java 8 新特性】Java 8 新特性大全_猫巳的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值