在 Java 8 中,增加了Lambda表达式、函数式接口、接口的默认方法和静态方法等语言新特性;在类库方面又新增了Stream API、Optional类等。

接口默认方法

从 Java 8 开始接口interface的方法可以使用 defaultstatic 修饰,这样就可以有方法体,且实现类不必进行重写。

1
2
3
4
5
6
7
8
9
10
11
12
public interface IService {

void write();

static void test1() {
System.out.println("111");
}

default void test2(){
System.out.println("222");
}
}

函数式接口

函数式接口(Functional Interface)就是有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。

像我们经常使用的 RunnableCallableComparator 就是函数式接口。

一般我们可以给函数式接口添加 @FunctionalInterface 注解,当然是不是函数式接口与加不加这个注解无关,只要符合函数式接口定义,即只包含一个抽象方法,虚拟机就会自动判断该接口为函数式接口。使用@FunctionalInterface 注解只是在编译时起到强制规范定义的作用。


实战运用

当我们在做项目时,如果遇到接口需要前端传递时间日期参数,此时我们只需配置一个日期转换类,即可实现自动将前端传递过来的时间戳字符串转换为 Date 类。

1
2
3
4
5
6
7
@Configuration
public class DateConfig implements Converter<String, Date> {
@Override
public Date convert(String str) {
return new Date(Long.parseLong(str));
}
}

这里我们只需实现 Converter 接口中的抽象方法 convert(),重写转换规则即可。同时我们可以看到 Converter 接口正是一个函数式接口。

1
2
3
4
5
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
T convert(S var1);
}

之后我们的接口便可以直接使用 Date 类来接收时间类型参数。

1
2
3
4
5
6
@ApiOperation(value = "查询审计", tags = "审计管理")
@GetMapping("/findAudit")
public Result<List<AuditResVo>> findAudit(Date createTimeStart, Date createTimeEnd){
List<AuditResVo> auditList = auditService.getAuditList(createTimeStart, createTimeEnd);
return Result.success(auditList);
}

方法引用


Lambda表达式

lambada表达式是一个可传递的代码块,可以在以后执行一次或多次。Lambda允许把函数作为一个方法的参数。

例如我们平时给集合或数组排序,都会使用Collections.sort Arrays.sort 进行排序,此时需要向 sort 方法传入一个 Comparator 对象:

1
2
3
4
5
6
7
List<String> strList = Arrays.asList("one", "two", "three");
Collections.sort(strList, new Comparator<String>() {
@Override
public int compare(String str1, String str2) {
return str2.length() - str1.length(); //将集合按照字符串长度降序排序
}
});

现在我们有了Lambada表达式,就能以更简洁的方式定制这个比较器:

1
2
3
List<String> strList = Arrays.asList("one", "two", "three");
Collections.sort(strList, (String str1, String str2)
-> str2.length() - str1.length());

这便是一种lambada表达式形式:参数,箭头(->)以及一个表达式。如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把代码放在 {} 中,并包含显示的 return 语句:

1
2
3
4
5
6
List<String> strList = Arrays.asList("one", "two", "three");
Collections.sort(strList, (String str1, String str2) -> {
if (str2.length() > str1.length()) return 1;
else if (str2.length() < str1.length()) return -1;
else return 0;
});

当lambada表达式没有参数时,仍然要提供小括号(),就像无参方法一样:

  • 传统方式
1
2
3
4
5
6
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行");
}
}).start();
  • lambada表达式
1
new Thread(() -> System.out.println("线程执行")).start();

如果编译器可以推导出参数类型,则可以忽略其类型:

1
Comparator<String> comparator = (str1, str2) -> str2.length() - str1.length();

如果方法只有一个参数,而且这个参数的类型可以推导出来,那么甚至可以省略小括号:

1
Predicate<Integer> predicate = data -> data > 0;

实战运用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Student {

private Integer no;

private String name;

public Student(Integer no, String name) {
this.no = no;
this.name = name;
}

public Integer getNo() {
return no;
}

public String getName() {
return name;
}
}

Stream流中的 filter 过滤需要通过一个 predicate 接口来过滤并只保留符合条件的元素,此时配合lambada表达式会非常方便。

1
2
3
4
5
6
Student student1 = new Student(1, "小明");
Student student2 = new Student(6, "小李");
Student student3 = new Student(12, "小月");
List<Student> studentList = Arrays.asList(student1, student2, student3);
List<Student> collect = studentList.stream()
.filter(student -> student.getNo() < 10).collect(Collectors.toList());

Stream API

什么是Stream

Stream(流)是一个来自数据源的元素队列,它可以支持聚合操作,极大简化了集合的操作。

  • 数据源:流的数据来源,构造Stream对象的数据源,比如通过一个List来构造Stream对象,这个List就是数据源;
  • 聚合操作:对Stream对象进行处理后使得Stream对象返回指定规则数据的操作称之为聚合操作,比如filter、map、limit、sorted等都是聚合操作。

Stream聚合操作

这里使用一个实体类(User)作为示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
@AllArgsConstructor
public class User {

private Long userId;

private String userName;

private Integer age;

private String address;

}

示例数据:

1
2
3
4
5
User user1 = new User(1001L, "小明", 21, "深圳");
User user2 = new User(1002L, "小红", 23, "成都");
User user3 = new User(1003L, "小华", 25, "广州");
User user4 = new User(1004L, "大海", 30, "杭州");
List<User> userList = Arrays.asList(user1, user2, user3, user4);

流的创建

1
2
3
4
5
6
7
8
List<String> list = new ArrayList<>();
//创建一个顺序流
Stream<String> stream = list.stream();
//创建一个并行流
Stream<String> parallelStream = list.parallelStream();

//将示例数据转换成流
Stream<User> userStream = userList.stream();
  • 顺序流与并行流的区别

stream是顺序流,由主线程按顺序对流执行操作; parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。

collect收集

collect方法用于传入一个Collector实例,将流转换为其他数据结构并返回

  • 将List集合转换为Set集合
1
2
Set<User> collect = userList.stream()
.collect(Collectors.toSet());
  • 将List集合转换为Map集合
1
2
Map<Long, User> collect = userList.stream()
.collect(Collectors.toMap(User::getUserId, user -> user));

filter过滤

根据一定规则对stream流进行过滤,将符合条件的元素提取到新的流中

  • 筛选出List集合大于6的数据
1
2
3
4
5
6
7
8
public class StreamAPI {

public static void main(String[] args) {
List<Integer> list = Arrays.asList(2, 5, 7, 9);
Stream<Integer> stream = list.stream();
stream.filter(num -> num > 6).forEach(System.out::println);
}
}
  • 将示例数据中年龄大于24的数据筛选出来
1
2
3
List<User> collect = userList.stream()
.filter(user -> user.getAge() < 24)
.collect(Collectors.toList());

map映射

将流的元素按照一定映射规则进行转换处理后映射到另一个流中

  • 将实例集合中的对象的name映射为新的List集合
1
2
3
List<String> collect = userList.stream()
.map(user -> user.getUserName())
.collect(Collectors.toList());

distinct去重

将stream流中的相同元素进行去重处理(通过流中元素的 hashCode() 和 equals() 去除重复元素)

1
2
3
List<User> collect = userList.stream()
.distinct()
.collect(Collectors.toList());

limit

从stream流中获取指定个数的元素

1
2
3
List<User> collect = userList.stream()
.limit(2)
.collect(Collectors.toList());

skip

跳过指定个数的流中的元素

1
2
3
List<User> collect = userList.stream()
.skip(2)
.collect(Collectors.toList());

分页操作

使用limit配合skip可实现分页操作

1
2
3
4
List<User> collect = userList.stream()
.skip(0)
.limit(2)
.collect(Collectors.toList());

count

返回stream流中元素个数

1
long count = userList.stream().count();

sorted排序

按照某种规则对元素进行排序

排序有两种方式:

1
2
3
4
// 自然排序,流中元素需要实现Comparable接口
Stream<T> sorted();
// Comparator自定义排序
Stream<T> sorted(Comparator<? super T> comparator);
  • 年龄大的排在前面
1
2
3
4
5
List<User> collect = userList.stream()
.sorted((userA, userB) -> {
return userB.getAge().compareTo(userA.getAge());
})
.collect(Collectors.toList());

Optional类

Optional类是 Java 8 提供的用于解决 空指针异常 NullPointerException 的工具,它能帮助我们减少各种 null 检查的代码,使程序变得更加简洁。

源码分析

从下面源码可以发现,Optional类维护了一个变量value,初始时其值为null。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public final class Optional<T> {

/**
* 全局EMPTY对象
*/
private static final Optional<?> EMPTY = new Optional<>();

/**
* Optional维护的值
*/
private final T value;

/**
* 初始值为null
*/
private Optional() {
this.value = null;
}

/**
* 返回Optional的Empty空对象
*/
public static<T> Optional<T> empty() {
Optional<T> t = (Optional<T>) EMPTY;
return t;
}

/**
* 私有构造方法,给Optional对象的value赋值
*/
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}

/**
* Objects.requireNonNull方法
* 可以发现使用of方法若value为null,则抛出NullPointerException异常
*/
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}

/**
* 创建指定value值的Optional对象并返回
*/
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}

/**
* 如果value为null则返回EMPTY,否则返回of(value)
*/
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}

/**
* 如果value值为空,则抛出异常
*/
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}

通过源码我们可以看到, of() 以及 ofNullable() 这两个方法都可以创建Optional对象并返回,那么它们有上面不同呢?主要在于使用of()方法传入的 value 值为 null 时,则会直接抛出 NullPointerException 空指针异常;而ofNullable()方法不会报空指针异常,而是返回 EMPTY (一个 value 为 null 值的Optional对象)。如果需要把 NullPointerException 暴漏出来就用 of,否则就用 ofNullable


of与ofNullable测试

我们先使用of()方法进行测试,当value不为null,使用get()方法能够正常获取。

1
2
3
4
5
String str1 = "hello";
String str2 = null;
Optional<String> optional = Optional.of(str1);
String str = optional1.get();
System.out.println(str);

当value为null时,在of()方法中直接抛出NullPointerException空指针异常。

1
2
3
4
5
String str1 = "hello";
String str2 = null;
Optional<String> optional = Optional.of(str2);//抛出异常
String str = optional1.get();
System.out.println(str);

我们再使用ofNullable()方法进行测试,当传入value为null值时,ofNullable()方法并不会抛出异常,而是在get()时抛出NoSuchElementException异常。

1
2
3
4
5
String str1 = "hello";
String str2 = null;
Optional<String> optional = Optional.ofNullable(str2);//未抛出异常
String str = optional.get();//抛出异常
System.out.println(str);

其他实用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean isPresent() {
return value != null;
}

/**
* 如果Optional内部维护的value不为null,则执行consumer
*/
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}

/**
* 如果Optional内部维护的value不为null则将其返回,否则返回other其他值
*/
public T orElse(T other) {
return value != null ? value : other;
}

map与flatMap

源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 如果value为null,返回EMPTY,否则返回Optional封装的参数值
*/
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}

/**
* 如果value为null,返回EMPTY,否则返回Optional封装的参数值,如果参数值返回null会抛 NullPointerException
*/
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}

区别:

  • 参数不一样
  • flatMap() 参数返回值如果是 null 会抛 NullPointerException,而 map() 返回EMPTY

小试身手

下面代码严格的逻辑判断避免了程序发生空指针异常,但也导致了代码冗杂。

1
2
3
4
5
6
7
8
9
public static void getName(School school){
if (school != null) {
Student student = school.getStudent();
if (student != null) {
String name = student.getName();
System.out.println(name);
}
}
}

此时使用Optional进行简化代码:

1
2
3
4
public static void getName(School school){
Optional.ofNullable(school).map(School::getStudent).map(Student::getName)
.ifPresent(name -> System.out.println(name));
}

若方法需要返回name,则可以改写为:

1
2
3
4
public static String getName(School school){
return Optional.ofNullable(school).map(School::getStudent).map(Student::getName)
.orElse("发现null值");
}

参考资料

《Java核心技术 卷1》

字节二面被问“Java Stream 流操作‘’?看完这篇,教你自信应对!

Java 8都出那么久了,Stream API了解下?

我,一个10年老程序员,最近才开始用 Java8 新特性 (qq.com)