終極CURD-4-java8新特性


1 概述

本篇博客主要介紹java8新特性,因為自己平常也使用到了一些java8的新特性,但是從來沒有從頭到尾,真真正正的把幾乎所有常用的java8新特性研究一遍,這次借助端午節3三天,好好把常用java8新特性梳理一下,畢竟java12馬上就要出來了。

本篇博客不會重點介紹java8新特性的基礎內容,因為網上有很多優秀的文章已經介紹,比如像lambda表達式的用法,stream的特性,Optional的基礎使用等等。在下就不重復造輪子了,我會給出幾個我認為非常優秀的文章,對java8還不熟悉的讀者,可以先看這些基礎內容。本篇博客主要聚焦於java8新特性難點和易錯點。

2 lambda表達式

如果對lambda還不清楚的讀者,可以先按順序閱讀下面兩篇文章

https://www.runoob.com/java/java8-lambda-expressions.html

http://blog.oneapm.com/apm-tech/226.html

2.1 lambda重要知識點總結

①lambda僅僅是一個語法糖,編譯成class文件后,就可以發現,lambda變成了內部類

②一個lambda表達式,看成是一個匿名對象

③lambda表達式 所代表的的接口必須是一個函數式接口

④ 函數式接口:接口中只有一個抽象方法,就稱為函數式接口。可以使用@FunctionalInterface確保該接口為函數式接口,如果不小心寫錯,java編譯器會報錯。

注意:函數式接口可以包含多個default方法和Object方法,以下程序編譯器不會報錯

@FunctionalInterface
public interface MyFunction {

    void hello();

    @Override
    boolean equals(Object o);

    default String sayHi() {
        System.out.println("hi ... everyone i am super ...");
        return "hi";
    }
}

⑤函數式接口的用處,某些方法的參數,可以使用這些函數式接口,那么我們在調用這些方法的時候,就可以使用lambda表達式。函數式接口的唯一作用,就是為了lambda的使用

lambda重點在於參數和body,所以參數類型和body邏輯一定要正確,java編譯期會自動將lambda轉換成對象。

    @Test // 使用匿名對象
    public void test1(){
        Comparable<Integer>  comparable = new Comparable<Integer>() {
            @Override
            public int compareTo(Integer o) {
                return 1;
            }
        };
    }

    @Test  // 使用lambda表達式
    public void test2(){
        Comparable<Integer> comparable = (o) -> 1;
    }

     /*
       想一想 為什么 下面4個會報錯
                                            (o) -> "1"
                                            (o) -> System.out.print("abc")
                                            () -> 1;
                                            (String o) -> 1;
       原因:java編譯器現在已經非常強大,可以根據上下文推到lambda表達式參數和body,因為lambda表達式
       實際上就是一個匿名對象,因此lambda的參數類型和body代碼邏輯必須要符合匿名對象的格式
     */

⑦lambda 表達式的局部變量可以不用聲明為 final,但是必須不可被后面的代碼修改(即隱性的具有 final 的語義,編輯器會自動幫我們加上final)

2.2 java內置函數接口

java內置函數接口,不需要我們自己寫函數接口。最常用的四大函數接口是,Consumer、Supplier、Function、Predicate所有內置函數接口均在 java.util.function

為什么java會內置如此多的函數式接口,我們明明可以自己寫函數式接口啊?

​ 原因:因為函數式接口非常簡單,一個接口聲明,加上一個抽象方法。既然如此,java的內置函數式接口就是業界規定,是一種規范。比如我想寫一個Myfunction函數式接口

public interface MyFunction<T> {
     void consume(T t);
}

而別人又寫寧一個MyFunction2、MyFunction3,既然如此,那大家就都用java內置的函數式接口,約定很重要!!!

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

2.3 方法引用

核心:方法引用只是lambda表現的另一種形式,它依舊還是一個匿名內部對象

1 對象::實例方法 該方法的參數類型和返回類型和函數式接口中的抽象方法必須一致

        // 1 匿名對象
        Consumer<String> consumer1 = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };

        // 2 lambda表達式
        Consumer<String> consumer2 = s -> System.out.println(s);

        // 3 方法引用 1
        PrintStream printStream = System.out;
        Consumer<String> consumer3 = printStream::println;

        // 4 方法引用 2
        Consumer<String> consumer4 = System.out::println;

2 類::靜態方法 該方法的參數類型和返回類型和函數式接口中的抽象方法必須一致

    @Test
    public void test4(){
        // 1
        Comparator<Integer> comparator1 = (x,y) -> Integer.compare(x,y);
        // 2
        Comparator<Integer> comparator2 = Integer::compare;
    }

3 類::實例方法 特殊 該方法的參數類型和返回類型和函數式接口中的抽象方法肯定不一致,不一致,不一致

    @Test
    public void test5(){
        // 1
        BiPredicate<String, String> bp = (x, y) -> x.equals(y);
        // 2
        BiPredicate<String, String> bp2 = String::equals;
    }
	/*
	若Lambda 的參數列表的第一個參數,是實例方法的調用者,第二個參數(或無參)是實例方法的參數時,格		式: ClassName::MethodName  比如 x就是body中的調用者,而y正是該方法的參數
	*/

比如下,下面的 類::靜態方法 類::實例方法 是等價的

public class TestUtil {
	// 實例方法  沒有參數
    private TestUtil println2() {
        return new TestUtil();
    }
	
    // 靜態方法 有參數
    private static TestUtil println3(TestUtil testUtil) {
        return testUtil;
    }

    public static void main(String[] args) {
        TestUtil testUtil = new TestUtil();
        // 實例方法引用 函數式接口和實際方法參數肯定不一致
        Function<TestUtil, TestUtil> function2222 = TestUtil::println2;
        // 靜態方法引用 函數式接口和實際方法參數必須一致
        Function<TestUtil, TestUtil> function33333 = TestUtil::println3;
    }
}

實際上TestUtil::println2,TestUtil::println3都可以看成是一個匿名對象,java編譯過后,class文件幾乎是一樣的。

平常我們還是寫 () -> {} 這種形式的,如果存在方法引用或者構造器引用,idea會自動提示我們,我們再修改即可。但是最好還是要給出詳細注釋,因為有的方法引用,一時半會看不明白。

2.4 構造器引用

構造器參數類型必須和函數式接口中的抽象方法一致,返回類型默認就是該類

    @Test
    public void test6(){
        // 1
        Supplier<Employee> supplier1 = () -> new Employee();
        // 2 默認使用Employee無參構造器,因為Supplier的get方法沒有任何參數
        Supplier<Employee> supplier2 = Employee::new;
        // 3 Employee必須要有一個 Integer構造器   public Employee(Integer age)
        Function<Integer,Employee> function = Employee::new;
    }

2.5 數組引用

構造器參數類型必須和函數式接口中的抽象方法一致,返回類型默認就是該數組

    @Test
    public void test7(){
        // 1
        Function<Integer,String[]> function1 = x -> new String[x];
        // 2
        Function<Integer,String[]> function2 = String[]::new;
    }

2.6 lambda表達式的陷阱

有一篇博客非常棒,推薦給大家

https://segmentfault.com/a/1190000018857239

3 Stream

集合講的是數據,流講的是計算!

注意:

①Stream 自己不會存儲元素。

②Stream 不會改變源對象。相反,他們會返回一個持有結果的新Stream。

③Stream 操作是延遲執行的。這意味着他們會等到需要結果的時候才執行。

3.1 stream 三個核心步驟

一:創建 Stream

二:中間操作

三:終止操作(終端操作)

3.2 stream 創建的5個方法

		//1. Collection 提供了兩個方法  stream() 與 parallelStream()
		List<String> list = new ArrayList<>();
		Stream<String> stream = list.stream(); //獲取一個順序流
		Stream<String> parallelStream = list.parallelStream(); //獲取一個並行流
		
		//2. 通過 Arrays 中的 stream() 獲取一個數組流
		Integer[] nums = new Integer[10];
		Stream<Integer> stream1 = Arrays.stream(nums);
		
		//3. 通過 Stream 類中靜態方法 of()
		Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);
		
		//4. 創建無限流
		//迭代
		Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
		stream3.forEach(System.out::println);
		
		//5. 創建無限流
		//生成
		Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
		stream4.forEach(TestUtil::println2);

3.3 stream 中間操作

多個中間操作可以連接起來形成一個流水線,除非流水 線上觸發終止操作,否則中間操作不會執行任何的處理!

而在終止操作時一次性全部處理,稱為“惰性求值”。

一:篩選與切片

方法 描述
filter(Predicate p) 接收 Lambda , 從流中排除某些元素。
distinct() 篩選,通過流所生成元素的 hashCode() 和 equals() 去除重復元素
limit(long maxSize) 截斷流,使其元素不超過給定數量。
skip(long n) 跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素 不足 n 個,則返回一個空流。與 limit(n) 互補

二:映射

方法 描述
map(Function f) 接收一個函數作為參數,該函數會被應用到每個元素上,並將其映射成一個新的元素。
mapToDouble(ToDoubleFunction f) 接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 DoubleStream。
mapToInt(ToIntFunction f) 接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 IntStream。
mapToLong(ToLongFunction f) 接收一個函數作為參數,該函數會被應用到每個元素上,產生一個新的 LongStream。
flatMap(Function f) 接收一個函數作為參數,將流中的每個值都換成另 一個流,然后把所有流連接成一個流

注意:map和flatMap的區別

區別就是 list方法中的 add 和 addAll

add(Object o) 如果添加一個集合,會直接把該集合添加進去。

addAll(Object o)如果添加一個集合,會把集合中的元素一個一個加入進去,而不會加入一整個集合。

三:排序

方法 描述
sorted() 產生一個新流,其中按自然順序排序
sorted(Comparator comp) 產生一個新流,其中按比較器順序排序

在stream中排序方法就只有sorted,如果不帶參數,就是自然排序

        List<String> list = Arrays.asList("ccc","aaa","ddd","bbb");
        list.stream().sorted().forEach(System.out::println);

因為集合中的元素 String 實現了 Comparable接口,所以會自動調用public int compareTo(T o)進行排序

如果sorted方法帶了參數,就是定制排序

emps.stream().sorted((e1,e2) -> e1.getAge()-e2.getAge()).forEach(System.out::println);

3.4 stream 終止操作

終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

一:查找與匹配:

方法 描述
allMatch(Predicate p) 檢查是否匹配所有元素
anyMatch(Predicate p) 檢查是否至少匹配一個元素
noneMatch(Predicate p) 檢查是否沒有匹配所有元素
findFirst() 返回第一個元素
findAny() 返回當前流中的任意元素
count() 返回流中元素總數
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 內部迭代(使用 Collection 接口需要用戶去做迭代,稱為外部迭代。相反,Stream API 使用內部迭代——它幫你把迭代做了)

二:歸約:

方法 描述
reduce(T iden, BinaryOperator b) 可以將流中元素反復結合起來,得到一個值。 返回 T
reduce(BinaryOperator b) 可以將流中元素反復結合起來,得到一個值。 返回 Optional

備注:map 和 reduce 的連接通常稱為 map-reduce 模式,因 Google 用它來進行網絡搜索而出名。

因為reduce(T iden, BinaryOperator b)中,起始值就是T iden所以肯定會有一個值,不會返回null值,所以返回值不是Optional

三:收集:

方法 描述
collect(Collector c) 將流轉換為其他形式。接收一個 Collector接口的實現,用於給Stream中元素做匯總的方法

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

方法 返回類型 作用
toList List 把流中元素收集到List
toSet Set 把流中元素收集到Set
toCollection Collection 把流中元素收集到創建的集合
counting Long 計算流中元素的個數
summingInt Integer 對流中元素的整數屬性求和
averagingInt Double 計算流中元素Integer屬性的平均值
summarizingInt IntSummaryStatistics 收集流中Integer屬性的統計值。比如:數量、總和、最小值、平均值、最大值
maxBy Optional 根據比較器選擇最大值
minBy Optional 根據比較器選擇最小值
groupingBy Map<K,List > 根據某屬性值對流分組,屬性為K,值為List
partitioningBy Map<Boolean,List > 根據true或false進行分區
joining String 連接流中每個字符串

注意:groupingBy可以多級分組

3.5 並行流和串行流

並行流就是把一個內容分成多個數據塊,並用不同的線程分 別處理每個數據塊的流。

Java 8 中將並行進行了優化,我們可以很容易的對數據進行行操作。Stream API 可以聲明性地通過 parallel() 與
sequential() 在並行流與順序流之間進行切換。

3.6 fork/join

Fork/Join 框架:就是在必要的情況下,將一個大任務,進行拆分(fork)成若干個小任務(拆到不可再拆時),再將一個個的小任務運算的結果進行 join 匯總。

Fork/Join 框架與傳統線程池的區別

采用 “工作竊取”模式(work-stealing):
當執行新的任務時它可以將其拆分分成更小的任務執行,並將小任務加到線程隊列中,然后再從一個隨機線程的隊列中偷一個並把它放在自己的隊列中。

相對於一般的線程池實現,fork/join框架的優勢體現在對其中包含的任務的 處理方式上.在一般的線程池中,如果一個線程正在執行的任務由於某些原因 無法繼續運行,那么該線程會處於等待狀態.而在fork/join框架實現中,如果某個子問題由於等待另外一個子問題的完成而無法繼續運行.那么處理該子 問題的線程會主動尋找其他尚未運行的子問題來執行.這種方式減少了線程 的等待時間,提高了性能.

在jdk1.8以前,fork/join比較復雜,代碼編寫比較難,而在jdk1.8中,利用stream中的parallel()可以非常輕松的實現fork/join的效果。(parallel底層就是利用fork/join)

4 java8中默認的接口和方法

因為要兼容java以前和接口特性,所以java1.8中才有接口默認方法。

不過有以下點注意點:

①接口默認方法 “類優先” 原則

若一個接口中定義了一個默認方法,而另外一個父類或接口又定義了一個同名的方法時 ,子類如果直接調用,會選擇父類的方法

②接口沖突

如果一個父接口提供一個默認方法,而另一個接 口也提供了一個具有相同名稱和參數列表的方法(不管方法是否是默認方法),那么必須覆蓋該方法來解決沖突

③如果存在接口沖突,如何選擇指定的接口方法呢?

public class SubClass implements Animal,Animal2{}
    @Override
    public String hello() {
        return Animal.super.hello();
    }

    @Override
    public String hello() {
        return Animal2.super.hello();
    }

通過 Interface.super.method()

④接口中可以存在靜態方法,和普通類的靜態方法調用方式一模一樣。

5 Optional

有兩篇非常不錯的文章,大家可以參考一下,本人就不重復造輪子了。

https://www.cnblogs.com/chenpi/p/5923829.html

http://www.importnew.com/22060.html

Optional的底層源碼其實非常簡單,建議大家多去看看源碼。

5.1 Optional 精華所在

請仔細思考以下代碼:

    @Test
    public void test1() {
        Animal animal = getAnimal();
        if (animal != null) {
            Person person = animal.getPerson();
            if (person != null) {
                Student student = person.getStudent();
                if (student != null) {
                    System.out.println(student.getUsername());
                }
            }
        }
    }

  @Test
    public void test2() {
        Optional<Animal> animal = Optional.ofNullable(getAnimal());
        String name = animal.map(Animal::getPerson)
                .map(Person::getStudent)
                .map(Student::getUsername)
                .orElse("animal name");
        System.out.println(name);
    }

test1 是典型的非空判斷,test2是使用Optional進行的非空判斷,很明顯后者的代碼更加優雅,並且如果對象依賴的層級關系越多,那么if也就會越多,這時候使用Optional能省去很多麻煩。

5.2 Optional 誤區

一:可以參考博客,文章內容非常不錯,值得一看!

https://segmentfault.com/a/1190000018936877?utm_source=tag-newest

二:optional中使用map和flatMap

map源碼:

    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }

flatMap源碼:

   public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

我們可以看到,在flatMap中,如果mapper.apply(value)返回null,那么將會直接拋出異常。

而在map中mapper.apply(value)為null,不會拋出異常,僅僅返回一個empty對象。

6 java8 新的時間api

LocalDate、LocalTime、LocalDateTime:

/**
     * LocalDate、LocalTime、LocalDateTime API用法幾乎一樣
     * LocalDate  只有 年-月-日
     * LocalTime  只有 時-分-秒-納秒
     * LocalDateTime 年-月-日-時-分-秒-納秒
     */
    @Test
    public void test1() {
        LocalDateTime ldt = LocalDateTime.now(); //2019-06-12T11:40:55.132
        LocalDateTime ld2 = LocalDateTime.of(2016, 11, 21, 10, 10, 10);//2016-11-																				21T10:10:10
        LocalDateTime ldt3 = ld2.plusYears(20);//2036-11-21T10:10:10
        LocalDateTime ldt4 = ld2.minusMonths(2);//2016-09-21T10:10:10
        System.out.println(ldt.getYear());//2019
        System.out.println(ldt.getMonthValue());//6
        System.out.println(ldt.getDayOfMonth());//12
        System.out.println(ldt.getHour());//11
        System.out.println(ldt.getMinute());//40
        System.out.println(ldt.getSecond());//55
    }

Instant:

    /**
     * Instant : 時間戳。 (使用 Unix 元年  1970年1月1日 00:00:00 所經歷的毫秒值)
     */
    @Test
    public void test2() {
        Instant ins = Instant.now();  //默認使用 UTC 時區
        System.out.println(ins); //2019-06-12T03:45:38.923Z
        OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));
        System.out.println(odt); //2019-06-12T11:46:08.147+08:00
        System.out.println(odt.toEpochSecond()); //1560311168  不論那個時區,輸出的毫秒和秒																			都是一樣的
        System.out.println(ins.getEpochSecond()); //1560311168
        System.out.println(ins.toEpochMilli()); // 和System.currentTimeMillis() 一樣
    }

Duration、Period:

	/**
     * Duration : 用於計算兩個“時間”間隔
     * Period : 用於計算兩個“日期”間隔
     */	
	@Test
    public void test3() throws InterruptedException {
        Instant ins1 = Instant.now();
        Thread.sleep(1000);
        Instant ins2 = Instant.now();
        System.out.println("所耗費時間為:" + Duration.between(ins1, ins2)); //所耗費時間																			為:PT1.001S
        System.out.println("----------------------------------");
        LocalDate ld1 = LocalDate.now();
        LocalDate ld2 = LocalDate.of(2011, 1, 1);
        Period pe = Period.between(ld2, ld1);
        System.out.println(pe.getYears()); //8
        System.out.println(pe.getMonths()); //5
        System.out.println(pe.getDays()); //11
    }

TemporalAdjuster:

	/**
     * TemporalAdjuster : 時間校正器。有時我們可能需要獲取例如:將日期調整到“下個周日”、“這個月的第一天”、"這一年的最后一天"等操作。
     * TemporalAdjusters : 該類通過靜態方法提供了大量的常用 TemporalAdjuster 的實現。
     * 主要涉及到LocalDateTime、LocalDate、LocalTime的with方法
     * public LocalDateTime with(TemporalAdjuster adjuster) { ... }
     */
    @Test
    public void test4() {
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(ldt); // 2019-06-12T13:53:06.814

        LocalDateTime ldt2 = ldt.withDayOfMonth(10);
        System.out.println(ldt2); // 2019-06-10T13:53:06.814

        LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
        System.out.println(ldt3); // 2019-06-16T13:53:06.814

        //自定義:下一個工作日
        LocalDateTime ldt5 = ldt.with((l) -> {
            LocalDateTime ldt4 = (LocalDateTime) l;

            DayOfWeek dow = ldt4.getDayOfWeek();

            if (dow.equals(DayOfWeek.FRIDAY)) {
                return ldt4.plusDays(3);
            } else if (dow.equals(DayOfWeek.SATURDAY)) {
                return ldt4.plusDays(2);
            } else {
                return ldt4.plusDays(1);
            }
        });
        System.out.println(ldt5); // 2019-06-13T13:53:06.814
    }

DateTimeFormatter

 	/**
     * DateTimeFormatter : 解析和格式化日期或時間
     */
    @Test
    public void test5() {
        // DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE; DateTimeFormatter內部定義了很多時間格式			     
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss:SSS E");  // 自定義格式,E表示周幾
        LocalDateTime ldt = LocalDateTime.now();
        String strDate = ldt.format(dtf); //LocalDateTime轉成String
        System.out.println(strDate); // 2019年06月12日 14:14:22:611 星期三
        LocalDateTime newLdt = LocalDateTime.parse(strDate, dtf); // 將String轉換成LocalDateTime
        System.out.println(newLdt); // 2019-06-12T14:14:22.611
    }

ZonedDateTime:

/**
 * ZonedDateTime : 帶時區的時間,內部包裝了一個LocalDateTime
 */
@Test
public void test7() {
    Set<String> set = ZoneId.getAvailableZoneIds();
    set.forEach(System.out::println); // 獲取所有的時區
    /*
        America/Los_Angeles
        SystemV/EST5EDT
        Pacific/Majuro
        America/Argentina/Buenos_Aires
        Europe/Nicosia
        Pacific/Guadalcanal
        Europe/Athens
        US/Pacific
        Europe/Monaco
        ... ...
        ... ...
    */
    ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
    System.out.println(zdt); //2019-06-12T14:33:16.543+08:00[Asia/Shanghai]
}

下面是一篇非常不錯的博客,建議讀者仔細閱讀

https://www.cnblogs.com/comeboo/p/5378922.html

這篇博客總共有20個知識點:

1 如何在java8中獲取當天的日期

2 如何在java8中獲取當前的年月日

3 在java8中如何獲取某個特定的日期

4 在java8中檢查兩個日期是否相等

5 在java8中如何檢查重復事件,比如生日

6 如何在java8中獲取當前時間

7 如何增加時間里面的小時數

8 如何獲取1周后的日期

9 一年前后的日期

10 在java8中使用時鍾

11 在java中如何判斷某個日期在另一個日期的前面還是后面

12 在java8中處理不同的時區

13 如何表示固定的日期,比如信用卡過期時間

14 如何在java8中檢查閏年

15 兩個日期之間包含多少天,多少月

16 帶時區的日期與時間

17 在java8中獲取當前時間戳

18 如何在java8中使用預定義的格式器來對日期進行解析/格式化

19 如何在java中使用自定義的格式器來解析日期

20 如何在java8中對日期進行格式化,轉換成字符串

這是一篇英語博客,內容非常不錯,感興趣的朋友可以閱讀。

https://www.baeldung.com/java-8-date-time-intro

這篇博客的 ZonedDateTime時區相關講解,感覺不錯。

7 重復注解和類型注解

如果對java的注解完全不熟悉的朋友,可以參考下面的博客。

https://www.cnblogs.com/xdp-gacl/p/3622275.html

這篇博客主要講解的是java1.8注解新特性

https://www.cnblogs.com/lxyit/p/9442586.html

注解有幾個容易被遺忘的地方:

①如果注解上面沒有寫@Target,那么默認@Target包含所有的屬性,下面兩個MyAnnotation是完全一樣的

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "hello world";
}

@Target( { ElementType.METHOD,ElementType.TYPE,ElementType.FIELD,ElementType.TYPE_USE
,ElementType.CONSTRUCTOR,ElementType.ANNOTATION_TYPE,ElementType.LOCAL_VARIABLE,
ElementType.PACKAGE,ElementType.PARAMETER,ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "hello world";
}

②使用重復注解的時候,用getAnnotations只能獲取 容器注解的類型(也是就@Repeatable中的類)

可重復注解:

@Target( { ElementType.METHOD,ElementType.TYPE,ElementType.FIELD,ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
    String value() default "hello world";
}

容器注解類

@Target( { ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
    MyAnnotation[] value();
}

使用注解的類

@MyAnnotation("value ... ...")
@MyAnnotation2
@MyAnnotation("personality design ... ...")
class AnnotationUse { }

主程序:

    @Test
    public void test1() {
        Annotation[] annotations = AnnotationUse.class.getAnnotations();
        Arrays.stream(annotations).forEach(System.out::println);
    }

輸出:

@com.atguigu.anonotation.MyAnnotations(value=[@com.atguigu.anonotation.MyAnnotation(value=hello world), @com.atguigu.anonotation.MyAnnotation(value=personality design ... ...)])
@com.atguigu.anonotation.MyAnnotation2(value=MyAnnotation2 ... ...)

我們可以看到,用getAnnotations並沒有拿到@MyAnnotation而是@MyAnnotations


參考資料:

https://www.runoob.com/java/java8-lambda-expressions.html

http://blog.oneapm.com/apm-tech/226.html

https://segmentfault.com/a/1190000018857239

https://www.cnblogs.com/chenpi/p/5923829.html

http://www.importnew.com/22060.html

https://segmentfault.com/a/1190000018936877?utm_source=tag-newest

https://www.cnblogs.com/comeboo/p/5378922.html

https://www.baeldung.com/java-8-date-time-intro

作者: 一杯熱咖啡AAA
出處: https://www.cnblogs.com/AdaiCoffee/
本文以學習、研究和分享為主,歡迎轉載。如果文中有不妥或者錯誤的地方還望指出,以免誤人子弟。如果你有更好的想法和意見,可以留言討論,謝謝!


免責聲明!

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



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