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 DateFormat 与 class 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 类的 equals、hashCode 和 toString 方法。
  • 接口中的静态方法必须是 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.Date和java.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.BASE64Encoder 和 sun.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

你可能需要魔法上网~~