【Java基础系列教程】第二十六章 JDK8新特性_Optional类、新日期时间API、Base64

  • Post author:
  • Post category:java


一、Optional类

Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException),提供了一些的方法代替过去的if-else处理逻辑,并与Stream流结合提供一致性的函数式编程。

【推荐】防止NPE,是程序员的基本修养,注意 NPE 产生的场景:

1、返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。

反例: public int f() { return Integer 对象}, 如果为 null ,自动解箱抛 NPE 。

2、数据库的查询结果可能为 null。

3、集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。

4、远程调用返回对象时,一律要求进行空指针判断,防止NPE。

5、对于 Session 中获取的数据,建议 NPE 检查,避免空指针。

6、级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE 。

1.1 API

1.1.1 类声明

以下是一个java.util.Optional<T>类的声明:

public final class Optional<T> extends Object

1.1.2 类方法

static <T> Optional<T> empty()    返回一个空的 Optional实例。

boolean equals(Object obj)    指示某个其他对象是否等于此可选项。

Optional<T> filter(Predicate<? super T> predicate)    如果一个值存在,并且该值给定的谓词相匹配时,返回一个 Optional描述的值,否则返回一个空的 Optional 。

<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)    如果一个值存在,应用提供的 Optional映射函数给它,返回该结果,否则返回一个空的 Optional 。

T get()    如果 Optional中有一个值,返回值,否则抛出 NoSuchElementException 。

int hashCode()    返回当前值的哈希码值(如果有的话),如果没有值,则返回0(零)。

void ifPresent(Consumer<? super T> consumer)    如果存在值,则使用该值调用指定的消费者,否则不执行任何操作。

boolean isPresent()    返回 true如果存在值,否则为 false 。

<U> Optional<U> map(Function<? super T,? extends U> mapper)    如果存在一个值,则应用提供的映射函数,如果结果不为空,则返回一个 Optional结果的 Optional 。

static <T> Optional<T> of(T value)    返回具有 Optional的当前非空值的Optional。

static <T> Optional<T> ofNullable(T value)    返回一个 Optional指定值的Optional,如果非空,则返回一个空的 Optional 。

T orElse(T other)    返回值如果存在,否则返回 other 。

T orElseGet(Supplier<? extends T> other)    返回值(如果存在),否则调用 other并返回该调用的结果。

<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)    返回包含的值(如果存在),否则抛出由提供的供应商创建的异常。

String toString()    返回此可选的非空字符串表示,适用于调试。

1.2 获取Optional对象

我们可以看到Optional总共也就10+个方法,其中有几个static方法。并且Optional的构造方法是private,不能new出来。

所以我们一般用这几个static来获取Optional的对象。

@Test
public void test1() {
    // 1、static <T> Optional<T> empty()    返回空的 Optional 实例
    Optional<Object> empty = Optional.empty();
    // Optional重写了toString(),return value != null ? String.format("Optional[%s]", value) : "Optional.empty";
    System.out.println(empty); // Optional.empty

    // 2、static <T> Optional<T> of(T value) 返回一个指定非null值的Optional。如果传递的参数是 null,抛出异常 NullPointerException
    Optional<String> hello = Optional.of("Hello"); // Optional[Hello]
    System.out.println(hello);
    // 下面这行代码会抛出异常
    // Optional<String> nullOpt = Optional.of(null);

    // 3、static <T> Optional<T> ofNullable(T value) 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
    Optional<Object> nullOpt = Optional.ofNullable(null);
    System.out.println(nullOpt); // Optional.empty

    Optional<String> abc = Optional.ofNullable("ABC");
    System.out.println(abc); // Optional[ABC]
}

很明显 of 对null对象没有做任何处理,ofNullable才做了处理。所以当我们不知道传入的对象是否为null的时候,我们应该选择用 ofNullable来做处理。

1.3 判断和获取

@Test
public void test2() {
    List<String> strs = new ArrayList<>();
    strs.add("xx");
    strs.add("yy");
    strs.add(null);

    for (String str : strs) {
        if(str != null){
            System.out.println(str.toUpperCase());
        }else {
            System.out.println("值为null");
        }
    }

    System.out.println("-------------------------");
    //boolean isPresent()	返回 true如果存在值,否则为 false 。
    Optional<String> stringOptional = Optional.ofNullable("Hello");
    Optional<Integer> integerOptional = Optional.ofNullable(123);
    Double dou = null;
    Optional<Double> doubleOptional = Optional.ofNullable(dou);
    System.out.println("stringOptional是否存在值:" + stringOptional.isPresent());
    System.out.println("integerOptional是否存在值:" + integerOptional.isPresent());
    System.out.println("doubleOptional是否存在值:" + doubleOptional.isPresent());

    System.out.println("---------------------------------");
    //void ifPresent(Consumer<? super T> consumer)	如果存在值,则使用该值调用指定的消费者,否则不执行任何操作。
    stringOptional.ifPresent(System.out::println);
    integerOptional.ifPresent(System.out::println);
    doubleOptional.ifPresent(System.out::println);

    System.out.println("---------------------------------");
    //T orElse(T other)	返回值如果存在,否则返回 other 。
    System.out.println(stringOptional.orElse("值为null"));
    System.out.println(integerOptional.orElse(0));
    System.out.println(doubleOptional.orElse(0.0));

    System.out.println("-------------------------");
    for (String str : strs) {
        System.out.println(Optional.ofNullable(str).orElse("值为null"));
    }

    System.out.println("-------------------------");
    //T get()	如果 Optional中有一个值,返回值,否则抛出 NoSuchElementException 。
    System.out.println(stringOptional.get());
    //System.out.println(doubleOptional.get());
}

1.4 转换

需求:如果传递的学生对象不为null,则获取学生姓名,否则返回unKnown。

import java.util.Optional;

public class Test {
    public static void main(String[] args) {
        Student stu1 = new Student("张三", 12);
        System.out.println(getName(stu1)); // 张三

        Student stu2 = new Student(null, 12);
        System.out.println(getName(stu2)); // unKnown
    }

    private static String getName(Student student) {
        String name = Optional.ofNullable(student).map(x -> x.getName())
                .orElse("unKnown");
        return name;
    }
}

class Student {
    private String name;
    private Integer age;

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

其他方法类似,不再演示;

二、日期时间 API

2.1 概述

如果我们可以跟别人说:“我们在1502643933071见面,别晚了!”那么就再简单不过了。但是我们希望时间与昼夜和四季有关,于是事情就变复杂了。JDK 1.0中包含了 一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar并不比Date好多少。它们面临的问题是:

可变性:像日期和时间这样的类应该是不可变的。无论是Date还是Calendar,修改的都是原日期和时间对象;

偏移性:Date中的年份是从1900开始的,而月份都从0开始。

格式化:格式化只对Date有用,Calendar则不行。

设计很差:Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。

此外,它们也不是线程安全的;不能处理闰秒等。

闰秒:闰秒,是指为保持协调世界时接近于世界时时刻,由国际计量局统一规定在年底或年中(也可能在季末)对协调世界时增加或减少1秒的调整。由于地球自转的不均匀性和长期变慢性(主要由潮汐摩擦引起的),会使世界时(民用时)和原子时之间相差超过到±0.9秒时,就把协调世界时向前拨1秒(负闰秒,最后一分钟为59秒)或向后拨1秒(正闰秒,最后一分钟为61秒); 闰秒一般加在公历年末或公历六月末。

总结:对日期和时间的操作一直是Java程序员最痛苦的地方之一。


现实生活的世界里,时间是不断向前的,如果向前追溯时间的起点,可能是宇宙出生时,又或是是宇宙出现之前,但肯定是我们目前无法找到的,我们不知道现在距离时间原点的精确距离。所以我们要表示时间,就需要人为定义一个原点。

原点被规定为,格林威治时间(GMT)1970年1月1日的午夜为起点,至于为啥是GMT时间,大概是因为本初子午线在那的原因吧。

Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理,第三次引入的API是成功的,并且Java 8中引入的java.time API 已经纠正了过去的缺陷,将来很长一段时间内它都会为我们服务。

Java 8 吸收了 Joda-Time 的精华,以一个新的开始为 Java 创建优秀的 API。 新的 java.time包 中包含了所有关于本地日期(LocalDate)、本地时间 (LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime) 和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了日期时间和本地化的管理。

新时间日期API常用的包:

java.time            包含值对象的基础包;

java.time.chrono    提供对不同的日历系统的访问;

java.time.format    格式化和解析时间和日期;

java.time.temporal    包括底层框架和扩展特性;

java.time.zone        包含时区支持的类;

说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。

下面是一些关键类:

Instant:它代表的是时间戳。

LocalDate:不包含具体时间的日期,比如2014-01-14。它可以用来存储生日,周年纪念日,入职日期等。

LocalTime:它代表的是不含日期的时间

LocalDateTime:它包含了日期及时间,不过还是没有偏移信息或者说时区。

ZonedDateTime:这是一个包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的。

2.2 LocalDate、LocalTime、LocalDateTime

LocalDate、LocalTime、LocalDateTime 类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。

LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储生日、纪念日等日期。

LocalTime表示一个时间,而不是日期。

LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。

注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法,也就是公历。

2.2.1 创建日期和时间对象的方法

注:这三个类的方法有很大相似之处,我们以LocalDateTime进行讲解,其他可以查阅API或看后面的示例代码;

static LocalDateTime now()                    从默认时区的系统时钟获取当前的日期时间。

static LocalDateTime now(Clock clock)        从指定的时钟获取当前的日期时间。

static LocalDateTime now(ZoneId zone)        从指定时区的系统时钟获取当前的日期时间。

static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute)    从年,月,日,小时和分钟获得 LocalDateTime的实例,将秒和纳秒设置为零。

static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second)    从年,月,日,小时,分钟和秒获得 LocalDateTime的实例,将纳秒设置为零。

static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond)    获取的实例 LocalDateTime从年,月,日,小时,分钟,秒和纳秒。

//创建日期和时间对象的方法
//类似于java.util.Calendar
@Test
public void test1() {
    //now():获取当前的日期、时间、日期+时间
    LocalDate localDate = LocalDate.now();
    LocalTime localTime = LocalTime.now();
    LocalDateTime localDateTime = LocalDateTime.now();

    System.out.println("当前日期:" + localDate);
    System.out.println("当前时间:" + localTime);
    System.out.println("当前日期和时间:" + localDateTime);

    //of():设置指定的年、月、日、时、分、秒。没有偏移量
    LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
    System.out.println("自定义日期和时间:" + localDateTime1);
}

2.2.2 获取指定字段的方法

int getYear()    获取年份。

int getMonthValue()        获得月份(1-12)。

Month getMonth()    获得月份, 返回一个 Month 枚举值。

int getDayOfYear()        获得年份天数(1-366)。

int getDayOfMonth()        获得月份天数(1-31)。

DayOfWeek getDayOfWeek()    获得星期几(返回一个 DayOfWeek 枚举值)。

int getHour()        获取时间。

int getMinute()     获取小时。

int getSecond()        获得秒数。

@Test
public void test2() {
    LocalDateTime localDateTime = LocalDateTime.now();
    System.out.println(localDateTime);

    //getXxx():获取相关的属性
    System.out.println("年:" + localDateTime.getYear());
    System.out.println("月份枚举对象:" + localDateTime.getMonth());
    System.out.println("月:" + localDateTime.getMonthValue());
    System.out.println("年中的天数:" + localDateTime.getDayOfYear());
    System.out.println("月中的天数:" + localDateTime.getDayOfMonth());
    System.out.println("周中的天数:" + localDateTime.getDayOfWeek());

    System.out.println("时:" + localDateTime.getHour());
    System.out.println("分:" + localDateTime.getMinute());
    System.out.println("秒:" + localDateTime.getSecond());
}

2.2.3 修改指定字段的方法

LocalDateTime withYear(int year)    将年份修改为指定的值并返回新的对象。

LocalDateTime withMonth(int month)    将月份修改为指定的值并返回新的对象。

LocalDateTime withDayOfMonth(int dayOfMonth)    将月份天数修改为指定的值并返回新的对象。

LocalDateTime withDayOfYear(int dayOfYear)    将年份天数修改为指定的值并返回新的对象。

LocalDateTime withHour(int hour)    将小时数修改为指定的值并返回新的对象。

LocalDateTime withMinute(int minute)    将分钟数修改为指定的值并返回新的对象。

LocalDateTime withSecond(int second)    将秒数修改为指定的值并返回新的对象。

LocalDateTime plusYears(long years)        向当前对象添加指定年数。

LocalDateTime plusMonths(long months)    向当前对象添加月数。

LocalDateTime plusDays(long days)    向当前对象添加指定天数。

LocalDateTime plusHours(long hours)        向当前对象添加指定小时数。

LocalDateTime plusMinutes(long minutes)        向当前对象添加指定分钟数。

LocalDateTime plusSeconds(long seconds)        向当前对象添加指定秒数。

LocalDateTime plusWeeks(long weeks)         向当前对象添加指定周数。

LocalDateTime minusYears(long years)    从当前对象减去年数。

LocalDateTime minusMonths(long months)    从当前对象减去月数。

LocalDateTime minusDays(long days)    从当前对象减去天数。

LocalDateTime minusHours(long hours)    从当前对象减去小时数。

LocalDateTime minusMinutes(long minutes)    从当前对象减去分钟数。

LocalDateTime minusSeconds(long seconds)    从当前对象减去秒数。

LocalDateTime minusWeeks(long weeks)    从当前对象减去周数。

@Test
public void test3(){
    //获取当前日期和时间
    LocalDateTime localDateTime = LocalDateTime.now();

    //体现不可变性
    //withXxx():设置相关的属性
    //设置小时
    LocalDateTime localDateTime2 = localDateTime.withHour(4);
    System.out.println(localDateTime);
    System.out.println(localDateTime2);

    System.out.println("--------------------------------------");
    //向当前对象添加月数。
    LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
    System.out.println(localDateTime);
    System.out.println(localDateTime3);

    System.out.println("--------------------------------------");
    //从当前对象减去天数。
    LocalDateTime localDateTime4 = localDateTime.minusDays(6);
    System.out.println(localDateTime);
    System.out.println(localDateTime4);
}

2.3 瞬时:Instant

Instant:时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳。

在处理时间和日期的时候,我们通常会想到年、月、日、时、分、秒。然而,这只是时间的一个模型,是面向人类的。第二种通用模型是面向机器的,或者说是连续的。在此模型中,时间线中的一个点表示为一个很大的数,这有利于计算机处理。在UNIX中,这个数从1970年开始,以秒为的单位;同样的,在Java中,也是从1970年开始,但以毫秒为单位。

java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位。Instant表示时间线上的一点,而不需要任何上下文信息,例如,时区。概念上讲,它只是简单的表示自 1970 年 1 月 1 日 0 时 0 分 0 秒(UTC)开始的秒数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。

1秒 = 1000毫秒 = 10^6微秒= 10^9纳秒

2.3.1 创建瞬时的方法

static Instant now()    从系统时钟获取当前瞬间。

static Instant now(Clock clock)        从指定的时钟获取当前时刻。

static Instant ofEpochMilli(long epochMilli)    使用从1970-01-01T00:00:00Z到现在的毫秒数获得的一个 Instant的实例。

static Instant ofEpochSecond(long epochSecond)     使用从1970-01-01T00:00:00Z到现在的的秒数获得一个 Instant的实例。

static Instant ofEpochSecond(long epochSecond, long nanoAdjustment)    使用从1970-01-01T00:00:00Z到现在的的秒数和纳秒数获得 Instant的实例。

static Instant parse(CharSequence text)        从一个文本字符串(如 2007-12-03T10:15:30.00Z获取一个 Instant的实例。

OffsetDateTime atOffset(ZoneOffset offset)    将此瞬间与偏移组合起来创建一个 OffsetDateTime 。

//类似于 java.util.Date类
@Test
public void test1() {
    //now():获取本初子午线对应的标准时间
    Instant instant = Instant.now();
    System.out.println(instant);

    //ofEpochMilli(long epochMilli)	使用从1970-01-01T00:00:00Z到现在的毫秒数获得的一个 Instant的实例。
    Instant instant2 = Instant.ofEpochMilli(6000);
    System.out.println(instant2);

    //ofEpochSecond(long epochSecond) 	使用从1970-01-01T00:00:00Z到现在的的秒数获得一个 Instant的实例。
    Instant instant3 = Instant.ofEpochSecond(6);
    System.out.println(instant3);

    //添加时间的偏移量
    OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
    System.out.println(offsetDateTime);
}

2.3.2 获取瞬时值的方法

long toEpochMilli()        返回1970-01-01 00:00:00到当前时间的毫秒数,即为时间戳。

时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。

long getEpochSecond()    返回1970-01-01 00:00:00到当前时间的秒数。

@Test
public void test2(){
    //now():获取本初子午线对应的标准时间
    Instant instant = Instant.now();
    System.out.println(instant);

    //toEpochMilli():获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数  ---> Date类的getTime()
    long milli = instant.toEpochMilli();
    System.out.println(milli);

    //long getEpochSecond()	返回1970-01-01 00:00:00到当前时间的秒数。
    long epochSecond = instant.getEpochSecond();
    System.out.println(epochSecond);
}

2.4 格式化与解析日期或时间

java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:

预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME

本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)

自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)


常用方法:

static DateTimeFormatter ofPattern(String pattern)        返回一个指定字符串格式的DateTimeFormatter。

String format(TemporalAccessor temporal)    格式化一个日期、时间,返回字符串。

TemporalAccessor parse(CharSequence text)    将指定格式的字符序列解析为一个日期、时间。

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.TemporalAccessor;

/*
    DateTimeFormatter:格式化或解析日期、时间
    类似于 java.text.SimpleDateFormat类
*/
public class DateTimeFormatterTest {
    public static void main(String[] args) {
        //获取当前日期和时间,用于格式化使用
        LocalDateTime localDateTime = LocalDateTime.now();

        //方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
        DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

        //格式化:日期-->字符串
        String str1 = formatter.format(localDateTime);
        System.out.println(str1);//2022-02-23T21:53:36.025

        //解析:字符串 -->日期
        TemporalAccessor parse = formatter.parse("2022-02-23T21:48:44.911");
        System.out.println(parse);//{},ISO resolved to 2022-02-23T21:48:44.911

        System.out.println("-------------------------------------------");

        //方式二:
        //本地化相关的格式。如:ofLocalizedDateTime()
        //FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
        DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
        //格式化
        String str2 = formatter1.format(localDateTime);
        System.out.println(str2);//2022年2月23日 下午09时53分36秒


        //本地化相关的格式。如:ofLocalizedDate()
        //FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
        DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
        //格式化
        String str3 = formatter2.format(LocalDate.now());
        System.out.println(str3);//2022-2-23

        System.out.println("-------------------------------------------");

        //重点:方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
        DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
        //格式化
        String str4 = formatter3.format(LocalDateTime.now());
        System.out.println(str4);//2022-02-23 09:53:36

        //解析
        TemporalAccessor accessor = formatter3.parse("2022-02-23 09:53:36");
        System.out.println(accessor);
    }
}

2.5 其他API

ZoneId:该类中包含了所有的时区信息,一个时区的ID,如 Europe/Paris。

ZonedDateTime:一个在ISO-8601日历系统时区的日期时间,如 2007-12-03T10:15:30+01:00 Europe/Paris。

其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式,例如:Asia/Shanghai等。

@Test
public void test1() {
    //ZoneId:类中包含了所有的时区信息
    // ZoneId的getAvailableZoneIds():获取所有的ZoneId
    Set<String> zoneIds = ZoneId.getAvailableZoneIds();
    for (String s : zoneIds) {
        System.out.println(s);
    }

    // ZoneId的of():获取指定时区的时间
    LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Asia/Tokyo"));
    System.out.println(localDateTime);

    //ZonedDateTime:带时区的日期时间
    // ZonedDateTime的now():获取本时区的ZonedDateTime对象
    ZonedDateTime zonedDateTime = ZonedDateTime.now();
    System.out.println(zonedDateTime);

    // ZonedDateTime的now(ZoneId id):获取指定时区的ZonedDateTime对象
    ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
    System.out.println(zonedDateTime1);
}

持续时间:Duration,用于计算两个“时间”间隔。

@Test
public void test2() {
    //Duration:用于计算两个“时间”间隔,以秒和纳秒为基准
    LocalTime localTime = LocalTime.now();
    LocalTime localTime1 = LocalTime.of(22, 06, 32);

    //between():静态方法,返回Duration对象,表示两个时间的间隔
    Duration duration = Duration.between(localTime, localTime1);
    System.out.println(duration);
    System.out.println(duration.getSeconds());
    System.out.println(duration.getNano());

    LocalDateTime localDateTime = LocalDateTime.of(2020, 6, 12, 15, 23, 32);
    LocalDateTime localDateTime1 = LocalDateTime.of(2021, 6, 12, 15, 23, 32);
    Duration duration1 = Duration.between(localDateTime, localDateTime1);
    System.out.println(duration1.toDays());
}

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

@Test
public void test3() {
    //Period:用于计算两个“日期”间隔,以年、月、日衡量
    LocalDate localDate = LocalDate.now();
    LocalDate localDate1 = LocalDate.of(2028, 3, 18);

    Period period = Period.between(localDate, localDate1);
    System.out.println(period);
    System.out.println(period.getYears());
    System.out.println(period.getMonths());
    System.out.println(period.getDays());

    Period period1 = period.withYears(2);
    System.out.println(period1);
}

TemporalAdjuster: 时间校正器。有时我们可能需要获取例如:将日期调整到“下一个周日/工作日”等操作。

TemporalAdjusters: 该类通过静态方法(firstDayOfXxx()/lastDayOfXxx()/nextXxx())提供了大量的常用TemporalAdjuster 的实现。

@Test
public void test4() {
    // TemporalAdjuster:时间校正器
    // 获取当前日期的下一个周日是哪天?
    TemporalAdjuster temporalAdjuster = TemporalAdjusters.next(DayOfWeek.SUNDAY);
    LocalDateTime localDateTime = LocalDateTime.now().with(temporalAdjuster);
    System.out.println(localDateTime);

    // 获取下一个工作日是哪天?
    LocalDate localDate = LocalDate.now().with(new TemporalAdjuster() {
        @Override
        public Temporal adjustInto(Temporal temporal) {
            LocalDate date = (LocalDate) temporal;
            if (date.getDayOfWeek().equals(DayOfWeek.FRIDAY)) {
                return date.plusDays(3);
            } else if (date.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
                return date.plusDays(2);
            } else {
                return date.plusDays(1);
            }
        }
    });
    System.out.println("下一个工作日是:" + localDate);
}

2.6 与传统日期处理的转换

三、Java8 Base64

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

Base 64主要用途不是加密,而是把一些二进制数转成普通字符,方便在网络上传输。 由于一些二进制字符在传输协议中属于控制字符,不能直接传送,所以需要转换一下才可以。由于某些系统中只能使用ASCII字符,Base64就是用来将非ASCII字符的数据转换成ASCII字符的一种方法,Base64特别适合在http,mime协议下快速传输数据。比如网络中图片的传输。

Base64,并非安全领域下的加密解密算法。虽然经常遇到所谓的base64的加密解密。但base64只能算是一个编码算法,对数据内容进行编码来适合传输。虽然base64编码过后原文也变成不能看到的字符格式,但是方式初级又简单。

Java 8 内置了 Base64 编码的编码器和解码器。

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

基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。

URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。

MIME:输出隐射到MIME友好格式。输出每行不超过76字符,并且使用’\r’并跟随’\n’作为分割。编码输出最后没有行分割。

3.1 内嵌类

序号 内嵌类 & 描述
1
static class Base64.Decoder

该类实现一个解码器用于,使用 Base64 编码来解码字节数据。
2
static class Base64.Encoder

该类实现一个编码器,使用 Base64 编码来编码字节数据。

3.2 方法

序号 方法名 & 描述
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 编码方案。

3.3 编码和解码

Basic编码是标准的BASE64编码,用于处理常规的需求:输出的内容不添加换行符,而且输出的内容由字母加数字组成。下面是用法:

// 编码
String s = Base64.getEncoder().encodeToString("some string".getBytes("utf-8"));
System.out.println("编码后:"+s);

// 解码
byte[] decode = Base64.getDecoder().decode(s);
System.out.println(new String(decode,"utf-8"));

与早期处理BASE64编码需求相比,不可能更简单了。因为无需外部依赖:commons-codec或sun.misc.BASE64Decoder或JAXB的DatatypeConverter。

URL编码也是我们经常会面对的需求,但由于URL对反斜线“/”有特殊的意义,因此URL编码需要替换掉它,使用下划线替换。比如下面的例子:

String basicEncoded = Base64.getEncoder().encodeToString("subjects?abcd".getBytes("utf-8"));
System.out.println("Using Basic Alphabet: " + basicEncoded);

String urlEncoded = Base64.getUrlEncoder().encodeToString("subjects?abcd".getBytes("utf-8"));
System.out.println("Using URL Alphabet: " + urlEncoded);

// 输出为:
// Using Basic Alphabet: c3ViamVjdHM/YWJjZA==
// Using URL Alphabet: c3ViamVjdHM_YWJjZA==

上面的例子可以看出,如果是使用基本的编码器,那么输出可能会包含反斜线“/”字符,但是如果使用URL编码器,那么输出的内容对URL来说是安全的。

MIME编码器会使用基本的字母数字产生BASE64输出,而且对MIME格式友好:每一行输出不超过76个字符,而且每行以“\r\n”符结束。比如下面的例子:

StringBuilder sb = new StringBuilder();
for (int t = 0; t < 10; ++t) {
    sb.append(UUID.randomUUID().toString());
}

byte[] toEncode = sb.toString().getBytes("utf-8");
String mimeEncoded = Base64.getMimeEncoder().encodeToString(toEncode);
System.out.println(mimeEncoded);
// 输出为:
/* 
OTg1YTI1MTgtYThiMC00Y2M4LTk1YWEtNWRjMTNkOWE1YzdkMTZiOWI0ZDAtMGFjMy00MzBlLWEx
ZDgtNjQ1NGJmOGM3NzI1ZjJiMzhjZTgtNTk1NC00MjAyLTkxYjgtNmQwNWE3MDUyODk3NjdjZDZl
YzMtNjYzYS00NGFlLWIyYTYtNTljOWI5Y2E0NTEwYjgwODkxNDQtMjM1YS00MTlkLWIxOTYtMzc4
Mjk0MWM2MjA5YjRkNWRkNDEtZjI5NS00MGVlLTllZDMtMjgzZGEwZjg0NTljZWU5OTM5NGEtY2Uy
ZC00OGRiLTk0MGItYzAwODhkOTE2NWUwZDI5MTk5MjMtNDU3Yi00NzMxLTlmYmEtODIwNGFlZjdj
MjZlYmUzYWZiMmEtZjJmYy00YWIzLTgyMWMtNjJiMTQ4ZTI4NGVlMTY4NjA3ZGEtMjc1Mi00MWM4
LWJlZTctMzk5ZTY1YTIyMDU0
*/



版权声明:本文为ligonglanyuan原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。