Java 8 的一些新特性

Java 8 的一些新特性

一、Lambda表达式

先看个集合排序的例子:

没用Lambda表达式前:

List<String> list = Arrays.asList("ccc", "ddd", "www", "aaa");

Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return o1.compareTo(o2);
    }
});

使用Lambda表达式:

List<String> list = Arrays.asList("ccc", "ddd", "www", "aaa");

Collections.sort(list, (o1, o2) -> {
    return o1.compareTo(o2);
});

Lambda表达式让代码变得更精简,但是代码的可读性有所损失。

1、Lambda表达式格式

image

  • ()代表方法参数;
  • {}代表方法要实现的内容。

2、函数式接口

如果要用 Lambda,我们可以使用 @FunctionalInterface 注解去标记接口,并且该接口必须只有一个未实现的方法:

@FunctionalInterface
public interface LambdaInterface {
    void hello(int i);
}

使用 lambda :

LambdaInterface lambda = (int i) -> {};

JDK8 中引入了 default 方法,允许我们通过 default 关键字对接口中定义的抽象方法提供一个默认的实现:

@FunctionalInterface
public interface LambdaInterface {

 void hello(int i);

 default int add(int a, int b){
     return a + b;
 }
}

3、Lambda 表达式的四种写法

  • 单个参数的

    (int i) -> {}(i) -> {}i -> {}

  • 多个参数

    (int a, int b) -> {}(a, b) -> {}

  • 写参数类型

    (int i) -> {}(int a, int b) -> {}

  • 方法体不止一行

    如果方法体不止一行,需要用上 {},如果方法体只有一行,则可以不需要 {}

4、JDK8 提供的函数式接口

接口 输入参数 返回类型 说明
UnaryOperator T T 一元函数,输入输出类型相同
Predicate T boolean 断言
Consumer T / 消费一个数据,只有输入没有输出
Function<T,R> T R 输入 T 返回 R,有输入也有输出
Supplier / T 提供一个数据,没有输入只有输出
BiFunction<T,U,R> (T,U) R 两个输入参数
BiPredicate<L, R> (L,R) boolean 两个输入参数
BiConsumer<T, U> (T,U) void 两个输入参数
BinaryOperator (T,T) T 二元函数,输入输出类型相同
  • UnaryOperator

    import java.util.function.UnaryOperator;
    
    public class User {
    
        private String username;
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String say(UnaryOperator<String> sayHello) {
            return sayHello.apply(this.username);
        }
    
        public static void main(String[] args) {
            User user = new User();
            user.setUsername("tom");
            String say = user.say((username) -> "hello " + username);
            System.out.println("say = " + say);
        }
    }
    
  • Predicate:输入一个 T 类型的参数,输出一个 boolean 类型的值

    例如集合流Stream中的filter 方法中传入的就是一个 Predicate 函数接口:

    list.stream().filter(s -> s.startsWith("a"))

  • Consumer:只有输入没有输出

    例如集合的遍历就可以使用 Consumer 函数接口 :

    list.stream().forEach(s -> System.out.println(s));

  • Supplier:只有输出没有输入

5、方法引用

操作符::

  • 引用静态方法

    // Function<Integer, String> func = a -> String.valueOf(a);
    Function<Integer, String> func = String::valueOf;
    String s = func.apply(99);
    
    // Consumer<String> consumer = s -> System.out.println(s);
    Consumer<String> consumer = System.out::println;
    consumer.accept("sssss");
    
  • 实例方法引用

    // IntUnaryOperator func = i -> random.nextInt(i);
    Random random = new Random();
    IntUnaryOperator func = random::nextInt;
    Integer r = func.applyAsInt(10);
    
  • 构造方法引用

    // Supplier<Cat> supplier = () -> new Cat();
    Supplier<Cat> supplier = Cat::new;
    Cat cat = supplier.get();
    
  • 数组构造方法引用

    // IntFunction<int[]> func = (i) -> new int[i];
    IntFunction<int[]> func = int[]::new;
    int[] arr = func.apply(10);
    

6、变量引用

  • 局部变量:内部类中使用外部定义的变量,需要这个变量是一个 final 类型的,如果用了 Lambda 表达式,这个规则依然适用。

    int m = 0;
    LambdaInterface lambdaInterface = (i, j) -> {
        System.out.println(m + 3);
    };
    

    与内部类不同的是我们不必显示的声明局部变量为final,但是它实际上已经是 final 类型的了,如果强行修改,就会报错。

  • 成员变量及静态变量:在 Lambda 表达式中对成员变量和静态变量拥有读写权限。


二、Stream 流

JDK8 中引入了一种新式的流式 API —— Stream,名字看起来很像 Java IO 中的各种流操作,但这其实是两个完全不同的东西。Java8 中的 Stream 不存储数据,它通过函数式编程模式来对集合进行链状流式操作。

1、Stream 流获取

数组

IntStream stream = Arrays.stream(new int[]{11, 22, 33, 44, 55, 66});

集合

List<String> list = new ArrayList<>();
Stream<String> s1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> s2 = set.stream();

数字 Stream:

IntStream s1 = IntStream.of(1, 2, 3);
DoubleStream s2 = DoubleStream.of(1, 2, 3);
LongStream s3 = LongStream.of(1L, 2L, 3L);

自己创建:

Random random = new Random();
Supplier<Integer> supplier = () -> random.nextInt(100);
Stream<Integer> stream = Stream.generate(supplier).limit(5);

调用 Stream.generate 方法可以自己创建一个流,自己创建的时候需要提供一个 Supplier,通过调用 Supplier 中的 get 方法自动获取到元素。

2、Stream 流操作

流操作的操作主要分为两个部分:

  • 中间操作:返回的任然是Stream 流;
  • 终止操作:会返回一个结果。

2.1 中间操作

  • map:Stream 中最常用的一个转换方法,能够帮助我们将集合中的每一个元素做功能处理

    // 给所有的元素乘 2,再打印
    IntStream.of(1, 2, 3).map(i -> 2 * i).forEach(System.out::println);
    

    JDK 中也提供了一些现成的格式转换,如下图:
    image
    可以直接将元素转为 Double、Long、Obj 等类型:

    String[] arr = {"1", "2", "3"};
    Stream<String> s1 = Arrays.stream(arr);
    s1.mapToLong(i -> Long.parseLong(i)).forEach(System.out::println);
    
  • flatMap:flatMap 可以把 Stream 中的每个元素都映射为一个 Stream,然后再把这多个 Stream 合并为一个 Stream。

    Stream<Integer> s = Stream.of(new Integer[]{1, 2, 3}, new Integer[]{4, 5, 6}, new Integer[]{7, 8, 9}).flatMap(i -> Arrays.stream(i));
    s.forEach(System.out::println);
    
  • filter:filter 操作会对一个 Stream 中的所有元素一一进行判断,不满足条件的就被过滤掉了,剩下的满足条件的元素就构成了一个新的 Stream。

    // 找到数组中所有大于 3 的元素
    IntStream.of(2, 3, 4, 5, 6, 7).filter(i -> i > 3).forEach(System.out::println);
    
  • peek:peek 的入参是 Consumer,没有返回值,因此当我们要对元素内部进行处理时,使用 peek 是比较合适的。

  • distinct:去重。

    IntStream.of(2, 3, 4, 3, 7, 6, 2, 5, 6, 7).distinct().forEach(System.out::println);
    
  • sorted:排序。

    IntStream.of(2, 3, 4, 3, 7, 6, 2, 5, 6, 7).distinct().sorted().forEach(System.out::println);
    
  • limit/skip:limit 和 skip 配合操作有点像数据库中的分页,skip 表示跳过 n 个元素,limit 表示取出 n 个元素。

    // 跳过 A 和 B,最终打印出 C D E
    Arrays.asList('A', 'B', 'C', 'D', 'E', 'F').stream().skip(2).limit(3).forEach(System.out::println);
    

2.2 终止操作

终止操作可以分为两类:

  • 短路操作:不用处理全部元素就可以返回结果。
  • 非短路操作:必须处理所有元素才能得到最终结果。

非短路操作

  • forEach/forEachOrdered:forEach 和 forEachOrdered 都是接收一个 Consumer 类型的参数,完成对参数的消费,不同的是,在并行流中,forEachOrdered 会保证执行顺序。

    int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    Arrays.stream(arr).parallel().forEach(System.out::println);
    Arrays.stream(arr).parallel().forEachOrdered(System.out::println);
    
  • collect/toArray:这两个都是收集器,可以将执行结果转为一个 List 集合或者一个数组。

    List<Integer> list = Stream.of(1, 2, 3, 4).filter(p -> p > 2).collect(Collectors.toList());
    System.out.println(list);
    
  • reduce:reduce 是 Stream 的一个聚合方法,它可以把一个 Stream 的所有元素按照聚合函数聚合成一个结果。reduce 方法传入的对象是BinaryOperator 接口,它定义了一个apply 方法,负责把上次累加的结果和本次的元素进行运算,并返回累加的结果。

    Optional<Integer> optional = Stream.of(1, 2, 3, 4).reduce((i, j) -> i + j);
    System.out.println(optional.orElse(-1));
    

    reduce 的参数是 BinaryOperator,这个接收两个参数,第一个参数是之前计算的结果,第二个参数是本次参与计算的元素,两者累加求和。

  • min/max/count:求最大值最小值,统计总个数。

    Stream<Integer> s = Stream.of(1, 2, 3, 4);
    long count = s.count();
    System.out.println("count = " + count);
    
    Stream<Integer> s = Stream.of(1, 2, 3, 4);
    Optional<Integer> min = s.min(Comparator.comparingInt(i -> i));
    System.out.println("min.get() = " + min.get());
    

短路操作

  • findFirst/findAny:这两个就是返回流中的第一个 / 任意一个元素,findAny 要在并行流中测试才有效果。

    for (int i = 0; i < 10; i++) {
        Optional<Integer> first = Stream.of(1, 2, 3, 4).parallel().findFirst();
        System.out.println("first.get() = " + first.get());
    }
    System.out.println("=============");
    for (int i = 0; i < 10; i++) {
        Optional<Integer> first = Stream.of(1, 2, 3, 4).parallel().findAny();
        System.out.println("first.get() = " + first.get());
    }
    
  • allMatch/anyMatch/noneMatch:allMatch、anyMatch、noneMatch 用来判断所有元素、任意元素或者没有元素满足给定的条件。这三个方法的参数都是一个 Predicate 接口函数。

    boolean b = Stream.of(1, 2, 3, 4).allMatch(i -> i > 5);
    System.out.println("b = " + b);
    

2.3 并行流

Stream 流通过调用 parallel()方法就能转换成一个可以并行处理的流,这在元素数量非常大的情况下,可以大大加快处理的速度。

new Random().ints().limit(50).parallel().forEach(i->{
    System.out.println(Thread.currentThread().getName() + "--->" + i);
});

2.4 统计

一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
 
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
 
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());

三、Optional类

JDK8的类中引入了Optional类,设计它的目的是为了防止空指针异常。可以将 Optional 看做是包装对象的容器。当你定义了一个方法,这个方法返回的对象可能是空,也有可能非空的时候,你就可以考虑用 Optional 来包装它,这也是在 Java 8 被推荐使用的做法。

1、Optional类中的方法

序号 方法 方法说明
1 private Optional() 无参构造,构造一个空Optional
2 private Optional(T value) 根据传入的非空value构建Optional
3 public static<T> Optional<T> empty() 返回一个空的Optional,该实例的value为空
4 public static <T> Optional<T> of(T value) 根据传入的非空value构建Optional,与Optional(T value)方法作用相同
5 public static <T> Optional<T> ofNullable(T value) 与of(T value)方法不同的是,ofNullable(T value)允许你传入一个空的value,当传入的是空值时其创建一个空Optional,当传入的value非空时,与of()作用相同
6 public T get() 返回Optional的值,如果容器为空,则抛出NoSuchElementException异常
7 public boolean isPresent() 判断当家Optional是否已设置了值
8 public void ifPresent(Consumer<? super T> consumer) 判断当家Optional是否已设置了值,如果有值,则调用Consumer函数式接口进行处理
9 public Optional<T> filter(Predicate<? super T> predicate) 如果设置了值,且满足Predicate的判断条件,则返回该Optional,否则返回一个空的Optional
10 public<U> Optional<U> map(Function<? super T, ? extends U> mapper) 如果Optional设置了value,则调用Function对值进行处理,并返回包含处理后值的Optional,否则返回空Optional
11 public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) 与map()方法类型,不同的是它的mapper结果已经是一个Optional,不需要再对结果进行包装
12 public T orElse(T other) 如果Optional值不为空,则返回该值,否则返回other
13 public T orElseGet(Supplier<? extends T> other) 如果Optional值不为空,则返回该值,否则根据other另外生成一个
14 public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)throws X 如果Optional值不为空,则返回该值,否则通过supplier抛出一个异常

2、举例

Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() ); 
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); 
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) ); 
// 结果:
// Full Name is set? false
// Full Name: [none]
// Hey Stranger! 

Optional< String > firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
// 结果:
// First Name is set? true
// First Name: Tom
// Hey Tom!

四、新的日期 API

1、Clock

提供对当前日期和时间的访问。

获取当前毫秒数:

Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();

2、ZoneId

时区类,提供时区的相关操作。

获取所有时区:

System.out.println(ZoneId.getAvailableZoneIds());

// [Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi, America/Marigot, Asia/Aqtau, Pacific/Kwajalein, America/El_Salvador,...

3、LocalTime

时间类,提供时间的相关操作。

获取当前默认时区下的时间:

System.out.println(LocalTime.now()); // 13:40:03.343

两个不同时区之间的时间比较:

ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");

LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);

System.out.println(now1.isBefore(now2));  // false

long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

System.out.println(hoursBetween);       // -3
System.out.println(minutesBetween);     // -239

4、LocalDate

日期类,提供对日期的相关操作。

LocalDate today = LocalDate.now();
System.out.println(today); // 2021-11-19
// 今天加一天
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
// 明天减两天
LocalDate yesterday = tomorrow.minusDays(2);

// 2014 年七月的第四天
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek);    // 星期五

5、LocalDateTime

日期-时间类,可以将其看成是 LocalDateLocalTime 的结合体。操作上,也大致相同。

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek);      // 星期三

Month month = sylvester.getMonth();
System.out.println(month);          // 十二月

// 获取改时间是该天中的第几分钟
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay);    // 1439

Instant instant = sylvester
        .atZone(ZoneId.systemDefault())
        .toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014

6、DateTimeFormatter

时间日期解析类。

// 解析字符串时间
DateTimeFormatter germanFormatter = DateTimeFormatter
        .ofLocalizedTime(FormatStyle.SHORT)
        .withLocale(Locale.GERMAN);

LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime);   // 13:37

// 解析字符串日期
DateTimeFormatter germanFormatter = DateTimeFormatter
        .ofLocalizedDate(FormatStyle.MEDIUM)
        .withLocale(Locale.GERMAN);

LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas);   // 2014-12-24

// 解析字符串日期时间
DateTimeFormatter formatter = DateTimeFormatter
        .ofPattern("MMM dd, yyyy - HH:mm");

LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string);     // Nov 03, 2014 - 07:13

参考文章

WebFlux 前置知识(二)

WebFlux 前置知识(三)

Java8 新特性教程

Java8新特性之五:Optional

posted @ 2021-11-19 14:10  金盛年华  阅读(65)  评论(0编辑  收藏  举报