- 接口默认方法
- 函数式接口
- 实战运用
- 方法引用
- Lambda表达式
- 实战运用
- Stream API
- 什么是Stream
- Stream聚合操作
- 流的创建
- collect收集
- filter过滤
- map映射
- distinct去重
- limit
- skip
- 分页操作
- count
- sorted排序
- Optional类
- 源码分析
- of与ofNullable测试
- 其他实用方法
- map与flatMap
- 小试身手
在 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{ @Override public Date convert(String str) { return new Date(Long.parseLong(str)); } }
这里我们只需实现 Converter 接口中的抽象方法 convert(),重写转换规则即可。同时我们可以看到 Converter 接口正是一个函数式接口。
@FunctionalInterface public interface Converter{ @Nullable T convert(S var1); }
之后我们的接口便可以直接使用 Date 类来接收时间类型参数。
@ApiOperation(value = "查询审计", tags = "审计管理")
@GetMapping("/findAudit")
public Result> findAudit(Date createTimeStart, Date createTimeEnd){
List auditList = auditService.getAuditList(createTimeStart, createTimeEnd);
return Result.success(auditList);
}
方法引用
Lambda表达式
lambada表达式是一个可传递的代码块,可以在以后执行一次或多次。Lambda允许把函数作为一个方法的参数。
例如我们平时给集合或数组排序,都会使用Collections.sort或 Arrays.sort 进行排序,此时需要向 sort 方法传入一个 Comparator 对象:
ListstrList = Arrays.asList("one", "two", "three"); Collections.sort(strList, new Comparator () { @Override public int compare(String str1, String str2) { return str2.length() - str1.length(); //将集合按照字符串长度降序排序 } });
现在我们有了Lambada表达式,就能以更简洁的方式定制这个比较器:
ListstrList = Arrays.asList("one", "two", "three"); Collections.sort(strList, (String str1, String str2) -> str2.length() - str1.length());
这便是一种lambada表达式形式:参数,箭头(->)以及一个表达式。如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把代码放在 {} 中,并包含显示的 return 语句:
ListstrList = 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();
- lambada表达式
new Thread(() -> System.out.println("线程执行")).start();
如果编译器可以推导出参数类型,则可以忽略其类型:
Comparatorcomparator = (str1, str2) -> str2.length() - str1.length();
如果方法只有一个参数,而且这个参数的类型可以推导出来,那么甚至可以省略小括号:
Predicate实战运用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, "小月"); ListstudentList = Arrays.asList(student1, student2, student3); List 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流的创建userList = Arrays.asList(user1, user2, user3, user4);
Listlist = new ArrayList<>(); //创建一个顺序流 Stream stream = list.stream(); //创建一个并行流 Stream parallelStream = list.parallelStream(); //将示例数据转换成流 Stream userStream = userList.stream();
- 顺序流与并行流的区别
stream是顺序流,由主线程按顺序对流执行操作; parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。
collect收集collect方法用于传入一个Collector实例,将流转换为其他数据结构并返回
- 将List集合转换为Set集合
Setcollect = userList.stream() .collect(Collectors.toSet());
- 将List集合转换为Map集合
Mapfilter过滤collect = userList.stream() .collect(Collectors.toMap(User::getUserId, user -> user));
根据一定规则对stream流进行过滤,将符合条件的元素提取到新的流中
- 筛选出List集合大于6的数据
public class StreamAPI {
public static void main(String[] args) {
List list = Arrays.asList(2, 5, 7, 9);
Stream stream = list.stream();
stream.filter(num -> num > 6).forEach(System.out::println);
}
}
- 将示例数据中年龄大于24的数据筛选出来
Listmap映射collect = userList.stream() .filter(user -> user.getAge() < 24) .collect(Collectors.toList());
将流的元素按照一定映射规则进行转换处理后映射到另一个流中
- 将实例集合中的对象的name映射为新的List集合
Listdistinct去重collect = userList.stream() .map(user -> user.getUserName()) .collect(Collectors.toList());
将stream流中的相同元素进行去重处理(通过流中元素的 hashCode() 和 equals() 去除重复元素)
Listlimitcollect = userList.stream() .distinct() .collect(Collectors.toList());
从stream流中获取指定个数的元素
Listskipcollect = userList.stream() .limit(2) .collect(Collectors.toList());
跳过指定个数的流中的元素
List分页操作collect = userList.stream() .skip(2) .collect(Collectors.toList());
使用limit配合skip可实现分页操作
Listcountcollect = userList.stream() .skip(0) .limit(2) .collect(Collectors.toList());
返回stream流中元素个数
long count = userList.stream().count();sorted排序
按照某种规则对元素进行排序
排序有两种方式:
// 自然排序,流中元素需要实现Comparable接口 Streamsorted(); // Comparator自定义排序 Stream sorted(Comparator super T> comparator);
- 年龄大的排在前面
Listcollect = 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{ private static final Optional> EMPTY = new Optional<>(); private final T value; private Optional() { this.value = null; } public static Optional empty() { Optional t = (Optional ) EMPTY; return t; } private Optional(T value) { this.value = Objects.requireNonNull(value); } public static T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; } public static Optional of(T value) { return new Optional<>(value); } public static Optional 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; Optionaloptional = Optional.of(str1); String str = optional1.get(); System.out.println(str);
当value为null时,在of()方法中直接抛出NullPointerException空指针异常。
String str1 = "hello"; String str2 = null; Optionaloptional = Optional.of(str2);//抛出异常 String str = optional1.get(); System.out.println(str);
我们再使用ofNullable()方法进行测试,当传入value为null值时,ofNullable()方法并不会抛出异常,而是在get()时抛出NoSuchElementException异常。
String str1 = "hello"; String str2 = null; Optionaloptional = 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 Optional map(Function super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
public Optional flatMap(Function super T, Optional> 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)



