前言
最近看了同事用lambda和stream写的代码,看起来着实优雅,读起来又有点费劲,便找资料学习了JAVA8的新特性,写此笔记,记录感悟。
新特性概览
Lambda 表达式
Lambda 表达式在Java 语言中引入了一个新的语法元
素和操作符。这个操作符为 ==->== , 该操作符被称
为 Lambda 操作符或剪头操作符。它将 Lambda 分为
两个部分:
左侧: 指定了 Lambda 表达式需要的所有参数
右侧: 指定了 Lambda 体,即 Lambda 表达式要执行
的功能。
示例1: 无参无返回值1
2
3
4
5
6
7
8
9
10// 匿名内部类实现
Runnable r1 = new Runnable() {
public void run() {
System.out.println("anno");
}
};
// lambda表达式
Runnable r2 = () -> System.out.println("lambda");
代码简洁度极大提升
示例2: 一个参数无返回值1
2
3
4
5
6
7
8
9Consumer<String> c1 = new Consumer<String>() {
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
12BinaryOperator<Long> b1 = new BinaryOperator<Long>() {
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
11BinaryOperator<Long> b1 = new BinaryOperator<Long>() {
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
7System.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 的操作三个步骤:
- 创建 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
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
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
33public 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
10public 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
21public 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程序员真的好方…