JAVA8新特性

前言

最近看了同事用lambda和stream写的代码,看起来着实优雅,读起来又有点费劲,便找资料学习了JAVA8的新特性,写此笔记,记录感悟。

新特性概览

  • 速度更快
  • 代码更少(增加了新的语法 Lambda 表达式)
  • 强大的 Stream API
  • 便于并行
  • 最大化减少空指针异常 Optiona

Lambda 表达式

Lambda 表达式在Java 语言中引入了一个新的语法元
素和操作符。这个操作符为 ==->== , 该操作符被称
为 Lambda 操作符或剪头操作符。它将 Lambda 分为
两个部分:

左侧: 指定了 Lambda 表达式需要的所有参数

右侧: 指定了 Lambda 体,即 Lambda 表达式要执行
的功能。

示例1: 无参无返回值

1
2
3
4
5
6
7
8
9
10
// 匿名内部类实现
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("anno");
}
};

// lambda表达式
Runnable r2 = () -> System.out.println("lambda");

代码简洁度极大提升

示例2: 一个参数无返回值

1
2
3
4
5
6
7
8
9
Consumer<String> c1 = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};

常规:Consumer<String> c2 = (s) -> System.out.println(s);
单参数可省略小括号:Consumer<String> c2 = s -> System.out.println(s);

示例3: 有返回值

1
2
3
4
5
6
7
8
9
10
11
12
BinaryOperator<Long> b1 = new BinaryOperator<Long>() {
@Override
public Long apply(Long x, Long y) {
System.out.println("hello");
return x + y;
}
};

BinaryOperator<Long> b2 = (x, y) -> {
System.out.println("hello");
return x + y;
};

示例4: 当lambda体只有一句时,return和{}都可省略

1
2
3
4
5
6
7
8
9
10
11
BinaryOperator<Long> b1 = new BinaryOperator<Long>() {
@Override
public Long apply(Long x, Long y) {
System.out.println("hello");
return x + y;
}
};

BinaryOperator<Long> b2 = (x, y) -> x + y;
等价于
BinaryOperator<Long> b2 = (Long x, Long y) -> x + y;

lambda表达式中的参数类型可以通过 ==编译器类型推断== 得出,所以可以省略不写。

函数式接口

  • 只包含一个抽象方法的接口,称为函数式接口。
  • 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda
    表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
  • 我们可以在任意函数式接口上使用 ==@FunctionalInterface== 注解,
    这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包
    含一条声明,说明这个接口是一个函数式接口。

方法引用

当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致! )

方法引用: 使用操作符 ==::== 将方法名和对象或类的名字分隔开来。
如下三种主要使用情况:

  • 对象::实例方法
  • 类::静态方法
  • 类::实例方法

例如:

1
2
3
4
5
6
7
System.out::println 
等价于
(x) -> System.out.println(x);

s -> "ss".equals(s)
可写为
"ss"::equals

代码简洁但可读性较低

Stream API

Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一
个则是 Stream API(java.util.stream.*)。

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对
集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数
据库查询。也可以使用 Stream API 来并行执行操作。简而言之,
Stream API 提供了一种高效且易于使用的处理数据的方式。

Stream(流)是什么

是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

集合讲的是数据,流讲的是计算!

注意:

  • Stream 自己不会存储元素。
  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

如何使用

Stream 的操作三个步骤:

  1. 创建 Stream

一个数据源(如: 集合、数组), 获取一个流

1
2
3
4
5
6
7
8
9
10
11
12
13
集合:
Java8 中的 Collection 接口被扩展,提供了
两个获取流的方法:
- default Stream<E> stream() : 返回一个顺序流
- default Stream<E> parallelStream() : 返回一个并行流

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
- static <T> Stream<T> stream(T[] array): 返回一个流

重载形式,能够处理对应基本类型的数组:
- public static IntStream stream(int[] array)
- public static LongStream stream(long[] array)
- public static DoubleStream stream(double[] array)

  1. 中间操作

一个中间操作链,对数据源的数据进行处理

1
2
3
4
5
6
7
8
中间层进行了具体的数据操作,
常见有:
filter(Predicate p) 接收lambda,筛选
distinct() 去重
limit(long size) 截断
skip(long number) 跳过(与截断互补)
map(Function f) 接收函数,作用到每个元素并映射为新元素
sorted()/sorted(Comparator c) 自然排序/按规则排序

  1. 终止操作(终端操作)

一个终止操作,执行中间操作链,并产生结果

以常用的集合操作为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class StreamDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("s1");
list.add("s2");
list.add("s3");
list.add("s4");
list.add("s5");

List<String> collect = list.stream().filter(s -> "s1".equals(s)).collect(Collectors.toList());
collect.forEach(System.out::println);
}
}

执行打印结果中就只有 "s1"


终止操作最常用的是返回集合collect(Collector c),并且Collectors提供了很多静态方法,可以满足不同需求
另外还提供一些接口:
---查找
findFirst() 返回第一个元素
findAny() 返回任意一个元素
count() 返回总数
max(Comparator c) 按规则返回最大值
min(Comparator c) 按规则返回最小值
forEach(Consumer c) 内部迭代(集合的迭代叫外部迭代)
---匹配
allMatch(Predicate p) 全匹配
anyMatch(Predicate p) 任意一个匹配
noneMatch(Predicate p) 全不匹配
---归约
reduce(BinaryOpreator b) 反复结合得到一个值, 返回Optional<T>
reduce(T iden, BinaryOpreator b) 反复结合得到一个值, 返回T

stream还有很多强大的功能,可以写个demo自行测试,api文档里也描述得非常全面。
java8中文文档

并行流与串行流

并行流就是把一个内容分成多个数据块,并用不同的线程分
别处理每个数据块的流。
Java 8 中将并行进行了优化,我们可以很容易的对数据进行并
行操作。 Stream API 可以声明性地通过 ==parallel()== 与
==sequential()== 在并行流与顺序流之间进行切换。

fork/join (了解)

Fork/Join 框架
就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。

新的时间API

  • LocalDate
  • LocalTime
  • LocalDateTime
  • Instant
  • Duration
  • Period

解决了时间处理的难题

简单概括为Date有的我都有,Date没的我也有

时间校正器

  • TemporalAdjuster:校正器,获取“下个周日”等操作
  • TemporalAdjusters:通过静态方法提供大量常用校正实实现

示例代码:

1
2
3
4
5
6
7
8
9
10
public class LocalDateTimeDemo {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
System.out.println(now.getDayOfWeek());
System.out.println(now.getDayOfYear());
LocalDateTime with = now.with(TemporalAdjusters.next(DayOfWeek.TUESDAY));
System.out.println(with.getDayOfWeek());
System.out.println(with.getDayOfYear());
}
}

解析与格式化

java.time.format.DateTimeFormatter 类:该类提供了三种
格式化方法:

  • 预定义的标准格式
  • 语言环境相关的格式
  • 自定义的格式

接口可以有默认实现和静态方法

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface DefaultAndStaticDemo {
/**
* 常规方法定义
*/
void method();

/**
* 默认方法
* @return
*/
default String sayHi() {
return "Hi default!";
}

/**
* 静态方法
*/
static void sayHello() {
System.out.println("Hello static!");
}
}

接口方法有默认实现后,就带来了必不可少的方法冲突问题,主要为以下两大类:

  • 继承的父类与实现的接口冲突:采用 ==类优先法则== ,即优先使用类中的方法实现,忽略接口的默认实现
  • 接口冲突:如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须重写该方法来解决冲突

后记

JAVA8中最主要的特点还是lambda表达式和stream API,功能强大且代码简洁,与JAVA一直以来被诟病代码格式刻板(个人觉得还是简单易读就好)有了一定的改善,总结来看只怪自己学习太晚,整天研究解决方案,连基本的语言基础都落下了。

JAVA8后的首个长期支持版本JAVA11已经于去年发布,目前不少团队已经将JAVA11投入生产了,当然主流版本仍然是JAVA8(1.稳定;2.JAVA8确实够用了)。

另外JAVA14也预计在2020年3月发布,作为JAVA程序员真的好方…

热评文章