Java 8的新特性还不了解?快进来!

能坚持别人不能坚持的,才能拥有你想拥有的。
关注 编程大道,让我们一起成长

Java 8的新特性还不了解?快进来!_第1张图片

哈喽,大家好,我是walking,今天我们说说Java 8的新特性。

Java 8从发布到现在已经6年多了,java 8在Java历史版本中是一个具有“里程碑”意义的重要版本,相信现在很多公司都用上了Java 8这个版本,用了之后你就知道为什么说是“里程碑”了,然后你会觉得“真香”!

但是事情总不是那么绝对,我相信目前还有很多公司依然在用java 7甚至是java 6,相信也会有很多小伙伴即便是用上了java 8但是却还不知道它到底有哪些新的特性,那么今天就和大家一块看下java 8比起以往的版本到底有哪些新的特性。(文章内容过长,建议收藏)

Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。

0、新特性

Java8 新增了非常多的特性,我们主要讨论以下几个:

  • Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。

  • 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

  • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。

  • 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。

  • Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。

  • Date Time API − 加强对日期与时间的处理。

  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。

  • Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

新的编码风格

Java 8 希望有自己的编程风格,并与 Java 7 区别开,以下实例展示了 Java 7 和 Java 8 的编程格式:

public class NewStyle {
    public static void main(String args[]) {         List names1 = Arrays.asList("TianMao", "CaiNiao", "TaoBao", "HeMa", "XianYu", "FeiZhu");         List names2 = Arrays.asList("TianMao", "CaiNiao", "TaoBao", "HeMa", "XianYu", "FeiZhu");         System.out.println("使用 Java 7 语法: ");         sortUsingJava7(names1);         System.out.println(names1);         System.out.println("使用 Java 8 语法: ");         sortUsingJava8(names2);         System.out.println(names2);     }     // 使用 java 7 排序     private static void sortUsingJava7(List names) {         Collections.sort(names, new Comparator() {             @Override             public int compare(String s1, String s2) {                 return s1.compareTo(s2);             }         });     }     // 使用 java 8 排序     private static void sortUsingJava8(List names) {         Collections.sort(names, (s1, s2) -> s1.compareTo(s2));     } } 

执行以上代码:

使用 Java 7 语法: 
[CaiNiao, FeiZhu, HeMa, TaoBao, TianMao, XianYu]
使用 Java 8 语法: 
[CaiNiao, FeiZhu, HeMa, TaoBao, TianMao, XianYu]

1、lambda表达式

1.1 用法

什么是lambda表达式,看下面的代码就知道了,首先对比我们之前的代码,创建线程对象时需要传入一个实现 Runnable 接口的对象,以往我们都是 new 一个 Runnable 对象然后实现接口的 run 方法(匿名内部类),那是用java8的lambda表达式就简洁多了,往下看,t2和t3的写法,这就是lambda表达式的写法。

public static void main(String[] args) {     Thread t1 = new Thread(new Runnable() {         @Override         public void run() {             System.out.println("t1 ==> old");         }     });     Thread t2 = new Thread(()->System.out.println("t2 ==> lambda"));     Thread t3 = new Thread(()-> {         System.out.println("t3 ==> lambda");         System.out.println("more code");     }); } 

我们来细看一下, Thread t2 = new Thread(()->System.out.println("t2 ==> lambda")); 其中的 ()->System.out.println("t2 ==> lambda") 这句代码,我们来解释一下, () 是你要实现的方法的参数名列表,注意是参数名,不用指定参数类型,如果没有参数就写一个空括号。(可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。)
对别t2和t3,当实现方法内部只有一行代码时可以直接写,如果多行代码,使用代码块的方式用{}包裹住,这是lambda的语法规定的。(可选的大括号:如果主体包含了一个语句,就不需要使用大括号)
再看一下下面的代码

public class Tester{
    public static void main(String[] args) {         String str = "print content";         Printer p1 = content -> System.out.println(content);         p1.doPrint(str);         Printer p2 = (String content) -> System.out.println(content);         p2.doPrint(str);     } } interface Printer{     void doPrint(String content); } 

其中 content -> System.out.println(content) ,前面的content变量名没有用()包裹,这是因为lambda其中一个特性,即,可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
当然等价于(String content) -> System.out.println(content)(可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。)
再看一下下面的代码

public class Tester{
    public static void main(String[] args) {         Judge judge = ()->1>2;         Judge judge2 = ()->{             System.out.println("judge...");             return 1>2;         };     } }     interface Judge{     boolean doJudge(); } 

我们定义的接口方法 doJudge() 返回参数是 boolean ,我们在实现接口方法是是这样写的, ()->1>2 ,不需要特别写 return 关键字,因为我们的表达式 1>2 的结果和该方法的返回类型一样,编译器会自动返回。(可选的返回关键字:如果主体只有一个表达式返回值,则编译器会自动返回值,否则需要显示返回值。)

总结一下Lambda的重要特征:
• 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
• 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
• 可选的返回关键字:如果主体只有一个表达式返回值,则编译器会自动返回值,否则需要显示返回值。


使用 Lambda 表达式需要注意以下两点:
• Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。
• Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

1.2 变量作用域

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

Java 8的新特性还不了解?快进来!_第2张图片

lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义),否则会报错

Java 8的新特性还不了解?快进来!_第3张图片

访问成员变量则没有这种限制,不必为final

在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量,否则编译不通过

2、方法引用

2.1 介绍

方法引用通过方法的名字来指向一个方法。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 :: 。

2.2 用法

下面,我们在 Sky 类中定义了 4 个方法作为例子来区分 Java 中 4 种不同方法的引用。

@FunctionalInterface
public interface Supplier<T> {     get(); } class Sky {     private String color;     //Supplier是jdk1.8的接口,这里和lambda一起使用了     public static Sky create(final Supplier supplier) {         return supplier.get();     }     //静态方法,有参     public static void collide(final Sky car) {         System.out.println("Collided " + car.toString());     }     //成员方法,有参     public void follow(final Sky another) {         System.out.println("Following the " + another.toString());     }     //成员方法,无参     public void repair() {         System.out.println("Repaired " + this.toString());     }     //getter and setter     //toString } 

我们来看一下怎么用的

public static void main(String[] args) {     //构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下:     final Sky sky = Sky.create( Sky::new );     sky.setColor("red");     final List skys = Arrays.asList(sky);     //静态方法引用:它的语法是Class::static_method,实例如下:     skys.forEach(Sky::collide);     //特定类的任意对象的方法引用:它的语法是Class::method实例如下:     skys.forEach(Sky::repair);     //特定对象的方法引用:它的语法是instance::method实例如下:     final Sky sky2 = Sky.create(Sky::new);     skys.forEach(sky2::follow); } 

final Sky sky= Sky.create(Sky::new)等价于final Sky sky1= Sky.create(()->new Sky());

3、函数式接口

3.1 介绍

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 lambda 表达式。
Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。
如定义了一个函数式接口如下:

@FunctionalInterface
interface Animal{
    void barks(); } 

那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):
Animal cat= ()-> System.out.println("miao miao~");
函数式接口可以对现有的函数友好地支持 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
JDK 1.8 新增加了很多函数接口,主要在 java.util.function 包下,用来支持 Java的 函数式编程,该包中的函数式接口有:

序号 接口 & 描述
1 BiConsumer

代表了一个接受两个输入参数的操作,并且不返回任何结果

2 BiFunction

代表了一个接受两个输入参数的方法,并且返回一个结果

3 BinaryOperator

代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果

4 BiPredicate

代表了一个两个参数的boolean值方法

5 BooleanSupplier

代表了boolean值结果的提供方

6 Consumer

代表了接受一个输入参数并且无返回的操作

7 DoubleBinaryOperator

代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。

8 DoubleConsumer

代表一个接受double值参数的操作,并且不返回结果。

9 DoubleFunction

代表接受一个double值参数的方法,并且返回结果

10 DoublePredicate

代表一个拥有double值参数的boolean值方法

11 DoubleSupplier

代表一个double值结构的提供方

12 DoubleToIntFunction

接受一个double类型输入,返回一个int类型结果。

13 DoubleToLongFunction

接受一个double类型输入,返回一个long类型结果

14 DoubleUnaryOperator

接受一个参数同为类型double,返回值类型也为double 。

15 Function

接受一个输入参数,返回一个结果。

16 IntBinaryOperator

接受两个参数同为类型int,返回值类型也为int 。

17 IntConsumer

接受一个int类型的输入参数,无返回值 。

18 IntFunction

接受一个int类型输入参数,返回一个结果 。

19 IntPredicate

:接受一个int输入参数,返回一个布尔值的结果。

20 IntSupplier

无参数,返回一个int类型结果。

21 IntToDoubleFunction

接受一个int类型输入,返回一个double类型结果 。

22 IntToLongFunction

接受一个int类型输入,返回一个long类型结果。

23 IntUnaryOperator

接受一个参数同为类型int,返回值类型也为int 。

24 LongBinaryOperator

接受两个参数同为类型long,返回值类型也为long。

25 LongConsumer

接受一个long类型的输入参数,无返回值。

26 LongFunction

接受一个long类型输入参数,返回一个结果。

27 LongPredicate

R接受一个long输入参数,返回一个布尔值类型结果。

28 LongSupplier

无参数,返回一个结果long类型的值。

29 LongToDoubleFunction

接受一个long类型输入,返回一个double类型结果。

30 LongToIntFunction

接受一个long类型输入,返回一个int类型结果。

31 LongUnaryOperator

接受一个参数同为类型long,返回值类型也为long。

32 ObjDoubleConsumer

接受一个object类型和一个double类型的输入参数,无返回值。

33 ObjIntConsumer

接受一个object类型和一个int类型的输入参数,无返回值。

34 ObjLongConsumer

接受一个object类型和一个long类型的输入参数,无返回值。

35 Predicate

接受一个输入参数,返回一个布尔值结果。

36 Supplier

无参数,返回一个结果。

37 ToDoubleBiFunction

接受两个输入参数,返回一个double类型结果

38 ToDoubleFunction

接受一个输入参数,返回一个double类型结果

39 ToIntBiFunction

接受两个输入参数,返回一个int类型结果。

40 ToIntFunction

接受一个输入参数,返回一个int类型结果。

41 ToLongBiFunction

接受两个输入参数,返回一个long类型结果。

42 ToLongFunction

接受一个输入参数,返回一个long类型结果。

43 UnaryOperator

接受一个参数为类型T,返回值类型也为T。

3.2 用法

Predicate接口是一个函数式接口,它接受一个输入参数 T,返回一个布尔值结果。
该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)。
该接口用于测试对象是 true 或 false。
我们可以通过以下实例(Java8Tester.java)来了解函数式接口 Predicate的使用:

public class Java8Tester {
    public static void main(String[] args) {         List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);         // Predicate predicate = n -> true         // n 是一个参数传递到 Predicate 接口的 test 方法         // n 如果存在则 test 方法返回 true         System.out.println("输出所有数据:");         eval(list, n -> true);         // Predicate predicate1 = n -> n%2 == 0         // n 是一个参数传递到 Predicate 接口的 test 方法         // 如果 n%2 为 0 test 方法返回 true         System.out.println("输出所有偶数:");         eval(list, n -> n % 2 == 0);         // Predicate predicate2 = n -> n > 3         // n 是一个参数传递到 Predicate 接口的 test 方法         // 如果 n 大于 3 test 方法返回 true         System.out.println("输出大于 3 的所有数字:");         eval(list, n -> n > 3);     }     public static void eval(List list, Predicate predicate) {         for (Integer n : list) {             if (predicate.test(n)) {                 System.out.print(n + " ");             }         }         System.out.println();     } } 

输出

输出所有数据:
1 2 3 4 5 6 7 8 9 
输出所有偶数:
2 4 6 8
输出大于 3 的所有数字:
4 5 6 7 8 9

4、默认方法

4.1 介绍

Java 8 新增了接口的默认方法。简单来说,默认方法就是接口定义的方法可以有一个默认的实现,而且不需要实现类去实现其方法。我们只需在方法名前面加个 default 关键字即可实现默认方法。

为什么要有这个特性?
首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8 之前的集合框架没有 foreach 方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

4.2 用法

默认方法语法格式如下:

public interface Car {
   default void print(){       System.out.println("我是一辆汽车!");    } } 

多个默认方法

一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法:

public interface Car {
   default void print(){       System.out.println("我是一辆汽车!");    } } public interface ElectricallyDriven {    default void print(){       System.out.println("我需要电力驱动!");    } } 

第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:

public class ElectricallyDrivenCar implements Car, ElectricallyDriven {    default void print(){       System.out.println("我是一辆电动汽车!");    } } 

第二种解决方案可以使用 super 来调用指定接口的默认方法:

public class ElectricallyDrivenCar implements Car, ElectricallyDriven {    public void print(){       ElectricallyDriven.super.print();    } } 

静态默认方法

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

public interface Car {
   default void print(){       System.out.println("我是一辆汽车!");    }    // 静态方法    static void blowHorn(){       System.out.println("按喇叭!!!");    } } 

默认方法实例

我们可以通过以下代码来了解关于默认方法的使用,可以将代码放入 DefaultMethodTest.java 文件中:

public class DefaultMethodTest {
   public static void main(String args[]){         Car electricallyDrivenCar = new ElectricallyDrivenCar();         electricallyDrivenCar.print();         System.out.println("=================");         Car.blowHorn();    } } interface Car {    default void print(){       System.out.println("我是一辆汽车!");    }    static void blowHorn(){       System.out.println("按喇叭!!!");    } } interface ElectricallyDriven {    default void print(){       System.out.println("我需要电力驱动!");    } } class ElectricallyDrivenCar implements Car, ElectricallyDriven {    public void print(){       Car.super.print();       ElectricallyDriven.super.print();       Car.blowHorn();       System.out.println("我是一辆电力驱动的汽车!");    } } 

执行以上脚本,输出结果为:

我是一辆汽车!
我需要电力驱动!
按喇叭!!!
我是一辆电力驱动的汽车!
=================
按喇叭!!!

5、Optional

5.1 介绍

Optio是一种容器对象,它内部可以认为只有一个字段,那就是你传入的值,这个值可以为空也可以不为空,这个类可以理解为是一个包装类,内部提供了很多判断和操作目标对象的方法。

Java 8的新特性还不了解?快进来!_第4张图片

例如,isPresent()方法,如果值存在则该方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,帮我们很好的解决空指针异常,这样我们就不用显式进行空值检测。

5.2 类方法

以下是Optional的方法:

序号 方法 & 描述
1 static Optional empty()

返回空的 Optional 实例。

2 boolean equals(Object obj)

判断其他对象是否等于 Optional。

3 Optional filter(Predicate predicate)

如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。

4 Optional flatMap(Function> mapper)

如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional

5 T get()

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

6 int hashCode()

返回存在值的哈希码,如果值不存在 返回 0。

7 void ifPresent(Consumer consumer)

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

8 boolean isPresent()

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

9 Optional map(Function mapper)

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

10 static Optional of(T value)

返回一个指定非null值的Optional。

11 static Optional ofNullable(T value)

如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。

12 T orElse(T other)

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

13 T orElseGet(Supplier other)

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

14 T orElseThrow(Supplier exceptionSupplier)

如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常

15 String toString()

返回一个Optional的非空字符串,用来调试

5.3 用法

public class User{
    String name;
    int age;     public User(String name, int age) {         this.name = name;         this.age = age;     }     //equals and toString } 

我们先来看一下of() 、ofNullable() 、equals() 、toString()方法

public static void main(String args[]) {     User walking = new User("walking", 18);     User walking2 = new User("walking", 18);     //of 返回一个指定非null值的Optional。如果传递的参数是 null,抛出异常 NullPointerException     Optional walkingOp = Optional.of(walking);     //ofNullable 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。允许传递为 null 参数     Optional walking2Op = Optional.ofNullable(walking2);     //equals 判断其他对象是否等于 Optional     boolean equals = walkingOp.equals(walking2Op);     System.out.println("两个optional的值是否一样:" + equals);     //toString 返回一个Optional的非空字符串,用来调试     System.out.println("Optional的toString:" + walkingOp.toString()); } 

控制台输出:

两个optional的值是否一样:true
Optional的toString:Optional[User{name='walking', age=18}]

我们再来看一下filter()isPresent()ifPresent(Consumer consumer)方法

public static void main(String args[]) {     Optional walkingOp = Optional.of(new User("walking", 18));     //filter 如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。     Optional userOptional = walkingOp.filter(user -> user.age == 30);     System.out.println("filter后:" + userOptional.toString());     //isPresent 如果值存在则方法会返回true,否则返回 false。     System.out.println("是否匹配:" + userOptional.isPresent());     //ifPresent 如果值存在则使用该值调用 consumer , 否则不做任何事情。     userOptional.ifPresent(user -> System.out.println("user==>" + user)); } 

控制台输出:

filter后:Optional.empty
是否匹配:false

以上代码可以判断Optional中的值是否满足我们filter的条件,很显然isPresent()返回false即不满足filter中的条件,所以ifPresent(Consumer consumer)中传入的函数也就不会执行。
然后再看一下map()方法

public static void main(String args[]) {     Optional walkingOp = Optional.of(new User("walking", 18));     //map 如果walkingOp有值,则对其执行调用传入的函数得到返回值。     // 如果返回值不为 null(现在我们返回的是new User("new walking-20", 20)),则创建包含该返回值的Optional作为map方法返回值,否则返回空Optional。     Optional userOptional1 = walkingOp.map(user -> new User("new walking-20", 20));     //walkingOP不为空,执行了函数,那么返回一个包含该值的Optional     System.out.println("包含返回值的Optional:" + userOptional1);     //现在函数返回值是null,则返回空Optional     Optional userOptional2 = walkingOp.map(user -> null);     //walkingOP不为空,执行了函数,但函数返回值为空,那么返回一个空Optional     System.out.println("空Optional:" + userOptional2.toString());     Optional nilOptional = Optional.ofNullable(null);     Optional userOptional3 = nilOptional.map(user -> new User("原optional为空", 22));     //原optional为空,不会执行函数,返回的Optional为空     System.out.println("原optional为空:" + userOptional3.toString()); } 

控制台:

包含返回值的Optional:Optional[User{name='new walking-20', age=20}]
空Optional:Optional.empty
原optional为空:Optional.empty

然后再来看一下orElse()、orElseGet()、orElseThrow()方法

public static void main(String args[]) {     Optional nilOptional = Optional.ofNullable(null);     //orElse 如果存在该值,返回值, 否则返回 other。     User u = nilOptional.orElse(new User("new walking-20", 20));     System.out.println("nilOptional无值,返回传入的对象:" + u);     Optional userOptional4 = Optional.of(new User("new walking-90", 90));     User user = userOptional4.orElse(new User("new walking-91", 91));     System.out.println("userOptional4有值,返回其值:" + user);     //orElseGet 如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。     User ss = nilOptional.orElseGet(() -> new User("new walking-99", 99));     System.out.println("nilOptional无值,返回函数的返回值:" + ss);     //orElseThrow 如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常     User user1 = nilOptional.orElseThrow(() -> new NullPointerException("nilOptional为空 执行函数返回的异常"));     System.out.println("nilOptional为空,返回值为空:" + user1); } 

控制台:

nilOptional无值,返回传入的对象:User{name='new walking-20', age=20}
userOptional4有值,返回其值:User{name='new walking-90', age=90}
nilOptional无值,返回函数的返回值:User{name='new walking-99', age=99}
Exception in thread "main" java.lang.NullPointerException: nilOptional为空 执行函数返回的异常     at com.walking.java8.OptionalTest.lambda$main$1(OptionalTest.java:27)     at java.util.Optional.orElseThrow(Optional.java:290)     at com.walking.java8.OptionalTest.main(OptionalTest.java:27) 

6、日期时间 API

Java 8通过发布新的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类,但他们同样存在上述所有的问题。

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

  • Local(本地) − 简化了日期时间的处理,没有时区的问题。

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

6.1 本地化日期时间 API

LocalDate/LocalTime 和 LocalDateTime 类可以在处理时区不是必须的情况。代码如下:

public static void main(String[] args) {     // 获取当前的日期时间     LocalDateTime currentTime = LocalDateTime.now();     System.out.println("当前时间: " + currentTime);     //获取当前日期     LocalDate date1 = currentTime.toLocalDate();     System.out.println("date1: " + date1);     //获取当前时间     LocalTime localTime = currentTime.toLocalTime();     System.out.println("time1: "+localTime);     int year = currentTime.getYear();     Month month = currentTime.getMonth();     int day = currentTime.getDayOfMonth();     int hour = currentTime.getHour();     int minute = currentTime.getMinute();     int seconds = currentTime.getSecond();     System.out.println("年:"+year+", 月: " + month.getValue() +", 日: " + day +",时:"+hour+",分: "+minute+", 秒: " + seconds);     //修改天,修改年     LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2018);     System.out.println("date2: " + date2);     // 2017 5 1     LocalDate date3 = LocalDate.of(2017, Month.MAY, 1);     System.out.println("date3: " + date3);     // 18 小时 20 分钟     LocalTime time2 = LocalTime.of(18, 20);     System.out.println("time2: " + time2);     // 解析字符串     LocalTime time3 = LocalTime.parse("19:50:40");     System.out.println("time3: " + time3); } 

输出:

当前时间: 2020-08-15T18:28:27.273
date1: 2020-08-15
time1: 18:28:27.273
年:2020, 月: 8, 日: 15,时:18,分: 28, 秒: 27
date2: 2018-08-10T18:28:27.273
date3: 2017-05-01
time2: 18:20
time3: 19:50:40

6.2 使用时区的日期时间API

如果我们需要考虑到时区,就可以使用时区的日期时间API:

public static void main(String[] args) {     // 获取当前的时间日期     ZonedDateTime now = ZonedDateTime.now();     System.out.println("当前时间日期: "+now.toString());     // 获取指定的时间日期     ZonedDateTime date1 = ZonedDateTime.parse("2020-08-15T18:15:30+05:30[Asia/Shanghai]");     System.out.println("指定时间日期: " + date1);     ZoneId id = ZoneId.of("Asia/Tokyo");     System.out.println("ZoneId: " + id);     ZoneId currentZone = ZoneId.systemDefault();     System.out.println("当期时区: " + currentZone); } 

控制台:

当前时间日期: 2020-08-15T18:42:47.706+08:00[Asia/Shanghai]
指定时间日期: 2020-08-15T18:15:30+08:00[Asia/Shanghai]
ZoneId: Asia/Tokyo
当期时区: Asia/Shanghai

6.3 日期格式化

public class DateTimeUtil{
    public static void main(String[] args) {         //不带时间的日期         String output1 = formattedDate("2019年07月15日", "yyyy年MM月dd日", "yyyy-MM-dd");         String output2 = formattedDate("2019/07/16", "yyyy/MM/dd", "yyyy-MM-dd");         String output3 = formattedDate("2019-07-16", "yyyy-MM-dd", "yyyy/MM/dd");         System.out.println(output1);         System.out.println(output2);         System.out.println(output3);         //带时间的日期         String output4 = formattedDateTime("20190713000000", "yyyyMMddHHmmss", "yyyy-MM-dd");         String output5 = formattedDateTime("20190713000000", "yyyyMMddHHmmss", "yyyy-MM-dd HH:mm:ss");         String output6 = formattedDateTime("2019年07月14日00时00分00秒", "yyyy年MM月dd日HH时mm分ss秒",                 "yyyy/MM/dd");         String output7 = formattedDateTime("2019年07月14日00时00分00秒", "yyyy年MM月dd日HH时mm分ss秒",                 "yyyyMMddHHmmss");         String output8 = formattedDateTime("2019-07-15 00:00:00", "yyyy-MM-dd HH:mm:ss", "yyyyMMdd");         String output9 = formattedDateTime("2019-07-15 00:00:00", "yyyy-MM-dd HH:mm:ss",                 "yyyy年MM月dd日HH时mm分ss");         System.out.println(output4);         System.out.println(output5);         System.out.println(output6);         System.out.println(output7);         System.out.println(output8);         System.out.println(output9);     }     /**      * 不带时分秒的日期字符串转化      *      * @param input 输入的日期      * @param inputFormat 输入日期的格式      * @param outputFormat 输出日期的格式      * @return 输出的日期,不带时分秒      */     public static String formattedDate(String input, String inputFormat, String outputFormat) {         DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern(inputFormat);         LocalDate localDate = LocalDate.parse(input, inputFormatter);         DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern(outputFormat);         return localDate.format(outputFormatter);     }     /**      * 带时分秒的日期字符串转换      *      * @param input 输入的日期      * @param inputFormat 输入日期的格式      * @param outputFormat 输出日期的格式      * @return 输出指定格式的日期,可以带时分秒,也可以不带      */     public static String formattedDateTime(String input, String inputFormat, String outputFormat) {         DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern(inputFormat);         LocalDateTime localDateTime = LocalDateTime.parse(input, inputFormatter);         DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern(outputFormat);         return localDateTime.format(outputFormatter);     } } 

6.4 Date和LocalDateTime互转

/**
 * LocalDateTime -> Date
 */
public static Date toDate(LocalDateTime localDateTime) {     ZoneId zoneId = ZoneId.systemDefault();     ZonedDateTime zdt = localDateTime.atZone(zoneId);     return Date.from(zdt.toInstant()); } /**  * Date -> LocalDateTime  */ public static LocalDateTime toLocalDateTime(Date date) {     Instant instant = date.toInstant();     ZoneId zoneId = ZoneId.systemDefault();     return instant.atZone(zoneId).toLocalDateTime(); } 

7、Base64

7.1 介绍

在Java 8中,Base64编码已经成为Java类库的标准。
Java 8 内置了 Base64 编码的编码器和解码器。
Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:

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

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

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

7.2 用法

public static void main(String args[]){     try {         // 使用基本编码         String base64encodedString = Base64.getEncoder().encodeToString("walking@2020".getBytes("utf-8"));         System.out.println("Base64 编码字符串 (基本) :" + base64encodedString);         // 解码         byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);         System.out.println("Base64 编码字符串 (基本) : " + new String(base64decodedBytes, "utf-8"));         base64encodedString = Base64.getUrlEncoder().encodeToString("walking@2020".getBytes("utf-8"));         System.out.println("Base64 编码字符串 (URL) :" + base64encodedString);         byte[] decode = Base64.getUrlDecoder().decode(base64encodedString.getBytes("utf-8"));         System.out.println("Base64 解码字符串 (URL) :"+new String(decode,"utf-8"));         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);         byte[] decode1 = Base64.getMimeDecoder().decode(mimeEncodedString.getBytes("utf-8"));         System.out.println("Base64 解码字符串 (MIME) :"+new String(decode1,"utf-8"));     }catch(UnsupportedEncodingException e){         System.out.println("Error :" + e.getMessage());     } } 

控制台输出:

Base64 编码字符串 (基本) :d2Fsa2luZ0AyMDIw
Base64 编码字符串 (基本) : walking@2020
Base64 编码字符串 (URL) :d2Fsa2luZ0AyMDIw
Base64 解码字符串 (URL) :walking@2020
Base64 编码字符串 (MIME) :N2NlZWQ2MzAtYTZiYy00NWIwLWE1OGItNzE5NzcxYTcxOGUxYzU3ODY2ZDItMTY4OC00YjczLWE5
YTEtNTFkZTViMTJlOWY4YTM2NjBkY2YtOGFjZC00M2EyLWJkNTEtMDY5Y2I3NjI2OTVjY2Q0MGI5
MzYtNjEyMC00MDBhLWIzMGEtMjU1MGQzNGQ2ODAxMDk1NzAxYTAtYjk3Yy00NTE5LTk3ZDItYzdj
MDYyM2EzZDNkNDk4MWFiYjMtZDg4Yi00NTk2LWI4MzYtMjQ5Y2RkODg5NDQ5ZWNlMDY5ZjctMzdh
Mi00ZTc3LTgxNmQtZjI2MzliMGJhMGYwM2M5OWI4NWUtMzZjOS00NjgxLWE5ZTktYjdjNTAwOTlm
YWMwZjQzOTI3YTctY2Y3ZS00ZDFhLWIxYzQtOWY1ODFkMzliY2Y1ZjYyMjM3YTUtOGI2Zi00N2Iw
LWJmNzctYjJiODJiOGM3ZDQx
Base64 解码字符串 (MIME) :7ceed630-a6bc-45b0-a58b-719771a718e1c57866d2-1688-4b73-a9a1-51de5b12e9f8a3660dcf-8acd-43a2-bd51-069cb762695ccd40b936-6120-400a-b30a-2550d34d6801095701a0-b97c-4519-97d2-c7c0623a3d3d4981abb3-d88b-4596-b836-249cdd889449ece069f7-37a2-4e77-816d-f2639b0ba0f03c99b85e-36c9-4681-a9e9-b7c50099fac0f43927a7-cf7e-4d1a-b1c4-9f581d39bcf5f62237a5-8b6f-47b0-bf77-b2b82b8c7d41

 

8、Nashorn JavaScript引擎

8.1 介绍

Nashorn是一个 javascript 引擎。
从JDK 1.8开始,Nashorn取代Rhino(JDK 1.6, JDK1.7)成为Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1规范以及一些扩展。它使用基于JSR 292的新语言特性,其中包含在JDK 7中引入的 invokedynamic,将JavaScript编译成Java字节码。与先前的Rhino实现相比,这带来了2到10倍的性能提升。
jjs是个基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码

8.2 用法

交互式编程: 打开CMD命令窗口,输入jjs回车即可进入jjs环境,然后输入print("hello world!!"),回车

Java 8的新特性还不了解?快进来!_第5张图片

传递参数: 输入 jjs -- a b c 回车,然后输入 print("连接:"+arguments.jion(", ")) 回车

Java 8的新特性还不了解?快进来!_第6张图片

执行javascript文件:我们在E盘新建test.js文件,打开文件键入: print("walking is a dashuaibi") ,打开CMD窗口进入到同目录下,输入 jjs test.js 回车

Java调用JavaScript
示例代码

public static void main(String args[]){     ScriptEngineManager scriptEngineManager = new ScriptEngineManager();     ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");     String name = "walking";     try {         nashorn.eval("print('" + name + "')");         Integer result = (Integer) nashorn.eval("1 + 1");         System.out.println(result);         boolean re = (boolean) nashorn.eval("1 > 2");         System.out.println(re);     }catch(ScriptException e){         e.printStackTrace();     } } 

输出

walking
2
false

JavaScript 中调用 Java

以下实例演示了如何在 JavaScript 中引用 Java 类,创建JSCallJava.js文件,内容如下:

var HashMap = Java.type('java.util.HashMap');
function getHashMap(name, age) {
   var result = new HashMap();    result.put("name",name);    result.put("age",age);    return result; } var result = getHashMap("编程大道",18); print(result); 

然后我们执行 jjs JSCallJava.js 回车


往期热文:

  • Redis的各种数据类型到底能玩出什么花儿?

  • Redis使用指南

  • Redis分布式锁实战

  • 联合索引在B+树上的存储结构及数据查找方式

  • MySQL索引那些事

  • Redis缓存穿透,缓存击穿,缓存雪崩,热点Key

 

Java 8的新特性还不了解?快进来!_第7张图片

 

觉得好看,请点赞哦~

觉得好看,请 点赞、关注、转发 哦~
关注公众号 编程大道 ,第一时间获文章推送。

Java 8的新特性还不了解?快进来!_第8张图片

你可能感兴趣的:(Java 8的新特性还不了解?快进来!)