JDK8 新特性


想更詳細的了解JDK8新特性可以瀏覽官方介紹

JDK8 新特性目錄導航:

  • Lambda 表達式
  • 函數式接口
  • 方法引用、構造器引用和數組引用
  • 接口支持默認方法和靜態方法
  • Stream API
  • 增強類型推斷
  • 新的日期時間 API
  • Optional 類
  • 重復注解和類型注解

Lambda 表達式

Lambda 是一個匿名函數,我們可以把 Lambda 表達式理解為是一段可以傳遞的代碼(將代碼像數據一樣進行傳遞)。可以寫出更簡潔、更靈活的代碼。作為一種更緊湊的代碼風格,使Java的語言表達能力得到了提升。

如下示例,將一個匿名類轉換為Lambda表達式:

//匿名內部類
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello world!");
    }
};
//Lambda 表達式
Runnable runnable = () -> System.out.println("Hello world!");

第一個匿名內部類的寫法new一個Runnable接口並重寫run方法打印Hello world!需要寫一堆代碼,但核心代碼就一句System.out.println("Hello world!"),然而Lambda表達式僅需要一句代碼() -> System.out.println("Hello world!")就可以替代上面的匿名內部類整個代碼。從這個轉換來看,Lambda表達式可以讓代碼更簡潔,更靈活。

Lambda 表達式語法

Lambda 表達式在Java語言中引入了一個新的語法元素和操作符。這個操作符為 "->" ,該操作符被稱為Lambda 操作符號或箭頭操作符。它將Lambda分為兩部分:

  • 左側: 指定了Lambda 表達式需要的所有參數。
  • 右側: 指定了Lambda 體,即Lambda 表達式要執行的功能。

語法格式一: 無參,無返回值。Lambda 體只需要一條語句

Runnable runnable = () -> System.out.println("Hello world!");

語法格式二: 一個參數無返回值。注:一個參數時,擴符可以省略

Consumer<String> consumer = (e) -> System.out.println(e);//一個參數時,參數的擴號可以省略。

語法格式三: 兩個參數並且有返回值。注:當Lambda 體只有一條語句時,可以省略 return 和 大括號。參數類型是可以省略的。通過編譯器類型上下文推斷出。同時也建議省略。如不省略則所有參數都必須加上類型。

BinaryOperator<Integer> bo = (x,y) ->{
    System.out.println("實現函數式接口方法!");
    return x  + y;
};
//當Lambda 體只有一條語句時,可以省略 return大括號
BinaryOperator<Integer> bo1 = (x,y) -> x  + y;
//Lambda 的參數類型是可以省略的。通過編譯器類型上下文推斷出。同時也建議省略。如不省略則所有參數都必須加上類型。
BinaryOperator<Integer> bo2 = (Integer x,Integer y) -> x  + y;

語法格式四: 作為參數傳遞Lambda 表達式:為了將 Lambda 表達式作為參數傳遞,接收Lambda 表達式的參數類型必須是與該Lambda 表達式兼容的函數式接口的類型。

 1 import java.util.function.Function;
 2 
 3 public class TestLambda {
 4 
 5     public static void main(String[] args) {
 6         String str = toUpperString("abcdefg", (e) -> e.toUpperCase());
 7         System.out.println(str);
 8     }
 9 
10     public static String toUpperString(String string, Function<String, String> function) {
11         return function.apply(string);
12     }
13 
14 }

函數式接口

只包含一個抽象方法的接口,稱為函數式接口。你可以通過Lambda 表達式來創建該接口的對象。我們可以在任意函數式接口上使用 @FunctionalInterface 注解,這樣做可以檢查它是否是一個函數式接口,同時 javadoc 也會包含一條聲明,說明這個接口是一個函數式接口。

如下所示,自定義函數式接口:

1 @FunctionalInterface
2 public interface MyInterface {
3     public void getValue();
4 }

Java 內置四大核心函數式接口

因為Lambda 表達式必須依賴函數式接口,然為了避免Lambda 表達式特意去書寫函數式接口。Java 內置了如下四大核心函數式接口:

  • Consumer<T>: 消費型接口,表示一個接受單個輸入參數並返回沒有結果的操作。對類型為T的對象應用操作。接口方法: void accept(T t)
  • Supplier<T>: 供給型接口,類似一個供應商,返回一個類型為T的對象。接口方法: T get()
  • Function<T, R>: 函數型接口,表示一個接受一個參數並產生結果的函數。接口方法: R apply(T t)
  • Predicate<T>: 斷言型接口,確定類型為T的對象是否滿足某約束,並返回boolean 值。接口方法: boolean test(T t)

除了以上四大內置接口外還有許許多多的函數式接口在 java.util.function 包下,比如:

  • BiFunction<T, U, R>: 與Function<T,R>類似,對類型為 T, U 參數應用操作,返回 R 類型的結果。接口方法:R apply(T t, U u);
  • UnaryOperator<T>: Function<T, T>的子接口。對類型為T的對象進行一元運算,並返回T類型的結果。接口方法:T apply(T t);
  • BinaryOperator<T>: BiFunction<T,T,T>的子接口,對類型為T的對象進行二元運算,並返回T類型的結果。接口方法:T apply(T t1, T t2);
  • ......
  • BiConsumer<T, U>: 對類型為T, U 參數應用操作。接口方法:void accept(T t, U u);

 

方法引用、構造器引用和數組引用

當要傳遞給Lambda體的操作,已經有實現的方法了,可以使用方法引用!(實現抽象方法的參數列表,必須與方法引用方法的參數列表保持一致!)

方法引用

使用操作符 “ ::” 將方法名和對象或類的名字分隔開來。有如下三種格式:

  • 引用靜態方法: 類 :: 靜態方法 
  • 引用特定對象的實例方法: 對象 :: 實例方法
  • 引用特定類型任意對象的實例方法: 特定類型 :: 實例方法

如下示例,調用Math類的靜態對象pow方法,可以直接使用Math::pow(格式:類::靜態方法),引用靜態方法代替Lambda 表達式。

BinaryOperator<Double> bo = (x, y) -> Math.pow(x, y);
System.out.println(bo.apply(2d, 3d));
//Math::pow 可以替代 (x, y) -> Math.pow(x, y)
BinaryOperator<Double> bo2 = Math::pow;
System.out.println(bo2.apply(2d, 4d));

輸出結果:

8.0
16.0

如下所示:調用System.out靜態方法獲取PrintStream對象再調用printf方法,可以直接使用System.out::printf(格式:對象::實例方法),引用特定對象的實例方法代替Lambda 表達式。

Consumer<String> consumer = (x) -> System.out.println(x);
consumer.accept("Hello");
//System.out::printf 可以替代 (x) -> System.out.println(x)
Consumer<String> consumer2 = System.out::printf;
consumer2.accept("world");

輸出結果:

Hello
world

如下所示:String特定類型的實例方法equals,可以直接使用String::equals(格式:特定類型::實例方法), 引用特定類型任意對象的實例方法代替Lambda 表達式。

BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System.out.println(bp.test("abcdef", "abcdef"));
//String::equals 可以替代 (x, y) -> x.equals(y)
BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("abcdef", "abcdef"));

輸出結果:

true
true

構造器引用

格式: 類::new  如下示例所示:new MyClass(n)構造器,可以直接使用MyClass::new。構造器引用可以直接代替Lambda 表達式。

 1 public class MyClass {
 2     Integer i;
 3 
 4     public MyClass() {
 5     }
 6 
 7     public MyClass(Integer i) {
 8         this.i = i;
 9     }
10 
11     @Override
12     public String toString() {
13         return "MyClass{" +
14                 "i=" + i +
15                 '}';
16     }
17 }
Function<Integer, MyClass> myClass = (n) -> new MyClass(n);
System.out.println(myClass.apply(15).toString());
//MyClass::new 可以替代 (n) -> new MyClass(n)
Function<Integer, MyClass> myClass2 = MyClass::new;
System.out.println(myClass2.apply(10).toString());

輸出結果:

MyClass{i=15}
MyClass{i=10}

數組引用

格式:type[] :: new  如下示例所示:new Integer[n] 數組可以直接使用Integer[]::new代替。數組引用可以直接代替Lambda 表達式。

Function<Integer, Integer[]> function = (n) -> new Integer[n];
System.out.println(function.apply(15).length);
//Integer[]::new 可以替代 (n) -> new Integer[n]
Function<Integer, Integer[]> function2 = Integer[]::new;
System.out.println(function2.apply(10).length);

輸出結果:

15
10

 

接口支持默認方法和靜態方法

JDK8 中允許接口中包含具體的實現方法,該方法稱為默認方法。同時接口中還支持靜態方法。

默認方法

默認方法使用 default 關鍵字修飾。使用default修飾的方法,則可以在接口中進行具體實現,如下所示:

 1 public interface MyInterface {
 2 
 3     //接口中的常規方法是不能實現的。
 4     int getValue();
 5 
 6     //接口中的具體實現默認方法:getName
 7     default String getName(){
 8         return "Hello JDK8!";
 9     }
10 
11     //接口中的具體實現默認方法:getAge
12     default int getAge(){
13         return 8;
14     }
15 
16 }
1 public interface MyFunc {
2 
3     default String getName(){
4         return "Hello MyFunc!";
5     }
6 }
1 public class MyClass {
2 
3     public String getName(){
4         return "Hello MyClass!";
5     }
6 }
public class SubClass extends MyClass implements MyFunc{
}

輸出結果:

Hello MyClass!

上面的示例可以看到,MyFunc接口中有默認方法getName、MyClass中也有getName()方法。然SubClass對象繼承MyClass對象,同時實現MyFunc接口,調用SubClass對象的getName方法,實際執行的是MyClass對象的方法。接口的默認方法實現“類優先”原則。

若一個接口中定義了一個默認方法,而另外一個父類又定義了一個同名的方法時,選擇父類中的方法。如果一個父類提供了具體的實現,那么接口中具有相同名稱和參數的默認方法會被忽略。

因接口可以多實現,則會出現如下示例:

 1 public class TestClass implements MyFunc, MyInterface {
 2 
 3     @Override
 4     public int getValue() {
 5         return 0;
 6     }
 7 
 8     @Override
 9     public String getName() {
10         //因為接口可以多實現,然MyFunc接口 和 MyInterface接口 都有getName默認方法。於是需要使用一下方法進行指定調用。
11 //        return MyInterface.super.getName();
12         return MyFunc.super.getName();
13     }
14 
15     @Override
16     public int getAge() {
17         return 0;
18     }
19 }
TestClass testClass = new TestClass();
System.out.println(testClass.getName());

輸出結果:

Hello MyFunc!

上面的示例可以看出。MyFunc接口和MyInterface接口中都有getName默認方法,然TestClass同時實現以上兩個接口時,必須覆蓋該方法來解決沖突。

靜態方法

在JDK8 中,接口中允許使用靜態方法。和類一樣通過接口名稱點靜態方法去調用,如下示例所示:

1 public interface MyFunction {
2 
3     //接口中使用靜態方法
4     static void show(){
5         System.out.println("Hello static!");
6     }
7 }
MyFunction.show();

輸出結果:

Hello static!

Stream API

Java8中有兩大最為重要的改變。第一個是 Lambda 表達式;另外一個則是 Stream API( java.util.stream .*) 。

Stream 是JDK8 中處理集合的關鍵抽象概念,他可以指定你希望對集合進行的操作,可以執行非常復雜的查找、過濾和映射數據等操作。使用Stream API 對集合數據進行操作,就類似於使用SQL 執行的數據庫查詢。也可以使用Stream API 來並行執行操作。簡而言之,Stream API 提供了一種非常高效且易於使用的處理數據的方式。

流(Stream)到底是什么?

是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列。“集合講的是數據,流講的是計算!”

注意一下三點:

  1. Stream 自己不會存儲元素。
  2. Stream 不會改變源對象。相反,他們會返回一個持有結果的新 Stream。
  3. Stream 操作是延遲執行的。這也意味着他們會等到需要結果的時候才執行。

Stream 的操作三個步驟:

  1. 創建 Stream: 一個數據源(如:集合、數組),獲取一個流。
  2. 中間操作: 一個中間操作鏈,對數據源的數據進行一系列處理。
  3. 終止操作(終端操作): 一個終止操作,執行中間操作鏈,並產生結果。 

創建 Stream

JDK8 中的 Collection 接口被拓展,提供了兩個獲取流的方法:

  • default Stream<E> stream : Collection 接口的默認方法,返回一個順序流。
  • default Stream<E> parallelStream: Collection 接口的的默認放,返回一個並行了。

同時JDK8 在Arrays類中提供許多重載的Stream()靜態方法 ,可以獲取數組流。如下所示,可以處理很多類型的數組。

  • public static <T> Stream<T> stream(T[] array)
  • public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive)
  • public static IntStream stream(int[] array)
  • public static IntStream stream(int[] array, int startInclusive, int endExclusive)
  • public static LongStream stream(long[] array)
  • public static LongStream stream(long[] array, int startInclusive, int endExclusive)
  • public static DoubleStream stream(double[] array)
  • public static DoubleStream stream(double[] array, int startInclusive, int endExclusive) 

Stream接口中提供了of靜態方法,來創建一個流。

  • public static<T> Stream<T> of(T t)
  • public static<T> Stream<T> of(T... values)

Stream接口還提供了iterate和generate方法創建無限流。

  • public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
  • public static<T> Stream<T> generate(Supplier<T> s)

Stream 的中間操作

Stream 可以將多個中間操作連接起來形成一條流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理。而在終止操作一次性全部處理,稱為“惰性求值”

Stream 的中間操作有以下幾種:

  • 篩選與切片: 將Stream流進行篩選或截斷處理。 
    • Stream<T> filter(Predicate<? super T> predicate): 接收Lambda 表達式,從流中排出某些元素;
    • Stream<T> distinct(): 篩選,通過流所生成元素的 hashCode() 和 equals() 去除重復元素;
    • Stream<T> limit(long maxSize): 截斷流,使其元素不超過給定數量;
    • Stream<T> skip(long n): 跳過元素,返回一個扔掉了前n個元素的流。若流中元素不足n個,則返回一個空流。與limit方法互補。
  • 映射: 將Stream流映射到一個新的元素上。
    • <R> Stream<R> map(Function<? super T, ? extends R> mapper): 接受一個函數作為參數,該函數被應用到每一個元素上,並將其映射成一個新的元素。
    • IntStream mapToInt(ToIntFunction<? super T> mapper):接受一個函數作為參數,該函數被應用到每一個元素上,並將其映射成一個新的IntStream。
    • LongStream mapToLong(ToLongFunction<? super T> mapper):接受一個函數作為參數,該函數被應用到每一個元素上,並將其映射成一個新的LongStream。
    • DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper):接受一個函數作為參數,該函數被應用到每一個元素上,並將其映射成一個新的DoubleStream。
    • <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper): 接受一個函數作為參數,將流中的每一個值都換成另一個流,然后把所有流連接成一個流。flatMapToDouble、flatMapToInt和flatMapToLong以上差不多。只是獲得具體的新流。
  • 排序: 將Stream流進行排序處理。 
    • Stream<T> sorted(): 返回一個新流,按自然順序排序。
    • Stream<T> sorted(Comparator<? super T> comparator): 返回一個新流,按comparator比較器進行排序。

Stream 的終止操作

Stream 的終止操作會從流的流水線操作操作上獲取一個新流。其結果可以是任何不是流的值。例如:List、Integer。甚至可以是 void。

Stream 的終止操作有如下幾種:

  • 查找與匹配: 查找流中的數據和進行匹配。
    • boolean allMatch(Predicate<? super T> predicate): 檢查所有元素是否匹配該規則,返回一個布爾值。
    • boolean anyMatch(Predicate<? super T> predicate): 檢查是否至少有一個匹配該規則,返回一個布爾值。
    • boolean noneMatch(Predicate<? super T> predicate): 檢查該規則沒有匹配所有元素,返回一個布爾值。
    • Optional<T> findFirst(): 返回流的第一個元素。
    • Optional<T> findAny(): 返回隨機的一個元素。
    • long count(): 返回流的總數。
    • Optional<T> max(Comparator<? super T> comparator): 返回流中的最大值。
    • Optional<T> min(Comparator<? super T> comparator): 返回流中的最小值。
    • void forEach(Consumer<? super T> action): 內部迭代(Stream API 使用了內部迭達。相反,使用Collection 接口需要用戶做的迭代是外部迭代)。
  • 歸約:  將流中的元素反復結合返回一個新值。(備注:map 和 reduce 的連接通常稱為 map-reduce 模式,因為Google 用它來進行網絡搜索而出名)
    • Optional<T> reduce(BinaryOperator<T> accumulator): 將流中的元素反復結合,並返回一個新值 Optional<T>。
    • T reduce(T identity, BinaryOperator<T> accumulator): 將流中的元素反復結合,並返回一個新值T。
  • 收集: 將流轉換為其他形式,用於將Stream中的元素做匯總。
    • <R, A> R collect(Collector<? super T, A, R> collector): 將流轉換為其他形式。接收一個Collector 接口的實現,用於給Stream 中元素做匯總的方法。

collector 接口中方法的實現決定了如何對流執行收集操作(如收集到 List 、Set、Map)。但是Collectors 實用類提供了很多靜態方法,可以方便地創建收集器實例,具體方法與實例如下表:

方法 返回類型 作用 示例
toList List<T> 把流中的元素收集到List List<Employee> emps= list.stream().collect(Collectors.toList());
toSet Set<T> 把流中的元素收集到Set Set<Employee> emps= list.stream().collect(Collectors.toSet());
toCollection Collection<T> 把流中的元素收集到創建的集合 Collection<Employee>emps=list.stream().collect(Collectors.toCollection(ArrayList::new));
counting Long 計算流中元素的個數 long count = list.stream().collect(Collectors.counting());
summingInt Integer 對流中元素的整數進行求和 inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingInt Double  計算流中元素Integer屬性的平均值 doubleavg= list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingInt IntSummaryStatistics 收集流中Integer屬性的統計值。如:平均值 IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joining String 連接流中每個字符串 String str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxBy Optional<T> 根據比較器選擇最大值 Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minBy Optional<T> 根據比較器選擇最小值 Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducing 歸約產生的類型

從一個作為累加器的初始值開始,利用BinaryOperator

與流中元素逐個結合,從而歸約成單個值

inttotal=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
collectingAndThen 轉換函數返回的類型 包裹另一個收集器,對其結果轉換函數 inthow= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingBy Map<K, List<T>>  根據某屬性值對流分組,屬性為K,結果為V Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus));
partitioningBy Map<Boolean, List<T>> 根據true或false進行分區 Map<Boolean,List<Emp>>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage));

並行流與串行流

並行流就是把一個內容分為多個數據塊,並使用不同的線程分別處理每個數據塊的流。JDK8 中將並行進行了優化,我們可以很容易的對數據進行並行操作。Stream API 可以聲明性地通過parallel方法 與 sequential方法在並行流與串行流中進行切換。

增強類型推斷

JDK8 中,編譯器利用目標類型來推斷泛型方法調用的類型參數。表達式的目標類型是編譯器期望的數據類型,這取決於表達式出現的位置。例如:在JDK7 中使用賦值語句的目標類型進行類型推斷。但是,在JDK8中,可以在更多上下文中使用目標類型進行類型推斷。最顯著的例子是使用方法調用的目標類型來推斷其參數的數據類型。思考下面例子:

//JDK7中,可以通過目標類型 stringList 的類型為String 推斷出 ArrayList() 泛型類型為String。
List<String> stringList = new ArrayList<>();
stringList.add("A");
//JDK8中,可以通過方法addALL的String類型,推斷出 Arrays.asList()泛型類型為String。
//在JDK7中,編譯器是不能接受這段代碼。因為它不支持目標方法調用來推斷參數類型。
//所以在JDK7 中必須這樣寫: stringList.addAll(Arrays.<String>asList());
stringList.addAll(Arrays.asList());

如上示例大概可以看出,增強的類型推斷主要就是,可以通過調用泛型而通過調用者stringList的類型String。推斷出Arrays.asList泛型的類型。

新的日期時間 API

JDK8中提供了一套全新的時間日期API(java.time.*)包下。使用了final修飾類,是起不可變,每次修改都是重新創建對象,類始於String對象,解決了線程安全問題。

LocalDate、LocalTime 和 LocalDateTime類

三個類的實例都是不可變的,每次修改操作都是新建一個實例對象。分別表示使用ISO-8601日歷系統的日期、時間、日期和時間。它們提供了簡單的日期或時間,並不包含當前的時間信息。也不包含與時區相關的信息。注:ISO-8601日歷系統是國際標准化組織制定的現代公民的日期和時間的表示法。

詳細的方法如下表:

方法 描述
now() 靜態方法,根據當前時間創建對象
of() 靜態方法,根據指定日期/時間創建對象

plusDays

plusWeeks

plusMonths

plusYears

向當前 LocalDate 對象添加幾天、
幾周、幾個月、幾年

plus

minus

添加或減少一個 Duration 或 Period

withDayOfMonth

withDayOfYear

withMonth

withYear

將月份天數、年份天數、月份、年份修改為指定 的值 並返回新的LocalDate 對象

getDayOfMonth

獲得月份天數(1-31)
getDayOfYear 獲得年份天數(1-366)
getDayOfWeek 獲得星期幾(返回一個 DayOfWeek枚舉值)
getMonth 獲得月份, 返回一個 Month 枚舉值
getMonthValue 獲得月份(1-12)
getYear 獲得年份
until 獲得兩個日期之間的Period 對象,或者指定 ChronoUnits 的數字

isBefore

isAfter

比較兩個 LocalDate
isLeapYear 判斷是否是閏年

列舉以下幾個例子:

LocalDate localDate = LocalDate.now();//獲取當前日期
LocalTime localTime = LocalTime.now();//獲取當前時間
LocalDateTime localDateTime = LocalDateTime.now();//獲取當前日期時間
LocalDateTime localDateTime1 = LocalDateTime.of(2018, 12, 19, 17, 00, 50);//通過指定數據去獲取日期時間
LocalDate localDate1 = localDate.plusDays(1);
System.out.println("localDate: " + localDate);
System.out.println("localTime: " + localTime);
System.out.println("localDateTime: " + localDateTime);
System.out.println("localDateTime1: " + localDateTime1);
System.out.println("localDate1: " + localDate1);
System.out.format("%s年%s月%s日 %s:%s:%s", localDateTime.getYear(),localDateTime.getMonthValue(),localDateTime.getDayOfMonth(),
        localDateTime.getHour(),localDateTime.getMinute(),localDateTime.getSecond());
System.out.println("localDateTime1 isBefore localDateTime" + localDateTime1.isBefore(localDateTime));
System.out.println("是否閏年:"+ localDate.isLeapYear());

輸出結果:

localDate: 2018-06-19
localTime: 17:12:37.701
localDateTime: 2018-06-19T17:12:37.701
localDateTime1: 2018-12-19T17:00:50
localDate1: 2018-06-20
2018年6月19日 17:12:37localDateTime1 isBefore localDateTimefalse
是否閏年:false

Instant 時間戳

Instant 用於 “時間戳” 的運算。它是在Unix元年(傳統的設定為UTC時區1970年1月1日午夜時分)開始進行計算。常用方法如下:

  • public int getNano(): 獲得納秒值。
  • public long getEpochSecond(): 獲得秒數。
  • public long toEpochMilli(): 獲得分鍾數。

Duration 和 Period

duration用來計算兩個時間的間隔。period用於計算兩個日期的間隔。

Optional 類

JDK8 中新增一個Optional<T>類 (java.util.Optional) 是一個容器類,代表一個值存在或不存在,原來用 null 表示一個值不存在,現在用 Optional 可以更好的表達這個概念。並且可以避免空指針異常。下圖是Optional類的大致內容:

常用的方法:

  • public static <T> Optional<T> of(T value): 創建一個Optional 實例。
  • public static<T> Optional<T> empty(): 創建一個空的Optional 實例。
  • public static <T> Optional<T> ofNullable(T value): 若T不為null,創建Optional實例,否則創建空實例。代碼如下:return value == null ? empty() : of(value)。
  • public boolean isPresent(): 判斷值是否為空。
  • public T orElse(T other): 如果值不為空返回該值,否則返回 other實例。
  • public T orElseGet(Supplier<? extends T> other): 如果調用該對象有值,返回該值,否則返回other的獲取值。
  • public<U> Optional<U> map(Function<? super T, ? extends U> mapper): 如果有值對其處理,並返回處理后的Optional,否則返回 Optional.empty()。
  • public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper): 與 map 類似,要求返回值必須是Optional。
  • public T get(): 獲取Optional對象的值。

重復注解和類型注解

重復注解

在某些特定的情況下,您希望將相同的注解應用於聲明或類型用途。思考如下示例:

 1 import java.lang.annotation.Repeatable;
 2 import java.lang.annotation.Retention;
 3 import java.lang.annotation.RetentionPolicy;
 4 import java.lang.annotation.Target;
 5 
 6 import static java.lang.annotation.ElementType.*;
 7 
 8 //使用Repeatable注解指定可以重復注解,注解容器為MyAnnotations
 9 @Repeatable(MyAnnotations.class)
10 @Target({TYPE,FIELD,METHOD,PARAMETER})
11 @Retention(RetentionPolicy.RUNTIME)
12 public @interface MyAnnotation {
13     String value();
14 }
 1 import java.lang.annotation.Retention;
 2 import java.lang.annotation.RetentionPolicy;
 3 import java.lang.annotation.Target;
 4 
 5 import static java.lang.annotation.ElementType.*;
 6 
 7 @Target({TYPE,FIELD,METHOD,PARAMETER})
 8 @Retention(RetentionPolicy.RUNTIME)
 9 public @interface MyAnnotations {
10     MyAnnotation[] value();
11 }
 1 import org.junit.Test;
 2 import java.lang.reflect.Method;
 3 
 4 public class TestClass {
 5 
 6     @Test
 7     public void test() throws Exception {
 8         Class clazz = TestClass.class;
 9         Method method = clazz.getMethod("show");
10         MyAnnotation[] myAnnotations = method.getDeclaredAnnotationsByType(MyAnnotation.class);
11         for (MyAnnotation myAnnotation : myAnnotations) {
12             System.out.println(myAnnotation.value());
13         }
14 
15     }
16 
17     @MyAnnotation("Hello")
18     @MyAnnotation("World")
19     public void show(){
20 
21     }
22 }

輸出結果:

Hello
World

JDK8中就可以這樣使用,出於兼容性原因,重復注解存儲在編譯器自動生成的注解容器中。重復注解需要包含兩個聲明:

  • 重復注解必須使用@Repeatable注解標記,並指定容器類注解。如上示例中MyAnnotation注解聲明了Repeatable標記並指定容器注解為MyAnnotations。
  • 容器類注解必須包含一個注解數組的value。如上示例中的:MyAnnotation[] value()。

類型注解

JDK8中,可以在類型上進行注解,以確保更強大的類型檢查,JDK8並不提供類型檢查框架,但它允許您編寫一個類型檢查框架,例如,你要確保程序中的特定變量永遠不會分配一個null;你想避免拋出一個NullPointException。你可以寫一個自定義插件來檢查這個。然后,你將修改你的代碼注解特定變量,表明它從未分配給null,變量聲明如下:@NonNull String str,當你編譯代碼時,編譯器會檢測到這個警告,從而使程序在運行時不會發生錯誤。注JDK8並沒提供具體的檢測框架,只提供了該注解功能,在類型上進行注解。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM