java8簡短教程(持續更新含部分9,10,11)

聲明:一方面提高下英文水平,一方面重溫下java各版本新特性,版權歸原做者全部 ,除了翻譯也會加本身的東西。水平有限,請理性查閱html

Modern Java - A Guide to Java 8

時髦的Java -java 8 參考手冊

This article was originally posted on my blog.java

這篇文章最初發表在個人博客.git

You should also read my Java 11 Tutorial (including new language and API features from Java 9, 10 and 11).github

**你也能夠翻閱個人[java11 教程]((https://winterbe.com/posts/2018/09/24/java-11-tutorial/) (包含java9,10,11的新語言和api特性)web

Welcome to my introduction to Java 8. This tutorial guides you step by step through all new language features. Backed by short and simple code samples you'll learn how to use default interface methods, lambda expressions, method references and repeatable annotations. At the end of the article you'll be familiar with the most recent API changes like streams, functional interfaces, map extensions and the new Date API.express

歡迎參讀個人入門關於java 8.該教程將逐步指導你瞭解全部的新語言特性。經過一些簡短的編碼示例我悶酒能夠學習怎麼使用默認接口方法,lambda表達式,方法引用和可重複註解。在文章的最後你將會熟悉最新的api變化像流,功能接口,map拓展和最新的日期api.c#

No walls of text, just a bunch of commented code snippets. Enjoy!api

** 該文本沒有牆,只是一連串的評論和代碼片斷。請享受Java8新特性之旅吧!**intellij-idea


<p align="center"> ★★★ Like this project? Leave a star, <a href="https://github.com/xiaomingtongxie/java8-tutorial"> to support my work. Thanks! ★★★ </p>oracle


Table of Contents

內容列表

Default Methods for Interfaces

接口默認方法

Java 8 enables us to add non-abstract method implementations to interfaces by utilizing the default keyword. This feature is also known as virtual extension methods.

Java 8 容許咱們在接口中使用default關鍵字來添加一個非抽象方法實現。這個特性也被稱做爲虛擬擴展方法.

Here is our first example:

第一個示例:

inerface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

Besides the abstract method calculate the interface Formula also defines the default method sqrt. Concrete classes only have to implement the abstract method calculate. The default method sqrt can be used out of the box.

除了抽象方法calculate之外,接口Formula也定義了一個默認方法sqrt. 實現類只須要實現抽象方法calculate.而默認方法sqrt開箱即用。

Formula formula = new Formula() {
    @Override
    public double calculate(int a) {
        return sqrt(a * 100);
    }
};

formula.calculate(100);     // 100.0
formula.sqrt(16);           // 4.0

The formula is implemented as an anonymous object. The code is quite verbose: 6 lines of code for such a simple calculation of sqrt(a * 100). As we'll see in the next section, there's a much nicer way of implementing single method objects in Java 8.

這個規則依靠一個匿名對象來實現。爲了實現一個簡單的計算要用六行代碼,這樣至關的冗長。咱們能夠在下節看到,用java8咱們有更好的方法來實現單個方法對象。

Lambda expressions

lambda 表達式

Let's start with a simple example of how to sort a list of strings in prior versions of Java:

咱們以在之前的java版本中怎麼樣去對一個字符串集合進行排序這樣簡單的例子做爲開始。

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});

The static utility method Collections.sort accepts a list and a comparator in order to sort the elements of the given list. You often find yourself creating anonymous comparators and pass them to the sort method.

靜態實例方法Collections.sort爲了對給定元素的集合進行排序接受一個集合和一個比較器。你一般會發現你本身會建立匿名比較器將它們傳遞給排序方法。

Instead of creating anonymous objects all day long, Java 8 comes with a much shorter syntax, lambda expressions:

java 8 提供了更短的語法,而不是成天的建立匿名對象。

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});

As you can see the code is much shorter and easier to read. But it gets even shorter:

你能夠看到代碼變得更短,更容易閱讀,但它能夠更短:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

For one line method bodies you can skip both the braces {} and the return keyword. But it gets even shorter:

對於行方法主體能夠跳過花括號和return關鍵詞。但它還能夠更短:

names.sort((a, b) -> b.compareTo(a));

List now has a sort method. Also the java compiler is aware of the parameter types so you can skip them as well. Let's dive deeper into how lambda expressions can be used in the wild.

集合list 如今有一個sort方法。java編譯器也知道參數類型,你也能夠忽略它們。讓咱們深刻研究一下lambda表達式怎麼在其餘地方應用。

Functional Interfaces

函數式接口

How does lambda expressions fit into Java's type system? Each lambda corresponds to a given type, specified by an interface. A so called functional interface must contain exactly one abstract method declaration. Each lambda expression of that type will be matched to this abstract method. Since default methods are not abstract you're free to add default methods to your functional interface.

lambda表達式如何適應java類型系統?每一個lambda對應於由制定接口給定的指定類型。一個所謂的functional interface 必須剛好包含一個抽象方法聲明。每一個這種類型的lambda表達式都將匹配到這個抽象方法。因爲默認方法不是抽象的你能夠自由的在函數式接口中添加默認方法。

We can use arbitrary interfaces as lambda expressions as long as the interface only contains one abstract method. To ensure that your interface meet the requirements, you should add the @FunctionalInterface annotation. The compiler is aware of this annotation and throws a compiler error as soon as you try to add a second abstract method declaration to the interface.

只要接口僅僅包含一個抽象方法咱們就能夠用任意的接口做爲lambda表達式。爲了確保你的接口符合條件,你應該添加「@FunctionalInterface"註解。編譯器知道這個聲明,並在你試圖向接口中添加第二個抽象方法聲明的時候拋出一個編譯錯誤。

Example:

@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);    // 123

Keep in mind that the code is also valid if the @FunctionalInterface annotation would be omitted.

請記住,若是省略‘@FunctionalInterface'註解,代碼仍然是有效的。

Method and Constructor References

方法和構造函數引用

The above example code can be further simplified by utilizing static method references:

利用靜態方法引用能夠進一步簡化上述示例代碼。

Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted);   // 123

Java 8 enables you to pass references of methods or constructors via the :: keyword. The above example shows how to reference a static method. But we can also reference object methods:

javad 8 容許你使用 '::' 關鍵字來傳遞方法或者構造器的引用。上面的示例展現瞭如何引用一個靜態方法。可是咱們也能夠引用對象方法:

class Something {
    String startsWith(String s) {
        return String.valueOf(s.charAt(0));
    }
}
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);    // "J"

Let's see how the :: keyword works for constructors. First we define an example class with different constructors:

讓咱們看一下關鍵字「::」是如何爲構造器工做的,首先,咱們用不一樣的構造器來定義一個示例類。

class Person {
    String firstName;
    String lastName;

    Person() {}

    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

Next we specify a person factory interface to be used for creating new persons:

接下來咱們指定一個‘person'工廠接口來建立person對象

interface PersonFactory<P extends Person> {
    P create(String firstName, String lastName);
}

Instead of implementing the factory manually, we glue everything together via constructor references:

咱們經過構造器將全部東西粘在一塊兒,而不是經過手動實現工廠方法。

PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

We create a reference to the Person constructor via Person::new. The Java compiler automatically chooses the right constructor by matching the signature of PersonFactory.create. 咱們經過‘Person::new’ 建立Person構造器的引用。java編譯器經過匹配‘PersonFactory.create'的簽名自動地選擇正確的構造函數。

Lambda Scopes

Lambda 範圍

Accessing outer scope variables from lambda expressions is very similar to anonymous objects. You can access final variables from the local outer scope as well as instance fields and static variables.

從lambda表達式訪問外部變量和匿名對象很是類似。您能夠從本地外部範圍以及實例字段和靜態變量訪問final變量。

Accessing local variables

訪問局部變量

We can read final local variables from the outer scope of lambda expressions:

咱們能夠從lambda表達式的外部範圍讀取final局部變量。

final int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

But different to anonymous objects the variable num does not have to be declared final. This code is also valid:

可是與匿名對象不一樣的是,變量' num '沒必要聲明爲final。此代碼也是有效的:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

However num must be implicitly final for the code to compile. The following code does not compile:

可是,要編譯代碼,num必須是隱式的final。如下代碼不編譯:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
num = 3;

Writing to num from within the lambda expression is also prohibited.

也禁止從lambda表達式中寫入' num '

Accessing fields and static variables

訪問域和靜態變量

In contrast to local variables, we have both read and write access to instance fields and static variables from within lambda expressions. This behaviour is well known from anonymous objects.

與局部變量相反,咱們能夠從lambda表達式中讀寫實例字段和靜態變量。這種行爲在匿名對象中很常見。

class Lambda4 {
    static int outerStaticNum;
    int outerNum;

    void testScopes() {
        Converter<Integer, String> stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);
        };

        Converter<Integer, String> stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }
}

Accessing Default Interface Methods

訪問默認接口方法

Remember the formula example from the first section? Interface Formula defines a default method sqrt which can be accessed from each formula instance including anonymous objects. This does not work with lambda expressions.

還記得第一部分的公式例子嗎?接口「Formula」定義了一個默認方法「sqrt」,能夠從包括匿名對象在內的每一個Formula實例訪問該方法。這不適用於lambda表達式。

Default methods cannot be accessed from within lambda expressions. The following code does not compile:

不能從lambda表達式中訪問默認方法。下列代碼沒法編譯:

Formula formula = (a) -> sqrt(a * 100);

Built-in Functional Interfaces

內置函數式接口

The JDK 1.8 API contains many built-in functional interfaces. Some of them are well known from older versions of Java like Comparator or Runnable. Those existing interfaces are extended to enable Lambda support via the @FunctionalInterface annotation.

JDK 1.8 API包含許多內置的函數接口。其中一些在較老版本的Java中頗有名,好比「Comparator」或「Runnable」。這些現有接口通過擴展,經過「@FunctionalInterface」註釋支持Lambda。

But the Java 8 API is also full of new functional interfaces to make your life easier. Some of those new interfaces are well known from the Google Guava library. Even if you're familiar with this library you should keep a close eye on how those interfaces are extended by some useful method extensions.

可是Java 8 API也充滿了新的功能接口,使您的工做更容易。其中一些新接口在谷歌Guava庫中很是有名。即便您熟悉這個庫,也應該密切關注那些接口是如何經過一些有用的方法擴展進行擴展的。

Predicates

判斷(斷言?)

Predicates are boolean-valued functions of one argument. The interface contains various default methods for composing predicates to complex logical terms (and, or, negate)

謂詞是一個參數的布爾值函數。該接口包含各類默認方法,用於將謂詞組合爲複雜邏輯術語(and, or, negate)

Predicate<String> predicate = (s) -> s.length() > 0;

predicate.test("foo");              // true
predicate.negate().test("foo");     // false

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();

Functions

函數

Functions accept one argument and produce a result. Default methods can be used to chain multiple functions together (compose, andThen).

函數接受一個參數併產生一個結果。默認方法可用於將多個函數連接在一塊兒(組合,而後)。

Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);

backToString.apply("123");     // "123"

Suppliers

生產者

Suppliers produce a result of a given generic type. Unlike Functions, Suppliers don't accept arguments.

供應商生成給定檢討類型的結果。不像函數,供應商不接受參數。

Supplier<Person> personSupplier = Person::new;
personSupplier.get();   // new Person

Consumers

消費者

Consumers represent operations to be performed on a single input argument.

消費者要單個輸入參數執行的操做.

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

Comparators

比較器

Comparators are well known from older versions of Java. Java 8 adds various default methods to the interface.

比較器在比較老的java版本中很出名。java8在接口中加入了各類默認方法。

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");

comparator.compare(p1, p2);             // > 0
comparator.reversed().compare(p1, p2);  // < 0

Optionals

選擇器

Optionals are not functional interfaces, but nifty utilities to prevent NullPointerException. It's an important concept for the next section, so let's have a quick look at how Optionals work.

選擇器不是函數接口,而是防止「空指針」的漂亮實用程序。這是下一節的一個重要概念,因此讓咱們快速瞭解一下選項的工做原理。

Optional is a simple container for a value which may be null or non-null. Think of a method which may return a non-null result but sometimes return nothing. Instead of returning null you return an Optional in Java 8.

選擇器是一個簡單的容器,其值能夠爲空或非空。考慮一個可能返回非空結果但有時什麼也不返回的方法。在Java 8中,返回的不是「null」,而是「Optional」

Optional<String> optional = Optional.of("bam");

optional.isPresent();           // true
optional.get();                 // "bam"
optional.orElse("fallback");    // "bam"

optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"

Streams

A java.util.Stream represents a sequence of elements on which one or more operations can be performed. Stream operations are either intermediate or terminal. While terminal operations return a result of a certain type, intermediate operations return the stream itself so you can chain multiple method calls in a row. Streams are created on a source, e.g. a java.util.Collection like lists or sets (maps are not supported). Stream operations can either be executed sequentially or parallely.

Streams are extremely powerful, so I wrote a separate Java 8 Streams Tutorial. You should also check out Sequency as a similiar library for the web.

Let's first look how sequential streams work. First we create a sample source in form of a list of strings:

List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");

Collections in Java 8 are extended so you can simply create streams either by calling Collection.stream() or Collection.parallelStream(). The following sections explain the most common stream operations.

Filter

Filter accepts a predicate to filter all elements of the stream. This operation is intermediate which enables us to call another stream operation (forEach) on the result. ForEach accepts a consumer to be executed for each element in the filtered stream. ForEach is a terminal operation. It's void, so we cannot call another stream operation.

stringCollection
    .stream()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

// "aaa2", "aaa1"

Sorted

Sorted is an intermediate operation which returns a sorted view of the stream. The elements are sorted in natural order unless you pass a custom Comparator.

stringCollection
    .stream()
    .sorted()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

// "aaa1", "aaa2"

Keep in mind that sorted does only create a sorted view of the stream without manipulating the ordering of the backed collection. The ordering of stringCollection is untouched:

System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1

Map

The intermediate operation map converts each element into another object via the given function. The following example converts each string into an upper-cased string. But you can also use map to transform each object into another type. The generic type of the resulting stream depends on the generic type of the function you pass to map.

stringCollection
    .stream()
    .map(String::toUpperCase)
    .sorted((a, b) -> b.compareTo(a))
    .forEach(System.out::println);

// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

Match

Various matching operations can be used to check whether a certain predicate matches the stream. All of those operations are terminal and return a boolean result.

boolean anyStartsWithA =
    stringCollection
        .stream()
        .anyMatch((s) -> s.startsWith("a"));

System.out.println(anyStartsWithA);      // true

boolean allStartsWithA =
    stringCollection
        .stream()
        .allMatch((s) -> s.startsWith("a"));

System.out.println(allStartsWithA);      // false

boolean noneStartsWithZ =
    stringCollection
        .stream()
        .noneMatch((s) -> s.startsWith("z"));

System.out.println(noneStartsWithZ);      // true

Count

Count is a terminal operation returning the number of elements in the stream as a long.

long startsWithB =
    stringCollection
        .stream()
        .filter((s) -> s.startsWith("b"))
        .count();

System.out.println(startsWithB);    // 3

Reduce

This terminal operation performs a reduction on the elements of the stream with the given function. The result is an Optional holding the reduced value.

Optional<String> reduced =
    stringCollection
        .stream()
        .sorted()
        .reduce((s1, s2) -> s1 + "#" + s2);

reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

Parallel Streams

As mentioned above streams can be either sequential or parallel. Operations on sequential streams are performed on a single thread while operations on parallel streams are performed concurrently on multiple threads.

The following example demonstrates how easy it is to increase the performance by using parallel streams.

First we create a large list of unique elements:

int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
    UUID uuid = UUID.randomUUID();
    values.add(uuid.toString());
}

Now we measure the time it takes to sort a stream of this collection.

Sequential Sort

long t0 = System.nanoTime();

long count = values.stream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));

// sequential sort took: 899 ms

Parallel Sort

long t0 = System.nanoTime();

long count = values.parallelStream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));

// parallel sort took: 472 ms

As you can see both code snippets are almost identical but the parallel sort is roughly 50% faster. All you have to do is change stream() to parallelStream().

Maps

As already mentioned maps do not directly support streams. There's no stream() method available on the Map interface itself, however you can create specialized streams upon the keys, values or entries of a map via map.keySet().stream(), map.values().stream() and map.entrySet().stream().

Furthermore maps support various new and useful methods for doing common tasks.

Map<Integer, String> map = new HashMap<>();

for (int i = 0; i < 10; i++) {
    map.putIfAbsent(i, "val" + i);
}

map.forEach((id, val) -> System.out.println(val));

The above code should be self-explaining: putIfAbsent prevents us from writing additional if null checks; forEach accepts a consumer to perform operations for each value of the map.

This example shows how to compute code on the map by utilizing functions:

map.computeIfPresent(3, (num, val) -> val + num);
map.get(3);             // val33

map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9);     // false

map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23);    // true

map.computeIfAbsent(3, num -> "bam");
map.get(3);             // val33

Next, we learn how to remove entries for a given key, only if it's currently mapped to a given value:

map.remove(3, "val3");
map.get(3);             // val33

map.remove(3, "val33");
map.get(3);             // null

Another helpful method:

map.getOrDefault(42, "not found");  // not found

Merging entries of a map is quite easy:

map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9

map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9concat

Merge either put the key/value into the map if no entry for the key exists, or the merging function will be called to change the existing value.

Date API

Java 8 contains a brand new date and time API under the package java.time. The new Date API is comparable with the Joda-Time library, however it's not the same. The following examples cover the most important parts of this new API.

Clock

Clock provides access to the current date and time. Clocks are aware of a timezone and may be used instead of System.currentTimeMillis() to retrieve the current time in milliseconds since Unix EPOCH. Such an instantaneous point on the time-line is also represented by the class Instant. Instants can be used to create legacy java.util.Date objects.

Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();

Instant instant = clock.instant();
Date legacyDate = Date.from(instant);   // legacy java.util.Date

Timezones

Timezones are represented by a ZoneId. They can easily be accessed via static factory methods. Timezones define the offsets which are important to convert between instants and local dates and times.

System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids

ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());

// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]

LocalTime

LocalTime represents a time without a timezone, e.g. 10pm or 17:30:15. The following example creates two local times for the timezones defined above. Then we compare both times and calculate the difference in hours and minutes between both times.

LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);

System.out.println(now1.isBefore(now2));  // false

long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

System.out.println(hoursBetween);       // -3
System.out.println(minutesBetween);     // -239

LocalTime comes with various factory methods to simplify the creation of new instances, including parsing of time strings.

LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late);       // 23:59:59

DateTimeFormatter germanFormatter =
    DateTimeFormatter
        .ofLocalizedTime(FormatStyle.SHORT)
        .withLocale(Locale.GERMAN);

LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime);   // 13:37

LocalDate

LocalDate represents a distinct date, e.g. 2014-03-11. It's immutable and works exactly analog to LocalTime. The sample demonstrates how to calculate new dates by adding or subtracting days, months or years. Keep in mind that each manipulation returns a new instance.

LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);

LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek);    // FRIDAY

Parsing a LocalDate from a string is just as simple as parsing a LocalTime:

DateTimeFormatter germanFormatter =
    DateTimeFormatter
        .ofLocalizedDate(FormatStyle.MEDIUM)
        .withLocale(Locale.GERMAN);

LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas);   // 2014-12-24

LocalDateTime

LocalDateTime represents a date-time. It combines date and time as seen in the above sections into one instance. LocalDateTime is immutable and works similar to LocalTime and LocalDate. We can utilize methods for retrieving certain fields from a date-time:

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek);      // WEDNESDAY

Month month = sylvester.getMonth();
System.out.println(month);          // DECEMBER

long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay);    // 1439

With the additional information of a timezone it can be converted to an instant. Instants can easily be converted to legacy dates of type java.util.Date.

Instant instant = sylvester
        .atZone(ZoneId.systemDefault())
        .toInstant();

Date legacyDate = Date.from(instant);
System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014

Formatting date-times works just like formatting dates or times. Instead of using pre-defined formats we can create formatters from custom patterns.

DateTimeFormatter formatter =
    DateTimeFormatter
        .ofPattern("MMM dd, yyyy - HH:mm");

LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string);     // Nov 03, 2014 - 07:13

Unlike java.text.NumberFormat the new DateTimeFormatter is immutable and thread-safe.

For details on the pattern syntax read here.

Annotations

Annotations in Java 8 are repeatable. Let's dive directly into an example to figure that out.

First, we define a wrapper annotation which holds an array of the actual annotations:

@interface Hints {
    Hint[] value();
}

@Repeatable(Hints.class)
@interface Hint {
    String value();
}

Java 8 enables us to use multiple annotations of the same type by declaring the annotation @Repeatable.

Variant 1: Using the container annotation (old school)

@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}

Variant 2: Using repeatable annotations (new school)

@Hint("hint1")
@Hint("hint2")
class Person {}

Using variant 2 the java compiler implicitly sets up the @Hints annotation under the hood. That's important for reading annotation information via reflection.

Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint);                   // null

Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length);  // 2

Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
System.out.println(hints2.length);          // 2

Although we never declared the @Hints annotation on the Person class, it's still readable via getAnnotation(Hints.class). However, the more convenient method is getAnnotationsByType which grants direct access to all annotated @Hint annotations.

Furthermore the usage of annotations in Java 8 is expanded to two new targets:

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}

Where to go from here?

My programming guide to Java 8 ends here. If you want to learn more about all the new classes and features of the JDK 8 API, check out my JDK8 API Explorer. It helps you figuring out all the new classes and hidden gems of JDK 8, like Arrays.parallelSort, StampedLock and CompletableFuture - just to name a few.

I've also published a bunch of follow-up articles on my blog that might be interesting to you:

You should follow me on Twitter. Thanks for reading!

相關文章
相關標籤/搜索