Java8新特性学习(二)

Java 8 为 Java 语言、编译器、类库、开发工具与 JVM( Java 虚拟机)带来了大量新特性。
上一篇了解了最重要的 Stream 和 Lambda 表达式(或者说闭包,虽然不是很恰当),或者可以理解为 Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中),或者把代码看成数据。
这篇就补全剩下的部分,默认方法啦、方法引用(双冒号运算符)、新的类库,当然这些也是不全的,我认为经常用的就这些了,全部的新特性可以见参考的链接。

接口的默认方法与静态方法

Java 8 用默认方法与静态方法这两个新概念来扩展接口的声明。
默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求。相反,如果接口定义了默认方法,那么必须提供一个所谓的默认实现,这样所有的接口实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private interface Defaulable {
// Interfaces now allow default methods, the implementer may or
// may not implement (override) them.
default String notRequired() {
return "Default implementation";
}
}

// 不需要实现默认方法
private static class DefaultableImpl implements Defaulable {}

// 可以覆盖默认方法
private static class OverridableImpl implements Defaulable {
@Override
public String notRequired() {
return "Overridden implementation";
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}

public static void main( String[] args ) {
Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
System.out.println( defaulable.notRequired() );

defaulable = DefaulableFactory.create( OverridableImpl::new );
System.out.println( defaulable.notRequired() );
}

在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的 Java 接口,而同时能够保障正常的编译过程。
这方面好的例子是大量的方法被添加到 java.util.Collection 接口中去:stream(),parallelStream(),forEach(),removeIf(),……
尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。

为什么要有默认方法

在 java 8 之前,接口与其实现类之间的 耦合度 太高了(tightly coupled),当需要为一个接口添加方法时,所有的实现类都必须随之修改。默认方法解决了这个问题,它可以为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式作为 java 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。
这个 forEach 方法是 jdk 1.8 新增的接口默认方法,正是因为有了默认方法的引入,才不会因为 Iterable 接口中添加了 forEach 方法就需要修改所有 Iterable 接口的实现类。

关于继承

和其它方法一样,接口默认方法也可以被继承。

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
interface InterfaceA {
default void foo() {
System.out.println("InterfaceA foo");
}
}

interface InterfaceB extends InterfaceA {}

interface InterfaceC extends InterfaceA {
@Override
default void foo() {
System.out.println("InterfaceC foo");
}
}

// 覆写默认方法并将它重新声明为抽象方法
interface InterfaceD extends InterfaceA {
@Override
void foo();
}

public class Test {
public static void main(String[] args) {
new InterfaceB() {}.foo(); // 打印:“InterfaceA foo”
new InterfaceC() {}.foo(); // 打印:“InterfaceC foo”
new InterfaceD() {
@Override
public void foo() {
System.out.println("InterfaceD foo");
}
}.foo(); // 打印:“InterfaceD foo”

// 或者使用 lambda 表达式
((InterfaceD) () -> System.out.println("InterfaceD foo")).foo();
}
}

接口默认方法的继承分三种情况(分别对应上面的 InterfaceB 接口、InterfaceC 接口和 InterfaceD 接口):

  • 不覆写默认方法,直接从父接口中获取方法的默认实现。
  • 覆写默认方法,这跟类与类之间的覆写规则相类似。
  • 覆写默认方法并将它重新声明为抽象方法,这样新接口的子类必须再次覆写并实现这个抽象方法。

然后来考虑下多继承的问题,是的,默认方法在接口里,接口可以继承,接口可以多实现,那么自然就带来了默认方法多继承的问题;但是 Java 使用的是单继承、多实现的机制,为的是避免多继承带来的调用歧义的问题。当接口的子类同时拥有具有相同签名的方法时,就需要考虑一种解决冲突的方案。

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
interface InterfaceA {
default void foo() {
System.out.println("InterfaceA foo");
}
}

interface InterfaceB {
default void bar() {
System.out.println("InterfaceB bar");
}
}

interface InterfaceC {
default void foo() {
System.out.println("InterfaceC foo");
}

default void bar() {
System.out.println("InterfaceC bar");
}
}

// 不存在冲突
class ClassA implements InterfaceA, InterfaceB {}

// 错误,存在冲突
//class ClassB implements InterfaceB, InterfaceC {}

class ClassB implements InterfaceB, InterfaceC {
@Override
public void bar() {
InterfaceB.super.bar(); // 调用 InterfaceB 的 bar 方法
InterfaceC.super.bar(); // 调用 InterfaceC 的 bar 方法
System.out.println("ClassB bar"); // 做其他的事
}
}

在 ClassB 类中,它实现的 InterfaceB 接口和 InterfaceC 接口中都存在相同签名的 foo 方法,需要手动解决冲突。覆写存在歧义的方法,并可以使用 InterfaceName.super.methodName(); 的方式手动调用需要的接口默认方法。


下面来看特殊情况:接口继承行为发生冲突时的解决规则。
比如,出现了下面的这种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface InterfaceA {
default void foo() {
System.out.println("InterfaceA foo");
}
}

interface InterfaceB extends InterfaceA {
@Override
default void foo() {
System.out.println("InterfaceB foo");
}
}

// 正确
class ClassA implements InterfaceA, InterfaceB {}

class ClassB implements InterfaceA, InterfaceB {
@Override
public void foo() {
// InterfaceA.super.foo(); // 错误
InterfaceB.super.foo();
}
}

因为 InterfaceB 接口继承了 InterfaceA 接口,那么 InterfaceB 接口一定包含了所有 InterfaceA 接口中的字段方法,因此一个同时实现了 InterfaceA 接口和 InterfaceB 接口的类与一个只实现了 InterfaceB 接口的类完全等价。
这很好理解,就相当于 class SimpleDateFormat extends DateFormatclass SimpleDateFormat extends DateFormat, Object 等价(如果允许多继承)。
而覆写意味着对父类方法的屏蔽,这也是 Override 的设计意图之一。因此在实现了 InterfaceB 接口的类中无法访问已被覆写的 InterfaceA 接口中的 foo 方法。
这是当接口继承行为发生冲突时的规则之一,即 被其它类型所覆盖的方法会被忽略
如果想要调用 InterfaceA 接口中的 foo 方法,只能通过自定义一个新的接口同样继承 InterfaceA 接口并显示地覆写 foo 方法,在方法中使用 InterfaceA.super.foo(); 调用 InterfaceA 接口的 foo 方法,最后让实现类同时实现 InterfaceB 接口和自定义的新接口,代码如下:

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
interface InterfaceA {
default void foo() {
System.out.println("InterfaceA foo");
}
}

interface InterfaceB extends InterfaceA {
@Override
default void foo() {
System.out.println("InterfaceB foo");
}
}

interface InterfaceC extends InterfaceA {
@Override
default void foo() {
InterfaceA.super.foo();
}
}

class ClassA implements InterfaceB, InterfaceC {
@Override
public void foo() {
InterfaceB.super.foo();
InterfaceC.super.foo();
}
}

注意! 虽然 InterfaceC 接口的 foo 方法只是调用了一下父接口的默认实现方法,但是这个覆写 不能省略,否则 InterfaceC 接口中继承自 InterfaceA 接口的隐式的 foo 方法同样会被认为是被 InterfaceB 接口覆写了而被屏蔽,会导致调用 InterfaceC.super.foo() 时出错。
通过这个例子,应该注意到在使用一个默认方法前,一定要考虑它是否真的需要。因为 默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。滥用默认方法可能给代码带来意想不到、莫名其妙的错误。

接口与抽象类

当接口继承行为发生冲突时的另一个规则是,类的方法声明优先于接口默认方法,无论该方法是具体的还是抽象的

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
interface InterfaceA {
default void foo() {
System.out.println("InterfaceA foo");
}

default void bar() {
System.out.println("InterfaceA bar");
}
}

abstract class AbstractClassA {
public abstract void foo();

public void bar() {
System.out.println("AbstractClassA bar");
}
}

class ClassA extends AbstractClassA implements InterfaceA {
@Override
public void foo() {
InterfaceA.super.foo();
}
}

public class Test {
public static void main(String[] args) {
ClassA classA = new ClassA();
classA.foo(); // 打印:“InterfaceA foo”
classA.bar(); // 打印:“AbstractClassA bar”
}
}

ClassA 类中并不需要手动覆写 bar 方法,因为优先考虑到 ClassA 类继承了的 AbstractClassA 抽象类中存在对 bar 方法的实现,同样的因为 AbstractClassA 抽象类中的 foo 方法是抽象的,所以在 ClassA 类中必须实现 foo 方法。
虽然 Java 8 的接口的默认方法就像抽象类,能提供方法的实现,但是他们俩仍然是 不可相互代替的

  • 接口可以被类多实现(被其他接口多继承),抽象类只能被单继承。
  • 接口中没有 this 指针,没有构造函数,不能拥有实例字段(实例变量)或实例方法,无法保存 状态state),抽象方法中可以。
  • 抽象类不能在 java 8 的 lambda 表达式中使用。
  • 从设计理念上,接口反映的是 “like-a” 关系,抽象类反映的是 “is-a” 关系。

顺便复习了下接口和抽象类的知识点~~

其他

补充下其他的知识点:

  • default 关键字只能在接口中使用(以及用在 switch 语句的 default 分支),不能用在抽象类中。
  • 接口默认方法不能覆写 Object 类的 equalshashCodetoString 方法。
  • 接口中的静态方法必须是 public 的,public 修饰符可以省略,static 修饰符不能省略。
  • 即使使用了 java 8 的环境,一些 IDE 仍然可能在一些代码的实时编译提示时出现异常的提示(例如无法发现 java 8 的语法错误),因此不要过度依赖 IDE。

方法引用

其实就是上篇所说的双冒号操作,不知道还有没有印象,即 目标引用::方法 ,下面就来看看具体的几种用法。
方法引用提供了非常有用的语法,可以直接引用已有 Java 类或对象的方法或构造器。与 lambda 联合使用(一般是不能独立使用的),方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
下面来看看 Java 支持的这四种不同的方法引用:

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
public static class Car {

public static Car create( final Supplier< Car > supplier ) {
return supplier.get();
}

public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}

public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}

public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}

// 第一种
Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );

// 第二种
cars.forEach( Car::collide );

// 第三种
cars.forEach( Car::repair );

// 第四种
final Car police = Car.create( Car::new );
cars.forEach( police::follow );

这四类可以定义为:

  1. 类名::new
  2. 类名::静态方法名
  3. 类名::实例方法名
    这种方法引用有些特殊之处:当使用这种方式时,一定是 lambda 表达式所接收的第一个参数来调用实例方法,如果lambda表达式接收多个参数,其余的参数作为方法的参数传递进去。
    参考:http://sfau.lt/b5ZD16
  4. 对象::实例方法名

下面就来解释下上面例子里的四种方式,说的都是在本例的情况下。
第一种方法引用是构造器引用,它的语法是 Class::new,或者更一般的 Class< T >::new。new 不就是调用构造函数嘛~请注意构造器没有参数。
第二种方法引用是静态方法引用,它的语法是 Class::static_method ,请注意这个方法接受一个 Car 类型的参数。
第三种方法引用是特定类的任意对象的方法引用,它的语法是 Class::method。请注意,这个方法没有参数,并且是非静态。
最后,第四种方法引用是特定对象的方法引用,它的语法是 instance::method。请注意,这个方法接受一个 Car 类型的参数

类库新特性

Java 8 通过增加大量新类,扩展已有类的功能的方式来改善对并发编程、函数式编程、日期/时间相关操作以及其他更多方面的支持。

Optional

到目前为止,臭名昭著的空指针异常是导致 Java 应用程序失败的最常见原因。以前,为了解决空指针异常,Google 公司著名的 Guava 项目引入了 Optional 类,Guava 通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。
受到 Google Guava 的启发,Optional 类已经成为 Java 8 类库的一部分。
Optional 实际上是个容器:它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。在 Javadoc 中的描述翻译过来就是:
这是一个可以为 null 的容器对象。如果值存在则 isPresent() 方法会返回 true,调用 get() 方法会返回该对象。
下面就来看看它的几个方法(在前面说 stream 的时候大量使用了 Optional ):

  • of
    非 null 的值创建一个 Optional。
    of 方法通过工厂方法创建 Optional 类。需要注意的是,创建对象时传入的参数不能为 null。如果传入参数为 null,则抛出 NPE。
  • ofNullable
    为指定的值创建一个 Optional,如果指定的值为 null,则返回一个空的 Optional。
  • empty
    此方法用于创建一个没有值的 Optional 对象;如果对 emptyOpt 变量调用 isPresent() 方法会返回 false,调用 get() 方法抛出 NullPointerException 异常。
  • isPresent
    如果值存在返回 true,否则返回 false。
  • ifPresent
    如果 Optional 实例有值则为其调用 consumer(比如 lambda 表达式),否则不做处理
  • get
    如果 Optional 有值则将其返回,否则抛出 NoSuchElementException。
  • orElse
    如果有值则将其返回,否则返回指定的其它值(默认值)。
    empty.orElse("There is no value present!");
  • orElseGet
    orElseGet 与 orElse 方法类似,区别在于得到的默认值。
    orElse 方法将传入的字符串作为默认值,orElseGet 方法可以接受 Supplier 接口的实现用来生成默认值。
    empty.orElseGet(() -> "Default Value");
  • orElseThrow
    如果有值则将其返回,否则抛出 supplier 接口创建的异常。
    在 orElseThrow 中我们可以传入一个 lambda 表达式或方法,如果值不存在来抛出异常。
    empty.orElseThrow(ValueAbsentException::new);
  • map
    如果有值,则对其执行调用 mapping 函数得到返回值。
    如果返回值不为 null,则创建包含 mapping 返回值的 Optional 作为 map 方法返回值,否则返回空 Optional。
    Optional<String> upperName = name.map((value) -> value.toUpperCase());
  • flatMap
    如果有值,为其执行 mapping 函数返回 Optional 类型返回值,否则返回空 Optional。
    flatMap 与 map(Funtion)方法类似,区别在于 flatMap 中的 mapper 返回值必须是 Optional。
    调用结束时,flatMap 不会对结果用 Optional 封装。
    upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
  • filter
    如果有值并且满足断言条件返回包含该值的 Optional,否则返回空 Optional。
    对于 filter 函数我们应该传入实现了 Predicate 接口的 lambda 表达式。
    Optional<String> longName = name.filter((value) -> value.length() > 6);

要理解 ifPresent 方法,首先需要了解 Consumer 类。
简答地说,Consumer 类包含一个抽象方法。该抽象方法对传入的值进行处理,但没有返回值。
Java8 支持不用接口直接通过 lambda 表达式传入参数。

在 Java 9 中,对 Optional 还进行了增强,多加了几个方法,感兴趣的可以去:http://sfau.lt/b5KDt8
最后通过一个例子来综合的展示下:

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class OptionalDemo {

public static void main(String[] args) {
//创建Optional实例,也可以通过方法返回值得到。
Optional<String> name = Optional.of("Sanaulla");

//创建没有值的Optional实例,例如值为'null'
Optional empty = Optional.ofNullable(null);

//isPresent方法用来检查Optional实例是否有值。
if (name.isPresent()) {
//调用get()返回Optional值。
System.out.println(name.get());
}

try {
//在Optional实例上调用get()抛出NoSuchElementException。
System.out.println(empty.get());
} catch (NoSuchElementException ex) {
System.out.println(ex.getMessage());
}

//ifPresent方法接受lambda表达式参数。
//如果Optional值不为空,lambda表达式会处理并在其上执行操作。
name.ifPresent((value) -> {
System.out.println("The length of the value is: " + value.length());
});

//如果有值orElse方法会返回Optional实例,否则返回传入的错误信息。
System.out.println(empty.orElse("There is no value present!"));
System.out.println(name.orElse("There is some value!"));

//orElseGet与orElse类似,区别在于传入的默认值。
//orElseGet接受lambda表达式生成默认值。
System.out.println(empty.orElseGet(() -> "Default Value"));
System.out.println(name.orElseGet(() -> "Default Value"));

try {
//orElseThrow与orElse方法类似,区别在于返回值。
//orElseThrow抛出由传入的lambda表达式/方法生成异常。
empty.orElseThrow(ValueAbsentException::new);
} catch (Throwable ex) {
System.out.println(ex.getMessage());
}

//map方法通过传入的lambda表达式修改Optonal实例默认值。
//lambda表达式返回值会包装为Optional实例。
Optional<String> upperName = name.map((value) -> value.toUpperCase());
System.out.println(upperName.orElse("No value found"));

//flatMap与map(Funtion)非常相似,区别在于lambda表达式的返回值。
//map方法的lambda表达式返回值可以是任何类型,但是返回值会包装成Optional实例。
//但是flatMap方法的lambda返回值总是Optional类型。
upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
System.out.println(upperName.orElse("No value found"));

//filter方法检查Optiona值是否满足给定条件。
//如果满足返回Optional实例值,否则返回空Optional。
Optional<String> longName = name.filter((value) -> value.length() > 6);
System.out.println(longName.orElse("The name is less than 6 characters"));

//另一个示例,Optional值不满足给定条件。
Optional<String> anotherName = Optional.of("Sana");
Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
System.out.println(shortName.orElse("The name is less than 6 characters"));
}
}

Java 8 提倡函数式编程,新增的许多 API 都可以用函数式编程表示,Optional类也是其中之一。这里有几条关于Optional使用的建议:

  1. 尽量避免在程序中直接调用Optional对象的get()isPresent()方法(活用 orElse 系列);
  2. 避免使用Optional类型声明实体类的属性;

第一条建议中直接调用get()方法是很危险的做法,如果Optional的值为空,那么毫无疑问会抛出 NPE 异常,而为了调用get()方法而使用isPresent()方法作为空值检查,这种做法与传统的用 if 语句块做空值检查没有任何区别。
第二条建议避免使用 Optional 作为实体类的属性,它在设计的时候就没有考虑过用来作为类的属性,如果你查看 Optional 的源代码,你会发现它没有实现 java.io.Serializable 接口,这在某些情况下是很重要的(比如你的项目中使用了某些序列化框架),使用了 Optional 作为实体类的属性,意味着他们不能被序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
User user = ...
if (user != null) {
String userName = user.getUserName();
if (userName != null) {
return userName.toUpperCase();
} else {
return null;
}
} else {
return null;
}

// 简化 if-else
User user = ...
Optional<User> userOpt = Optional.ofNullable(user);

return user.map(User::getUserName)
.map(String::toUpperCase)
.orElse(null);

当你很确定一个对象不可能为 null 的时候,应该使用 of() 方法,否则,尽可能使用 ofNullable() 方法

新的时间和日期API

Java 8 另一个新增的重要特性就是引入了新的时间和日期 API,它们被包含在 java.time 包中。借助新的时间和日期 API 可以以更简洁的方法处理时间和日期。
在 Java 8 之前,所有关于时间和日期的 API 都存在各种使用方面的缺陷,主要有:

  1. Java 的 java.util.Datejava.util.Calendar类易用性差,不支持时区,并且是可变的,也就意味着他们都不是线程安全的;
  2. 用于格式化日期的类DateFormat被放在java.text包中,它是一个抽象类,所以我们需要实例化一个 SimpleDateFormat 对象来处理日期格式化,并且 DateFormat 也是非线程安全,这意味着如果你在多线程程序中调用同一个 DateFormat 对象,会得到意想不到的结果。
  3. 对日期的计算方式繁琐,而且容易出错,因为月份是从0开始的,这意味着从Calendar中获取的月份需要加一才能表示当前月份。

由于以上这些问题,出现了一些三方的日期处理框架,例如 Joda-Time,data4j 等开源项目。
但是,Java 需要一套标准的用于处理时间和日期的框架,于是 Java 8 中引入了新的日期 API。新的日期 API 是 JSR-310 规范的实现,Joda-Time 框架的作者正是 JSR-310 的规范的倡导者,所以能从 Java 8 的日期 API 中看到很多 Joda-Time 的特性。
常用的几个类就是 LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration 等.
下面通过几个示例代码来快速学会使用新版的日期时间 API:

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// 获取当前日期
LocalDate today = LocalDate.now();
System.out.println("Today's Local date : " + today); // 2014-01-14

// 获取年月日
LocalDate today = LocalDate.now();
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);
int length = today.lengthOfMonth(); // 月份的天数
boolean leapYear = today.isLeapYear(); // 是否为闰年

// 处理特定日期(只需要传入年月日)
LocalDate dateOfBirth = LocalDate.of(2010, 01, 14);
System.out.println("Your Date of birth is : " + dateOfBirth); // 2010-01-14

// 判断日期是否相等
LocalDate date1 = LocalDate.of(2014, 01, 14);
if(date1.equals(today)){
System.out.printf("Today %s and date1 %s are same date %n", today, date1);
}

// 检查像生日这种周期性事件,类似的还有 YearMonth
LocalDate dateOfBirth = LocalDate.of(2010, 01, 14);
MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(today);

if(currentMonthDay.equals(birthday)){
System.out.println("Many Many happy returns of the day !!");
}else{
System.out.println("Sorry, today is not your birthday");
}

// 获取当前时间(这次不是日期是时间)
LocalTime time = LocalTime.now();
System.out.println("local time now : " + time);

// 获取日期和时间,还可以进行拼接,或者 toLocalDate 拆分
LocalDateTime ldt1 = LocalDateTime.of(2017, Month.JANUARY, 4, 17, 23, 52);

LocalDate localDate = LocalDate.of(2017, Month.JANUARY, 4);
LocalTime localTime = LocalTime.of(17, 23, 52);
LocalDateTime ldt2 = localDate.atTime(localTime);

// 时间的操作(因为是不可变对象,所以操作后的是新实例)
LocalTime time = LocalTime.now();
LocalTime newTime = time.plusHours(2); // adding two hours
System.out.println("Time after 2 hours : " + newTime);

// 日期的操作,通过 withMonth 等方法可修改指定日期
LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS); // 可处理天,周,月
System.out.println("Today is : " + today);
System.out.println("Date after 1 week : " + nextWeek);

LocalDate previousYear = today.minus(1, ChronoUnit.YEARS); // 处理年
System.out.println("Date before 1 year : " + previousYear);
LocalDate nextYear = today.plus(1, YEARS);
System.out.println("Date after 1 year : " + nextYear);

// 是否早于或者晚于一个日期
LocalDate tomorrow = LocalDate.of(2014, 1, 15);
if(tommorow.isAfter(today)){
System.out.println("Tomorrow comes after today");
}
LocalDate yesterday = today.minus(1, DAYS);
if(yesterday.isBefore(today)){
System.out.println("Yesterday is day before today");
}

// 处理时区
ZoneId america = ZoneId.of("America/New_York");
LocalDateTime localtDateAndTime = LocalDateTime.now();
ZonedDateTime dateAndTimeInNewYork = ZonedDateTime.of(localtDateAndTime, america );
System.out.println("Current date and time in a particular timezone : " + dateAndTimeInNewYork);

// 计算两个日期之间的天数和月数
LocalDate java8Release = LocalDate.of(2014, Month.MARCH, 14);
Period periodToNextJavaRelease = Period.between(today, java8Release);
System.out.println("Months left between today and Java 8 release : "
+ periodToNextJavaRelease.getMonths() );

// 获取时间戳
Instant timestamp = Instant.now();
Instant instant = Instant.ofEpochSecond(120, 100000); // 第一个参数秒,第二个纳秒
System.out.println("What is value of this instant " + timestamp);

// 自定义格式化工具,第一个为字符串转日期,第二个日期转字符串
String goodFriday = "Apr 18 2014";
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd yyyy");
LocalDate holiday = LocalDate.parse(goodFriday, formatter);
System.out.printf("Successfully parsed String %s, date is %s%n", goodFriday, holiday);
} catch (DateTimeParseException ex) {
System.out.printf("%s is not parsable!%n", goodFriday);
ex.printStackTrace();
}

LocalDateTime arrivalDate = LocalDateTime.now();
try {
DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM dd yyyy hh:mm a");
String landing = arrivalDate.format(format);
System.out.printf("Arriving at : %s %n", landing);
} catch (DateTimeException ex) {
System.out.printf("%s can't be formatted!%n", arrivalDate);
ex.printStackTrace();
}

上面所用的 API 大部分都是不可变的,也就是说是线程安全的,可放心食用!
Instant 用于表示一个时间戳,它与我们常使用的 System.currentTimeMillis() 有些类似,不过 Instant 可以精确到纳秒(Nano-Second),System.currentTimeMillis() 方法只精确到毫秒(Milli-Second)。
类似的还有 Duration、Period 它们通过 between 方法来确定一段时间。

Base64的API

在 JDK1.6 之前,JDK 核心类一直没有 Base64 的实现类,有人建议用 Sun/Oracle JDK 里面的 sun.misc.BASE64Encodersun.misc.BASE64Decoder,使用它们的优点就是不需要依赖第三方类库,缺点就是可能在未来版本会被删除(用 maven 编译会发出警告),而且性能不佳,性能测试见最后的参考链接。
JDK1.6 中添加了另一个 Base64 的实现,javax.xml.bind.DatatypeConverter 两个静态方法 parseBase64Binary 和 printBase64Binary,隐藏在 javax.xml.bind 包下面,不被很多开发者知道。
在 Java 8 在 java.util 包下面实现了 BASE64 编解码 API,而且性能不俗,API 也简单易懂,下面展示下这个类的使用例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* Basic编码:是标准的BASE64编码,用于处理常规的需求 */
// 编码
String asB64 = Base64.getEncoder().encodeToString("some string".getBytes("utf-8"));
System.out.println(asB64); // 输出为: c29tZSBzdHJpbmc=
// 解码
byte[] asBytes = Base64.getDecoder().decode("c29tZSBzdHJpbmc=");
System.out.println(new String(asBytes, "utf-8")); // 输出为: some string

/* URL编码:使用下划线替换URL里面的反斜线“/” */
String urlEncoded = Base64.getUrlEncoder().encodeToString("subjects?abcd".getBytes("utf-8"));
System.out.println("Using URL Alphabet: " + urlEncoded);
// 输出为: Using URL Alphabet: c3ViamVjdHM_YWJjZA==

/* 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);

之前我们常用的第三方工具有:
Apache Commons Codec library 里面的 org.apache.commons.codec.binary.Base64
Google Guava 库里面的 com.google.common.io.BaseEncoding.base64() 这个静态方法;
net.iharder.Base64 ,这个 jar 包就一个类;
号称 Base64 编码速度最快的 MigBase64,而且是 10 年前的实现.
关于他们之间的性能测试去参考里面的最后一个链接查看,总之,用 Java 8 自带的就足足够了!

JVM新特性

PermGen 空间被移除了,取而代之的是 Metaspace(JEP 122)。
JVM 选项 -XX:PermSize-XX:MaxPermSize 分别被 -XX:MetaSpaceSize-XX:MaxMetaspaceSize 所代替。

参考

http://ebnbin.com/2015/12/20/java-8-default-methods/
http://www.importnew.com/11908.html
http://www.importnew.com/6675.html
http://www.importnew.com/15637.html
https://lw900925.github.io/java/java8-newtime-api.html
http://www.importnew.com/14961.html

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~