长大后想做什么?做回小孩!

0%

Java8特性(二)

Java8特性—流式语法总结过Java8流式语法相关特性:函数式接口、默认方法、Lambda表达式、方法引用。

这次学一下Java8的其他特性:Stream API、Optional类、Date-Time API、Base64编码、Nashorn。

——转载自RUNOOB孤独烟回梦游先

[TOC]

Stream API

Java8API添加了一个新的抽象成为流Stream,可以让你以一种声明的方式处理数据。

Stream使用一种类似于SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达的高阶抽象。

  • StreamAPI可以极大提高Java程序员的生产力,让程序员写出高效率,干净,简洁的代码。

这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的结点上进行处理,比如筛选,排序,聚合等。。。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

流程:stream of elements —–> filter-> sorted-> map-> collect

以上的流程转换为Java代码:

1
2
3
4
5
6
List<Integer> transactionsIds = 
widgets.stream()
.filter(b -> b.getColor() == RED)
.sorted((x,y) -> x.getWeight() - y.getWeight())
.mapToInt(Widget::getWeight)
.sum();

什么是Stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成的一个队列。Java中的Steam并不会存储元素,而是按需计算。
  • 数据源 流的来源。可以是集合、数组、I/O channel、产生器geanerator等等。
  • 聚合操作 类似SQL语句一样的操作,比如filter、map、reduce、find、match、sorted等。和以前的Collection操作不同,Stream操作还有两个基础的特征:
    • Pipelining:中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格(fluent style)。这样做可以对操作进行优化,比如延迟执行(laziness)和短路(shor-circuiting)。
    • 内部迭代:以前对集合遍历都是通过Iterator或者For-Each的方式,显式的在集合外部进行迭代,这叫做外部迭代。Stream提供了内部迭代的方式,通过访问者模式(Visitor)实现。

生成流

在Java8中,集合接口有两个方法来生成流:

  • stream()—为集合创建串行流。
  • parallel Stream()—为集合创建并行流。
1
2
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

forEach

Stream提供了新的方法“forEach”来迭代流中的每个数据。以下代码片段使用forEach输出了10个随机数:

1
2
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

map

map方法用于映射每个元素到对应的结果,以下代码片段使用map输出了元素对应的平方数:

1
2
3
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取对应的平方数
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

filter

filter方法用于通过设置的条件过滤出元素。以下代码片段使用filter方法过滤出空字符串:

1
2
3
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
int count = strings.stream().filter(string -> string.isEmpty()).count();

limit

limit方法用于获取指定数量的流。以下代码片段使用limit方法打印出10条数据:

1
2
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

sorted

sorted方法用于对流进行排序。以下代码片段使用sorted方法对输出的10个随机数进行排序:

1
2
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);

并行(parallel)程序

parallelStream是流并行处理程序的替代方法。以下实例我们使用parallelStream来输出空字符串的数量:

1
2
3
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
int count = strings.parallelStream().filter(string -> string.isEmpty()).count();

Collectors

Collectors类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors可用于返回列表或字符串:

1
2
3
4
5
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("筛选列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);

统计

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

1
2
3
4
5
6
7
8
9
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
//mapToInt((x) -> x)返回一个IntStream对象
//summaryStatistics()返回一个IntSummaryStatistics对象
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();

System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());

Optional类

Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Optional是个容器:他可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

Optional类的引用很好的解决空指针异常。

类声明

java.util.Optinoal<T>类的声明:

1
public final class Optional<T>extends Object

类方法

注意: 这些方法是从 java.lang.Object 类继承来的。

序号 方法 & 描述
1 *static <T> Optional<T> empty() * 返回空的 Optional 实例。
2 boolean equals(Object obj) 判断其他对象是否等于 Optional。
3 *Optional<T> filter(Predicate<? super <T> predicate) *如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。
4 Optional<T> filter(Predicate<? super <T> predicate) 如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。
5 *T get() * 如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException,使用get()方法前,最好进行isPresent()校验。
6 int hashCode() 返回存在值的哈希码,如果值不存在 返回 0。
7 void ifPresent(Consumer<? super T> consumer) 如果值存在则使用该值调用 consumer
8 boolean isPresent() 如果值存在则方法会返回true,否则返回 false。
9 <U>Optional<U> map(Function<? super T,? extends U> mapper) 如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。
10 static <T> Optional<T> of(T value) 返回一个指定非null值的Optional。
11 *static <T> Optional<T> ofNullable(T value) *如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
12 T orElse(T other) 如果存在该值,返回值, 否则返回 other。
13 T orElseGet(Supplier<? extends T> other) 如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。
14 *<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) *如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常。
15 String toString() 返回一个Optional的非空字符串,用来调试。

1、Optional(T value),empty(),of(T value),ofNullable(T value)

这四个函数之间具有相关性,因此放在一组进行记忆。

先说明一下,Optional(T value)即构造函数,它是private权限的,不能由外部调用的。其余三个函数是public权限,供用户调用。那么Optional的本质就是内部储存了一个真实的值,在构造的时候,就直接判断其值是否为空。直接上Optional(T value)构造函数的源码,如下图:

nxrlVO.png

of(T value)的源码:

1
2
3
public static <T> Optional<T> of(T var0) {
return new Optional(var0);
}

其内部直接调用了构造函数,根据构造函数可以得到两个结论:

  1. 通过of(T value)函数所构造出的Optional对象,当value属性值为空时,依然会报出NPE。
  2. 通过of(T value)函数所构造出的Optional对象,当value属性值不为空是,能正常构造Optional对象。

除此之外,Optional内部还维护一个value为null的对象,大概长下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>();
private Optional() {
this.value = null;
}
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
//省略...
}

那么,empty()的作用就是返回EMPTY对象。

说前面三个方法都是为了铺垫,可以说ofNullable(T value)的作用了,上源码:

1
2
3
public static <T> Optional<T> ofNullable(T var0) {
return var0 == null ? empty() : of(var0);
}

显而易见,相比较of(T value)的区别就是,当value值为null时,of(T value)会报NPE异常;而ofNullable(T value)不会throw Exception,ofNullable(T value)直接返回一个EMPTY对象。

那是不是意味着,我们在项目中只用ofNullable(T value)函数而不需要of(T value)函数了呢?

不是的,既然存在那么自然有存在的价值。当我们在运行过程中,不想隐藏NPE,而是要立刻报告,这种情况下就用of(T value)函数。但是不得不说,这种场景很少很少,博主也仅在写junit测试用例中用到过此函数。

2、orElse(T other)、orElseGet(Supplier<? extendsT> other)、orElseThrow(Supplier<? extends X> exceptionSupplier)

这三个函数放一组进行记忆,都是在构造函数传入的value值为null时,进行调用的。orElseorElseGet的用法如下所示,相当于value值为null时,给予一个默认值:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test() {
User user = null;
user = Optional.ofNullable(user).orElse(createUser());
user = Optional.ofNullable(user).orElseGet(() -> createUser());

}

public User createUser(){
User user = new User();
user.setName("zhangsan");
return user;
}

这两个函数的区别:当user值不为null时,orElse函数依然会执行createUser()函数,而orElseGet函数并不会执行createUser()函数。

至于orElseThrow,就是value值为null时,直接抛一个异常出去,用法如下所示

1
2
User user = null;
Optional.ofNullable(user).orElseThrow(()->new Exception("用户不存在"));

3、map(Function <? super T,? extends U> mapper)和flatMap(Function <? super T,Optional<U>> mapper)

这两个函数放在一组记忆,这两个函数做的是转换值的操作。上源码:

1
2
3
4
5
6
7
8
9
10
11
12
public final class Optional<T> {
public <U> Optional<U> map(Function<? super T, ? extends U> var1) {
Objects.requireNonNull(var1);
return !this.isPresent() ? empty() : ofNullable(var1.apply(this.value));
}

public <U> Optional<U> flatMap(Function<? super T, Optional<U>> var1) {
Objects.requireNonNull(var1);
return !this.isPresent() ? empty() : (Optional)Objects.requireNonNull(var1.apply(this.value));
}
//省略。。。
}

这两个函数,在函数体的上的区别不大,主要的区别就是入参,map函数所接受的入参类型为Function<? supper T,? extends U>,而flapMap的入参类型为Function<? super T,Optional<U>>。

具体用法上,对于map而言:

如果User结构是下面这样

1
2
3
4
5
6
public class User {
private String name;
public String getName() {
return name;
}
}

这时候取name的写法如下所示:

1
String name = Optional.ofNullable(user).map(u-> u.getName()).get();

对于flatmap而言则是这样:

如果User结构是下面这样的

1
2
3
4
5
6
public class User {
private String name;
public Optional<String> getName() {
return Optional.ofNullable(name);
}
}

这时候取name的写法如下所示

1
String name = Optional.ofNullable(user).flatMap(u->u.getName()).get();

4、isPresent()和ifPresent(Consumer<? super T> consumer)

这两个函数放在一起记忆,isPresent即判断value值是否为空,而ifPresent就是在value值不为空时,做一些操作。上源码:

1
2
3
4
5
6
7
8
9
public boolean isPresent() {
return this.value != null;
}

public void ifPresent(Consumer<? super T> var1) {
if (this.value != null) {
var1.accept(this.value);
}
}

注意:不要进行下面这种鸡肋的操作

1
2
3
4
5
6
7
8
if (user != null){
// TODO: do something
}
//写成
User user = Optional.ofNullable(user);
if (Optional.isPresent()){
// TODO: do something
}

因为这样写,代码结构依然丑陋。后面会给出正确写法

至于ifPresent(Consumer <?super T> consumer),用法也很简单,如下所示:

1
2
3
Optional.ofNullable(user).ifPresent(u->{
// TODO: do something
});

5、filter(Predicate<? super T> predicate)

直接上源码:

1
2
3
4
5
6
7
8
9
10
public final class Optional<T> {
//省略....
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
}

filter方法接受一个Predicate来对Optional中包含的值进行过滤,如果包含的值满足条件,那么还是返回这个Optional;否则返回Optional.empty。

用法如下:

1
Optional<User> user1 = Optional.ofNullable(user).filter(u -> u.getName().length()<6);

如上所示,如果user的name的长度是小于6的,则返回。如果是大于6的,则返回一个EMPTY对象。

实战使用

例一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//以前写法
public String getCity(User user) throws Exception{
if(user!=null){
if(user.getAddress()!=null){
Address address = user.getAddress();
if(address.getCity()!=null){
return address.getCity();
}
}
}
throw new Excpetion("取值错误");
}
//JAVA8写法
public String getCity(User user) throws Exception{
return Optional.ofNullable(user)
.map(u-> u.getAddress())
.map(a->a.getCity())
.orElseThrow(()->new Exception("取指错误"));
}

例二

1
2
3
4
5
6
7
8
9
//以前写法
if(user!=null){
dosomething(user);
}
//JAVA8写法
Optional.ofNullable(user)
.ifPresent(u->{
dosomething(u);
});

例三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//以前写法
public User getUser(User user) throws Exception{
if(user!=null){
String name = user.getName();
if("zhangsan".equals(name)){
return user;
}
}else{
user = new User();
user.setName("zhangsan");
return user;
}
}
//java8写法
public User getUser(User user) {
return Optional.ofNullable(user)
.filter(u->"zhangsan".equals(u.getName()))
.orElseGet(()-> {
User user1 = new User();
user1.setName("zhangsan");
return user1;
});
}

还有很多应用的例子,就不一一列举了。不过采用这种链式编程,虽然代码优雅了。但是,逻辑性没那么明显,可读性有所下降,大家项目中看情况酌情使用。


Date-Time API

Java8发布新的Date-Time API(JSR 310)来进一步加强对日期和时间的处理。

在旧版的Java中,日期时间API存在诸多问题,其中有:

  • 非线程安全:java.util.Date是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  • 设计很差:Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有问题。

Java8在java.time包下提供了很多的新的API。以下为两个比较重要的API:

  • Local(本地):简化了日期时间的处理,没有时区的问题。
  • Zoned(时区):通过制定的时区处理日期时间。

新的java.time包涵盖了所有处理日期、时间、日期/时间、时区、时刻(instants)、过程(during)与时钟(clock)的操作。

常用、重要对象介绍:

ZoneId:时区ID,用来确定Instant和LocalDateTime互相转换的规则。

Instant:用来表示时间线上的一个点(瞬时)。

LocalDate:表示没有时区的日期,LocalDate是不可变并且线程安全的。

LocalTime:表示没有时区的时间,LocalTime是不可变并且线程安全的。

LocalDateTime:表示没有时区的日期时间,LocalDateTime是不可变并且线程安全的。

Clock:用于访问当前时刻、日期、时间,用到时区。

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

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

其中,LocalDate、LocalTime、LocalDateTime都是新API里的基础对象,绝大多数操作都是围绕这三个对象来进行的,有必要啰嗦一遍:

LocalDate:只含年 月 日的日期对象。

LocalTime:只含时 分 秒的时间对象。

LocalDateTime:同时含有年 月 日 时 分 秒的日期对象。

直接上示例:

1.获取当前时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime(){
LocalDateTime localDateTime = LocalDateTime.now();
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
System.out.println(localDateTime);
System.out.println(localDate);
System.out.println(localTime);
}
}
//2019-09-22T12:22:10.214
//2019-09-22
//12:22:10.215

2.根据指定日期/时间创建对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime(){
LocalDateTime localDateTime = LocalDateTime.of(2018,8,8,8,8,8);
LocalDate localDate = LocalDate.of(2018,8,8);
LocalTime localTime = LocalTime.of(8,8,8);
System.out.println(localDateTime);
System.out.println(localDate);
System.out.println(localTime);
}
}
//2018-08-08T08:08:08
//2018-08-08
//08:08:08

3.日期时间的加减:

注意:对于LocalDate,只有精度大于等于日的加减,如年,月,日;对于LocalTime,只有精度小于等于时加减,如时,分,秒,纳秒;对于LocalDateTime,则可以进行任意精度的时间相加减;否则会抛出异常:java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: XXX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime(){
LocalDateTime localDateTime = LocalDateTime.now();
//以下方法的参数都是long型,返回值都是LocalDateTime
LocalDateTime plusYearsResult = localDateTime.plusYears(2L);
LocalDateTime plusMonthsResult = localDateTime.plusMonths(4L);
LocalDateTime plusDaysResult = localDateTime.plusDays(7L);
LocalDateTime plusHoursResult = localDateTime.plusHours(2L);
LocalDateTime plusMinutesResult = localDateTime.plusMinutes(10L);
LocalDateTime plusSecondsResult = localDateTime.plusSeconds(10L);
System.out.println("当前时间是 : " + localDateTime + "\n"
+ "当前时间加2年后为 : " + plusYearsResult + "\n"
+ "当前时间加3个月后为 : " + plusMonthsResult + "\n"
+ "当前时间加7日后为 : " + plusDaysResult + "\n"
+ "当前时间加2小时后为 : " + plusHoursResult + "\n"
+ "当前时间加10分钟后为 : " + plusMinutesResult + "\n"
+ "当前时间加10秒后为 : " + plusSecondsResult + "\n"
);
//也可以以另一种方式来相加减日期,即plus(long amountToAdd, TemporalUnit unit)
// 参数1 : 相加的数量, 参数2 : 相加的单位
LocalDateTime nextMonth = localDateTime.plus(1, ChronoUnit.MONTHS);
LocalDateTime nextYear = localDateTime.plus(1, ChronoUnit.YEARS);
LocalDateTime nextWeek = localDateTime.plus(1, ChronoUnit.WEEKS);

System.out.println("now : " + localDateTime + "\n"
+ "nextYear : " + nextYear + "\n"
+ "nextMonth : " + nextMonth + "\n"
+ "nextWeek :" + nextWeek + "\n"
);
//日期的减法用法一样,在此不再举例用minus()方法或者minusXXX()方法
LocalDateTime minus = localDateTime.minus(1L, ChronoUnit.YEARS);
System.out.println(minus);
}
}
//当前时间是 : 2019-09-22T13:01:41.773
//当前时间加2年后为 : 2021-09-22T13:01:41.773
//当前时间加3个月后为 : 2020-01-22T13:01:41.773
//当前时间加7日后为 : 2019-09-29T13:01:41.773
//当前时间加2小时后为 : 2019-09-22T15:01:41.773
//当前时间加10分钟后为 : 2019-09-22T13:11:41.773
//当前时间加10秒后为 : 2019-09-22T13:01:51.773

//now : 2019-09-22T13:01:41.773
//nextYear : 2020-09-22T13:01:41.773
//nextMonth : 2019-10-22T13:01:41.773
//nextWeek :2019-09-29T13:01:41.773

//2018-09-22T13:01:41.773

4.将年、月、日、等修改为指定的值,并返回新的日期/时间对象:

注意:在指定日期/时间时要注意取值范围,日期还要注意日期基础上是否为闰年(leap year),如果超出取值范围会报出:java.time.DateTimeException: Invalid value for DayOfYear (valid values 1 - 365/366): XXX,如果没有注意是否为闰年去特殊范围值的时候会报出:java.time.DateTimeException: Invalid date ‘DayOfYear XXX’ as ‘XXXX’ is not a leap year

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime(){
LocalDate localDate = LocalDate.now();
//当前时间基础上,指定本年当中的第几天,取值范围为1-365,366
LocalDate withDayOfYearResult = localDate.withDayOfYear(200);
//当前时间基础上,指定本月当中的第几天,取值范围为1-29,30,31
LocalDate withDayOfMonthResult = localDate.withDayOfMonth(5);
//当前时间基础上,直接指定年份
LocalDate withYearResult = localDate.withYear(2017);
//当前时间基础上,直接指定月份
LocalDate withMonthResult = localDate.withMonth(5);
System.out.println("当前时间是 : " + localDate + "\n"
+ "指定本年当中的第200天 : " + withDayOfYearResult + "\n"
+ "指定本月当中的第5天 : " + withDayOfMonthResult + "\n"
+ "直接指定年份为2017 : " + withYearResult + "\n"
+ "直接指定月份为5月 : " + withMonthResult + "\n"
);
}
}
//当前时间是 : 2019-09-22
//指定本年当中的第200天 : 2019-07-19
//指定本月当中的第5天 : 2019-09-05
//直接指定年份为2017 : 2017-09-22
//直接指定月份为5月 : 2019-05-22

5.获取日期的年月日周时分秒:

直接用getDayOfWeek或者getMonth取得的是枚举类型(enum)的英文单词,可以用可以再用Week或者Month枚举类提供的getValue方法转换为数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime(){
LocalDateTime localDateTime = LocalDateTime.now();
int dayOfYear = localDateTime.getDayOfYear();
int dayOfMonth = localDateTime.getDayOfMonth();
DayOfWeek dayOfWeek = localDateTime.getDayOfWeek();
System.out.println("今天是" + localDateTime + "\n"
+ "本年当中第" + dayOfYear + "天" + "\n"
+ "本月当中第" + dayOfMonth + "天" + "\n"
+ "本周中星期" + dayOfWeek.getValue() + "-即" + dayOfWeek + "\n");

//获取当天时间的年月日时分秒
int year = localDateTime.getYear();
Month month = localDateTime.getMonth();
int day = localDateTime.getDayOfMonth();
int hour = localDateTime.getHour();
int minute = localDateTime.getMinute();
int second = localDateTime.getSecond();
System.out.println("今天是" + localDateTime + "\n"
+ "年 : " + year + "\n"
+ "月 : " + month.getValue() + "-即 "+ month + "\n"
+ "日 : " + day + "\n"
+ "时 : " + hour + "\n"
+ "分 : " + minute + "\n"
+ "秒 : " + second + "\n"
);
}
}
//今天是2019-09-22T13:30:03.943
//本年当中第265天
//本月当中第22天
//本周中星期7-即SUNDAY

//今天是2019-09-22T13:30:03.943
//年 : 2019
//月 : 9-即 SEPTEMBER
//日 : 22
//时 : 13
//分 : 30
//秒 : 3

6.时间/日期前后的比较与判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime(){
//判断两个时间点的前后
LocalDate localDate1 = LocalDate.of(2019, 8, 8);
LocalDate localDate2 = LocalDate.of(2018, 8, 8);
boolean date1IsBeforeDate2 = localDate1.isBefore(localDate2);
System.out.println("date1IsBeforeDate2 : " + date1IsBeforeDate2);
//API还提供了isAfter()、isEqual(),顾名思义就不必举例了
}
}
//date1IsBeforeDate2 : false

7.判断是否为闰年:

1
2
3
4
5
6
7
8
9
10
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime(){
LocalDate now = LocalDate.now();
System.out.println("now : " + now + ", is leap year ? " + now.isLeapYear());
}
}
//now : 2019-09-22, is leap year ? false

8.java8时钟 : clock()

1
2
3
4
5
6
7
8
9
10
11
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime(){
//返回当前时间,根据系统时间和UTC
Clock clock = Clock.systemUTC();
System.out.println(clock);
}
}
//SystemClock[Z]

9.时间戳:

事实上Instant就是Java8之前的Date,可以使用以下两个类中的方法在这两个类型之间进行转换,比如Date.from(Instant)就是用来把Instant转换成java.util.date的,而new Date().toInstant()就是将Date转换为Instant的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime(){
Instant instant = Instant.now();
System.out.println(instant);
Date date = Date.from(instant);
Instant instant2 = date.toInstant();
System.out.println(date);
System.out.println(instant2);
}
}
//2019-09-22T05:51:30.257Z
//Sun Sep 22 13:51:30 CST 2019
//2019-09-22T05:51:30.257Z

10.计算时间/日期间隔:

Duration:用于计算两个“时间”间隔

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

注意:当获取两个日期的间隔时,并不是单纯的年月日对应的数字相加减,而是会先算出具体差多少天,在折算成相差几年几月几日的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime(){
//计算两个日期的日期间隔-年月日
LocalDate date1 = LocalDate.of(2018, 2, 13);
LocalDate date2 = LocalDate.of(2017, 3, 12);
//内部是用date2-date1,所以得到的结果是负数
Period period = Period.between(date1, date2);
System.out.println("相差年数 : " + period.getYears());
System.out.println("相差月数 : " + period.getMonths());
System.out.println("相差日数 : " + period.getDays());
//还可以这样获取相差的年月日
System.out.println("-------------------------------");
long years = period.get(ChronoUnit.YEARS);
long months = period.get(ChronoUnit.MONTHS);
long days = period.get(ChronoUnit.DAYS);
System.out.println("相差的年月日分别为 : " + years + "," + months + "," + days);

//计算两个时间的间隔
System.out.println("-------------------------------");
LocalDateTime date3 = LocalDateTime.now();
LocalDateTime date4 = LocalDateTime.of(2018, 1, 13, 22, 30, 10);
Duration duration = Duration.between(date3, date4);
System.out.println(date3 + " 与 " + date4 + " 间隔 " + "\n"
+ " 天 :" + duration.toDays() + "\n"
+ " 时 :" + duration.toHours() + "\n"
+ " 分 :" + duration.toMinutes() + "\n"
+ " 毫秒 :" + duration.toMillis() + "\n"
+ " 纳秒 :" + duration.toNanos() + "\n"
);
}
}
//相差年数 : 0
//相差月数 : -11
//相差日数 : -1
//-------------------------------
//相差的年月日分别为 : 0,-11,-1
//-------------------------------
//2019-09-22T13:58:43.623 与 2018-01-13T22:30:10 间隔
// 天 :-616
// 时 :-14799
// 分 :-887968
// 毫秒 :-53278113623
// 纳秒 :-53278113623000000

11.当计算程序的运行时间时,应当使用时间戳Instant:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime(){
Instant ins1 = Instant.now();
for (int i = 0; i < 1000000; i++) {
//循环一百万次
}
Instant ins2 = Instant.now();
Duration duration = Duration.between(ins1, ins2);
System.out.println("程序运行耗时为 : " + duration.toMillis() + "毫秒");
}
}
//程序运行耗时为 : 1毫秒

12.时间日期的格式化(格式化后返回的类型是String):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime(){
//使用jdk自身配置好的日期格式
DateTimeFormatter formatter1 = DateTimeFormatter.ISO_DATE_TIME;
LocalDateTime date1 = LocalDateTime.now();
System.out.println("LocalDateTime:"+date1);
//反过来调用也可以 : date1.format(formatter1);
String date1Str = formatter1.format(date1);
System.out.println("jdk自身配置好的日期格式:"+date1Str);
//使用自定义的日期格式
DateTimeFormatter dtf= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatDateTime = date1.format(dtf);
System.out.println("自定义日期格式:"+formatDateTime);
}
}
//LocalDateTime:2019-09-22T14:21:21.762
//jdk自身配置好的日期格式:2019-09-22T14:21:21.762
//自定义日期格式:2019-09-22 14:21:21

注意:格式的写法必须与字符串的形式一样:如:

2018-01-13 21:27:30 对应 yyyy-MM-dd HH:mm:ss

20180113213328 对应 yyyyMMddHHmmss

否则会有运行时异常!

看一个例子,思考原因:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime() {
//新的格式化API中,格式化后的结果都默认是String,有时我们也需要返回经过格式化的同类型对象
LocalDateTime ldt1 = LocalDateTime.now();
System.out.println(ldt1);
DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String temp = dtf1.format(ldt1);
LocalDateTime formatedDateTime = LocalDateTime.parse(temp, dtf1);
System.out.println(formatedDateTime);
}
}
//2019-09-22T14:34:24.653
//2019-09-22 14:39:54
//2019-09-22T14:34:24

记住:得到的最终结果都是类似2019-09-22T14:34:24的格式因为在输出LocalDateTime对象时,会调用其重写的toString方法,读者可以读读源码就可知道,对于LocalDateTime对象,无论是String类型的时间日期转为LocalDatetime对象,还是LocalDateTime对象转为格式化后的LocalDateTime对象,得到的最终输出都是:yyyy-MM-ddThh:mm:ss 这样的格式!

至于为什么这样,应该是为了符合国际化吧,也正因为如此,才有了LocalDate和LocalTime,以便于开发者有更多的选择。

但上述说法仅限于LocalDateTime对象,而如果是LocalDateTime对象格式化转为String对象是可以任意格式的,如上文自定义格式示例!

13.long毫秒值转换为日期:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo {
public static void main(String[] args) {
Demo.testZonedDateTime();
}
public static void testZonedDateTime() {
System.out.println("---------long毫秒值转换为日期---------");
DateTimeFormatter df= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String longToDateTime = df.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(System.currentTimeMillis()), ZoneId.of("Asia/Shanghai")));
System.out.println(longToDateTime);
}
}
//---------long毫秒值转换为日期---------
//2019-09-22 14:41:25

Base64编码

在Java8中,Base64编码已经成为Java类库的标准。

Java8内置了Base64编码的编码器和解码器。

Base64工具类提供了一套静态方法获取下面三种Base64编解码器:

  • 基本:输出被映射到一组字符A-Z a-z 0-9+/,编码不添加任何行标,输出的解码仅支持A-Z a-z 0-9+/。
  • URL:输出映射到一组字符A-Z a-z 0-9+_,输出时URL和文件。
  • MIME:输出映射到MIME友好格式。输出每行不超过76字符,并且使用’\r’并跟随’\n’作为分割。编码输出最后没有行分割。

内嵌类

序号 内嵌类 & 描述
1 static class Base64.Decoder 该类实现一个解码器用于,使用 Base64 编码来解码字节数据。
2 static class Base64.Encoder 该类实现一个编码器,使用 Base64 编码来编码字节数据。

方法

序号 方法名 & 描述
1 static Base64.Decoder getDecoder() 返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。
2 *static Base64.Encoder getEncoder() * 返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。
3 *static Base64.Decoder getMimeDecoder() * 返回一个 Base64.Decoder ,解码使用 MIME 型 base64 编码方案。
4 *static Base64.Encoder getMimeEncoder() * 返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案。
5 static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) 返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案,可以通过参数指定每行的长度及行的分隔符。
6 static Base64.Decoder getUrlDecoder() 返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。
7 static Base64.Encoder getUrlEncoder() 返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Java8Tester {
public static void main(String args[]){
try {

// 使用基本编码
String base64encodedString = Base64.getEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
System.out.println("Base64 编码字符串 (基本) :" + base64encodedString);

// 解码
byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);

System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8"));
base64encodedString = Base64.getUrlEncoder().encodeToString("runoob?java8".getBytes("utf-8"));
System.out.println("Base64 编码字符串 (URL) :" + base64encodedString);

StringBuilder stringBuilder = new StringBuilder();

for (int i = 0; i < 10; ++i) {
stringBuilder.append(UUID.randomUUID().toString());
}

byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8");
String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
System.out.println("Base64 编码字符串 (MIME) :" + mimeEncodedString);

}catch(UnsupportedEncodingException e){
System.out.println("Error :" + e.getMessage());
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
输出结果:
$ javac Java8Tester.java
$ java Java8Tester
原始字符串: runoob?java8
Base64 编码字符串 (URL) :VHV0b3JpYWxzUG9pbnQ_amF2YTg=
Base64 编码字符串 (MIME) :M2Q4YmUxMTEtYWRkZi00NzBlLTgyZDgtN2MwNjgzOGY2NGFlOTQ3NDYyMWEtZDM4ZS00YWVhLTkz
OTYtY2ZjMzZiMzFhNmZmOGJmOGI2OTYtMzkxZi00OTJiLWEyMTQtMjgwN2RjOGI0MTBmZWUwMGNk
NTktY2ZiZS00MTMxLTgzODctNDRjMjFkYmZmNGM4Njg1NDc3OGItNzNlMC00ZWM4LTgxNzAtNjY3
NTgyMGY3YzVhZWQyMmNiZGItOTIwZi00NGUzLTlkMjAtOTkzZTI1MjUwMDU5ZjdkYjg2M2UtZTJm
YS00Y2Y2LWIwNDYtNWQ2MGRiOWQyZjFiMzJhMzYxOWQtNDE0ZS00MmRiLTk3NDgtNmM4NTczYjMx
ZDIzNGRhOWU4NDAtNTBiMi00ZmE2LWE0M2ItZjU3MWFiNTI2NmQ2NTlmMTFmZjctYjg1NC00NmE1
LWEzMWItYjk3MmEwZTYyNTdk

Nashorn

这部分仅仅是科普一下。。。知道有这么个东西,具体细节等到后序用到时再记录。。。

Nashorn一个JavaScript引擎。

从JDK1.8开始,Nashorn取代Rhino(JDK1.6,JDK1.7)成为Java嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1规范以及一些扩展。它使用基于JSP 292的新语言特性,其中包含在JDK7中引入的invokedynamic,将JavaScript编译成Java字节码。

与先前的Rhino实现相比,这带来了2到10倍的性能提升。

jjs

jjs是个基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。jjs可以提供交互式编程体验。

Java中调用JavaScript

使用ScriptEngineManager类,JavaScript代码可以在Java中执行。