Java8 新特性
Java8 优势:速度快、代码更少(增加了新的语法 Lambda 表达式)、强大的 Stream API、便于并行、最大化减少空指针异常 Optional;
一、Lambda 表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以取代大部分的匿名内部类,可以写出更简洁、更灵活的代码。尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到提升。JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
【1】从匿名类到 Lambda 的转换:虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法。
jdk8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。后续有专门的介绍。
■ 左侧:指定了 Lambda 表达式需要的所有参数;
■ 右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能;
【语法格式一】:无参,无返回值,Lambda 体只需要一条语句;
Runnable runnable2 = ()-> System.out.printf("Lambda 表达式");
【语法格式二】:Lambda 需要一个参数;
【语法格式三】:Lambda 只需要一个参数时,参数的小括号可以省略;
【语法格式四】:Lambda 需要两个参数,并且有返回值;
【语法格式五】:当 Lambda 体只有一条语句时,return 与大括号可以省略;
【语法格式六】:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”:根据上下文环境推断参数类型;
【4】遍历集合: 可以调用集合的 forEach(Consumer<? super E> action) 方法,通过 lambda 表达式的方式遍历集合中的元素。Consumer 接口是 jdk 为我们提供的一个函数式接口。
【5】删除集合:通过
removeIf(Predicate<? super E> filter) 方法来删除集合中的某个元素,Predicate 也是 jdk 为我们提供的一个函数式接口,可以简化程序的编写。
【6】集合内元素的排序:若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。
二、函数式接口
【1】只包含一个抽象方法的接口,称为函数式接口;
【2】你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
【3】我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
【4】Java 内置四大核心函数式接口 :
函数式接口 | 参数类型 | 返回类型 | 用途 |
Consumer<T> 消费型接口 | T | void | 对类型为T的对象应用操作,包含方法:void accept(T t); |
Supplier<T> 供给型接口 | 无 | T | 返回类型为 T 对象,包含方法:T get(); |
Function<T, R> 函数型接口 | T | R | 对类型为 T 的对象应用操作,并返回结果。结果时 R 类型的对象。包含方法:R apply(T t) |
Predicate<T> 断定型接口 | T | boolean | 确定类型为 T 的对象是否满足某约束,并返回 Boolean 值,包含方法 Boolean test(T t) |
三、方法引用与构造器引用
【1】方法引用:当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用中方法的参数列表保持一致!)方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。如下三种主要使用情况:使用方法引用的时候需要保证引用方法的参数列表和返回值类型与我们当前所要实现的函数式接口方法的参数列表和返回值类型保持一致
①、对象::实例方法;②、类::静态方法;③、类::实例方法;
注意:当需要引用方法的第一个参数是调用对象,并且第二个参数是需要引用方法的第二个参数(或无参数)时:
【2】构造器引用: 格式: ClassName::new 与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!调用哪个构造器取决于函数式接口中的方法形参的定义。我们通过两种方式创建对象(无参和有参),具体如下:
【3】数组引用: 同构造器引用,格式为 type[] :: new
四、Stream API
Java8 中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API(java.util.stream.*)。Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
【1】Stream 到底是什么:是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合讲的是数据,流讲的是计算!
【2】注意:①、Stream 自己不会存储数据。②、Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream。③、Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
【3】Stream 的操作三个步骤:①、创建 Stream 一个数据源(如:集合、数组),获取一个流。 ②、中间操作:一个中间操作链,对数据源的数据进行处理。③、终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果:
【5】由数组创建流:Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:static <T> Stream<T> stream(T[] array):返回一个流;重载形式,能够处理对应基本类型的数组:
【6】由值创建流:可以使用静态方法 Stream.of(),通过显示值创建一个流。它可以接收任意数量的参数。
【7】由函数创建流:创建无限流,可以使用静态方法 Stream.iterate() 和 Stream.generate(),创建无限流。
Stream 的中间操作:多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
【1】筛选与切片:
方法 | 描述 |
filter(Predicate p) | 接收 Lambda , 从流中排除某些元素。 |
distinct() | 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素。 |
limit(long maxSize) | 截断流,使其元素不超过给定数量。 |
skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补。 |
【2】 映射:
方法 | 描述 |
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。 |
【3】 排序:上面介绍的流的转换方法都是无状态的。即从一个已经转换的流中取某个元素时,结果并不依赖于之前的元素。除此之外还有两个方法在转换流时是需要依赖于之前流中的元素的。一个是 distinct方法一个是 sorted方法。distinct方法会根据原始流中的元素返回一个具有相同顺序、去除了重复元素的流,这个操作显然是需要记住之前读取的元素。
方法 | 描述 |
sorted() | 产生一个新流,其中按自然排序排序。 |
sorted(Comparator comp) | 产生一个新流,其中按比较器顺序排序。 |
Stream 的聚合操作:终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
【1】查找与匹配:
方法 | 描述 |
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。 |
findAny() | 返回当前流中的任意元素 |
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了) |
【2】 归约:
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 T |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。返回 Optional<T> |
备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。
【3】收集:当处理完流之后,通常是想查看一下结果,而不是将他们聚合为一个值。Collectorts 类为我们提供了常用的收集类的各个工厂方法。
方法 | 描述 |
collect(Collector c) | 将流转换为其它形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法。 |
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 使用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
方法 | 返回类型 | 作用 |
toList | List<T> | 把流中元素收集到List |
List<Employee> emps= list.stream().collect(Collectors.toList()); | ||
toSet | Set<T> | 把流中元素收集到Set |
Set<Employee> emps= list.stream().collect(Collectors.toSet()); | ||
toCollection | Collection<T> | 把流中元素收集到创建的集合 |
Collection<Employee>emps=list.stream().collect(Collectors.toCollection(ArrayList::new)); | ||
counting | Long | 计算流中元素的个数 |
long count = list.stream().collect(Collectors.counting()); | ||
summingInt | Integer | 对流中元素的整数属性求和 |
inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary)); | ||
averagingInt | Double | 计算流中元素Integer属性的平均值 |
doubleavg= list.stream().collect(Collectors.averagingInt(Employee::getSalary)); | ||
summarizingInt | IntSummaryStatistics | 收集流中Integer属性的统计值。 |
IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary)); | ||
joining | String | 连接流中每个字符串 |
String str= list.stream().map(Employee::getName).collect(Collectors.joining()); | ||
maxBy | Optional<T> | 根据比较器选择最大值 |
Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary))); | ||
minBy | Optional<T> | 根据比较器选择最小值 |
Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary))); | ||
reducing | 归约产生的类型 | 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 |
inttotal=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum)); | ||
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果转换函数 |
inthow= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size)); | ||
groupingBy | Map<K, List<T>> | 根据某属性值对流分组,属性为K,结果为V |
Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus)); | ||
partitioningBy | Map<Boolean, List<T>> | 根据 true或 false进行分区 |
Map<Boolean,List<Emp>>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage)); |
【常见面试题】:将结果集收集到 Map;当我们希望将集合中的元素收集到 Map中时,可以使用 Collectors.toMap方法。这个方法有两个参数,用来生成 Map的 key和 value。例如将一个 Form 对象的 high作为键 width作为值:每个 toMap方法,都会有一个对应的 toConCurrentMap方法,用来生成一个并发Map。
并行流与串行流:并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。Java8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。但是并行流在使用的时候也是需要注意的。首先,必须是一个并行流,只要在终止方法执行时,流处于并行模式,那么所有的流操作就都会并行执行。
parallel 方法可以将任意的串行流转换为一个并行流。其次要确保传递给并行流操作的函数是线程安全的。
我们使在处理集合数据量较大的时候才能体现出并行流的优势,并且目的是为了在保证线程安全的情况下,提升效率,利用多核 CPU的资源。使用 Stream 的 API时,在遍历或处理流的过程中当引用外部变量的时候会默认的将变量当成 fianl变量来处理。所以有些同学就会觉得在遍历的过程中取不出来集合的索引。其实可以换一种思想可以只遍历集合索引,然后在遍历中取值。
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
Fork/Join 框架与传统线程池的区别:采用 “工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。相对于一般的线程池实现,fork/join 框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因
无法继续运行,那么该线程会处于等待状态。而在 fork/join 框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行。这种方式减少了线程的等待时间,提高了性能。
五、接口中的默认方法与静态方法
Java8 中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用 default 关键字修饰。例如:
接口默认方法的 ”类优先” 原则:若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时。
【1】选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
【2】接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法
是否是默认方法),那么必须覆盖该方法来解决冲突 。
【接口默认方法的”类优先”原则】:
【接口中的静态方法】:Java8 中,接口中允许添加静态方法;
六、新时间日期 API
【1】LocalDate、LocalTime、LocalDateTime:的实例是不可变的对象,分别表示使用 ISO-8601 日期系统的日期、时间、日期和时间。它提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。
方法 | 描述 | 示例 |
now() | 静态方法,根据当前时间创建对象 |
LocalDate localDate = LocalDate.now(); LocalTime localTime = LocalTime.now(); LocalDateTime localDateTime = LocalDateTime.now() |
of() | 静态方法,根据指定日期/时间创建对象 | LocalDate localDate = LocalDate.of(2016, 10, 26); LocalTime localTime = LocalTime.of(02, 22, 56); LocalDateTime localDateTime = LocalDateTime.of(2016, 10, 26, 12, 10, 55); |
plusDays,plusWeeks, plusMonths, plusYears |
向当前LocalDate 对象添加几天、几周、几个月、几年 | |
minusDays, minusWeeks, minusMonths, minusYears |
从当前 LocalDate 对象减去几天、几周、几个月、几年 | |
plus, minus | 添加或减少一个 Duration 或 Period | |
withDayOfMonth, withDayOfYear, withMonth,withYear |
将月份天数、年份天数、月份、年份修改为指定的值并返 回新的LocalDate 对象 | |
getDayOfMonth | 获得月份天数(1-31) | |
getDayOfYear | getDayOfYear 获得年份天数(1-366) | |
getDayOfWeek | 获得星期几(返回一个 DayOfWeek 枚举值) | |
getMonth | 获得月份, 返回一个 Month 枚举值 | |
getMonthValue | 获得月份(1-12) | |
getYear | 获得年份 | |
until | 获得两个日期之间的 Period 对象,或者指定 ChronoUnits 的数字 | |
isBefore, isAfter | 比较两个 LocalDate | |
isLeapYear | 判断是否是闰年 |
【2】Instant 时间戳:用于 “时间戳” 的运算。它是以 Unix 元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算;
【3】Duration 和 Period:Duration:用于计算两个“时间”间隔;Period:用于计算两个“日期”间隔;
【4】日期的操纵: TemporalAdjuster:时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。TemporalAdjusters:该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现。例如获取下个周日:
【5】解析与格式化:java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:
● 预定义的标准格式;
● 语言环境相关的格式;
● 自定义的格式;
【6】时区的处理:Java8 中加入了对时区的支持,带时区的时间为分别为:ZonedDate、ZonedTime、ZonedDateTime 其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式;例如 :Asia/Shanghai 等;
● ZoneId:该类中包含了所有的时区信息;
● getAvailableZoneIds():可以获取所有时区时区信息;
● of(id):用指定的时区信息获取 ZoneId 对象;
【7】与传统日期处理的转换:
类 | To 遗留类 | From 遗留类 |
java.time.Instant java.util.Date |
Date.from(instant) | date.toInstant() |
java.time.Instant java.sql.Timestamp |
Timestamp.from(instant) | timestamp.toInstant() |
java.time.ZonedDateTime java.util.GregorianCalendar |
GregorianCalendar.from(zonedDateTime) | cal.toZonedDateTime() |
java.time.LocalDate java.sql.Time |
Date.valueOf(localDate) | date.toLocalDate() |
java.time.LocalTime java.sql.Time |
Date.valueOf(localDate) | date.toLocalTime() |
java.time.LocalDateTime java.sql.Timestamp |
Timestamp.valueOf(localDateTime) | timestamp.toLocalDateTime() |
java.time.ZoneId java.util.TimeZone |
Timezone.getTimeZone(id) | timeZone.toZoneId() |
java.time.format.DateTimeFormatter java.text.DateFormat |
formatter.toFormat() | 无 |
七、其他新特性
【1】Optional类:Optional<T> 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。常用方法:
● Optional.of(T t):创建一个 Optional 实例;
● Optional.empty():创建一个空的 Optional 实例;
● Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例;
● isPresent():判断是否包含值;
● orElse(T t):如果调用对象包含值,返回该值,否则返回t;
● orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回 s 获取的值;
● map(Function f):如果有值对其处理,并返回处理后的 Optional,否则返回 Optional.empty();
● flatMap(Function mapper):与 map 类似,要求返回值必须是Optional;
【2】重复注解与类型注解:Java8 对注解处理提供了两点改进,可重复的注解及可用于类型的注解。如下自定义可重复注解:使用 @Repeatable 元注解,参数为可重复注解的容器。
【测试方法】
【类型注解】