Java8特性大全(最新版)

简介: 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());

3、字符串处理

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

实体类

publicclassSUser {

  privateIntegeruserId;

  privateStringUserName;

  // 本应该是Double类型,错误的使用为String类型

  privateStringscore;

}

正序、逆序排序

// 对集合按照年龄排序(正序排列)

Collections.sort(userList, Comparator.comparingDouble(e->newDouble(e.getScore())));

数据类型转换排序时,使用 JDK 内置的 API 并不流畅,推荐使用commons-collection4包中的排序工具类。了解更多,请移步查看ComparatorUtils

// 对集合按照年龄排序(逆序排列)

Collections.sort(userList, ComparatorUtils.reversedComparator(Comparator.comparingDouble(e->newDouble(e.getScore()))));

小结:通过以排序为例,实现 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);

// 自定义正逆序

Comparator<XUser>comparator=Comparator.comparing(XUser::getUserName,Comparator.reverseOrder())

 .thenComparing(XUser::getScore,Comparator.reverseOrder());

三、Steam API

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

(一)创建流

(1)通过集合创建流

// 通过集合创建流

List<String>lists=newArrayList<>();

lists.stream();

(2)通过数组创建流

// 通过数组创建流

String[] strings=newString[5];

Stream.of(strings);

应用较多的是通过集合创建流,然后经过中间操作,最后终止回集合。

(二)中间操作

1、筛选(filter)

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

// 通过断言型接口实现元素的过滤

stream.filter(x->x.getSalary()>10);

非空过滤

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

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

// 非空断言

java.util.function.Predicate<Boolean>nonNull=Objects::nonNull;

查看Objects类了解更详细信息。

2、去重(distinct)

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

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

stream.distinct();

3、映射(map)

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

stream.map(x->x.getEmpId());

filtermap操作通常结合使用,取出流中某行某列的数据,建议先行后列的方式定位。

Optional<MainExportModel>model=data.stream().filter(e->e.getResId().equals(resId)).findFirst();

if (model.isPresent()) {

  StringitemName=model.get().getItemName();

  StringitemType=model.get().getItemType();

  returnnewMainExportVo(itemId, itemName);

}

4、排序(sorted)

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

// 按照默认字典顺序排序

stream.sorted();

// 按照工资大小排序

stream.sorted((x,y)->Integer.compare(x.getSalary(),y.getSalary()));

(1)函数式接口排序

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

// 正向排序(默认)

pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getPeriod));

// 逆向排序

pendingPeriod.stream().sorted(Comparator.comparingInt(ReservoirPeriodResult::getPeriod).reversed());

(2)LocalDate 和 LocalDateTime 排序

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

// 准备测试数据

Stream<DateModel>stream=Stream.of(newDateModel(LocalDate.of(2020, 1, 1)), newDateModel(LocalDate.of(2019, 1, 1)), newDateModel(LocalDate.of(2021, 1, 1)));

正序、逆序排序

// 正向排序(默认)

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());

由集合创建流的收集需注意:仅仅修改流字段中的内容,没有返回新类型,如下操作直接修改原始集合,无需处理返回值。

// 直接修改原始集合

userVos.stream().map(e->e.setDeptName(hashMap.get(e.getDeptId()))).collect(Collectors.toList());

(2)收集为Set

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

Set<String>collect=stream.collect(Collectors.toSet());

2、高级收集

(1)收集为Map

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

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

ID 到 NAME 映射

@Data

publicclassItemEntity {

  privateIntegeritemId;

  privateStringitemName;

}

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

// 模拟从数据库中查询批量的数据

List<ItemEntity>entityList=Stream.of(newItemEntity(1,"A"), newItemEntity(2,"B"), newItemEntity(3,"C")).collect(Collectors.toList());

将集合数据转化成 ID 与 NAME 的 Map

// 将集合数据转化成ID与NAME的Map

Map<Integer, String>hashMap=entityList.stream().collect(Collectors.toMap(ItemEntity::getItemId, ItemEntity::getItemName));

IDObject类映射

@Data

publicclassItemEntity {

  privateIntegeritemId;

  privateStringitemName;

  privateBooleanstatus;

}

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

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

Map<Integer, ItemEntity>hashMap=entityList.stream().collect(Collectors.toMap(ItemEntity::getItemId, e->e));

其中Collectors类中的toMap参数是函数式接口参数,能够自定义返回值。

publicstatic<T, K, U>Collector<T, ?, Map<K,U>>toMap(Function<?superT, ?extendsK>keyMapper,

                                  Function<?superT, ?extendsU>valueMapper) {

  returntoMap(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())));

四、Stream 拓展

(一)集合与对象互转

将对象包装成集合的形式和将集合拆解为对象的形式是常见的操作。

1、对象转集合

返回默认类型的集合实例

/**

* 将单个对象转化为集合

*

* @param t   对象实例

* @param <T> 对象类型

* @param <C> 集合类型

* @return 包含对象的集合实例

*/

publicstatic<T, CextendsCollection<T>>Collection<T>toCollection(Tt) {

  returntoCollection(t, ArrayList::new);

}

用户自定义返回的集合实例类型

/**

* 将单个对象转化为集合

*

* @param t       对象实例

* @param supplier 集合工厂

* @param <T>     对象类型

* @param <C>     集合类型

* @return 包含对象的集合实例

*/

publicstatic<T, CextendsCollection<T>>Collection<T>toCollection(Tt, Supplier<C>supplier) {

  returnStream.of(t).collect(Collectors.toCollection(supplier));

}

2、集合转对象

使用默认的排序规则,注意此处不是指自然顺序排序。

/**

* 取出集合中第一个元素

*

* @param collection 集合实例

* @param <E>       集合中元素类型

* @return 泛型类型

*/

publicstatic<E>EtoObject(Collection<E>collection) {

  // 处理集合空指针异常

  Collection<E>coll=Optional.ofNullable(collection).orElseGet(ArrayList::new);

  // 此处可以对流进行排序,然后取出第一个元素

  returncoll.stream().findFirst().orElse(null);

}

上述方法巧妙的解决两个方面的异常问题:一是集合实例引用空指针异常;二是集合下标越界异常。

(二)其它

1、并行计算

基于流式计算中的并行流,能够显著提高大数据下的计算效率,充分利用 CPU 核心数。

// 通过并行流实现数据累加

LongStream.rangeClosed(1,9999999999999999L).parallel().reduce(0,Long::sum);

2、序列数组

生成指定序列的数组或者集合。

// 方式一:生成数组

int[] ints=IntStream.rangeClosed(1, 100).toArray();

// 方式二:生成集合

List<Integer>list=Arrays.stream(ints).boxed().collect(Collectors.toList());

五、其它

(一)新日期时间 API

1、LocalDateTime

// 获取当前日期(包含时间)

LocalDateTimelocalDateTime=LocalDateTime.now();

// 获取当前日期

LocalDatelocalDate=localDateTime.toLocalDate();

// 获取当前时间

LocalTimelocalTime=localDateTime.toLocalTime();

日期格式化

// 月份MM需要大写、小时字母需要大写(小写表示12进制)

DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")

// 获取当前时间(字符串)

StringdateTime=LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

System.out.println("dateTime = "+dateTime);

2、Duration

Durationduration=Duration.between(Instant.now(), Instant.now());

System.out.println("duration = "+duration);

3、获取当前时间戳

如下方式获取的是 13 位时间戳,单位是毫秒。

// 方式一

longnow=Timestamp.valueOf(LocalDateTime.now()).getTime();

// 方式二

longnow=Instant.now().toEpochMilli();

(二)Optional

Optional类出现之前,null异常几乎折磨着每一位开发者,为了构建健壮的应用程序,不得不使用繁琐的if逻辑判断来回避空指针异常。解锁Optional类,让你编写的应用健壮性更上一层楼。

1、先判断后使用

ifPresent方法提供了先判断是否为空,后进一步使用的能力。

2、链式取值

链式取值是指,层层嵌套对象取值,在上层对象不为空的前提下,才能读取其属性值,然后继续调用,取出最终结果值。有时候只关心链末端的结果状态,即使中间状态为空,直接返回空值。如下提供了一种无 if 判断,代码简介紧凑的实现方式:

Optional<Long>optional=Optional.ofNullable(tokenService.getLoginUser(ServletUtils.getRequest()))

  .map(LoginUser::getUser).map(SysUser::getUserId);

// 如果存在则返回,不存在返回空

LonguserId=optional.orElse(null);

六、流的应用

(一)列表转树

传统方式下构建树形列表需要反复递归调用查询数据库,效率偏低。对于一棵结点较多的树,效率更低。这里提供一种只需调用一次数据库,通过流将列表转化为树的解决方式。

/**

* 列表转树

*

* @param rootList     列表的全部数据集

* @param parentId 第一级目录的父ID

* @return 树形列表

*/

public List<IndustryNode> getChildNode(List<Industry> rootList, String parentId) {

   List<IndustryNode> lists = rootList.stream()

        .filter(e -> e.getParentId().equals(parentId))

           .map(IndustryNode::new).collect(toList());

   lists.forEach(e -> e.setChilds(getChildNode(rootList, e.getId())));

   return lists;

}


原文地址

相关文章
|
IDE Java 关系型数据库
Java14发布,16大新特性,代码更加简洁明快
Java14发布,16大新特性,代码更加简洁明快
321 0
Java14发布,16大新特性,代码更加简洁明快
|
10天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
19天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
6天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
25 9
|
9天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
6天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
9天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
24 3
|
8天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
9天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
20 1
|
9天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。