在 Java 8 中,增加了Lambda表达式、函数式接口、接口的默认方法和静态方法等语言新特性;在类库方面又新增了Stream API、Optional类等。
接口默认方法 从 Java 8 开始接口interface
的方法可以使用 default
或 static
修饰,这样就可以有方法体,且实现类不必进行重写。
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 表达式。
像我们经常使用的 Runnable
、Callable
、Comparator
就是函数式接口。
一般我们可以给函数式接口添加 @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();
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实例,将流转换为其他数据结构并返回
1 2 Set<User> collect = userList.stream() .collect(Collectors.toSet());
1 2 Map<Long, User> collect = userList.stream() .collect(Collectors.toMap(User::getUserId, user -> user));
filter过滤 根据一定规则对stream流进行过滤,将符合条件的元素提取到新的流中
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); } }
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 Stream<T> sorted () ; 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 > { 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()
方法能够正常获取。
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 ; } 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 源码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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
小试身手 下面代码严格的逻辑判断避免了程序发生空指针异常,但也导致了代码冗杂。
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)