Java8 不算新的新特性

default关键字

Java 8 允许我们使用default关键字,为接口声明添加非抽象的方法实现。这个特性又被称为扩展方法,子类可以选择实现或者不实现该方法

interface Formula {
    double calculate(int a);
    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

Formula的实现类只需要实现抽象方法caculate就可以了。默认方法sqrt可以直接使用。

Lambda表达式

对一个string列表进行排序

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
    @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<String, String> 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 extends Person> {
    P create(String firstName, String lastName);
}
//通过Person::new来创建一个Person类构造函数的引用
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

对于lambdab表达式外部的变量,其访问权限的粒度与匿名对象的方式非常类似。能够访问局部对应的外部区域的局部final变量,以及成员变量和静态变量。

默认方法无法在lambda表达式内部被访问。

内置函数式接口

JDK 1.8之前已有的函数式接口

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是一个布尔类型的函数,该函数只有一个输入参数。Predicate接口包含了多种默认方法,用于处理复杂的逻辑动词(and, or,negate)

// 接收一个参数, 判断这个参数是否匹配某种规则, 匹配成功返回true, 匹配失败则返回false
boolean test(T t);
//接收另外一个Predicate<T>类型参数进行逻辑与操作
default Predicate<T> and(Predicate<? super T> other)
//返回当前Predicate取反操作之后的Predicate
default Predicate<T> negate() 
//接收另外一个Predicate<T>类型参数进行逻辑或操作    
default Predicate<T> or(Predicate<? super T> other)
//接收一个Object targetRef, 返回一个Predicate<T>类型 返回的Predicate的test方法是用来判断传入的参数是否等于targetRef
static <T> Predicate<T> isEqual(Object targetRef)     

示例:

//设置一个大于10的过滤条件
Predicate<Integer> 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 函数式接口

Function<T,R>接口接收一个参数,并返回单一的结果。默认方法可以将多个函数串在一起(compse, andThen)

// 输入T类型的参数,运行相关逻辑后,返回R类型的结果。
R apply(T t);
// 先执行传入的V,在执行原有的
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
   Objects.requireNonNull(before);
   return (V v) -> apply(before.apply(v));
}
// 先执行当前的逻辑,再执行传入的逻辑
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
}
//
static <T> Function<T, T> identity() {
    return t -> t;
}
Function<Integer,Integer> test=i->i+1; 
test.apply(5);
// 6

Function<Integer,Integer> A=i->i+1;
Function<Integer,Integer> 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 供给型接口

Supplier 这个接口是一个提供者的意思,只有一个get的抽象类,没有默认的方法以及静态的方法,传入一个泛型T的,get方法,返回一个泛型T

Consumer 消费型接口

Consumer 给定义一个参数,对其进行(消费)处理,处理的方式可以是任意操作

accept方法 该函数式接口的唯一的抽象方法,接收一个参数,没有返回值.

andThen方法 在执行完调用者方法后再执行传入参数的方法.

与Consumer相关的接口

  • 处理一个两个参数

BiConsumer<T, U>

  • 处理一个double类型的参数

DoubleConsumer

  • 处理一个int类型的参数

IntConsumer

  • 处理一个long类型的参数

LongConsumer

  • 处理两个参数,且第二个参数必须为int类型

ObjIntConsumer

  • 处理两个参数,且第二个参数必须为long类型

ObjLongConsumer

Comparator 比较型接口

比较器

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Comparator<Person> 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<Person> people = new ArrayList<>();
        people.add(p1);
        people.add(p2);
        people.add(p3);

Collections.sort(people,comparator);
people.sort(Comparator.comparing(Person::getFirstName));// (另一种比较方式)

Optionals

Optionals 不是一个函数式接口,而是一个精巧的工具接口用于防止 NullPointerException 的工具。Optional是一个简单的值容器,这个值可以是null,也可以是non-null。考虑到一个方法可能会返回一个non-null的值,也可能返回一个空值。为了不直接返回null,在Java 8中就返回一个Optional.

Optional<String> optional = Optional.of("bam");

optional.isPresent();           // true
optional.get();                 // "bam"
optional.orElse("fallback");    // "bam"

optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"

Streams(串行流)

java中Stream的定义

A sequence of elements supporting sequential and parallel aggregate operations.
支持数据处理操作的一个source(资源) 中的元素序列

  1. Sequence of elements(元素序列):简单来说,就是我们操作的集合中的所有元素
  2. source(数据源) :Stream流的作用就是操作数据,那么source 就是为Stream提供可操作的源数据(一般,集合、数组或I/OI/O resources 都可以成为Stream的source )
  3. Data processing operations(数据处理操作):filter、sorted、map、collect,reduce、find、match 等都属于Stream 的一些操作数据的方法接口。这些操作可以顺序进行,也可以并行执行。
  4. Pipelining(管道、流水线):Stream对数据的操作类似数据库查询,也像电子厂的生产流线一样,Stream的每一个中间操作(后面解释什么是中间操作)比如上面的filter、sorted、map,每一步都会返回一个新的流,这些操作全部连起来就是想是一个工厂得生产流水线:
    img
  5. Internal iteration(内部迭代):Stream API 实现了对数据迭代的封装,不用你再像操作集合一样,手动写for循环显示迭代数据。

java.util.Stream表示了某一种元素的序列,在这些元素上可以进行各种操作。Stream操作可以是中间操作,也可以是完结操作。完结操作会返回一个某种类型的值,而中间操作会返回流对象本身,并且你可以通过多次调用同一个流操作方法来将操作结果串起来

Stream是在一个源的基础上创建出来的,例如java.util.Collection中的list或者set(map不能作为Stream的源)。Stream操作往往可以通过顺序或者并行两种方式来执行。

可以直接通过调用Collections.stream()或者Collection.parallelStream()方法来创建一个流对象。

List<String> 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(过滤)

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  aaa2

skip(跳过)

返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补

list
.stream()
.skip(2)
.forEach(System.out::println)

//bbb1  aaa1 bbb3 ccc bbb2 ddd1

distinct(筛选)

通过流所生成的元素的 hashCode() 和 equals() 去除重复元素

distinct不提供按照属性对对象列表进行去重的直接实现,如果我们想要按照对象的属性,对对象列表进行去重,我们可以通过其它方法来实现。

static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Map<Object,Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
} 

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<String[]>类型的数据
.flatMap(Arrays::stream)    // 将Stream<String[]>转换为Stream<String>
.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变量里返回。

Optional<String> reduced =
        list
        .stream()
        .sorted()
        .reduce((s1, s2) -> s1 + "#" + s2);

reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
/*
 reduce方法接受一个函数,这个函数有两个参数
 第一个参数是上次函数执行的返回值(也称为中间结果),第二个参数是stream中的元素,
 这个函数把这两个值按操作执行,得到的值 会被赋值给下次执行这个函数的第一个参数
 第一次执行的时候第一个参数的值是Stream的第一个元素,第二个参数是Stream的第二个元素
*/
 
 Optional<String> string =
                        list.stream()
                        .sorted()
                        .reduce((s1, s2) -> s1.toUpperCase() + "#" + s2);

 string.ifPresent(System.out::println);
 //AAA1#AAA2#BBB1#BBB2#BBB3#CCC#DDD1#ddd2      
        

parallel(并行处理)

同下 parallelStream(并行流)

peek(调试)

主要目的是用调试,通过peek()方法可以看到流中的数据经过每个处理点时的状态。

 Person a = new Person("a", 18);
 Person b = new Person("b", 23);
 Person c = new Person("c", 34);
 Stream<Person> 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<Person> 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并行处理,然后进行合并

可能?并行一定比串行更快吗?这不一定,取决于两方面条件:

  1. 处理器核心数量,并行处理核心数越多自然处理效率会更高。
  2. 处理的数据量越大,优势越强。
List<String> servers = 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的负载很大,还到处用并行流,并不能起到作用;

在使用并行流的时候是无法保证元素的顺序的,也就是即使你用了同步集合也只能保证元素都正确但无法保证其中的顺序

Collectors

 List<String> servers = new ArrayList<>();
 servers.add("Felordcn");
 servers.add("Tomcat");
 servers.add("Jetty");
 servers.add("Undertow");
 servers.add("Resin");

1.类型归纳。作用是将元素分别归纳进可变容器 ListMapSetCollection 或者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<Integer, List<String>> 中
servers.stream().collect(Collectors.groupingBy(String::length))
//Map<Integer, Set<String>>
servers.stream.collect(Collectors.groupingBy(String::length, Collectors.toSet()))
    
Supplier<Map<Integer,Set<String>>> mapSupplier = HashMap::new;
Map<Integer,Set<String>> collect = servers.stream().collect(Collectors.groupingBy(String::length, mapSupplier, Collectors.toSet()));
// 使用线程安全的同步容器
 Supplier<Map<Integer, Set<String>>> mapSupplier = () -> Collections.synchronizedMap(new HashMap<>());
 Map<Integer, Set<String>> collect = servers.stream().collect(Collectors.groupingBy(String::length, mapSupplier, Collectors.toSet()));

其实同步安全问题 Collectors 的另一个方法 groupingByConcurrent 给我们提供了解决方案。用法和 groupingBy 差不多。    

5.partitioningBy groupingBy 的一个特例,基于断言(Predicate)策略分组

// 按照条件进行分组, 符合条件的元素将组成一个 List 映射到以条件长度为key 的 Map<Boolean, List<String>> 中 直接使用groupingBy也可以实现
Map<Boolean, List<String>> 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 归纳。

List<String> collect1 = 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

举例:统计每个城市个子最高的人

 Comparator<Person> byHeight = Comparator.comparing(Person::getHeight);
Map<String, Optional<Person>> tallestByCity = people.stream()
             .collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(BinaryOperator.maxBy(byHeight))));

上面这一层是根据 Height 属性找最高的 Person ,而且如果这个属性没有初始化值或者没有数据,很有可能拿不到结果所以给出的是 Optional<Person>。 如果我们给出了 identity 作一个基准值,那么我们首先会跟这个基准值进行 BinaryOperator 操作。

比如我们给出高于 2 米 的人作为 identity。 我们就可以统计每个城市不低于 2 米 而且最高的那个人,当然如果该城市没有人高于 2 米则返回基准值identity

 Comparator<Person> byHeight = Comparator.comparing(Person::getHeight);
 Person identity= new Person();
      identity.setHeight(2);
      identity.setName("identity");
 Map<String, Person> 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 来干这个活。那么上面的逻辑就可以变更为:

  Comparator<Person> byHeight = Comparator.comparing(Person::getHeight);
    Person identity = new Person();
    identity.setHeight(2);
    identity.setName("identity");
    // 定义映射 处理 四舍五入
    Function<Person, Person> 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<String, Person> collect = persons.stream()
        .collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(identity, mapper, BinaryOperator.maxBy(byHeight))));

Data API(日期相关API)

Clock

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:54

Duration: 用秒和纳秒表示时间的数量(长短),用于计算两个日期的“时间”间隔

LocalTime time2 = LocalTime.of(23, 59, 59);
Duration duration = Duration.between(time, time2);
long seconds = duration.getSeconds();  // 24065

Period: 用于计算两个“日期”间隔

LocalDate date2 = LocalDate.of(2018, 10, 31);
Period period = Period.between(date, date2);
int monthsBetween = period.getMonths(); // 1
// 1   这里的天数,只是单纯地天数计算,不包含月份
int daysBetween = period.getDays(); 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值