Java8详解

Java8详解

1 Java8

Java8简介

Java8Java发布以来改动最大的一个版本
添加了函数式编程、Stream、全新的日期处理类 函数式编程新加了一些概念:Lambda表达式、函数式接口、函数引用、默认方法、Optional类等 Stream中提供了一些流式处理集合的方法,并提供了一些归约、划分等类的方法 日期中添加了ZoneDateTimeDataFormat等线程安全的方法类

1.1 Lambda

Lambda简介

  • Lambda 可定义为一种简洁、可传递的匿名函数,它是推动Java 8发布的最重要新特性
  • Lambda 本质上是一个函数,虽然它不属于某个特定的类,但具备参数列表、函数主体、返回类型,甚至 能够抛出异常
  • Lambda 是匿名的,它没有具体的函数名称
  • Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)
  • Lambda 可以使代码变的更加简洁

基本语法

  • 参数列表 -> 表达式
  • 参数列表 -> {表达式集合}
  • 需要注意的是 lambda 表达式默认隐含了 return 关键字,在单个表达式中,我们无需在写 return 关键字,也无需写花括号。
  • 只有当表达式是一个集合的时候,才需要写花括号及 return关键字

代码示例

// 返回给定字符串的长度(隐含return语句) 
(String str) -> str.length()

// 始终返回233的无参方法(隐含return语句) 
() -> 233

// 返回当前用户是否年龄大于20岁,返回一个boolean值(隐含return语句) 
(User user) -> user.getAge() > 20

// 包含多行表达式,需用花括号括起来,并使用return关键字返回
(int x, int y) -> {
      
    int z = x * y; 
    return x + z; 
}

使用Lambda与传统写法对比

//使用Lambda
Runnable r1 = () -> System.out.println("Hello World 1");
//传统匿名类
Runnable r2 = new Runnable(){
     
   public void run(){
     
			System.out.println("Hello World 2"); 
   }
};
//执行Runnable方法
//使用Lambda
Runnable r1 = () -> System.out.println("Hello World 1");

//传统匿名类
Runnable r2 = new Runnable(){
      
    public void run(){
      
        System.out.println("Hello World 2"); 
    } 
};

//执行Runnable方法
public static void process(Runnable r){
      
    r.run(); 
} 

//打印 "Hello World 1"
process(r1); 
//打印 "Hello World 2"
process(r2); 
//利用直接传递的 Lambda 打印 "Hello World 3"
process(() -> System.out.println("Hello World 3"));	r.run(); 
}
//打印 "Hello World 1"
process(r1);
//打印 "Hello World 2"
process(r2);
//利用直接传递的 Lambda 打印 "Hello World 3" 
process(() -> System.out.println("Hello World 3"));

了解了 lambda 的基本用法,下面我们就可以开始着手Stream API的学习了!

1.2 Lambda 受检异常处理

简介

  • Lambda 表达式利用函数式编程提供精简的方式表达行为。
  • 然而,JDK函数式接口没有很好地处理异常,使得处理异常代码非常臃肿和麻烦。
  • 下面我们来探讨下 Lambda 表达式中处理异常的解决方案

代码示例

首先我们看一段简单的代码,将50与List中每个元素相除并打印出结果

List integers = Arrays.asList(1, 2, 3, 4, 5, 6); 

integers.forEach(i -> System.out.println(50 / i));

这样看是不会有问题的,代码简洁。
但是如果List中包含元素0,那么就会抛出异常:ArithmeticException: / by zero
有经验的小伙伴可能会立马给出解决方案,使用传统的try-catch来处理异常,代码如下:

List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 0);
integers.forEach(i -> {
     
    try {
     
        System.out.println(50 / i);
    } catch (ArithmeticException e) {
     
        System.err.println( "Arithmetic Exception occured : " + e.getMessage());
    }
});

使用try-catch解决了问题,但是失去了lambda表达式的精简,代码变得臃肿,想必并不是完美的解决方案。
对于一些强迫症老哥来说,这种代码是绝对不能存活的,所以我们需要如下的解决方案。

解决方案

我们将会对抛出异常的函数进行包装,使其不抛出受检异常
如果一个 FunctionInterface 的方法会抛出受检异常(比如 Exception ),那么该

FunctionInterface 便可以作为会抛出受检异常的 Lambda 的目标类型。 我们定义如下一个 FunctionInterface :

@FunctionalInterface
interface UncheckedFunction<T, R> {
     
    R apply(T t) throws Exception;
}

那么该 FunctionInterface 便可以作为抛出受检异常的 Lambda 的目标类型,此时 Lambda 中并不需要 捕获异常,因为目标类型的 apply 方法已经将异常抛出了。
我们如何使用 UncheckedFunction 到流式操作的 Lambda 中呢?
首先我们定义一个 Try 类,它的 of 方法提供将 UncheckedFunction 包装为 Function 的功能:

public class Try {
     

   public static <T, R> Function<T, R> of(UncheckedFunction<T, R> mapper) {
     
      Objects.requireNonNull(mapper);
      return t -> {
     
         try {
     
            return mapper.apply(t);
         } catch (Exception e) {
     
            throw Exceptions.unchecked(e);
         }
      };
   }

   @FunctionalInterface
   public interface UncheckedFunction<T, R> {
     
      R apply(T t) throws Exception;
   }

}

然后在原先的代码中,我们使用 Try.of方法来对会抛出受检异常的 Lambda 进行包装:

List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 0);
integers.forEach(Try.of(i -> System.out.println(50 / i)));

此时,我们便可以选择是否去捕获异常( RuntimeException )。这种解决方法下,我们一般不关心抛出异 常的情况 。 比如自己写的小例子,抛出了异常程序就该终止;或者你知道这个 Lambda 确实 100% 不会抛出异 常。

1.3 Stream 简介

Stream 关于流

什么是流?

流是Java8引入的全新概念,它用来处理集合中的数据,暂且可以把它理解为一种高级集合。 众所周知,集合操作非常麻烦,若要对集合进行筛选、投影,需要写大量的代码,而流是以声明的形式操作集 合,它就像SQL语句,我们只需告诉流需要对集合进行什么操作,它就会自动进行操作,并将执行结果交给你, 无需我们自己手写代码。

因此,流的集合操作对我们来说是透明的,我们只需向流下达命令,它就会自动把我们想要的结果给我们。由于 操作过程完全由Java处理,因此它可以根据当前硬件环境选择最优的方法处理,我们也无需编写复杂又容易出错 的多线程代码了。

**流的特点 **

  1. 只能遍历一次 我们可以把流想象成一条流水线,流水线的源头是我们的数据源(一个集合),数据源中的元素依次被输送到流 水线上,我们可以在流水线上对元素进行各种操作。 一旦元素走到了流水线的另一头,那么这些元素就被“消费掉了”,我们无法再对这个流进行操作。当然, 我们可以从数据源那里再获得一个新的流重新遍历一遍。
  2. 采用内部迭代方式
    若要对集合进行处理,则需我们手写处理代码,这就叫做外部迭代。 而要对流进行处理,我们只需告诉流我们需要什么结果,处理过程由流自行完成,这就称为内部迭代。

**流的操作种类 **

流的操作分为两种,分别为中间操作和终端操作。

  1. 中间操作 当数据源中的数据上了流水线后,这个过程对数据进行的所有操作都称为“中间操作”。 中间操作仍然会返回一个流对象,因此多个中间操作可以串连起来形成一个流水线。
  2. 终端操作 当所有的中间操作完成后,若要将数据从流水线上拿下来,则需要执行终端操作。 终端操作将返回一个执行结果,这就是你想要的数据。

**流的操作过程 **

使用流一共需要三步:

  1. 准备一个数据源

  2. 执行中间操作

    中间操作可以有多个,它们可以串连起来形成流水线。

  3. 执行终端操作

    执行终端操作后本次流结束,你将获得一个执行结果。

1.4 Stream API 一览

List 转 Stream

// 转stream 
list.stream()
// 并发处理 
list.parallelStream()

filter(过滤)

Stream<T> filter(Predicate<? super T> predicate);

map(元素转换)

<R> Stream<R> map(Function<? super T, ? extends R> mapper);
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

flatMap(元素转换)

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);

distinct(去除重复,对象需要重写 equals、hashCode)

Stream<T> distinct();

sorted(排序)

Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);	

peek(生成新的流:流是单向的,例如用于日志打印)

Stream<T> peek(Consumer<? super T> action);

limit(取前面 n 个元素)

Stream<T> limit(long maxSize);

skip(跳过 n 个元素)

Stream<T> skip(long n);

forEach(遍历)

void forEach(Consumer<? super T> action);
void forEachOrdered(Consumer<? super T> action);

toArray(转换成数组)

Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);

reduce(结果归并)

T reduce(T identity, BinaryOperator<T> accumulator);
  Optional<T> reduce(BinaryOperator<T> accumulator);
  <U> U reduce(U identity,
              BiFunction<U, ? super T, U> accumulator,
              BinaryOperator<U> combiner);

collect(转换成集合)

<R> R collect(Supplier<R> supplier,
               BiConsumer<R, ? super T> accumulator,
               BiConsumer<R, R> combiner);
  <R, A> R collect(Collector<? super T, A, R> collector);

转list

// 转list 
Collectors.toList();
// 转set 
Collectors.toSet();
// 转map
List<TestVo> testList = new ArrayList<>(10);
Map<Long, TestVo> data = releaseList.stream() .collect(Collectors.toMap(TestVo::getId, x -> x));

count(计数)

long count();

查找

boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);

查找

Optional<T> findFirst();
Optional<T> findAny();

1.5 Stream API (上)

创建流

在使用流之前,首先需要拥有一个数据源,并通过StreamAPI提供的一些方法获取该数据源的流对象。数据源可 以有多种形式:

  1. 集合
    这种数据源较为常用,通过stream()方法即可获取流对象:
List<Person> list = new ArrayList<Person>();
Stream<Person> stream = list.stream();
  1. 数组

    通过Arrays类提供的静态函数stream()获取数组的流对象:

String[] names = {
     "chaimm","peter","john"};
Stream<String> stream = Arrays.stream(names);
  1. 直接将几个值变成流对象:

Stream<String> stream = Stream.of("chaimm","peter","john");
  1. 文件
try(Stream lines = Files.lines(Paths.get(“文件路径名”),Charset.defaultCharset())) {
     
	//可对lines做一些操作 
}catch(IOException e){
     
}
  1. iterator

    创建无限流

 Stream.iterate(0, n -> n + 2) 
    .limit(10)
		.forEach(System.out::println);

PS:Java7简化了IO操作,把打开IO操作放在try后的括号中即可省略关闭IO的代码。

筛选 filter

filter 函数接收一个Lambda表达式作为参数,该表达式返回boolean,在执行过程中,流将元素逐一输送给 filter,并筛选出执行结果为true的元素。
如,筛选出所有学生:

  List<Person> result = list.stream()
    							.filter(Person::isStudent)
    							.collect(toList());

去重distinct

去掉重复的结果:

List<Person> result = list.stream()
  							.limit(3)
  							.collect(toList());

跳过

跳过流的前n个元素:

List<Person> result = list.stream()
                    .skip(3)
                    .collect(toList());

映射

对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。流会将每一个元素输送给map函数,并执行map中的Lambda表达式,最后将执行结果存入一个新的流中。
如,获取每个人的姓名(实则是将Perosn类型转换成String类型):

List<Person> result = list.stream()
                    .map(Person::getName)
                    .collect(toList());

合并多个流

例:列出List中各不相同的单词,List集合如下:

List<String> list = new ArrayList<String>();
list.add("I am a boy");
list.add("I love the girl");
list.add("But the girl loves another girl");

思路如下:

首先将list变成流:

list.stream();

按空格分词:

list.stream()
            .map(line->line.split(" "));

分完词之后,每个元素变成了一个String[]数组。

将每个 String[] 变成流:

list.stream()
            .map(line->line.split(" "))
            .map(Arrays::stream)

此时一个大流里面包含了一个个小流,我们需要将这些小流合并成一个流。

将小流合并成一个大流:用 flatMap 替换刚才的 map

list.stream()
    .map(line->line.split(" "))
    .flatMap(Arrays::stream)

去重

list.stream()
    .map(line->line.split(" "))
    .flatMap(Arrays::stream)
    .distinct()
    .collect(toList());

是否匹配任一元素:anyMatch

anyMatch用于判断流中是否存在至少一个元素满足指定的条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
如,判断list中是否有学生:

boolean result = list.stream()
            .anyMatch(Person::isStudent);

是否匹配所有元素:allMatch

allMatch用于判断流中的所有元素是否都满足指定条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
如,判断是否所有人都是学生:

boolean result = list.stream()
            .allMatch(Person::isStudent);

是否未匹配所有元素:noneMatch

noneMatch与allMatch恰恰相反,它用于判断流中的所有元素是否都不满足指定条件:

boolean result = list.stream()
            .noneMatch(Person::isStudent);

获取任一元素findAny

findAny能够从流中随便选一个元素出来,它返回一个Optional类型的元素。

Optional person = list.stream().findAny();

获取第一个元素findFirst

Optional person = list.stream().findFirst();

归约

归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出。

在流中,reduce函数能实现归约。
reduce函数接收两个参数:

  1. 初始值
  2. 进行归约操作的Lambda表达式

元素求和:自定义Lambda表达式实现求和

例:计算所有人的年龄总和

@Test
public void contextLoads() {
     
   List<Person> list = new ArrayList<>();
   list.add(new Person().setAge(20));
   list.add(new Person().setAge(25));
   int age = list.stream().map(Person::getAge).reduce(0, Integer::sum);
   System.out.println(age);
}
@Data
@Accessors(chain = true)
class Person {
     
   private int age;
}
  1. reduce的第一个参数表示初试值为0;
  2. reduce的第二个参数为需要进行的归约操作,它接收一个拥有两个参数的Lambda表达式,reduce会把流中的元素两两输给Lambda表达式,最后将计算出累加之和。

元素求和:使用Integer.sum函数求和

上面的方法中我们自己定义了Lambda表达式实现求和运算,如果当前流的元素为数值类型,那么可以使用Integer提供了sum函数代替自定义的Lambda表达式,如:

int age = list.stream().reduce(0, Integer::sum);

Integer类还提供了 minmax 等一系列数值操作,当流中元素为数值类型时可以直接使用。

数值流的使用

采用reduce进行数值操作会涉及到基本数值类型和引用数值类型之间的装箱、拆箱操作,因此效率较低。
当流操作为纯数值操作时,使用数值流能获得较高的效率。

将普通流转换成数值流

StreamAPI提供了三种数值流:IntStream、DoubleStream、LongStream,也提供了将普通流转换成数值流的三种方法:mapToInt、mapToDouble、mapToLong。
如,将Person中的age转换成数值流:

IntStream stream = list.stream().mapToInt(Person::getAge);

数值计算

每种数值流都提供了数值计算函数,如max、min、sum等。如,找出最大的年龄:

OptionalInt maxAge = list.stream()
                                .mapToInt(Person::getAge)
                                .max();

由于数值流可能为空,并且给空的数值流计算最大值是没有意义的,因此max函数返回OptionalInt,它是Optional的一个子类,能够判断流是否为空,并对流为空的情况作相应的处理。
此外,mapToInt、mapToDouble、mapToLong进行数值操作后的返回结果分别为:OptionalInt、OptionalDouble、OptionalLong

中间操作和收集操作

操作 类型 返回类型 使用的类型/函数式接口 函数描述符
filter 中间 Stream Predicate T -> boolean
distinct 中间 Stream
skip 中间 Stream long
map 中间 Stream Function T -> R
flatMap 中间 Stream Function> T -> Stream
limit 中间 Stream long
sorted 中间 Stream Comparator (T, T) -> int
anyMatch 终端 boolean Predicate T -> boolean
noneMatch 终端 boolean Predicate T -> boolean
allMatch 终端 boolean Predicate T -> boolean
findAny 终端 Optional
findFirst 终端 Optional
forEach 终端 void Consumer T -> void
collect 终端 R Collector
reduce 终端 Optional BinaryOperator (T, T) -> T
count 终端 long

1.6 Stream API (下)

Collector 收集

收集器用来将经过筛选、映射的流进行最后的整理,可以使得最后的结果以不同的形式展现。
collect 方法即为收集器,它接收 Collector 接口的实现作为具体收集器的收集方法。
Collector 接口提供了很多默认实现的方法,我们可以直接使用它们格式化流的结果;也可以自定义 Collector 接口的实现,从而定制自己的收集器。

归约

流由一个个元素组成,归约就是将一个个元素“折叠”成一个值,如求和、求最值、求平均值都是归约操作。

一般性归约

若你需要自定义一个归约操作,那么需要使用 Collectors.reducing 函数,该函数接收三个参数:

  • 第一个参数为归约的初始值
  • 第二个参数为归约操作进行的字段
  • 第三个参数为归约操作的过程

汇总

Collectors类专门为汇总提供了一个工厂方法:Collectors.summingInt
它可接受一 个把对象映射为求和所需int的函数,并返回一个收集器;该收集器在传递给普通的 collect 方法后即执行我们需要的汇总操作。

分组

数据分组是一种更自然的分割数据操作,分组就是将流中的元素按照指定类别进行划分,类似于SQL语句中的 GROUPBY

多级分组

多级分组可以支持在完成一次分组后,分别对每个小组再进行分组。
使用具有两个参数的 groupingBy 重载方法即可实现多级分组。

  • 第一个参数:一级分组的条件
  • 第二个参数:一个新的 groupingBy 函数,该函数包含二级分组的条件

Collectors 类的静态工厂方法

工厂方法 返回类型 用途 示例
toList List 把流中所有项目收集到一个 List List projects = projectStream.collect(toList());
toSet Set 把流中所有项目收集到一个 Set,删除重复项 Set projects = projectStream.collect(toSet());
toCollection Collection 把流中所有项目收集到给定的供应源创建的集合 Collection projects = projectStream.collect(toCollection(), ArrayList::new);
counting Long 计算流中元素的个数 long howManyProjects = projectStream.collect(counting());
summingInt Integer 对流中项目的一个整数属性求和 int totalStars = projectStream.collect(summingInt(Project::getStars));
averagingInt Double 计算流中项目 Integer 属性的平均值 double avgStars = projectStream.collect(averagingInt(Project::getStars));
summarizingInt IntSummaryStatistics 收集关于流中项目 Integer 属性的统计值,例如最大、最小、 总和与平均值 IntSummaryStatistics projectStatistics = projectStream.collect(summarizingInt(Project::getStars));
joining String 连接对流中每个项目调用 toString 方法所生成的字符串 String shortProject = projectStream.map(Project::getName).collect(joining(", "));
maxBy Optional 按照给定比较器选出的最大元素的 Optional, 或如果流为空则为 Optional.empty() Optional fattest = projectStream.collect(maxBy(comparingInt(Project::getStars)));
minBy Optional 按照给定比较器选出的最小元素的 Optional, 或如果流为空则为 Optional.empty() Optional fattest = projectStream.collect(minBy(comparingInt(Project::getStars)));
reducing 归约操作产生的类型 从一个作为累加器的初始值开始,利用 BinaryOperator 与流中的元素逐个结合,从而将流归约为单个值 int totalStars = projectStream.collect(reducing(0, Project::getStars, Integer::sum));
collectingAndThen 转换函数返回的类型 包含另一个收集器,对其结果应用转换函数 int howManyProjects = projectStream.collect(collectingAndThen(toList(), List::size));
groupingBy Map> 根据项目的一个属性的值对流中的项目作问组,并将属性值作 为结果 Map 的键 Map> projectByLanguage = projectStream.collect(groupingBy(Project::getLanguage));
partitioningBy Map> 根据对流中每个项目应用断言的结果来对项目进行分区 Map> vegetarianDishes = projectStream.collect(partitioningBy(Project::isVegetarian));

转换类型

有一些收集器可以生成其他集合。比如前面已经见过的 toList,生成了 java.util.List 类的实例。
还有 toSettoCollection,分别生成 SetCollection 类的实例。
到目前为止, 我已经讲了很多流上的链式操作,但总有一些时候,需要最终生成一个集合——比如:

  • 已有代码是为集合编写的,因此需要将流转换成集合传入;
  • 在集合上进行一系列链式操作后,最终希望生成一个值;
  • 写单元测试时,需要对某个具体的集合做断言。

使用 toCollection,用定制的集合收集元素

stream.collect(toCollection(TreeSet::new));

还可以利用收集器让流生成一个值。 maxByminBy 允许用户按某种特定的顺序生成一个值。

数据分区

分区是分组的特殊情况:由一个断言(返回一个布尔值的函数)作为分类函数,它称分区函数。
分区函数返回一个布尔值,这意味着得到的分组 Map 的键类型是 Boolean,于是它最多可以分为两组: true是一组,false是一组。

分区的好处在于保留了分区函数返回true或false的两套流元素列表。

并行流

并行流就是一个把内容分成多个数据块,并用不不同的线程分别处理每个数据块的流。最后合并每个数据块的计算结果。

将一个顺序执行的流转变成一个并发的流只要调用 parallel() 方法

public static long parallelSum(long n){
     
    return Stream.iterate(1L, i -> i +1).limit(n).parallel().reduce(0L,Long::sum);
}

将一个并发流转成顺序的流只要调用 sequential() 方法

stream.parallel().filter(...).sequential().map(...).parallel().reduce();

这两个方法可以多次调用,只有最后一个调用决定这个流是顺序的还是并发的。

并发流使用的默认线程数等于你机器的处理器核心数。

通过这个方法可以修改这个值,这是全局属性。

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "12");

并非使用多线程并行流处理数据的性能一定高于单线程顺序流的性能,因为性能受到多种因素的影响。
如何高效使用并发流的一些建议:

  1. 如果不确定, 就自己测试。
  2. 尽量使用基本类型的流 IntStream, LongStream, DoubleStream
  3. 有些操作使用并发流的性能会比顺序流的性能更差,比如limit,findFirst,依赖元素顺序的操作在并发流中是极其消耗性能的。findAny的性能就会好很多,应为不依赖顺序。
  4. 考虑流中计算的性能(Q)和操作的性能(N)的对比, Q表示单个处理所需的时间,N表示需要处理的数量,如果Q的值越大, 使用并发流的性能就会越高。
  5. 数据量不大时使用并发流,性能得不到提升。
  6. 考虑数据结构:并发流需要对数据进行分解,不同的数据结构被分解的性能时不一样的。

流的数据源和可分解性

可分解性
ArrayList 非常好
LinkedList
IntStream.range 非常好
Stream.iterate
HashSet
TreeSet

流的特性以及中间操作对流的修改都会对数据对分解性能造成影响。 比如固定大小的流在任务分解的时候就可以平均分配,但是如果有filter操作,那么流就不能预先知道在这个操作后还会剩余多少元素。

考虑终端操作的性能:如果终端操作在合并并发流的计算结果时的性能消耗太大,那么使用并发流提升的性能就会得不偿失。

1.7 Optional 干掉空指针

简介

  • 空指针异常是导致Java应用程序失败的最常见原因。
  • 为了解决空指针异常更加优雅,Java8 提供了 Optional 类库。
  • Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。
  • Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

代码示例

  1. Optional.of()或者Optional.ofNullable():创建Optional对象,差别在于of不允许参数是null,而ofNullable则无限制。
// 参数不能是null
Optional optional1 = Optional.of(1);
// 参数可以是null
Optional optional2 = Optional.ofNullable(null);
// 参数可以是非null
Optional optional3 = Optional.ofNullable(2);
  1. Optional.empty():所有null包装成的Optional对象
Optional optional1 = Optional.ofNullable(null);
Optional optional2 = Optional.ofNullable(null);

System.out.println(optional1 == optional2);// true
System.out.println(optional1 == Optional.empty());// true

Object o1 = Optional.empty();
Object o2 = Optional.empty();

System.out.println(o1 == o2);// true
  1. isPresent():判断值是否存在
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
// isPresent判断值是否存在
System.out.println(optional1.isPresent() == true);
System.out.println(optional2.isPresent() == false);
  1. ifPresent(Consumer consumer):如果option对象保存的值不是null,则调用consumer对象,否则不调用
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);

// 如果不是null,调用Consumer
optional1.ifPresent(new Consumer<Integer>() {
     
   @Override
   public void accept(Integer t) {
     
      System.out.println("value is " + t);
   }
});

// null,不调用Consumer
optional2.ifPresent(new Consumer<Integer>() {
     
   @Override
   public void accept(Integer t) {
     
      System.out.println("value is " + t);
   }
});
  1. orElse(value):如果optional对象保存的值不是null,则返回原来的值,否则返回value
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
// orElse
System.out.println(optional1.orElse(1000) == 1);// true
System.out.println(optional2.orElse(1000) == 1000);// true
  1. orElseGet(Supplier supplier):功能与orElse一样,只不过orElseGet参数是一个对象
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
System.out.println(optional1.orElseGet(() -> 1000) == 1);//true
System.out.println(optional2.orElseGet(() -> 1000) == 1000);//true
  1. orElseThrow():值不存在则抛出异常,存在则什么不做,有点类似GuavaPrecoditions
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);

optional1.orElseThrow(() -> {
     
   throw new IllegalStateException();
});

try {
     
   optional2.orElseThrow(() -> {
     
      throw new IllegalStateException();
   });
} catch (IllegalStateException e) {
     
   e.printStackTrace();
}
  1. filter(Predicate):判断Optional对象中保存的值是否满足Predicate,并返回新的Optional
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);

Optional<Integer> filter1 = optional1.filter((a) -> a == null);
Optional<Integer> filter2 = optional1.filter((a) -> a == 1);
Optional<Integer> filter3 = optional2.filter((a) -> a == null);

System.out.println(filter1.isPresent());// false
System.out.println(filter2.isPresent());// true
System.out.println(filter2.get().intValue() == 1);// true
System.out.println(filter3.isPresent());// false
  1. map(Function):对Optional中保存的值进行函数运算,并返回新的Optional(可以是任何类型)
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);

Optional<String> str1Optional = optional1.map((a) -> "key" + a);
Optional<String> str2Optional = optional2.map((a) -> "key" + a);

System.out.println(str1Optional.get());// key1
System.out.println(str2Optional.isPresent());// false

10.flatMap():功能与map()相似,差别请看如下代码。flatMap方法与map方法类似,区别在于mapping函数的返回值不同。map方法的mapping函数返回值可以是任何类型T,而flatMap方法的mapping函数必须是Optional

Optional<Integer> optional1 = Optional.ofNullable(1);

Optional<Optional<String>> str1Optional = optional1.map((a) -> Optional.of("key" + a));
Optional<String> str2Optional = optional1.flatMap((a) -> Optional.of("key" + a));

System.out.println(str1Optional.get().get());// key1
System.out.println(str2Optional.get());// key1

Optional类的方法

方法 描述
empty 返回一个空的 Optional 实例
filter 如果值存在并且满足提供的断言, 就返回包含该值的 Optional 对象;否则返回一个空的 Optional 对象
map 如果值存在,就对该值执行提供的 mapping 函数调用
flatMap 如果值存在,就对该值执行提供的 mapping 函数调用,返回一个 Optional 类型的值,否则就返 回一个空的 Optional 对象
get 如果该值存在,将该值用 Optional 封装返回,否则抛出一个 NoSuchElementException 异常
ifPresent 如果值存在,就执行使用该值的方法调用,否则什么也不做
isPresent 如果值存在就返回 true,否则返回 false
of 将指定值用 Optional 封装之后返回,如果该值为 null,则抛出一个 NullPointerException 异常
ofNullable 将指定值用 Optional 封装之后返回,如果该值为 null,则返回一个空的 Optional 对象
orElse 如果有值则将其返回,否则返回一个默认值
orElseGet 如果有值则将其返回,否则返回一个由指定的 Supplier 接口生成的值
orElseThrow 如果有值则将其返回,否则抛出一个由指定的 Supplier 接口生成的异常

1.8函数式接口

Java8 函数式接口一览

// Function -T作为输入,返回的R作为输出   
Function<String,String> fun = (x) -> {
     System.out.print(x+": ");return "Function";};  
System.out.println(function.apply("hello world"));

//Predicate -T作为输入,返回的boolean值作为输出
Predicate<String> pre = (x) ->{
     System.out.print(x);return false;};  
System.out.println(": "+pre.test("hello World"));

//Consumer - T作为输入,执行某种动作但没有返回值   
Consumer<String> con = (x) -> {
     System.out.println(x);};  
con.accept("hello world");

//Supplier - 没有任何输入,返回T   
Supplier<String> supp = () -> {
     return "Supplier";};  
System.out.println(supp.get());

//BinaryOperator -两个T作为输入,返回一个T作为输出,对于“reduce”操作很有用   
BinaryOperator<String> bina = (x,y) ->{
     System.out.print(x+" "+y);return "BinaryOperator";};  
System.out.println(" "+bina.apply("hello ","world"));

自定义函数式

@FunctionalInterface

1.9新的日期 API

简介

Java 8中的时区操作被很大程度上简化了,新的时区类 java.time.ZoneId 是原有的 java.util.TimeZone 类的替代品。
ZoneId对象可以通过 ZoneId.of() 方法创建,也可以通过 ZoneId.systemDefault() 获取系统默认时区:

ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai");
ZoneId systemZoneId = ZoneId.systemDefault();

of() 方法接收一个“区域/城市”的字符串作为参数,你可以通过 getAvailableZoneIds() 方法获取所有合法的“区域/城市”字符串:

Set<String> zoneIds = ZoneId.getAvailableZoneIds();

对于老的时区类 TimeZone,Java 8也提供了转化方法:

ZoneId oldToNewZoneId = TimeZone.getDefault().toZoneId();

有了 ZoneId,我们就可以将一个 LocalDateLocalTimeLocalDateTime 对象转化为 ZonedDateTime 对象:

LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, shanghaiZoneId);

ZonedDateTime 对象由两部分构成,LocalDateTimeZoneId,其中 2018-03-03T15:26:56.147 部分为 LocalDateTime+08:00[Asia/Shanghai] 部分为ZoneId。

另一种表示时区的方式是使用 ZoneOffset,它是以当前时间和 世界标准时间(UTC)/格林威治时间(GMT) 的偏差来计算,例如:

ZoneOffset zoneOffset = ZoneOffset.of("+09:00");
LocalDateTime localDateTime = LocalDateTime.now();
OffsetDateTime offsetDateTime = OffsetDateTime.of(localDateTime, zoneOffset);

Instant

Instant类在Java日期与时间功能中,表示了时间线上一个确切的点,定义为距离初始时间的时间差(初始时间为GMT 1970年1月1日00:00)经测量一天有86400秒,从初始时间开始不断向前移动。

创建一个Instant实例

你可以通过Instant类的工厂方法创建一个Instant实例,例如你可以调用instant.now()来创建一个确切的表达当前时间的Instant对象:

Instant now = Instant.now();

另外也有一些其它方法能创建Instant,具体请查阅Java官方文档。

访问Instant的时间

一个Instant对象里有两个域:距离初始时间的秒钟数、在当前一秒内的第几纳秒,他们的组合表达了当前时间点。你可以通过以下两个方法得到它们的值:

long seconds =  getEpochSecond()
int nanos   =   getNano()

Instant的计算

Instant类有一些方法,可以用于获得另一Instant的值,例如:

  • plusSeconds()
  • plusMillis()
  • plusNanos()
  • minusSeconds()
  • minusMillis()
  • minusNanos()

我下面将向你展示两个例子,来说明这些方法如何使用:

Instant now     = Instant.now();
Instant later   = now.plusSeconds(3);
Instant earlier = now.minusSeconds(3);

第一行获得了一个Instant对象,表示当前时间。第二行创建了一个Instant表示三秒后,第三行创建了一个Instant表示三秒前。

seconds 表示从 1970-01-01 00:00:00 开始到现在的秒数,nanos 表示纳秒部分(nanos的值不会超过999,999,999)

Clock

Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。
某一个特定的时间点也可以使用Instant类来表示,Instant 类也可以用来创建老的 java.util.Date 对象。

Clock clock = Clock.systemDefaultZone();  
long millis = clock.millis();    
Instant instant = clock.instant();  
Date legacyDate = Date.from(instant);   // legacy java.util.Date  

LocalDate

LocalDate类是Java 8中日期时间功能里表示一个本地日期的类,它的日期是无时区属性的。
可以用来表示生日、节假日期等等。这个类用于表示一个确切的日期,而不是这个日期所在的时间(如java.util.Date中的2000.01.01表示的实际是这一天的00:00这个瞬间)。

LocalDate类位于java.time包下,人名叫java.time.LocalDate,创建出来的实例也是不可变对象,所以涉及它的计算方法将返回一个新的LocalDate。

创建一个LocalDate实例

我们有多种方式可以创建出 LocalDate 实例。第一种方法是使用 now() 方法获得值为今天当日的 LocalDate 对象:

LocalDate localDate = LocalDate.now();

另一种方法是使用年月日信息构造出LocalDate对象:

LocalDate localDate2 = LocalDate.of(2018, 3, 3);

LocalDate 的 of() 方法创建出一个指定年月日的日期,并且没有时区信息。

访问日期信息

可以用如下方法访问LocalDate中的日期信息:

int   year       = localDate.getYear();
Month month      = localDate.getMonth();
int   dayOfMonth = localDate.getDayOfMonth();
int   dayOfYear  = localDate.getDayOfYear();
DayOfWeek dayOfWeek = localDate.getDayOfWeek();

可以注意到getMonth()与getDayOfWeek()方法返回了一个枚举类型代替一个int。你可以通过枚举类型中的getValue()来获得信息。

LocalDate计算

你可以进行一堆简单的日期计算,只要使用如下的方法:

  • plusDays()
  • plusWeeks()
  • plusMonths()
  • plusYears()
  • minusDays()
  • minusWeeks()
  • minusMonths()
  • minusYears()

以下举几个使用的例子来帮助理解使用:

LocalDate d  = LocalDate.of(2018, 3, 5);
LocalDate d1 = localDate.plusYears(3);
LocalDate d2 = localDate.minusYears(3);
  1. 第一行创建出一个新的LocalDate对象d,表示2018.3.5。
  2. 第二行创建了值等于d日期3年后的LocalDate对象,第三行也是一样,只是值改为d日期的三年前。

LocalTime

LocalTime类是Java 8中日期时间功能里表示一整天中某个时间点的类,它的时间是无时区属性的(早上10点等等)。比如你需要描述学校几点开学,这个时间不涉及在什么城市,这个描述是对任何国家城市都适用的,此时使用无时区的LocalTime就足够了。
LocalTime类的对象也是不可变的,所以计算方法会返回一个新的LocalTime实例。

创建一个LocatTime实例

有多种方式可以新建LocalTime实例。比如使用当前时间作为值新建对象:

LocalTime localTime = LocalTime.now();

另一种方式是使用指定的时分秒和纳秒来新建对象:

LocalTime localTime2 = LocalTime.of(21, 30, 59, 11001);

也有另一种版本的 of() 方法只需要小时分钟两项,或时分秒三项值作为参数。

访问LocalTime对象的时间

你可以通过这些方法访问其时、分、秒、纳秒:

  • getHour()
  • getMinute()
  • getSecond()
  • getNano()

LocalTime的计算

LocalTime类包含一系列方法,能帮你完成时间计算:

  • plusHours()
  • plusMinutes()
  • plusSeconds()
  • plusNanos()
  • minusHours()
  • minusMinutes()
  • minusSeconds()
  • minusNanos()

以下举一个例子:

LocalTime localTime2 = LocalTime.of(21, 30, 59, 11001);
LocalTime localTimeLater   = localTime.plusHours(3);
LocalTime localTimeEarlier = localTime.minusHours(3);
  1. 第一行新建一个LocalTime实例,表示21:30:50的第11001纳秒。
  2. 第二行新建了一个LocalTime实例表示这个时间的三小时后,第三行表示三小时前。
  3. LocalTime类是Java 8中日期时间功能里表示一整天中某个时间点的类,它的时间是无时区属性的(早上10点等等)。比如你需要描述学校几点开学,这个时间不涉及在什么城市,这个描述是对任何国家城市都适用的,此时使用无时区的LocalTime就足够了。

LocalTime类的对象也是不可变的,所以计算方法会返回一个新的LocalTime实例。

LocalDateTime

LocalDateTime类是Java 8中日期时间功能里,用于表示当地的日期与时间的类,它的值是无时区属性的。你可以将其视为Java 8中LocalDate与LocalTime两个类的结合。

LocalDateTime类的值是不可变的,所以其计算方法会返回一个新的LocalDateTime实例。

创建一个LocatDateTime实例

可以通过LocalDateTime的静态工厂方法来创建LocalDateTime实例。以下举例使用 now() 方法创建:

LocalDateTime localDateTime = LocalDateTime.now();

另一种方式是使用指定的年月日、时分秒、纳秒来新建对象:

LocalDateTime localDateTime2 = LocalDateTime.of(2018, 11, 26, 13, 55, 36, 123);

访问LocalDateTime对象的时间

你可以通过这些方法访问其日期时间:

  • getYear()
  • getMonth()
  • getDayOfMonth()
  • getDayOfWeek()
  • getDayOfYear()
  • getHour()
  • getMinute()
  • getSecond()
  • getNano()

这些方法中有一些返回int有一些返回枚举类型,你可以通过枚举类型中的 getValue() 方法来获得int值。

LocalDateTime的计算

LocalDateTime 类包含一系列方法,能帮你完成时间计算:

  • plusYears()
  • plusMonths()
  • plusDays()
  • plusHours()
  • plusMinutes()
  • plusSeconds()
  • plusNanos()
  • minusYears()
  • minusMonths()
  • minusDays()
  • minusHours()
  • minusMinutes()
  • minusSeconds()
  • minusNanos()

以下举一个例子:

LocalDateTime localDateTime  = LocalDateTime.now();
LocalDateTime localDateTime1 = localDateTime.plusYears(3);
LocalDateTime localDateTime2 = localDateTime.minusYears(3);
  1. 第一行新建一个LocalDateTime实例表示当前这个时间。
  2. 第二行新建了一个LocalDateTime实例表示三年后。
  3. 第三行也新建了一个LocalDateTime实例表示三小时前。

ZonedDateTime

ZonedDateTime类是Java 8中日期时间功能里,用于表示带时区的日期与时间信息的类。可以用于表示一个真实事件的开始时间,如某火箭升空时间等等。

ZonedDateTime 类的值是不可变的,所以其计算方法会返回一个新的ZonedDateTime 实例。

创建一个ZonedDateTime实例

有多种方式可以新建ZonedDateTime实例。比如使用当前时间作为值新建对象:

ZonedDateTime dateTime = ZonedDateTime.now();

另一种方式是使用指定的年月日、时分秒、纳秒以及时区ID来新建对象:

ZoneId zoneId = ZoneId.of("UTC+1");
ZonedDateTime dateTime2 = ZonedDateTime.of(2015, 11, 30, 23, 45, 59, 1234, zoneId);

访问ZonedDateTime对象的时间

你可以通过这些方法访问其日期时间:

  • getYear()
  • getMonth()
  • getDayOfMonth()
  • getDayOfWeek()
  • getDayOfYear()
  • getHour()
  • getMinute()
  • getSecond()
  • getNano()

这些方法中有一些返回int有一些返回枚举类型,但可以通过枚举类型中的getValue()方法来获得int值。

ZonedDateTime的计算

ZonedDateTime类包含一系列方法,能帮你完成时间计算:

  • plusYears()
  • plusMonths()
  • plusDays()
  • plusHours()
  • plusMinutes()
  • plusSeconds()
  • plusNanos()
  • minusYears()
  • minusMonths()
  • minusDays()
  • minusHours()
  • minusMinutes()
  • minusSeconds()
  • minusNanos()

但注意计算时,若不巧跨越了夏令时(会补一小时或减一小时),可能得不到希望的结果。一个替代的正确做法是使用Period:

ZonedDateTime zoneDateTime = previousDateTime.plus(Period.ofDays(3));

时区

时区是用ZoneId类表示的,你可以使用ZoneId.now()或ZoneId.of(“xxx”)来实例化:

ZoneId zoneId = ZoneId.of("UTC+1");

传给 of() 方法的参数是时区的ID,如“UTC+1”指距离UTC(格林威治时间)有一小时的时差,你可以使用你想要的时差来表示ZoneId(如+1与-5等等)
你也可以使用另一种方式表示zone id,即使用地区名字,也是可以的:

ZoneId zoneId2 = ZoneId.of("Europe/Copenhagen");
ZoneId zoneId3 = ZoneId.of("Europe/Paris");

DateTimeFormatter

DateTimeFormatter类是Java 8中日期时间功能里,用于解析和格式化日期时间的类,位于 java.time.format 包下。

预定义的DateTimeFormatter实例

DateTimeFormatter类包含一系列预定义(常量)的实例,可以解析和格式化一些标准时间格式。这将让你免除麻烦的时间格式定义,类中包含如下预定义的实例:

BASIC_ISO_DATE

ISO_LOCAL_DATE
ISO_LOCAL_TIME
ISO_LOCAL_DATE_TIME

ISO_OFFSET_DATE
ISO_OFFSET_TIME
ISO_OFFSET_DATE_TIME

ISO_ZONED_DATE_TIME

ISO_INSTANT

ISO_DATE
ISO_TIME
ISO_DATE_TIME

ISO_ORDINAL_TIME
ISO_WEEK_DATE

RFC_1123_DATE_TIME

每个预定义的DateTimeFormatter实例都有不同的日期格式,我就不解释全部的了。具体的可以查阅Java官方文档,但我在这篇的后续中会解释其中几个,以方便理解。

格式化日期

当你获取一个DateTimeFormatter实例后,就可以用format()方便来将一个日期格式化为某种字符串,例如:

DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
String formattedDate = formatter.format(LocalDate.now());	
System.out.println(formattedDate);

这个样例把LocalDate对象格式化了,并输出20150703,这个输出表示现在2018年,3月5日。
再举一个关于ZonedDateTime的例子:

DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
String formattedZonedDate = formatter.format(ZonedDateTime.now());
System.out.println("formattedZonedDate = " + formattedZonedDate);

这个例子会输出:20180305+0800
表示今年2018年,3月5日,位于UTC+8时区。

Duration

一个Duration对象表示两个Instant间的一段时间,是在Java 8中加入的新功能。

一个Duration实例是不可变的,当创建出对象后就不能改变它的值了。你只能通过Duration的计算方法,来创建出一个新的Durtaion对象。你会在之后的教程中见到的。

创建Duration实例

使用 Duration 类的工厂方法来创建一个 Duration 对象,以下是一个使用 between() 的例子:

Instant first = Instant.now();
// wait some time while something happens
Instant second = Instant.now();
Duration duration = Duration.between(first, second);

访问Duration的时间

一个Duration对象里有两个域:纳秒值(小于一秒的部分),秒钟值(一共有几秒),他们的组合表达了时间长度。注意屯使用System.getCurrentTimeMillis()时不同,Duration不包含毫秒这个属性。
你可以通过以下两个方法得到它们的值:

long seconds =  getSeconds()
int nanos   =   getNano()

你也可以转换整个时间到其它单位如纳秒、分钟、小时、天:

  • toNanos()
  • toMillis()
  • toMinutes()
  • toHours()
  • toDays()

举例而言:toNanos()getNano() 不同,toNanos() 获得的是 Duration 整个时间共有多少纳秒,
getNano() 只是获得这段时间中小于一秒的部分。

你也许会问,为什么没有 toSeconds() 方法,因为已经有 getSeconds() 这个方法能达到同样的功能了。

Duration计算

Duration类包含一系列的计算方法:

  • plusNanos()
  • plusMillis()
  • plusSeconds()
  • plusMinutes()
  • plusHours()
  • plusDays()
  • minusNanos()
  • minusMillis()
  • minusSeconds()
  • minusMinutes()
  • minusHours()
  • minusDays()

这些方法所做的事都是相似的,我在这儿也不展示内部实现细节了,就展示一个加减的例子吧:

Duration start = ... //obtain a start duration
Duration added      = start.plusDays(3);
Duration subtracted = start.minusDays(3);
  1. 第一行创建了一个Duration对象叫start,具体怎么创建可以参考前面的代码。
  2. 第二三行样例创建了两个新的Duration,通过调用start的加减操作,使得added对象表示的时间比start多三天,而substracted则少三天。

所有的计算方法都会返回一个新的Duration,以保证Duration的不可变属性。

long days = duration.toDays();              // 这段时间的总天数
long hours = duration.toHours();            // 这段时间的小时数
long minutes = duration.toMinutes();        // 这段时间的分钟数
long seconds = duration.getSeconds();       // 这段时间的秒数
long milliSeconds = duration.toMillis();    // 这段时间的毫秒数
long nanoSeconds = duration.toNanos();      // 这段时间的纳秒数

其他操作

增加和减少日期

Java 8中的日期/时间类都是不可变的,这是为了保证线程安全。当然,新的日期/时间类也提供了方法用于创建对象的可变版本,比如增加一天或者减少一天:

LocalDate date = LocalDate.of(2017, 1, 5);          // 2017-01-05

LocalDate date1 = date.withYear(2016);              // 修改为 2016-01-05
LocalDate date2 = date.withMonth(2);                // 修改为 2017-02-05
LocalDate date3 = date.withDayOfMonth(1);           // 修改为 2017-01-01

LocalDate date4 = date.plusYears(1);                // 增加一年 2018-01-05
LocalDate date5 = date.minusMonths(2);              // 减少两个月 2016-11-05
LocalDate date6 = date.plus(5, ChronoUnit.DAYS);    // 增加5天 2017-01-10

上面例子中对于日期的操作比较简单,但是有些时候我们要面临更复杂的时间操作,比如将时间调到下一个工作日,
或者是下个月的最后一天,这时候我们可以使用 with() 方法的另一个重载方法,它接收一个TemporalAdjuster参数,
可以使我们更加灵活的调整日期:

LocalDate date7 = date.with(nextOrSame(DayOfWeek.SUNDAY));      // 返回下一个距离当前时间最近的星期日
LocalDate date9 = date.with(lastInMonth(DayOfWeek.SATURDAY));   // 返回本月最后一个星期六

要使上面的代码正确编译,你需要使用静态导入 TemporalAdjusters 对象:

import static java.time.temporal.TemporalAdjusters.*;

TemporalAdjusters 类中包含了很多静态方法可以直接使用,下面的表格列出了一些方法:

方法名 描述
dayOfWeekInMonth 返回同一个月中每周的第几天
firstDayOfMonth 返回当月的第一天
firstDayOfNextMonth 返回下月的第一天
firstDayOfNextYear 返回下一年的第一天
firstDayOfYear 返回本年的第一天
firstInMonth 返回同一个月中第一个星期几
lastDayOfMonth 返回当月的最后一天
lastDayOfNextMonth 返回下月的最后一天
lastDayOfNextYear 返回下一年的最后一天
lastDayOfYear 返回本年的最后一天
lastInMonth 返回同一个月中最后一个星期几
next / previous 返回后一个/前一个给定的星期几
nextOrSame / previousOrSame 返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回

如果上面表格中列出的方法不能满足你的需求,你还可以创建自定义的 TemporalAdjuster 接口的实现,
TemporalAdjuster 也是一个函数式接口,所以我们可以使用Lambda表达式:

@FunctionalInterface
public interface TemporalAdjuster {
     
    Temporal adjustInto(Temporal temporal);
}

比如给定一个日期,计算该日期的下一个工作日(不包括星期六和星期天):

LocalDate date = LocalDate.of(2017, 1, 5);
date.with(temporal -> {
     
    // 当前日期
    DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));

    // 正常情况下,每次增加一天
    int dayToAdd = 1;

    // 如果是星期五,增加三天
    if (dayOfWeek == DayOfWeek.FRIDAY) {
     
        dayToAdd = 3;
    }
    
    // 如果是星期六,增加两天
    if (dayOfWeek == DayOfWeek.SATURDAY) {
     
        dayToAdd = 2;
    }

    return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});
其他历法

Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。闰年的定义是:非世纪年,能被4整除;世纪年能被400整除。为了计算的一致性,公元1年的前一年被当做公元0年,以此类推。

此外Java 8还提供了4套其他历法(很奇怪为什么没有汉族人使用的农历),每套历法都包含一个日期类,分别是:

  • ThaiBuddhistDate:泰国佛教历
  • MinguoDate:中华民国历
  • JapaneseDate:日本历
  • HijrahDate:伊斯兰历

每个日期类都继承 ChronoLocalDate 类,所以可以在不知道具体历法的情况下也可以操作。不过这些历法一般不常用,除非是有某些特殊需求情况下才会使用。

这些不同的历法也可以用于向公历转换:

LocalDate date = LocalDate.now();
JapaneseDate jpDate = JapaneseDate.from(date);

由于它们都继承ChronoLocalDate类,所以在不知道具体历法情况下,可以通过ChronoLocalDate类操作日期:

Chronology jpChronology = Chronology.ofLocale(Locale.JAPANESE);
ChronoLocalDate jpChronoLocalDate = jpChronology.dateNow();

我们在开发过程中应该尽量避免使用 ChronoLocalDate,尽量用与历法无关的方式操作时间,因为不同的历法计算日期的方式不一样,
比如开发者会在程序中做一些假设,假设一年中有12个月,如果是中国农历中包含了闰月,一年有可能是13个月,
但开发者认为是12个月,多出来的一个月属于明年的。
再比如假设年份是累加的,过了一年就在原来的年份上加一,但日本天皇在换代之后需要重新纪年,所以过了一年年份可能会从1开始计算。

在实际开发过程中建议使用 LocalDate,包括存储、操作、业务规则的解读;除非需要将程序的输入或者输出本地化,
这时可以使用 ChronoLocalDate 类。

资料
  • SimpleDateFormat的线程安全问题与解决方案
  • 为什么SimpleDateFormat不是线程安全的?
  • Java获取N天前,N天后的日期(如3天)

2 Lombok

简介

Lombok是一种Java™实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注解实现这一目的。

插件安装

  • 由于Lombok采取注解形式,在编译后,自动生成相应的方法,所以需要下载插件来支持它。
  • 以 idea 为例:查找插件lombok plugin安装即可。

引入依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>${
     lombok.version}</version>
    <scope>provided</scope>
</dependency>

Lombok注解一览

  • @Setter
  • @Getter
  • @Data
  • @Log(这是一个泛型注解,具体有很多种形式)
  • @AllArgsConstructor
  • @NoArgsConstructor
  • @EqualsAndHashCode
  • @NonNull
  • @Cleanup
  • @ToString
  • @RequiredArgsConstructor
  • @Value
  • @SneakyThrows
  • @Synchronized

常用注解介绍

  • @Getter和@Setter :该注解可以使用在类上也可以使用在属性上。生成的getter遵循布尔属性的约定。在使用该注解时,会默认生成一个无参构造。和对应的gettersetter方法
@Getter
@Setter
public class Notice {
     

   private Integer id;
   private String title;
   private Boolean isDeleted;

}

编译后将会转化为

public class Notice {
     

   private Integer id;
   private String title;
   private Boolean isDeleted;

   public Notice() {
     }

   public Integer getId() {
     
      return id;
   }

   public void setId(Integer id) {
     
      this.id = id;
   }

   public String getTitle() {
     
      return title;
   }

   public void setTitle(String title) {
     
      this.title = title;
   }

   public Boolean getDeleted() {
     
      return isDeleted;
   }

   public void setDeleted(Boolean deleted) {
     
      isDeleted = deleted;
   }
}
  • @ToString :该注解使用在上,编译后toString方法返回将会以字段的名称-值的形式输出
  • @EqualsAndHashCode :该注解使用在上,同时生成equalshashCode
  • @AllArgsConstructor :该注解使用在上,提供全参数的构造方法,默认不提供无参构造。
  • @NoArgsConstructor :该注解使用在上,提供无参构造
  • @Data :使用@Data注解就可以有下面几个注解的功能:@ToString@Getter@Setter@EqualsAndHashCode@NoArgsConstructor
    需要注意的是:同时使用@Data@AllArgsConstructor后 ,默认的无参构造函数失效,如果需要它,要重新设置@NoArgsConstructor
  • @Slf4j :在类上注解后,可直接调用log
log.info(xxxx);
  • @Builder:bulder 模式构建对象。
  • @Cleanup:自动化关闭流,相当于 try with resource
@Cleanup 
InputStream in = new FileInputStream(args[0]);
@Cleanup 
OutputStream out = new FileOutputStream(args[1]);
  • @NonNull :增加不为空判断
public NonNullExample(@NonNull Person person) {
     
    this.name = person.getName();
 }

转换为:

public NonNullExample(@NonNull Person person) {
     
    if (person == null) {
     
      throw new NullPointerException("person");
    }
    this.name = person.getName();
 }
  • @SneakyThrows :当我们需要抛出异常,在当前方法上调用,不用显示的在方法名后面写 throw
@SneakyThrows(Exception.class)
  • @Synchronized :方法中所有的代码都加入到一个代码块中,默认静态方法使用的是全局锁,普通方法使用的是对象锁,当然也可以指定锁的对象。
private final Object lock = new Object();
@Synchronized("lock")
public void foo() {
     
    // Do something
}

你可能感兴趣的:(Java,java,lambda,stream)