Java8日期一文搞定Java8日期新特性

Posted @来杯咖啡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java8日期一文搞定Java8日期新特性相关的知识,希望对你有一定的参考价值。

目录标题

前言

Java对日期、日历及时间的处理一直以来都饱受诟病,比如java.util.Date和java.util.Calendar类易用性差,不支持时区,非线程安全;还有用于格式化日期的类DateFormat也是非线程安全的等问题

Java8引入的新的一系列API,对时间日期的处理提供了更好的支持,清楚的定义了时间日期的一些概念,比如说,瞬时时间(Instant),持续时间(duration),日期(date),时间(time),时区(time-zone)以及时间段(Period)

Java8日期类型

简介

1、核心类

Java8常用的日期和时间类包含LocalDate、LocalTime、Instant、Duration、Period、LocalDateTime以及ZonedDateTime等。

LocalDate:不包含时间的日期,比如2019-10-14。可以用来存储生日,周年纪念日,入职日期等。
LocalTime:与LocalDate想对照,它是不包含日期的时间。
LocalDateTime:包含了日期及时间,没有偏移信息(时区)。
ZonedDateTime:包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的。
Instant时间戳,与System.currentTimeMillis()类似。
Duration:表示一个时间段。
Period:用来表示以年月日来衡量一个时间段。

另外,还有新的日期解析格式化类DateTimeFormatter

2、关键点

Java8中关于时间日期的API有以下关键点:

  1. 提供了javax.time.ZoneId用来处理时区。
  2. 提供了LocalDate与LocalTime类。
  3. 时间与日期API中的所有类都是线程安全的。
  4. 明确定义了基本的时间与日期概念。
  5. 核心API:Instant、LocalDate、LocalTime、LocalDateTime、ZonedDateTime。
  6. DateTimeFormatter类用于在Java中进行日期的格式化与解析。

LocalDate

LocalDate类内只包含日期,不包含具体时间。只需要表示日期而不包含时间,就可以使用它。

1、获取当前日期、年、月、日

// 只获取日期
LocalDate today = LocalDate.now();
System.out.println(today);

int year = today.getYear();
int month = today.getMonthValue();
int day = today.getDayOfMonth();

System.out.printf("Year : %d Month : %d day : %d \\t %n", year, month, day);

2、获取月、周的第几天

同时,还可以通过LocalDate获取日期是月份的第几天、周的第几天,月份的天数,是否为闰年等。看下面的代码是不是非常方便。

LocalDate today = LocalDate.now();
// 月份中的第几天
int dayOfMonth = today.getDayOfMonth();
// 一周的第几天
DayOfWeek dayOfWeek = today.getDayOfWeek();
// 月份的天数
int length = today.lengthOfMonth();
// 是否为闰年
boolean leapYear = today.isLeapYear();

3、随意创建日期

上面通过now获取LocalDate对象,也可以通过静态方法of()或parse创建任意一个日期。再也不用像之前一样年只能从1900年开始,月必须从0开始等。

LocalDate oneDay = LocalDate.of(2019,10,1);
System.out.println(oneDay);

4、比较两个LocalDate是否相同

LocalDate重写了equals方法,让日期的比较也变得简单了。

// 定义任意日期
LocalDate oneDay = LocalDate.of(2019, 10, 1);
System.out.println(oneDay);

// 定义任意比较
LocalDate anyDay = LocalDate.of(2019, 10, 1);
System.out.println(oneDay.equals(anyDay));

同时,针对日期还可延伸出MonthDay或YearMonth类,顾名思义,只包含月天或年月。同样适用于equals方法来比较。

5、比较两个LocalDate的大小

boolean notBefore = LocalDate.parse("2019-10-01").isBefore(LocalDate.parse("2019-10-02"));
boolean isAfter = LocalDate.parse("2019-10-01").isAfter(LocalDate.parse("2019-10-02"));

6、对天、月进行加减

对日期进行前一天后一天或前一个月的加减也变得十分方便。

LocalDate tomorrowDay = LocalDate.now().plusDays(1);
LocalDate nextMonth =  LocalDate.now().plusMonths(1);

7、获取两个日期相减间隔

between内的参数类型可以是:LocalDateTime、LocalDate、LocalTime。

LocalDateTime from = LocalDateTime.now();
LocalDateTime to = LocalDateTime.now().plusDays(1);
Duration duration = Duration.between(from, to);
// 区间统计换算
// 总天数
long days = duration.toDays();
// 小时数
long hours = duration.toHours();
// 分钟数
long minutes = duration.toMinutes();
// 秒数
long seconds = duration.getSeconds();
// 毫秒数
long milliSeconds = duration.toMillis();
// 纳秒数
long nanoSeconds = duration.toNanos();

8、字符串转日期对象

LocalDate parseDay = LocalDate.parse("2019-10-01");
System.out.println(parseDay);

LocalTime

LocalTime和LocalDate类似,区别在于LocalDate不包含具体时间,而LocalTime包含具体时间。

  1. localtime可以表示的最大值是:23:59:59,24小时转换为秒值是86399,超过这个值报错;
  2. LocalTime获得的时间格式为:11:41:58.904。也就是,HH:mm:ss.nnn,这里nnn是纳秒*

1、获取当前时间&&随意创建时间对象

同样可以使用now或of方法来获得对象。

//获取当天时间  HH:mm:ss.nnn
LocalTime localTime = LocalTime.now();

// of方式创建时间对象
LocalTime oneTime = LocalTime.of(10,10,10);
// ofSecondOfDay方式创建时间对象
LocalTime localTime = LocalTime.ofSecondOfDay(seconds);

2、获取时分秒 && 比较时间大小 && 字符串转时间对象

LocalDate类似它也拥有parse(字符串转时间对象)、isBefore(比较两个时间大小)、获取时间单元(时分秒)等方法,就不再赘述。

3、获取最大时间和最小时间

还有一个在实战中查询日期区间时我们经常定义的“23:59:59.99”常量再也不用自己定义了。

// 23:59:59.999999999
LocalTime maxTime = LocalTime.MAX;
// 00:00
LocalTime minTime = LocalTime.MIN;

4、获取两个时间相差间隔

between内的参数类型可以是:LocalDateTime、LocalDate、LocalTime。

LocalDateTime from = LocalDateTime.now();
LocalDateTime to = LocalDateTime.now().plusDays(1);
Duration duration = Duration.between(from, to);
// 区间统计换算
// 总天数
long days = duration.toDays();
// 小时数
long hours = duration.toHours();
// 分钟数
long minutes = duration.toMinutes();
// 秒数
long seconds = duration.getSeconds();
// 毫秒数
long milliSeconds = duration.toMillis();
// 纳秒数
long nanoSeconds = duration.toNanos();

LocalDateTime

LocalDateTime表示日期和时间组合。

1、获取当前日期时间 && 随意创建日期时间 && 拼接

可以通过of()方法直接创建,也可以调用LocalDate的atTime()方法或LocalTime的atDate()方法将LocalDate或LocalTime合并成一个LocalDateTime。

LocalDateTime now = LocalDateTime.now();

LocalDateTime oneTime = LocalDateTime.of(2019,10,14,10,12,12);

// 拼接日期
LocalTime.now().atDate(LocalDate.now());

2、日期时间增、减、比较大小

LocalDateTime与LocalDate和LocalTime之间可以相互转化。其他日期增减等操作与上面的类似。

3、获取两个日期时间相减间隔

```java
LocalDateTime from = LocalDateTime.now();
LocalDateTime to = LocalDateTime.now().plusDays(1);
Duration duration = Duration.between(from, to);
// 区间统计换算
// 总天数
long days = duration.toDays();
// 小时数
long hours = duration.toHours();
// 分钟数
long minutes = duration.toMinutes();
// 秒数
long seconds = duration.getSeconds();
// 毫秒数
long milliSeconds = duration.toMillis();
// 纳秒数
long nanoSeconds = duration.toNanos();

4、获取一天的开始、结束时间

//获取一天的开始时间,2017,7,22 00:00
  public static LocalDateTime getDayStart(LocalDateTime time) 
    return time.withHour(0)
        .withMinute(0)
        .withSecond(0)
        .withNano(0);
  
 
  //获取一天的结束时间,2017,7,22 23:59:59.999999999
  public static LocalDateTime getDayEnd(LocalDateTime time) 
    return time.withHour(23)
        .withMinute(59)
        .withSecond(59)
        .withNano(999999999);
  

Instant-获取时间戳

Instant用于一个时间戳,与System.currentTimeMillis()类似,但Instant可以精确到纳秒(Nano-Second)。

1、获取当前时间戳

Instant除了可以使用now()方法创建,还可以通过ofEpochSecond方法创建。

Instant now = Instant.now();
// 其中ofEpochSecond第一个参数表示秒,第二个参数表示纳秒。整体表示:从1970-01-01 00:00:00开始后的365天100纳秒的时间点
long tiemstamp = Instant.ofEpochSecond(365 * 24 * 60, 100);

Duration-获取时间段

Duration的内部实现与Instant类似,但Duration表示时间段。

1、创建Duration

通过between方法创建:

LocalDateTime from = LocalDateTime.now();
LocalDateTime to = LocalDateTime.now().plusDays(1);
Duration duration = Duration.between(from, to);

Duration对象还可以通过of()方法创建,该方法参数为时间段长度和时间单位:

// 7天
Duration duration1 = Duration.of(7, ChronoUnit.DAYS);
// 60秒
Duration duration2 = Duration.of(60, ChronoUnit.SECONDS);

2、获取日期时间相差天、时、分、秒、毫秒

between内的参数类型可以是:LocalDateTime、LocalDate、LocalTime。

LocalDateTime from = LocalDateTime.now();
LocalDateTime to = LocalDateTime.now().plusDays(1);
Duration duration = Duration.between(from, to);
// 区间统计换算
// 总天数
long days = duration.toDays();
// 小时数
long hours = duration.toHours();
// 分钟数
long minutes = duration.toMinutes();
// 秒数
long seconds = duration.getSeconds();
// 毫秒数
long milliSeconds = duration.toMillis();
// 纳秒数
long nanoSeconds = duration.toNanos();

Period-获取日期段

Period与Duration类似,获取一个时间段,只不过单位为年月日

1、创建Period时间段

也可以通过of方法和between方法创建,between方法接收的参数为LocalDate。

Period period = Period.of(1, 10, 25);
Period period1 = Period.between(LocalDate.now(), LocalDate.now().plusYears(1));

ZonedDateTime-创建时区时间

ZonedDateTime类,用于处理带时区的日期和时间。ZoneId表示不同的时区。大约有40不同的时区。

//获取所有时区集合:
Set allZoneIds = ZoneId.getAvailableZoneIds();

//创建时区:
ZoneId zoneId = ZoneId.of("Asia/Shanghai");

//把LocalDateTime转换成特定的时区:
ZonedDateTime zonedDateTime = ZonedDateTime.of(LocalDateTime.now(), zoneId);

另外和时区一起使用的类是OffsetDateTime类,OffsetDateTime是不变的,表示date-time偏移,存储所有日期和时间字段,精确至纳秒,从UTC/Greenwich计算偏移。

日期相关的类型转换

Java8日期与字符串转换

1、日期格式的两种定义方式

Java8对日期的格式化操作非常简单,首先看到上面的类大多都提供了parse方法,可以直接通过解析字符串得到对应的对象。

  1. 可以使用DateTimeFormatter预置的格式DateTimeFormatter.ISO_LOCAL_DATE_TIME;
  2. 可以通过DateTimeFormatter.ofPattern方法来指定格式。
LocalDateTime dateTime = LocalDateTime.now();
String str = dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
System.out.println(str);
str = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
System.out.println(str);

2、字符串与日期、时间、日期时间互转

public class DateTimePatternConstant 

    public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";

    public static final String DATE_PATTERN = "yyyy-MM-dd";

    public static final String TIME_PATTERN = "HH:mm:ss";

public class DateTimeAndStringConvertTest 
    public static void main(String[] args) 
        String startDateStr = "2021-08-15 09:09:09";
        String endDateStr = "2021-08-15 10:10:10";

        String date = "2021-08-15";

        String time = "01:01:01";

        String dateTimePattern = DateTimePatternConstant.DATE_TIME_PATTERN;

        String datePattern = DateTimePatternConstant.DATE_PATTERN;

        String timePattern = DateTimePatternConstant.TIME_PATTERN;

        // 日期时间与字符串相互转换示例
       // dateTime_str_convert(startDateStr, dateTimePattern);

        // 日期与字符串相互转换示例
        date_string_convert(date, datePattern);

        // 时间与字符串相互转换示例同理

    

    private static void date_string_convert(String date, String datePattern) 
        // 字符串-->日期对象
        LocalDate localDate = LocalDate.parse(date, DateTimeFormatter.ofPattern(datePattern));
        // 日期对象-->指定格式的字符串
        String format = localDate.format(DateTimeFormatter.ofPattern(datePattern));
        // 2021-08-15
        System.out.println(format);
    

    private static void dateTime_str_convert(String startDateStr, String pattern) 

        // Exception in thread "main" java.time.format.DateTimeParseException: Text '2021-08-15' could not be parsed at index 10
        //LocalDateTime localDate = LocalDateTime.parse(date);

         // 字符串-->日期时间对象
        LocalDateTime localDateTime = LocalDateTime.parse(startDateStr, DateTimeFormatter.ofPattern(pattern));

        // 日期时间对象-->指定格式的字符串
        String format = localDateTime.format(DateTimeFormatter.ofPattern(pattern)));
        // 2021-08-15 09:09:09
        System.out.println(format);
    

LocalDateTime与Date互转

LocalDateTime 转 Date(亲测有效):

LocalDateTime localDateTime = LocalDateTime.now();
Date date = Date.from(localDateTime.atZone( ZoneId.systemDefault()).toInstant());

Date 转 LocalDateTime:

Date startDate=new Date();
LocalDateTime localDateTime = startDate.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime()

LocalDateTime与时间戳转换

13位的时间戳。

将timestamp转为LocalDateTime:

public LocalDateTime timestamToDatetime(long timestamp)
  Instant instant = Instant.ofEpochMilli(timestamp);
  return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
 

将LocalDataTime转为timestamp:

新特性

以下是Java 8 新增的部分特性,更多新特性了解请详细参考:​​Whats New in JDK 8​

• Lambda 表达式
• 方法引用
• 函数式接口
• 默认方法
• Stream
• Optional 类
• Nashorn, JavaScript 引擎
• Date/Time API 新的日期时间 API
• Base64
• 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。

一、Lambda 表达式

Lambda 表达式(也可称为闭包),它是推动 Java 8 发布的最重要新特性。
Lambda 表达式允许把​​函数作为一个方法的参数​​,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。

什么是 Lambda 表达式

先来看看 Lambda 表达式的​​官方解释​​:
Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个​​匿名函数​​,即没有函数名的函数。

这样的解释还是让人摸不着头脑,那我们接着往下看。

首先介绍, Lambda 表达式的语法格式:​​() -> ​


其中 () 用来描述参数列表, 用来描述方法体,->  lambda运算符 ,读作(goes to)
简单例子:
(int x, int y) -> x + y //接收2个int型整数,返回他们的和
(String s) -> System.out.print(s) // 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)

前面我们说了 Lambda 表达式可以取代大部分的匿名内部类,举个例子:


@FunctionalInterface
public interface NoReturnMultiParam
void method(int a, int b);


public class Test 

public void sayHello()
// 匿名类实现NoReturnMultiParam接口
NoReturnMultiParam noReturnMultiParam = new NoReturnMultiParam()
@Override
public void method(int a, int b)
System.out.println("param:a=" + a + ",b=" + b + "");

;
// 调用接口
noReturnMultiParam.method(1,2);


public static void main(String[] args)
Test test = new Test();
test.sayHello();

运行结果:​​param:a=1,b=2​

接着,我们将匿名类实现替换为 Lambda表达式


public class Test 

public void sayHello()
// Lambda实现NoReturnMultiParam接口
NoReturnMultiParam lambda = (a, b) -> System.out.println("param:a=" + a + ",b=" + b + "");
// 调用接口
lambda.method(1,2);


public static void main(String[] args)
Test test = new Test();
test.sayHello();

运行结果与之前相同,可以看到,Lambda 表达式可以来定义行内执行的方法类型接口,免去了使用匿名方法的麻烦。
简单来说,在 Java 中可以将 Lambda 表达式看成一个接口的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。

Lambda 规定接口中只能有一个需要被实现的方法,即​​函数式接口​​,不是规定接口中只能有一个方法。

闭包问题

lambda 表达式只能引用标记了 ​​final​​ 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。


public static void main(String[] args) 
int c = 10;
NoReturnMultiParam lambda = (a, b) -> System.out.println("param:a=" + a + ",b=" + b + ",c="+c);
lambda.method(1,2);

这里c没有标识为​​final​​,但是没有被后续代码修改,所以在编译期间虚拟机会帮我们加上 final 修饰关键字(即隐性的具有 final 的语义)

修改代码,出现错误​​java: 从lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量​


public static void main(String[] args) 
int c = 10;
NoReturnMultiParam lambda = (a, b) -> System.out.println("param:a=" + a + ",b=" + b + ",c="+c);
c = c + 2;
lambda.method(1,2);

二、方法引用

方法引用通过方法的名字来指向一个方法,在使用 Lambda 表达式时,有时候我们不是必须要自己重写某个匿名内部类的方法,而是可以利用 Lambda 表达式的接口快速指向一个已经被实现的方法。


语法: 方法归属者::方法名 静态方法的归属者为类名,普通方法归属者为对象

Java 中 4 种不同方法的引用:
构造器引用 ClassName::new
静态方法引用 Class::static_method
特定类的任意对象的方法引用 Class::method
特定对象的方法引用 instance::method

代码示例:


package com.local.springboot.springbootservice.sysuser;

interface InstanceCreate
Test get();


@FunctionalInterface
interface ReturnMultiParam
int method(int a, int b);

public class Test
public static int addNum(int a, int b)
return a + b;


public int deleteNum(int a, int b)
return a - b;

public void sayHello()
System.out.println("Hello World");

public static void main(String[] args)

ReturnMultiParam lambda = (a, b) -> addNum(a, b);
System.out.println(lambda.method(1, 2));


// 构造器引用
InstanceCreate create = Test::new;
System.out.println(create.get());

// 静态方法引用
ReturnMultiParam result = Test::addNum;
System.out.println(result.method(1, 2));

// 特定对象的方法引用
Test test = new Test();
ReturnMultiParam result2 = test::deleteNum;
System.out.println(result2.method(2, 2));


运行结果:


3
com.local.springboot.springbootservice.sysuser.Test@179d3b25
3
0

三、函数式接口

上面提到接口中只有一个需要被实现的方法的接口,叫做函数式接口

函数式接口需要用​​@FunctionalInterface​​注解修饰,要求接口中的抽象方法只有一个,但可以有多个非抽象方法

函数式接口实例

函数式接口可以对现有的函数友好地支持 lambda。比如常用的​​Comparator​​或者​​Consumer​​接口。

比如常见的集合内元素的排序


List<Cat> list = new ArrayList<>();
list.add(new Cat(5,"Tom"));
list.add(new Cat(2,"Aimi"));
list.add(new Cat(3,"Doe"));

list.sort(new Comparator<Cat>()
@Override
public int compare(Cat o1, Cat o2)
return o1.getIndex()- o2.getIndex();

);
list.forEach(item ->
System.out.println(item.toString());
);

在以前我们若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。


list.sort((o1,o2)->o1.getIndex()-o2.getIndex());
//list.sort((Comparator.comparing(Cat::getIndex)));
list.forEach(item ->
System.out.println(item.toString());
);

更多函数式接口

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

JDK 1.8 新增加的函数接口:

• ​​java.util.function​​ java.util.function 它包含了很多类,用来支持 Java的 函数式编程,详细的函数式接口请查看源码。

四、默认方法

简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
用法:只需在方法名前面加个 ​​default​​ 关键字即可实现默认方法


被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用

相同的默认方法

当一个类实现多个接口,而且这些接口存在相同的默认方法,会发生什么情况呢?


interface Interface1
default void print()
System.out.println("我是接口1默认方法实现!");



interface Interface2
default void print()
System.out.println("我是接口2默认方法实现!");




public class InterfaceImpl implements Interface1, Interface2
@Override
public void print()
// 可以使用 super 来调用指定接口的默认方法
// Interface1.super.print();
System.out.println("我是接口实现类!");


public static void main(String[] args)
InterfaceImpl interfaceImpl = new InterfaceImpl();
interfaceImpl.print();

运行结果:


我是接口实现类!

当出现这种接口冲突,一般有两种解决方案 ​​覆盖重写接口的默认方法​​、​​使用 super 来调用指定接口的默认方法​

静态默认方法

Java 8 中接口是可以声明静态方法(并且可以提供实现)的。例如:


interface Interface2
default void print()
System.out.println("我是接口2默认方法实现!");

// 静态方法
static void doSomething()
System.out.println("我是接口2中的静态方法");

为什么要有这个特性

说了这么多,那么问题来了,为什么要新增这个特性?

新增默认方法是为了解决接口的修改与现有的实现不兼容的问题。
我们都知道接口是面向抽象而不是面向具体编程的,所以当需要修改接口时,就需要修改全部实现改接口的类。
所以对于以前发布的版本,是做不到在修改接口的同时不影响已有的实现。

五、Stream

Java 8 API 添加了一个新的抽象称为流 Stream,可以让你以一种​​声明的方式​​处理数据。

​语法糖​​ Stream 以一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 ​​Java 集合运算和表达的高阶抽象​​。简单来说流是Java 8 中对Collection对象功能的加强。

流操作

【数据集合】 ->【数据源】 - > 【转换(聚合)操作】 ->【终点操作】

• Intermediate(转换操作):中间操作都会返回流对象本身。就是说,仅仅调用到这类方法,并没有真正开始流的遍历。多次的转换操作只会在遇到终点操作之后,才会依次执行。

转换操作:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel...

• Terminal(终点操作):一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

终点操作:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny...

• Short-Circuiting(短路操作):短路操作其实和终点操作也是一样的,可能不再返回一个流,或是返回一个被截取过的流。比如anyMatch方法,通过Predicate<T>接口返回了一个真值。由于流Stream在理论上是无限大的,短路操作被用以对流进行截取,把无限的变成有限的流,比如limit方法,可以限制获取数据源的数量。

短路操作:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit...

外部迭代与内部迭代

这里值得一提的是以前对集合遍历都是通过Iterator或者for-each的方式,显式的在集合外部进行迭代, 这叫做​​外部迭代​​。与以前的Collection操作不同,流操作提供了​​内部迭代​​的方式, 通过访问者模式(Visitor)实现

那什么是外部迭代和内部迭代呢?举个简单的列子:

比如你请人打扫房间,但有觉得不放心,于是你觉得现场指示工人先擦桌子,再拖地,最后洗碗...直到打扫完毕,这就是所谓的外部迭代,即显示地取出元素进行处理。
后来你和清洁工人熟悉之后,你只需要和她说把房间打扫干净,清洁工人自己选择先做什么,再做什么,你等着接收成果就行了。这就是内部迭代

生成流

顶层集合类Collection添加了两个方法:​​stream()​​、​​parallelStream()​​。
• stream() − 为集合创建串行流。
• parallelStream() − 为集合创建并行流。

开启流计算时根据操作的数据量选择调用stream()或者parallelStream()

使用实例

forEach

Stream 提供了新的方法 forEach 来迭代流中的每个数据。


List<String> strings = Arrays.asList("abc", "bc", "efg", "abd","jkl");
strings.forEach(System.out::println);
map

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


List<String> strings = Arrays.asList("abc", "bc", "efg", "abd","jkl");
strings.stream().map(String::toUpperCase).sorted((a, b) -> b.compareTo(a)).forEach(System.out::println);
filter

filter 方法用于通过设置的条件过滤出元素。把除了​​abc​​的字符串过滤出来


List<String> strings = Arrays.asList("abc", "bc", "efg", "abd","jkl");
strings.stream().filter(string -> !"abc".equals(string)).forEach(System.out::println);
limit

limit 方法用于获取指定数量的流。 打印3条数据


List<String> strings = Arrays.asList("abc", "bc", "efg", "abd","jkl");
strings.stream().limit(3).forEach(System.out::println);

流操作简单说明

• ​​filter​​:用于过滤出满足条件的元素
• ​​distinct​​:去重,需要重写equals()和hashCode()
• ​​sorted​​:对元素进行排序
• ​​limit​​:返回前n个元素
• ​​skip​​:去掉前n个元素
• ​​map​​:方法用于映射每个元素对应的结果
• ​​flapMap​​:将流中的每一个元素T映射成为一个流,再把每一个流连接成一个流
• ​​anyMatch​​:是否存在任意一个元素满足条件(返回布尔值)
• ​​allMatch​​:是否所有元素都满足条件(返回布尔值)
• ​​noneMatch​​:是否所有元素都不满足条件(返回布尔值)
• ​​findAny​​:找到其中一个元素 (使用 stream() 时找到的是第一个元素;使用 parallelStream() 并行时找到的是其中一个元素)
• ​​findFirst​​:找到第一个元素
• ​​reduce​​:用于组合流中的元素,如求和,求积,求最大值等
• ​​count​​:返回流中元素个数,结果为 long 类型
• ​​collect​​:收集方法,我们很常用的是 collect(toList()),当然还有 collect(toSet()) 等,参数是一个收集器接口

Stream流式计算的使用

说了这么多,那么流使用好处以及对性能的影响如何呢?

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

通过实际示例对比了常规的集合类的过滤、封装、统计操作,几百的小数据量操作,常规外部迭代更快;数据量再大一点,stream()串行的流式计算会更快;上万级别的数据量后,parallelStream()并行流式计算会更快。

六、Optional 类

相信大家在编码中最常遇见的就是空指针异常,而Optional 类的引入就是为了很好地解决空指针异常。

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

常用类方法

方法

描述

T get()

如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException

void ifPresent(Consumer<? super T> consumer)

如果值存在则使用该值调用 consumer , 否则不做任何事情。

boolean isPresent()

如果值存在则方法会返回true,否则返回 false。

<U>Optional<U> map(Function<? super T,? extends U> mapper)

如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。

T orElse(T other)

如果存在该值,返回值, 否则返回 other。

T orElseGet(Supplier<? extends T> other)

如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。

简单举例

​orElse​​ 存在则返回​​aa​​,不存在则返回​​bb​


Optional<String> string= Optional.of("aa");
string.orElse("bb");

七、日期时间 API

旧版日期时间API问题:
• ​​非线程安全​​: java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
• ​​日期/时间类的定义并不一致​​:在java.util和java.sql的包中都有日期类
• ​​时区处理麻烦​​:日期类并不提供国际化,没有时区支持

Java 8 在 ​​java.time​​ 包下提供了很多新的 API:
• ​​Local(本地)​​:简化了日期时间的处理,没有时区的问题。
• ​​Zoned(时区​​:通过制定的时区处理日期时间。

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

使用时区的日期时间API

时区使用 ​​ZoneId​​ 来表示,使用静态方法​​of​​来获取时区


// 获取当前时间日期
ZonedDateTime date = ZonedDateTime.parse("2021-11-13T10:15:30+05:30[Asia/Shanghai]");
System.out.println("date: " + date);

ZoneId id = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + id);

ZoneId currentZone = ZoneId.systemDefault();
System.out.println("当期时区: " + currentZone);

本地化日期时间API

LocalDate、LocalTime 、LocalDateTime 都是用于处理日期时间的 API,在处理日期时间时可以不用强制性指定时区


// 获取当前的日期时间
LocalDateTime currentTime = LocalDateTime.now();
System.out.println("当前时间: " + currentTime);//当前时间: 2016-04-15T16:55:48.668

LocalDate date1 = currentTime.toLocalDate();
System.out.println("date1: " + date1);//date1: 2016-04-15

Month month = currentTime.getMonth();
int day = currentTime.getDayOfMonth();
int seconds = currentTime.getSecond();

System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);//月: APRIL, 日: 15, 秒: 48

LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
System.out.println("date2: " + date2);//date2: 2012-04-10T16:55:48.668

// 12 december 2014
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);//date3: 2014-12-12

// 22 小时 15 分钟
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);//date4: 22:15

// 解析字符串
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);//date5: 20:15:30

自定义格式使用​​DateTimeFormatter​​,它是不可变的(线程安全


LocalDateTime currentTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
System.out.println(formatter.format(currentTime));// 2021-11-13 10:34:55.731

八、Base64

在Java 8中,Base64编码已经成为Java类库的标准。
至于它的使用则十分简单,来看个例子:


// 编码
String base64encodedString = Base64.getEncoder().encodeToString("Java8-Base64".getBytes("utf-8"));
System.out.println(base64encodedString);

// 解码
String base64decodedString = new String(Base64.getDecoder().decode(base64encodedString), "utf-8");
System.out.println(base64decodedString);

输出结果为:

SmF2YTgtQmFzZTY0
Java8-Base64

此外,Base64工具类还提供了URL、MIME

方法

描述

static Base64.Decoder getMimeDecoder()

返回一个 Base64.Decoder ,解码使用 MIME 型 base64 编码方案。

static Base64.Encoder getMimeEncoder()

返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案。

static Base64.Decoder getUrlDecoder()

返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。

static Base64.Encoder getUrlEncoder()

返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。

九、Nashorn JavaScript引擎

Nashorn 是一个 javascript 引擎,使得JavaScript 代码可以在 Java 中执行,如下:


ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine nashorn = scriptEngineManager.getEngineByName("JavaScript");
System.out.println(engine.eval("function f()return 10;; f() + 1;"));//12

jjs

jjs是个基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。

使用Nashorn运行脚本的示例

jjs script.js

在交互模式下运行Nashorn的示例

jjs
jjs> println("Hello, World!")
Hello, World!
jjs> quit()

将参数传递给Nashorn的示例

$ jjs -- a b c
jjs> arguments.join(", ")
a, b, c
jjs>

值得注意的是:

随着ECMAScript语言标准的快速发展,维护Nashorn引擎变得越发挑战,因此该引擎将在Java中废弃。Java11将声明弃用Nashorn JavaScript脚本引擎,被标注为@Deprecated(forRemoval=true)​。

十、类依赖分析器jdeps

jdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。

我们可以利用jedps分析下​JAVA8新特性-----新时间与日期API

java8新特性——新日期和时间API

JAVA8新特性随笔

Java8新特性_日期时间新类 LocalDateLocalTimeLocalDateTime

别再用 Date 了,死磕 Java8 新特性,18 种日期处理方式!

2020了你还不会Java8新特性?时间日期API&&Java8总结

(c)2006-2024 SYSTEM All Rights Reserved IT常识