在 Java 8 中,增加了Lambda表达式、函数式接口、接口的默认方法和静态方法等语言新特性;在类库方面又新增了Stream API、Optional类等。
接口默认方法
从 Java 8 开始接口interface 的方法可以使用 default 或 static 修饰,这样就可以有方法体,且实现类不必进行重写。
public interface IService {
void write();
static void test1() {
System.out.println("111");
}
default void test2(){
System.out.println("222");
}
}
函数式接口
函数式接口(Functional Interface)就是有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。
像我们经常使用的 Runnable 、Callable 、Comparator 就是函数式接口。
一般我们可以给函数式接口添加 @FunctionalInterface 注解,当然是不是函数式接口与加不加这个注解无关,只要符合函数式接口定义,即只包含一个抽象方法,虚拟机就会自动判断该接口为函数式接口。使用@FunctionalInterface 注解只是在编译时起到强制规范定义的作用。
实战运用
当我们在做项目时,如果遇到接口需要前端传递时间日期参数,此时我们只需配置一个日期转换类,即可实现自动将前端传递过来的时间戳字符串转换为 Date 类。
@Configuration
public class DateConfig implements Converter<String, Date> {
@Override
public Date convert(String str) {
return new Date(Long.parseLong(str));
}
}
这里我们只需实现 Converter 接口中的抽象方法 convert() ,重写转换规则即可。同时我们可以看到 Converter 接口正是一个函数式接口。
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
T convert(S var1);
}
之后我们的接口便可以直接使用 Date 类来接收时间类型参数。
@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 对象:
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表达式,就能以更简洁的方式定制这个比较器:
List<String> strList = Arrays.asList("one", "two", "three");
Collections.sort(strList, (String str1, String str2)
-> str2.length() - str1.length());
这便是一种lambada表达式形式:参数,箭头(->)以及一个表达式。如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把代码放在 {} 中,并包含显示的 return 语句:
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表达式没有参数时,仍然要提供小括号() ,就像无参方法一样:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行");
}
}).start();
new Thread(() -> System.out.println("线程执行")).start();
如果编译器可以推导出参数类型,则可以忽略其类型:
Comparator<String> comparator = (str1, str2) -> str2.length() - str1.length();
如果方法只有一个参数,而且这个参数的类型可以推导出来,那么甚至可以省略小括号:
Predicate<Integer> predicate = data -> data > 0;
实战运用
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表达式会非常方便。
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)作为示例:
@Data
@AllArgsConstructor
public class User {
private Long userId;
private String userName;
private Integer age;
private String address;
}
示例数据:
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);
流的创建
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实例,将流转换为其他数据结构并返回
Set<User> collect = userList.stream()
.collect(Collectors.toSet());
Map<Long, User> collect = userList.stream()
.collect(Collectors.toMap(User::getUserId, user -> user));
filter过滤
根据一定规则对stream流进行过滤,将符合条件的元素提取到新的流中
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);
}
}
List<User> collect = userList.stream()
.filter(user -> user.getAge() < 24)
.collect(Collectors.toList());
map映射
将流的元素按照一定映射规则进行转换处理后映射到另一个流中
- 将实例集合中的对象的name映射为新的List集合
List<String> collect = userList.stream()
.map(user -> user.getUserName())
.collect(Collectors.toList());
distinct去重
将stream流中的相同元素进行去重处理(通过流中元素的 hashCode() 和 equals() 去除重复元素)
List<User> collect = userList.stream()
.distinct()
.collect(Collectors.toList());
limit
从stream流中获取指定个数的元素
List<User> collect = userList.stream()
.limit(2)
.collect(Collectors.toList());
skip
跳过指定个数的流中的元素
List<User> collect = userList.stream()
.skip(2)
.collect(Collectors.toList());
分页操作
使用limit配合skip可实现分页操作
List<User> collect = userList.stream()
.skip(0)
.limit(2)
.collect(Collectors.toList());
count
返回stream流中元素个数
long count = userList.stream().count();
sorted排序
按照某种规则对元素进行排序
排序有两种方式:
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
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。
public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
private Optional() {
this.value = null;
}
public static<T> Optional<T> empty() {
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(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() 方法能够正常获取。
String str1 = "hello";
String str2 = null;
Optional<String> optional = Optional.of(str1);
String str = optional1.get();
System.out.println(str);
当value为null时,在of() 方法中直接抛出NullPointerException空指针异常。
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异常。
String str1 = "hello";
String str2 = null;
Optional<String> optional = Optional.ofNullable(str2);
String str = optional.get();
System.out.println(str);
其他实用方法
public boolean isPresent() {
return value != null;
}
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
public T orElse(T other) {
return value != null ? value : other;
}
map与flatMap
源码分析
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));
}
}
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
小试身手
下面代码严格的逻辑判断避免了程序发生空指针异常,但也导致了代码冗杂。
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进行简化代码:
public static void getName(School school){
Optional.ofNullable(school).map(School::getStudent).map(Student::getName)
.ifPresent(name -> System.out.println(name));
}
若方法需要返回name,则可以改写为:
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)
|