- default关键字
- Lambda表达式
- 内置函数式接口
- Predicate 断言型接口
- Function 函数式接口
- Supplier 供给型接口
- Consumer 消费型接口
- 与Consumer相关的接口
- Comparator 比较型接口
- Optionals
- Streams(串行流)
- 中间操作
- filter(过滤)
- limit(截断)
- skip(跳过)
- distinct(筛选)
- sorted(排序)
- map(映射)
- flatMap(映射)
- match(匹配)
- count(计数)
- reduce(规约)
- parallel(并行处理)
- peek(调试)
- 终止操作
- allMatch(匹配)
- anyMatch(匹配)
- noneMatch(匹配)
- findFirst(返回元素)
- findAny(返回元素)
- max(最大值)
- min(最小值)
- forEach(循环)
- collect(收集)
- parallelStreams(并行流)
- Collectors
- Data API(日期相关API)
- Clock
- ZoneId
- LocalDate 只含年月日的日期对象 , LocalDate是不可变并且线程安全的
- LocalTime 只含时分秒的时间对象, LocalTime是不可变并且线程安全的
- LocalDateTime 是LocalDate和LocalTime的组合形式,包含了年月日时分秒信息
- Duration: 用秒和纳秒表示时间的数量(长短),用于计算两个日期的“时间”间隔
- Period: 用于计算两个“日期”间隔
Java 8 允许我们使用default关键字,为接口声明添加非抽象的方法实现。这个特性又被称为扩展方法,子类可以选择实现或者不实现该方法
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
Formula的实现类只需要实现抽象方法caculate就可以了。默认方法sqrt可以直接使用。
Lambda表达式对一个string列表进行排序
Listnames = Arrays.asList("peter", "anna", "mike", "xenia"); Collections.sort(names, new Comparator () { @Override public int compare(String a, String b) { return b.compareTo(a); } }); //Lambda表达式 Collections.sort(names, (String a, String b) -> { return b.compareTo(a); }) 或 Collections.sort(names, (String a, String b) -> b.compareTo(a)); 亦或(甚至可以连大括号对{}和return关键字都省略不要) Collections.sort(names, (a, b) -> b.compareTo(a));
Java 8 允许通过 :: 关键字获取方法或者构造函数的的引用
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
Something something = new Something();
Converter converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted); // "J"
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
//定义一个person工厂接口,用来创建新的person对象:
interface PersonFactory {
P create(String firstName, String lastName);
}
//通过Person::new来创建一个Person类构造函数的引用
PersonFactory personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");
对于lambdab表达式外部的变量,其访问权限的粒度与匿名对象的方式非常类似。能够访问局部对应的外部区域的局部final变量,以及成员变量和静态变量。
默认方法无法在lambda表达式内部被访问。
内置函数式接口JDK 1.8之前已有的函数式接口
Predicate 断言型接口java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
predicate是一个布尔类型的函数,该函数只有一个输入参数。Predicate接口包含了多种默认方法,用于处理复杂的逻辑动词(and, or,negate)
// 接收一个参数, 判断这个参数是否匹配某种规则, 匹配成功返回true, 匹配失败则返回false boolean test(T t); //接收另外一个Predicate类型参数进行逻辑与操作 default Predicate and(Predicate super T> other) //返回当前Predicate取反操作之后的Predicate default Predicate negate() //接收另外一个Predicate 类型参数进行逻辑或操作 default Predicate or(Predicate super T> other) //接收一个Object targetRef, 返回一个Predicate 类型 返回的Predicate的test方法是用来判断传入的参数是否等于targetRef static Predicate isEqual(Object targetRef)
示例:
//设置一个大于10的过滤条件 PredicateFunction 函数式接口predicate = x-> x > 10; System.out.println(predicate.test(5)); System.out.println(predicate.test(20)); //在上面大于10的条件下,添加是偶数的条件 predicate = predicate.and(x->x % 2 == 0); System.out.println(predicate.test(4)); System.out.println(predicate.test(9)); System.out.println(predicate.test(10));
Function
// 输入T类型的参数,运行相关逻辑后,返回R类型的结果。 R apply(T t); // 先执行传入的V,在执行原有的 defaultFunction compose(Function super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } // 先执行当前的逻辑,再执行传入的逻辑 default Function andThen(Function super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } // static Function identity() { return t -> t; }
FunctionSupplier 供给型接口test=i->i+1; test.apply(5); // 6 Function A=i->i+1; Function B=i->i*i; System.out.println("F1:"+B.apply(A.apply(5))); System.out.println("F1:"+B.compose(A).apply(5)); System.out.println("F2:"+A.apply(B.apply(5))); System.out.println("F2:"+B.andThen(A).apply(5)); // F1:36 // F1:36 // F2:26 // F1:26 // compose等价于B.apply(A.apply(5)),而andThen等价于A.apply(B.apply(5))。
Supplier 这个接口是一个提供者的意思,只有一个get的抽象类,没有默认的方法以及静态的方法,传入一个泛型T的,get方法,返回一个泛型T
Consumer 消费型接口Consumer 给定义一个参数,对其进行(消费)处理,处理的方式可以是任意操作
与Consumer相关的接口accept方法 该函数式接口的唯一的抽象方法,接收一个参数,没有返回值.
andThen方法 在执行完调用者方法后再执行传入参数的方法.
- 处理一个两个参数
BiConsumer
- 处理一个double类型的参数
DoubleConsumer
- 处理一个int类型的参数
IntConsumer
- 处理一个long类型的参数
LongConsumer
- 处理两个参数,且第二个参数必须为int类型
ObjIntConsumer
- 处理两个参数,且第二个参数必须为long类型
Comparator 比较型接口ObjLongConsumer
比较器
ComparatorOptionalscomparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Comparator otherComparator = Comparator.comparing(Person::getFirstName);// (另一种比较器创建方式) Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland"); comparator.compare(p1, p2); // > 0 默认从大到小排序 comparator.reversed().compare(p1, p2); // < 0 反转,从小到大 List people = new ArrayList<>(); people.add(p1); people.add(p2); people.add(p3); Collections.sort(people,comparator); people.sort(Comparator.comparing(Person::getFirstName));// (另一种比较方式)
Optionals 不是一个函数式接口,而是一个精巧的工具接口用于防止 NullPointerException 的工具。Optional是一个简单的值容器,这个值可以是null,也可以是non-null。考虑到一个方法可能会返回一个non-null的值,也可能返回一个空值。为了不直接返回null,在Java 8中就返回一个Optional.
OptionalStreams(串行流)optional = Optional.of("bam"); optional.isPresent(); // true optional.get(); // "bam" optional.orElse("fallback"); // "bam" optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
java中Stream的定义
A sequence of elements supporting sequential and parallel aggregate operations.
支持数据处理操作的一个source(资源) 中的元素序列
- Sequence of elements(元素序列):简单来说,就是我们操作的集合中的所有元素
- source(数据源) :Stream流的作用就是操作数据,那么source 就是为Stream提供可操作的源数据(一般,集合、数组或I/OI/O resources 都可以成为Stream的source )
- Data processing operations(数据处理操作):filter、sorted、map、collect,reduce、find、match 等都属于Stream 的一些操作数据的方法接口。这些操作可以顺序进行,也可以并行执行。
- Pipelining(管道、流水线):Stream对数据的操作类似数据库查询,也像电子厂的生产流线一样,Stream的每一个中间操作(后面解释什么是中间操作)比如上面的filter、sorted、map,每一步都会返回一个新的流,这些操作全部连起来就是想是一个工厂得生产流水线:
- Internal iteration(内部迭代):Stream API 实现了对数据迭代的封装,不用你再像操作集合一样,手动写for循环显示迭代数据。
java.util.Stream表示了某一种元素的序列,在这些元素上可以进行各种操作。Stream操作可以是中间操作,也可以是完结操作。完结操作会返回一个某种类型的值,而中间操作会返回流对象本身,并且你可以通过多次调用同一个流操作方法来将操作结果串起来
Stream是在一个源的基础上创建出来的,例如java.util.Collection中的list或者set(map不能作为Stream的源)。Stream操作往往可以通过顺序或者并行两种方式来执行。
可以直接通过调用Collections.stream()或者Collection.parallelStream()方法来创建一个流对象。
List中间操作 filter(过滤)list = new ArrayList<>(); list.add("ddd2"); list.add("aaa2"); list.add("bbb1"); list.add("aaa1"); list.add("bbb3"); list.add("ccc"); list.add("bbb2"); list.add("ddd1");
filter接受一个predicate接口类型的变量,并将所有流对象中的元素进行过滤。该操作是一个中间操作,因此它允许我们在返回结果的基础上再进行其他的流操作(forEach)
list
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa2", "aaa1"
limit(截断)
接受一个数量,截断使其元素不超过给定数量
list .stream() .limit(2) .forEach(System.out::println) //ddd2 aaa2skip(跳过)
返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
list .stream() .skip(2) .forEach(System.out::println) //bbb1 aaa1 bbb3 ccc bbb2 ddd1distinct(筛选)
通过流所生成的元素的 hashCode() 和 equals() 去除重复元素
distinct不提供按照属性对对象列表进行去重的直接实现,如果我们想要按照对象的属性,对对象列表进行去重,我们可以通过其它方法来实现。
staticPredicate distinctByKey(Function super T, ?> keyExtractor) { Map
distinctByKey() 方法返回一个使用ConcurrentHashMap 来维护先前所见状态的 Predicate 实例
sorted(排序)sorted 是一个中间操作,能够返回一个排过序的流对象的视图。流对象中的元素会默认按照自然顺序进行排序,除非你自己指定一个Comparator接口来改变排序规则。
list
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa1", "aaa2"
sorted只是创建一个流对象排序的视图,而不会改变原来集合中元素的顺序。原来string集合中的元素顺序是没有改变的。
map(映射)map是一个对于流对象的中间操作,通过给定的方法,它能够把流对象中的每一个元素对应到另外一个对象上。
还可以把每一种对象映射成为其他类型。对于带泛型结果的流对象,具体的类型还要由传递给map的泛型方法来决定
list .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2","BBB1", "AAA2", "AAA1"flatMap(映射)
map相当于add,flatMap相当于addAll
接受一个函数作为参数,将流中的每个值都转换成另一个流,然后把所有流连成一个流
list
.stream()
.map(s -> s.split("\d+")) // 根据数字拆分字符串,返回Stream类型的数据
.flatMap(Arrays::stream) // 将Stream转换为Stream
.distinct() // 去重
.forEach(System.out::println);
//ddd , aaa ,bbb, ccc
match(匹配)
匹配操作有多种不同的类型,都是用来判断某一种规则是否与流对象相互吻合的。所有的匹配操作都是终结操作,只返回一个boolean类型的结果。
count(计数)count是一个终结操作,它的作用是返回一个数值,用来标识当前流对象中包含的元素数量。
long startsWithB =
list
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
reduce(规约)
reduce是一个终结操作,它能够通过某一个方法,对元素进行削减操作。该操作的结果会放在一个Optional变量里返回。
Optionalparallel(并行处理)reduced = list .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2" Optional string = list.stream() .sorted() .reduce((s1, s2) -> s1.toUpperCase() + "#" + s2); string.ifPresent(System.out::println); //AAA1#AAA2#BBB1#BBB2#BBB3#CCC#DDD1#ddd2
同下 parallelStream(并行流)
peek(调试)主要目的是用调试,通过peek()方法可以看到流中的数据经过每个处理点时的状态。
Person a = new Person("a", 18);
Person b = new Person("b", 23);
Person c = new Person("c", 34);
Stream persons = Stream.of(a, b, c);
persons.filter(person -> person.getAge() < 30)
.peek(person -> System.out.println("filter " + person))
.map(person -> new Person(person.getName() + " map", person.getAge()))
.peek(person -> System.out.println("map " + person))
.collect(Collectors.toList());
// filter Person{name='a', age=18}
// map Person{name='a map', age=18}
// filter Person{name='b', age=23}
// map Person{name='b map', age=23}
除去用于调试,peek()也可以修改元素内部状态。也可以使用map()和flatMap实现,但是相比来说peek()更加方便,因为我们并不想替代流中的数据。
Person a = new Person("a", 18);
Person b = new Person("b", 23);
Person c = new Person("c", 34);
Stream persons = Stream.of(a, b, c);
persons.peek(person -> person.setName(person.getName().toUpperCase()))
.forEach(System.out::println);
// Person{name='A', age=18}
// Person{name='B', age=23}
// Person{name='C', age=34}
终止操作
allMatch(匹配)
检查是否匹配所有元素,相当于制定一个规则,将集合中的对象一一与规则进行对象,判断是否所有的集合对象都符合该规则
anyMatch(匹配)检查是否至少匹配一个元素 ,类似于多选一
noneMatch(匹配)检查是否没有匹配的元素
findFirst(返回元素)返回第一个元素
findAny(返回元素)返回当前流中的任意元素
max(最大值)返回流中最大值
min(最小值)返回流中最小值
forEach(循环)内部迭代/循环
collect(收集)将流转换成其他形式。接受一个Collector接口的实现,用于给Stream中元素做汇总的方法
Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)
Collector需要使用Collectors提供实例。另外, Collectors实用类提供了很多静态方法,可以方便地创建常见收集器实例
parallelStreams(并行流)parallelStream其实就是一个并行执行的流,它通过默认的ForkJoinPool,可能提高你的多线程任务的速度。
并行执行时,将流划分为多个子流,分散在不同CPU并行处理,然后进行合并
可能?并行一定比串行更快吗?这不一定,取决于两方面条件:
- 处理器核心数量,并行处理核心数越多自然处理效率会更高。
- 处理的数据量越大,优势越强。
Listservers = new ArrayList<>(); servers.add("Felordcn"); servers.add("Tomcat"); servers.add("Jetty"); servers.add("Undertow"); servers.add("Resin"); // 并行流 servers.stream().parallel().forEach(i->{ Thread thread = Thread.currentThread(); System.err.println(i+",currentThread:" + thread.getName()); }); // 这个运行结果并不是唯一的, 实际运行的时候可能会得到多个结果 // Jetty,currentThread:main // Resin,currentThread:ForkJoinPool.commonPool-worker-1 // Tomcat,currentThread:ForkJoinPool.commonPool-worker-2 // Felordcn,currentThread:ForkJoinPool.commonPool-worker-1 // Undertow,currentThread:main // 或 //Jetty,currentThread:main //Tomcat,currentThread:ForkJoinPool.commonPool-worker-1 //Resin,currentThread:ForkJoinPool.commonPool-worker-2 //Undertow,currentThread:ForkJoinPool.commonPool-worker-1 //Felordcn,currentThread:ForkJoinPool.commonPool-worker-3
并行流内部使用了默认的ForkJoinPool,它默认的线程数量就是你的处理器数量,这个值是由Runtime.getRuntime().available- Processors()得到的。
但是你可以通过系统属性java.util.concurrent.ForkJoinPool.common. parallelism来改变线程池大小,如下所示: System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12"); 这是一个全局设置,因此它将影响代码中所有的并行流,他也是final类型的,整个JVM中只允许设置一次。
若要进行不同线程数量的并发测试,则引入 ForkJoinPool(int parallelism) 。parallelism 可以指定并行度
反过来说,目前还无法专为某个 并行流指定这个值。一般而言,让ForkJoinPool的大小等于处理器数量是个不错的默认值, 除非你有很好的理由,否则我们强烈建议你不要修改它。
使用parallelStream时需要注意的一点是,多个parallelStream之间默认使用的是同一个线程池,所以IO操作尽量不要放进parallelStream中,否则会阻塞其他parallelStream。
// 获取当前机器CPU处理器的数量 System.out.println(Runtime.getRuntime().availableProcessors());// 输出 4 // parallelStream默认的并发线程数 System.out.println(ForkJoinPool.getCommonPoolParallelism());// 输出 3
parallelStream默认的并发线程数要比CPU处理器的数量少1个,因为主线程也算一个线程,所以要占一个名额。
parallelStream 适用的场景是CPU密集型的。假如本身电脑CPU的负载很大,还到处用并行流,并不能起到作用;
在使用并行流的时候是无法保证元素的顺序的,也就是即使你用了同步集合也只能保证元素都正确但无法保证其中的顺序;
CollectorsListservers = new ArrayList<>(); servers.add("Felordcn"); servers.add("Tomcat"); servers.add("Jetty"); servers.add("Undertow"); servers.add("Resin");
1.类型归纳。作用是将元素分别归纳进可变容器 List、Map、Set、Collection 或者ConcurrentMap 。
Collectors.toList(); Collectors.toMap(); Collectors.toSet(); Collectors.toCollection(); Collectors.toConcurrentMap();
2.joining。将元素以某种规则连接起来
// 输出 FelordcnTomcatJettyUndertowResin
servers.stream().collect(Collectors.joining());
// 输出 Felordcn,Tomcat,Jetty,Undertow,Resin
servers.stream().collect(Collectors.joining("," ));
// 输出 [Felordcn,Tomcat,Jetty,Undertow,Resin]
servers.stream().collect(Collectors.joining(",", "[", "]"));
3.collectingAndThen。先执行了一个归纳操作,然后再对归纳的结果进行 Function 函数处理输出一个新的结果。
// 将servers joining 然后转成大写,结果为: FELORDCN,TOMCAT,JETTY,UNDERTOW,RESIN
servers.stream().collect(Collectors.collectingAndThen(Collectors.joining(","), String::toUpperCase));
4.groupingBy。按照条件对元素进行分组
// 按照字符串长度进行分组 符合条件的元素将组成一个 List 映射到以条件长度为key 的 Map> 中 servers.stream().collect(Collectors.groupingBy(String::length)) //Map > servers.stream.collect(Collectors.groupingBy(String::length, Collectors.toSet())) Supplier
5.partitioningBy groupingBy 的一个特例,基于断言(Predicate)策略分组
// 按照条件进行分组, 符合条件的元素将组成一个 List 映射到以条件长度为key 的 Map> 中 直接使用groupingBy也可以实现 Map > collect = servers.stream().collect(Collectors.partitioningBy(s -> s.length() > 5));
6.counting 归纳元素的的数量
7.maxBy/minBy 查找大小元素的操作,它们基于比较器接口 Comparator 来比较 ,返回的是一个 Optional 对象。(遵循 “先入为主” 的原则)
// Jetty String str = servers.stream().collect(Collectors.minBy(Comparator.comparing(String::length))).get();
8.summingInt/Double/Long 用来做累加计算。计算元素某个属性的总和
9.summarizingInt/Double/Long 对元素某个属性的提取,会返回对元素该属性的统计数据对象(包含了 总数,总和,最小值,最大值,平均值 五个指标)
10.mapping 先对元素使用 Function 进行再加工操作,然后用另一个Collector 归纳。
Listcollect1 = servers.stream().collect(Collectors.mapping(s -> s.substring(1), Collectors.toList())); // [elordcn, omcat, etty, ndertow, esin] collect1.forEach(System.out::println);
有点类似 Stream 先进行了 map 操作再进行 collect
11.reducing元素两两之间进行比较根据策略淘汰一个,随着轮次的进行元素个数就是 reduce 的
举例:统计每个城市个子最高的人
ComparatorbyHeight = Comparator.comparing(Person::getHeight); Map > tallestByCity = people.stream() .collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(BinaryOperator.maxBy(byHeight))));
上面这一层是根据 Height 属性找最高的 Person ,而且如果这个属性没有初始化值或者没有数据,很有可能拿不到结果所以给出的是 Optional
比如我们给出高于 2 米 的人作为 identity。 我们就可以统计每个城市不低于 2 米 而且最高的那个人,当然如果该城市没有人高于 2 米则返回基准值identity :
ComparatorbyHeight = Comparator.comparing(Person::getHeight); Person identity= new Person(); identity.setHeight(2); identity.setName("identity"); Map collect = persons.stream() .collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(identity, BinaryOperator.maxBy(byHeight))));
这时候就确定一定会返回一个 Person 了,最起码会是基准值identity 不再是 Optional
还有些情况,我们想在 reducing 的时候把 Person 的身高先四舍五入一下。这就需要我们做一个映射处理。定义一个 Function super T, ? extends U> mapper 来干这个活。那么上面的逻辑就可以变更为:
ComparatorData API(日期相关API) ClockbyHeight = Comparator.comparing(Person::getHeight); Person identity = new Person(); identity.setHeight(2); identity.setName("identity"); // 定义映射 处理 四舍五入 Function mapper = ps -> { Double height = ps.getHeight(); BigDecimal decimal = new BigDecimal(height); Double d = decimal.setScale(1, BigDecimal.ROUND_HALF_UP).doublevalue(); ps.setHeight(d); return ps; }; Map collect = persons.stream() .collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(identity, mapper, BinaryOperator.maxBy(byHeight))));
Clock 提供了对当前时间和日期的访问功能。Clock是对当前时区敏感的,并可用于替代System.currentTimeMillis()方法来获取当前的毫秒时间。
当前时间线上的时刻可以用Instance类来表示。Instance也能够用于创建原先的java.util.Date对象。
Clock clock = Clock.systemDefaultZone(); long millis = clock.millis(); Instant instant = clock.instant(); Date legacyDate = Date.from(instant);ZoneId
时区类可以用一个ZoneId来表示。时区类的对象可以通过静态工厂方法方便地获取。时区类还定义了一个偏移量,用来在当前时刻或某时间与目标时区时间之间进行转换。
System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
LocalDate 只含年月日的日期对象 , LocalDate是不可变并且线程安全的
LocalDate date = LocalDate.of(2018, 8, 30); // 2018-08-30 int year = date.getYear(); // 2018 int month = date.getMonth().getValue(); // 8 int day = date.getDayOfMonth(); // 30 // 查看该月有多少天 int days = date.lengthOfMonth(); // 31 // 是否是闰年 boolean isLeap = date.isLeapYear(); // false // 查看当天 年月日 LocalDate today = LocalDate.now(); // 2021-10-14 int year1 = date.get(ChronoField.YEAR); // 2018 int month1 = date.get(ChronoField.MONTH_OF_YEAR); // 8 int day1 = date.get(ChronoField.DAY_OF_MONTH); // 30 // 当前日期属于该月第几周 int weekOfMonth = date.get(ChronoField.ALIGNED_WEEK_OF_MONTH); // 4
TemporalAdjusters类提供了许多静态方法来修改LocalDate对象。当我们需要获取下一个周天,下一个工作日,本月的最后一天等信息时,TemporalAdjusters类便可派上用场
import static java.time.temporal.TemporalAdjusters.*; // 2018-09-03 8-30 如果是周五,那就是本天,如果不是那就是下周五 LocalDate date10 = date.with(nextOrSame(DayOfWeek.MONDAY)); // 2018-08-31 本月的最后一天 LocalDate date11 = date.with(lastDayOfMonth()); // 2018-08-25 前一周的周六 LocalDate date12 = date.with(previous(DayOfWeek.SATURDAY));LocalTime 只含时分秒的时间对象, LocalTime是不可变并且线程安全的
LocalTime time = LocalTime.of(20, 13, 54); // 20:13:54
int hour = time.getHour(); // 20
int minute = time.getMinute(); // 13
int second = time.getSecond(); // 54
// LocalDate和LocalTime都可以通过字符串来创建:
LocalDate date = LocalDate.parse("2018-09-30");
LocalTime time = LocalTime.parse("17:18:54");
LocalDateTime 是LocalDate和LocalTime的组合形式,包含了年月日时分秒信息
LocalDateTime ldt1 = LocalDateTime.of(2018, 9, 30, 17, 18, 54); // 2018-09-30T17:18:54 LocalDateTime ldt2 = LocalDateTime.of(date, time); // 2018-09-30T17:18:54 //LocalDateTime可以转换为LocalDate和LocalTime,转换后包含的信息减少了 LocalDate date1 = ldt1.toLocalDate(); // 2018-09-30 LocalTime time1 = ldt1.toLocalTime(); // 17:18:54 //LocalDate和LocalTime也可以转换为LocalDateTime,只需要补上日期或者时间: LocalDateTime ldt3 = date.atTime(time); // 2019-09-30T17:18:54 LocalDateTime ldt4 = date.atTime(17, 18, 54); // 2019-09-30T17:18:54 LocalDateTime ldt5 = time.atDate(date); // 2019-09-30T17:18:54Duration: 用秒和纳秒表示时间的数量(长短),用于计算两个日期的“时间”间隔
LocalTime time2 = LocalTime.of(23, 59, 59); Duration duration = Duration.between(time, time2); long seconds = duration.getSeconds(); // 24065Period: 用于计算两个“日期”间隔
LocalDate date2 = LocalDate.of(2018, 10, 31); Period period = Period.between(date, date2); int monthsBetween = period.getMonths(); // 1 // 1 这里的天数,只是单纯地天数计算,不包含月份 int daysBetween = period.getDays();



