Java 8新特性:Lambda表达式, Stream API, Optional类,函数式接口


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

接口默认方法

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

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 类。

@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.sortArrays.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();
  • lambada表达式
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实例,将流转换为其他数据结构并返回

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

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

  • 筛选出List集合大于6的数据
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的数据筛选出来
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排序

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

排序有两种方式:

// 自然排序,流中元素需要实现Comparable接口
Stream<T> sorted();		
// Comparator自定义排序
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> {

    /**
     * 全局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()方法能够正常获取。

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;
}

/**
 * 如果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

源码分析

/**
* 如果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

小试身手

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

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)

  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值