想更詳細的了解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)到底是什么?
是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列。“集合講的是數據,流講的是計算!”
注意一下三點:
- Stream 自己不會存儲元素。
- Stream 不會改變源對象。相反,他們會返回一個持有結果的新 Stream。
- Stream 操作是延遲執行的。這也意味着他們會等到需要結果的時候才執行。
Stream 的操作三個步驟:
- 創建 Stream: 一個數據源(如:集合、數組),獲取一個流。
- 中間操作: 一個中間操作鏈,對數據源的數據進行一系列處理。
- 終止操作(終端操作): 一個終止操作,執行中間操作鏈,並產生結果。
創建 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並沒提供具體的檢測框架,只提供了該注解功能,在類型上進行注解。