轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

      • Java8--Lambda 表达式、Stream 和时间 API
        • 一、Lambda
          • 1、Lambda基本结构
          • 2、通用的函数式接口
          • 3、方法引用操作符
          • 4、接口中的默认方法
          • 5、LambdaMetafactory
        • 二、Stream API
          • 1、创建Stream
          • 2、filter、map和flatMap方法
          • 3、提取子流和组合流
          • 4、有状态的转换
          • 5、简单的聚合方法
          • 6、Optional类型
          • 7、reduce聚合函数
          • 8、收集结果
          • 9、分组和分片
          • 10、原始类型流
          • 11、并行流
        • 三、时间API
          • 1、对日期进行加减
          • 2、日期的格式化
          • 3、对两个日期的判断和运算
          • 4、带时区的日期
          • 5、与旧API的相互操作
          • 6、时间调整之TemporalAdjusters
        • 总结
      • Java集合
      • Java IO
      • Java 文件操作
      • Java 网络编程
      • Java运行期动态能力
      • Java可插入注解处理器
      • Java基准测试(JMH)
      • Java性能分析(Profiler)
      • Java调试(JDI与JDWP)
      • Java管理与监控(JMX)
      • Java加密体系(JCA)
      • Java服务发现(SPI)
      • Java随机数生成研究
      • Java数据库连接(JDBC)
      • Java历代版本新特性
      • 写好Java Doc
      • 聊聊classpath及其资源获取
    • 并发

    • 经验

    • JVM

    • 企业应用

  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 核心
轩辕李
2018-05-17
目录

Java8--Lambda 表达式、Stream 和时间 API

Java10 已经在 2018 年发布了,Java 也步入了小步迭代的阶段。 现如今很多实际的项目中,因为 Java9 模块化变动太大,用到 Java7 和 Java8 的还是比较多,Java7 中令人印象深刻的自然是新的 I/O 操作 API 了,Java8 中则有非常重要的新特性 --Lambda 和 Stream,自从 Java8 出来之后,本人就一直在使用,下面是一个 Java8 的总结,也是一个经验的分享。

# 一、Lambda

Java没有Lambda之前,我们只能羡慕的看着Ruby或JavaScript在语法层面玩出花儿,Java天下没有闭包这一特性,广大程序员苦其久矣。
闭包我们应该都不陌生,其他语言中多有见到,在Java中,Lambda表达式一般用来简化表示一种特殊的接口:函数式接口。
所谓函数式,其实就是只定义了一个方法的接口,在Java8中用@FunctionalInterface来修饰。
看一下我们熟悉的Runnable,Java8的源码如下:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

平常我们把一个匿名Runnable对象传给Thread,如:

new Thread(new Runnable() {
	@Override
	public void run() {
		System.out.println("hello");
	}
}).run();

用Lambda的话:

new Thread(() -> {
	System.out.println("hello");
}).run();

因为这里方法体只有一句代码,那么可以更简洁一些:

new Thread(() -> System.out.println("hello")).run();

# 1、Lambda基本结构

一个参数:

Consumer<String> consumer = event -> syso("hello "+ event)

二个参数:

Comparator<String> comp = ( first, second ) -> { return first.length() > second.length(); }

无参数:

Runable runable = () -> { syso("hello"); }

有时,可不用return,java会自动推断类型:

Comparator<String> comp = ( first, second ) -> { first.length() > second.length(); }

# 2、通用的函数式接口

为了方便,Java中定义了许多通用的函数式接口,其实以前我们在Guava中见到过这些类:

  • Predicate 传入一个参数,返回一个bool结果, 方法为boolean test(T t)
  • BiPredicate 传入两个参数,返回一个bool结果, 方法为boolean test(T t, U u)
  • Consumer 传入一个参数,无返回值,纯消费。 方法为void accept(T t)
  • BiConsumer 传入两个参数,无返回值,纯消费。 方法为void accept(T t, U u)
  • Function 传入一个参数,返回一个结果,方法为R apply(T t)
  • BiFunction<T, U, R> 入两个参,返回一个值:R apply(T t, U u)
  • Supplier 无参数传入,返回一个结果,方法为T get()
  • UnaryOperator 一元操作符, 继承Function,传入参数的类型和返回类型相同。
  • BinaryOperator 二元操作符, 传入的两个参数的类型和返回类型相同, 继承BiFunction

# 3、方法引用操作符

先看一个例子:

button.setOnAction(event -> System.out.println(event))    
//等价于: 
button.setOnAction(System.out::println)

可以看到一个新的操作符::,在Java8中它是方法引用操作符。 先定义一个类,便于下面举例使用:

private static class TUser {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

方法引用操作符主要是四种用法:

  • 对象::实例方法,如
TUser tUser = new TUser();
Supplier<String> supplier = tUser::getName
//tUser::getName = tUser -> tUser.getName
  • 类::静态方法,如
button.setOnAction(System.out::println)
//或
Math::pow = (x, y) -> Math.pow(x, y)
  • 类::实例方法,如
this::equals
//或
Function<TUser, String> function = TUser::getName;
//TUser::getName = tUser -> tUser.getName

需要注意对象::实例方法和类::实例方法的不同

  • 构造器引用
int[]::new  =  x -> new int[x]    
//或
List<Object> list = new ArrayList<Object>();        
list.stream().toArray(String[]::new);

# 4、接口中的默认方法

语言本身语法的改进,会带来效率方面的很大提升(羡慕其他JVM语言如Kotlin的语法糖)。例如Java5的泛型,Java7的try-cath-resource,Java8中提供了接口默认方法的特性,可以在接口中定义方法,更利于模块化。可以和Ruby的Mixin对照参考一下。
需要用default关键字,直接用JDK中的例子来看吧,拿我们熟悉的java.util.List接口来说,就有了新的接口默认方法:

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

# 5、LambdaMetafactory

LambdaMetafactory可以生成函数映射来替代反射,理论上任何的公开方法都可以使用函数映射,而且他的强大之处在于他的性能远远超过了反射调用。
在fastjson2中,LambdaMetafactory得到了大量使用,可以参考:fastjson2为什么这么快? (opens new window)
我们来看一下函数映射怎么替代反射:

    @Test
    void direct() {
        String toBeTrimmed = " text with spaces ";
        System.out.println(toBeTrimmed.trim());

        Supplier<String> trimSupplier = toBeTrimmed::trim;
        System.out.println(trimSupplier.get());

        Function<String, String> trimFunc = String::trim;
        System.out.println(trimFunc.apply(toBeTrimmed));
    }

    @Test
    void reflection() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        String toBeTrimmed = " text with spaces ";
        Method reflectionMethod = String.class.getMethod("trim");
        Object invoke = reflectionMethod.invoke(toBeTrimmed);
        System.out.println(invoke);
    }

    @Test
    void methodHandle() throws Throwable {
        String toBeTrimmed = " text with spaces ";
        Lookup lookup = MethodHandles.lookup();
        MethodType mt = MethodType.methodType(String.class);
        MethodHandle mh = lookup.findVirtual(String.class, "trim", mt);
        Object invoke = mh.invoke(toBeTrimmed);
        System.out.println(invoke);
    }

    @Test
    void lambdametaFactory1() throws Throwable {
        String toBeTrimmed = " text with spaces ";
        Lookup lookup = MethodHandles.lookup();
        MethodType mt = MethodType.methodType(String.class);
        MethodHandle mh = lookup.findVirtual(String.class, "trim", mt);
        CallSite callSite = LambdaMetafactory.metafactory(lookup, "get", MethodType.methodType(Supplier.class, String.class),
                MethodType.methodType(Object.class), mh, MethodType.methodType(String.class));
        Supplier<String> lambda = (Supplier<String>) callSite.getTarget().bindTo(toBeTrimmed).invoke();
        System.out.println(lambda.get());
    }

    @Test
    void lambdametaFactory2() throws Throwable {
        String toBeTrimmed = " text with spaces ";
        Lookup lookup = MethodHandles.lookup();
        MethodType mt = MethodType.methodType(String.class);
        MethodHandle mh = lookup.findVirtual(String.class, "trim", mt);
        CallSite callSite = LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Function.class),
                MethodType.methodType(Object.class, Object.class), mh, MethodType.methodType(String.class, String.class));
        Function<String, String> trimFunc = (Function<String, String>) callSite.getTarget().invokeExact();
        System.out.println(trimFunc.apply(toBeTrimmed));
    }

上面代码分别演示了直接调用、反射、方法句柄、函数映射的使用,从功能上来说,他们得到的结果是一致的。不同的在于,函数映射的性能和直接调用近似,而反射和方法句柄调用的性能则较差。

# 二、Stream API

假如我们要从一个集合中进行元素的筛选,一般来说会用到for循环和新建集合对象获得结果。
在Java8中新增了Stream API,先来体验一下他是如何筛选和统计的:

List<String> words = new ArrayList<>();
...
long count = words.stream().filter(w -> w.length() > 10).count();

Java8之后所有的集合对象都有stream()方法,他会返回一个Stream对象,Stream对象和集合的主要区别是:

  • Stream自己不存储元素
  • Stream操作符不会改变源对象,它会返回一个持有结果的新Stream
  • Stream可能是延迟执行的。等到需要结果的时候才执行

如果要获得一个并行的Stream,可以使用words.parallelStream()

# 1、创建Stream

创建主要有这么几种方式:

  • Stream.of(arr) of函数接受一个值或一个数组
  • Stream.empty() 一个空的流
  • Stream.generate(() -> "Echo") 按照函数逻辑生成一个值
  • Stream.generate(Math::random) 同上
  • Stream.iterate(BigInteger.ZERO,n -> n.add(BigInteger.ONE)) 无限序列。第一个参数是初始值,第二个参数是对于前一个值进行的操作
  • Stream<String> lines = Files.lines(path) 有的函数直接返回一个Stream

# 2、filter、map和flatMap方法

说一下Stream的几个主要方法。
filter在前面已经体验过了,来看看map方法,他获得了流的一个副本:

Stream<String> lowercaseWords = words.map(String::toLowerCase)
Stream<Character> firstChars = words.map(s -> s.charAt(0))

flatMap将多个Stream合并为一个Stream:我们假定characterStream(String s)方法返回一个Stream<Character>,用flatMap:

Stream<Character> letters = words.flatMap(w -> characterStream(w))

# 3、提取子流和组合流

直接看例子:

words.stream().limit(100)    //limit()返回一个包含n个元素的新流
words.stream().skip(5)    //会丢弃掉前面的n个元素
Stream.concat(words1, words2)    //把两个流连接起来
Stream.iterate(1, n -> n + 1).peek(e -> System.out.println(e)).limit(20).toArray();  //peek函数是一个中间操作,一般用于调试比较多

# 4、有状态的转换

之前介绍的流转换都是无状态的,结果不依赖之前的元素。
看一下有状态的转换,也就是元素之间有依赖关系的转换:

Stream.of(arr).distinct() 

words.sorted(Comparator.comparing(String:length).reversed())

# 5、简单的聚合方法

聚合方法一般都是终止操作,看代码:

words.max(String::compareToIgnoreCase)
words.filter(...).findFirst()
words.filter(...).findAny()
list.stream().anyMatch(s -> s.startsWith(""))
list.stream().noneMatch(s -> s.startsWith(""))

# 6、Optional类型

Optional主要用来解决空值的问题,直接看例子:

Optional<Integer> num = Optional.of(100);    
num.ifPresent(v -> System.out.println(v));    //不为null,才调用里面的方法
num = num.map(v -> v = 19);
System.out.println(num.get());

Optional<String> str = Optional.empty();
System.out.println(str.orElse("123"));    //默认值
str.orElseGet(() -> "");    

Optional.ofNullable("");    //如果入参是null,返回empty()
Optional<Double> num1 = Optional.of(12.3);    
Optional<String> str1 = num1.flatMap((x) -> Optional.ofNullable(x + ""));    //转换类型

感觉Optional中最常用的就是orElse()方法了。

# 7、reduce聚合函数

在大数据处理中,MapReduce是比较经典的思想了,来自于lisp语言的map和reduce函数。
前面讲过map函数,主要是用来分发并获得副本,下面来看看reduce聚合函数:

Stream<Integer> values = Stream.of(1, 2, 3, 4);
Optional<Integer> sum = values.reduce(0, (x, y) -> x + y);
values.reduce(Integer::sum);

reduce的第一个参数是初始值,第二个参数是BinaryOperator接口,接口中的方法要有两个入参,这两个入参很有意思。第一个参数是结果值,第二个参数是当前循环值,什么意思呢?我们看到reduce入参的方法体有一个计算x+y,这是一个返回值,这个返回值在下一次循环会变成方法的第一个入参。
reduce的处理逻辑相当于下面的代码:

T result = null;
for (T element : this stream) {
   result = accumulator.apply(result, element);
}
return Optional.of(result);

//accumulator = reduce入参的BinaryOperator

上述reduce方法的第一个参数可以省略,那么初始值将会是Stream的第一个元素。

Stream<Integer> values = Stream.of(1, 2, 3, 4);
Optional<Integer> sum = values.reduce((x, y) -> x + y);
values.reduce(Integer::sum);

reduce还有更复杂的用法:

List<String> words = new ArrayList<>();
words.add("a");
words.add("bed");
words.add("c");

int num = words.stream().reduce(0, (x, y) -> x + y.length(), Integer::sum);
System.out.println(num);   //=5

可以看到这个一个统计集合中所有元素总字数的一段代码。这个是reduce的重载函数,有三个入参。第一个参数是初始的result;第二个参数是一个BiFunction,用来做累计结果操作;第三个参数是一个BinaryOperator,合并两个累积结果。他的接口方法参数有两个:prevResult, nextResul,分别是前一次操作的结果和下一次操作的结果。

# 8、收集结果

Stream是一个流,如果要想把他转换为我们熟悉的集合,可以这样做:

Stream strem = ..;
stream.collect(Collectors.toList());    //转换为List集合
stream.collect(Collectors.toSet());    //转换为Set集合
stream.collect(Collectors.toCollection(TreeSet::new));    //转换为TreeSet

如果要收集到Map中:

Stream<TUser> values = Lists.newArrayList(TUser.getInstance()).stream();
values.collect(Collectors.toMap(TUser::getId, TUser::getName));
values.collect(Collectors.toMap(TUser::getId, Function.identity()));    //Function.identity()表示元素本身

//如果多个元素拥有相同的键,那么收集方法会抛出一个异常,所以我们需要定义第三个参数用于指定map中相同键的value的合并策略:
Stream<TUser> values = Stream.of(new TUser(1L, "a"), new TUser(2L, "b"), new TUser(1L, "c"));
Map<Long, String> collect = values
		.collect(Collectors.toMap(TUser::getId, TUser::getName, (t1, t2) -> t1 + "-" + t2));
collect.forEach((k, v) -> System.out.println(v));

此外,还可以进行字符串连接操作:

stream.collect(Collectors.joining())    
stream.collect(Collectors.joining(","))    
stream.map(Object::toString).collect(Collectors.joining(","))   

还可以收集一些我们常用的计算方式:

IntSummaryStatistics summaryStatistics = stream.collect(Collectors.summarizingInt(String::length));    //里面包含总和、平均值、最大值和最小值
summaryStatistics.getAverage();
summaryStatistics.getMax();

//还有Collectors.summingInt系列方法,只求总值
list.stream().collect(Collectors.summingInt(String::length));
//其实等同于下面的聚合操作
list.stream().mapToInt(String::length).sum();

求最大值、最小值:

Optional<TUser> max = list.stream().collect(Collectors.maxBy(Comparator.comparing(TUser::getId)));
// 等同于下面的聚合操作
Optional<TUser> max = list.stream().max(Comparator.comparing(TUser::getId));

Optional<TUser> min = list.stream().collect(Collectors.minBy(Comparator.comparing(TUser::getId)));

# 9、分组和分片

先看分组:

Stream<TUser> values = Stream.of(new TUser(1L, "a"), new TUser(2L, "b"), new TUser(1L, "c"));
Map<Long, List<TUser>> collect = values.collect(Collectors.groupingBy(TUser::getId));
Map<Long, Set<TUser>> collect2 = values.collect(Collectors.groupingBy(TUser::getId, Collectors.toSet()));

看一些比较高级的功能:

Map<Long, Long> collect3 = values.collect(Collectors.groupingBy(TUser::getId,Collectors.counting()));
Map<Long, LongSummaryStatistics> collect4 = values.collect(Collectors.groupingBy(TUser::getId,Collectors.summarizingLong(TUser::getId)));
Map<Long, String> collect5 = values.collect(Collectors.groupingBy(TUser::getId,Collectors.mapping(TUser::getName, Collectors.joining(","))));

Collectors还有很多有用的方法,感兴趣可以自己试验一下。
分片的例子:

Map<Boolean, List<TUser>> collect5 = values.collect(Collectors.partitioningBy(t -> t.getName().endsWith("&")));

# 10、原始类型流

看一下int的原始流,一叶知秋:

IntStream stream = IntStream.of(1, 2, 3, 4, 5);
stream = Arrays.stream(new int[] { 1, 2, 3 }, 0, 2);

IntStream.range(0, 10).mapToObj(e -> String.valueOf(e)).collect(Collectors.toList()); // 不包括上限
IntStream.rangeClosed(0, 10).mapToObj(e -> String.valueOf(e)).collect(Collectors.toList()); // 包括上限

IntStream.range(0, 100).boxed(); // 原始类型流转换为一个对象流

# 11、并行流

并行流就是会并行处理的流。例如,当计算stream.map(fun)时,流可以被分为n段,每一段都会被并发处理,然后再按顺序将结果组合起来:

words.parallelStream();
stream.parallel();

要调整并发值:

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "16");
// 默认为Runtime.getRuntime().availableProcessors()

# 三、时间API

在Java8之前,Java中对于日期的操作是不够那么方面的。比如对日期进行加减操作、对日期的格式化、对日期的大小进行判断等。所以会出现Joda Time工具包来帮我们解决问题。
Java8之后,就不需要再引入第三方工具包来处理日期了。
刚开始的时候,看着新的API还是非常不适应的,习惯了之后就会爱上他了。

# 1、对日期进行加减

Java8中提供了LocalDate、LocalTime和LocalDateTime分别表示日期、时间和日期时间。LocalDate的格式如2018-05-22,LocalTime的格式如15:49:50.494,那么LocalDateTime就是他们的结合体了。
直接看LocalDateTime的例子吧:

LocalDateTime dateTime = LocalDateTime.now();
// 三天之后
dateTime.plusDays(3);
// 三月之前
dateTime.minusMonths(3);
// 星期几
dateTime.getDayOfWeek();
// 月份中的第几天
dateTime.getDayOfMonth();
// 获得秒值
dateTime.getSecond();

// 获得今天的开始时间
dateTime.with(LocalTime.MIN);
// 获得今天的结束时间
dateTime.with(LocalTime.MAX);

plus系列方法用于相加,minus系列方法用于相减,get系列方法用于获得值,with系列方法用于获得处理后的副本。

# 2、日期的格式化

原来会用到SimpleDateFormat类,但他是线程不安全的。
看新的线程安全的API:

LocalDate.parse("2005-12-03", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

# 3、对两个日期的判断和运算

判断两个日期:

dateTime.isAfter(other);
dateTime.isBefore(other);
dateTime.equels(other);

计算两个日期之间相差的情况:

LocalDate date = LocalDate.now();
Period period = Period.between(date, date.plusWeeks(2));
System.out.println(period.getYears() + "  " + period.getDays()); //0  14

除了Period之外,还有一个Duration也可以用于计算两个日期的差值。他们的区别是:当处理带时区的时间时,请使用Period,因为会涉及到夏令时问题

# 4、带时区的日期

语言对时区的支持,主要体现在UTC时间的转换上。这里需要注意一下,时区的名词有UTC和GMT,简单理解他们其实表示一个意思。
比如现在我想知道纽约现在是几点,一种方式是:

ZoneId america = ZoneId.of("America/New_York");
LocalDateTime localtDateAndTime = LocalDateTime.now();
ZonedDateTime dateAndTimeInNewYork = ZonedDateTime.of(localtDateAndTime, america);
System.out.println("Current date and time in a particular timezone : " + dateAndTimeInNewYork);

// Output :
// Current date and time in a particular timezone : 2018-05-22T16:39:47.474-05:00[America/New_York]

ZonedDateTime用于处理带时区的日期。
这里的ZoneId一定要写对,否则会抛异常。


还有另一种方式,用时区偏移量也可以实现时区时间转换:

LocalDateTime datetime = LocalDateTime.now();
ZoneOffset offset = ZoneOffset.of("-05:00");
OffsetDateTime date = OffsetDateTime.of(datetime, offset);
System.out.println("Date and Time with timezone offset in Java : " + date);

// Output :
// Date and Time with timezone offset in Java : 2018-05-22T16:39:47.474-05:00

这种方式对机器友好,第一种方式对人类友好。

# 5、与旧API的相互操作

Java8中用Instant表示时间轴上的一个点,和原先的Date很像。
下面是Date、Instant和LocalDateTime之间的相互转换:

Date date = new Date();
Instant instant = date.toInstant();
LocalDateTime.ofInstant(instant,ZoneId.systemDefault());
instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
instant.atZone(ZoneId.systemDefault()).toLocalDate();

ZoneId zoneId = ZoneId.systemDefault();
LocalDate localDate = LocalDate.now();
// LocalDate必须先转为LocalDateTime(通过atStartOfDay方法),才可以toInstant
ZonedDateTime zdt = localDate.atStartOfDay().atZone(zoneId);
// zdt = localDate.atStartOfDay(zoneId);  简写
Date date = Date.from(zdt.toInstant());

LocalDate或LocalDateTime转换为时间戳,都要先转换为Instant:

Instant instant = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();
instant = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant();
Long milli = instant.toEpochMilli();

LocalDateTime time2 =LocalDateTime.ofEpochSecond(timestamp/1000,0,ZoneOffset.ofHours(8));

时间戳转换为LocalDateTime,同理,需要从Instant中来:

LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.systemDefault());
// 有了localDateTime之后可以获得LocalDate
localDateTime.toLocalDate();

# 6、时间调整之TemporalAdjusters

比如我要获取这个月的最后一天,可以这么做:

LocalDate date = LocalDate.of(2019,1, 1);
LocalDate lastDay = date.with(TemporalAdjusters.lastDayOfMonth());
System.out.println(lastDay);

这里出现了TemporalAdjusters类,它包含的方法如下:
image
TemporalAdjusters其实是TemporalAdjuster的工具类,而TemporalAdjuster则是一个函数接口,可以执行复杂的时间操作。
比如要获取某天之后的工作日:

public static void main(String[] args) {
    LocalDate localDate = LocalDate.of(2019, 7, 8);
    TemporalAdjuster temporalAdjuster = NEXT_WORKING_DAY;
    LocalDate result = localDate.with(temporalAdjuster);
}

static TemporalAdjuster NEXT_WORKING_DAY = TemporalAdjusters.ofDateAdjuster(date -> {
    DayOfWeek dayOfWeek = date.getDayOfWeek();
    int daysToAdd;
    if (dayOfWeek == DayOfWeek.FRIDAY)
        daysToAdd = 3;
    else if (dayOfWeek == DayOfWeek.SATURDAY)
        daysToAdd = 2;
    else
        daysToAdd = 1;
    return date.plusDays(daysToAdd);
});

# 总结

Java 8是一个重要的版本,Lambda表达式、Stream给Java带来了现代语言的一些特性,编程的效率得到很大提升。
掌握Java 8中的核心功能变为了我们的必修课,希望此文对你有所帮助!

祝你变得更强!

编辑 (opens new window)
#Java8新特性
上次更新: 2024/12/26
Java集合

Java集合→

最近更新
01
Spring Boot版本新特性
09-15
02
Spring框架版本新特性
09-01
03
Spring Boot开发初体验
08-15
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式