Java 8 新特性總結

首先列出Java 8 新特性,如下:

  • Lambda表達式
  • 函數式接口
  • 方法引用
  • Stream流
  • Parallel並行流
  • Optional類
  • 接口中的默認方法和靜態方法
  • Date API

接下來逐一介紹這些新特性。

1、Lambda表達式

Lambda 表達式,也可稱爲閉包,本質上是一段匿名內部類。允許把函數作爲一個方法的參數(函數作爲參數傳遞進方法中)。

lambda 表達式的語法格式如下:

(parameters) -> expression 或者 (parameters) ->{ statements; }

以下是lambda表達式的重要特徵:

前置 語法
無參數無返回值 () -> System.out.println(“aaa”)
有一個參數無返回值 (x) -> System.out.println(x)
有且只有一個參數無返回值 x -> System.out.println(x)
有多個參數,有返回值,有多條lambda體語句 (x,y) -> {System.out.println(“aaa”);return xxxx;};
有多個參數,有返回值,只有一條lambda體語句 (x,y) -> xxxx
  • 可選類型聲明:不需要聲明參數類型,編譯器可以統一識別參數值。
  • 可選的參數圓括號:一個參數無需定義圓括號,但多個參數需要定義圓括號。
  • 可選的大括號:如果主體包含了一個語句,就不需要使用大括號。
  • 可選的返回關鍵字:如果主體只有一個表達式返回值則編譯器會自動返回值,大括號需要指明表達式返回了一個數值。

接下來舉個例子展示下Lambda表達式:

首先看下之前的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);
    }
});

然而在Java 8 中你就沒必要使用這種傳統的匿名對象的方式了,Java 8提供了更簡潔的語法,lambda表達式:

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

對於函數體只有一行代碼的,可以去掉大括號{}及return關鍵字,並且由於Java編譯器可以自動推導出參數類型,所以類型也可以不用寫,簡潔後的代碼如下:

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

Lambda 作用域:

在lambda表達式中訪問外層作用域和老版本的匿名對象中的方式很相似。你可以直接訪問標記了final的外層局部變量,或者實例的字段以及靜態變量。

(1)訪問局部變量
我們可以直接在lambda表達式中訪問外層的局部變量:

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

但是和匿名對象不同的是,這裏的變量num可以不用聲明爲final,該代碼同樣正確:

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

不過這裏的num必須不可被後面的代碼修改(即隱性的具有final的語義),例如下面的就無法編譯:

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

在lambda表達式中試圖修改num同樣是不允許的。

(2)訪問對象字段與靜態變量

和本地變量不同的是,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);
        };
    }
}

2、函數式接口

當一個接口中存在多個抽象方法時,如果使用lambda表達式,並不能智能匹配對應的抽象方法,因此引入了函數式接口的概念。

什麼是函數式接口?
簡單來說就是隻定義了一個抽象方法的接口,就是函數式接口,並且還提供了註解:@FunctionalInterface。

四大核心函數式接口

/*
Consumer<T> :消費型接口
    void accept(T t);

Supplier<T> :供給型接口
    T get();

Function<T,R> :函數型接口
    R apply(T t);

Predicate<T> :斷言型接口
    boolean test(T t);
*/

Consumer 《T》:消費型接口,有參無返回值

    @Test
    public void test(){
        changeStr("hello",(str) -> System.out.println(str));
    }

    /**
     *  Consumer<T> 消費型接口
     * @param str
     * @param con
     */
    public void changeStr(String str, Consumer<String> con){
        con.accept(str);
    }

Supplier 《T》:供給型接口,無參有返回值

    @Test
    public void test2(){
        String value = getValue(() -> "hello");
        System.out.println(value);
    }

    /**
     *  Supplier<T> 供給型接口
     * @param sup
     * @return
     */
    public String getValue(Supplier<String> sup){
        return sup.get();
    }

Function 《T,R》::函數式接口,有參有返回值

    @Test
    public void test3(){
        Long result = changeNum(100L, (x) -> x + 200L);
        System.out.println(result);
    }

    /**
     *  Function<T,R> 函數式接口
     * @param num
     * @param fun
     * @return
     */
    public Long changeNum(Long num, Function<Long, Long> fun){
        return fun.apply(num);
    }

Predicate《T》: 斷言型接口,有參有返回值,返回值是boolean類型

public void test4(){
        boolean result = changeBoolean("hello", (str) -> str.length() > 5);
        System.out.println(result);
    }

    /**
     *  Predicate<T> 斷言型接口
     * @param str
     * @param pre
     * @return
     */
    public boolean changeBoolean(String str, Predicate<String> pre){
        return pre.test(str);
    }

總結:函數式接口是爲了讓我們更加方便的使用lambda表達式,不需要自己再手動創建一個函數式接口,直接使用即可。

3、方法引用

若lambda體中的內容有方法已經實現了,那麼可以使用“方法引用”
也可以理解爲方法引用是lambda表達式的另外一種表現形式並且其語法比lambda表達式更加簡單。

(1)方法引用

三種語法:

  • 對象::實例方法名
  • 類::靜態方法名
  • 類::實例方法名 (lambda參數列表中第一個參數是實例方法的調用 者,第二個參數是實例方法的參數時可用)
 public void test() {
       
        Consumer<Integer> con = (x) -> System.out.println(x);
        con.accept(100);

        // 方法引用-對象::實例方法
        Consumer<Integer> con2 = System.out::println;
        con2.accept(200);

        // 方法引用-類名::靜態方法名
        BiFunction<Integer, Integer, Integer> biFun = (x, y) -> Integer.compare(x, y);
        BiFunction<Integer, Integer, Integer> biFun2 = Integer::compare;
        Integer result = biFun2.apply(100, 200);

        // 方法引用-類名::實例方法名
        BiFunction<String, String, Boolean> fun1 = (str1, str2) -> str1.equals(str2);
        BiFunction<String, String, Boolean> fun2 = String::equals;
        Boolean result2 = fun2.apply("hello", "world");
        System.out.println(result2);
    }

注意:
①Lambda體中調用方法的參數列表與返回值類型,要與函數式接口中抽象方法的函數列表和返回值類型一致。
②若Lambda參數列表中的第一參數是實例方法的調用者,而第二個參數是實例方法的參數時,可以使用ClassName::method進行調用。

(2)構造器引用
格式:ClassName::new

public void test2() {

        // 構造方法引用  類名::new
        Supplier<Employee> sup = () -> new Employee();
        System.out.println(sup.get());
        Supplier<Employee> sup2 = Employee::new;
        System.out.println(sup2.get());

        // 構造方法引用 類名::new (帶一個參數)
        Function<Integer, Employee> fun = (x) -> new Employee(x);
        Function<Integer, Employee> fun2 = Employee::new;
        System.out.println(fun2.apply(100));
 }

注意:
需要引用的構造器的參數列表要與函數式接口中抽象方法的參數列表保持一致。

(3)數組引用

格式:Type[]::new

public void test(){
        // 數組引用
        Function<Integer, String[]> fun = (x) -> new String[x];
        Function<Integer, String[]> fun2 = String[]::new;
        String[] strArray = fun2.apply(10);
        Arrays.stream(strArray).forEach(System.out::println);
}

4、Stream流

一系列流水線式的中間操作。
流是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列。

注意:
①Stream自己不會存儲元素。
②Stream不會改變源對象。相反,會返回持有新結果的新Stream。
③Stream操作是延遲執行的。這意味着他們會等到需要結果的時候才執行。

Stream操作的三個步驟:

(1)創建一個stream流
(2)中間操作(過濾、map)
(3)終止操作

接下來逐一介紹這三個步驟

創建stream流:

//1. 可以通過Collections系列集合提供的stream()或parallelStream()
 List<String> list = new ArrayList<>();
 Stream<String> stream = list.stream();

 //2. 通過Arrays中的靜態方法stream()獲取數組流
 String[] arr = new String[]{"1","2"};
 Stream<String> stream = Arrays.stream(arr);
 stream.forEach(System.out::println);

 //3. 通過Stream類中的靜態方法of()
 Stream<String> stream3 = Stream.of("a", "b", "c");

 //4. 創建無限流
 //迭代
 Stream.iterate(0,x -> x+2).limit(10).forEach(System.out::println);

 //生成
 Stream<Double> generate = Stream.generate(() -> Math.random());
 generate.limit(5).forEach(System.out::println);

中間操作:

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

(1)篩選、過濾、去重

   /**
    * 篩選、過濾、去重
    *
    * filter -- 過濾出符合條件的流元素
    * limit -- 截斷流,使其元素不超過某個給定數量
    * skip -- 跳過元素,返回一個扔掉了前n個元素的流,若流中元素不足n個,則返回一個空流,與limit互補。
    * distinct -- 去重,通過hashcode和equals去重。
    */
   @Test
   public void test1(){
        Stream<Employee> stream = empList.stream();//創建流
        stream
                .filter(e -> e.getAge()>25)//過濾出符合條件的流元素
                .limit(3)//只取3個
                .skip(1)//跳過1個
                .distinct()//去重,需重寫hashcode和equals方法
                .forEach(System.out::println);//終止操作,獲取流

   }

(2)映射

接受Lambda,將元素轉換成其他形式或提取信息,接受一個函數作爲參數, 該函數會被應用到每個元素上,並將其映射成一個新的元素。

public class StreamTest3 {
     /**
     * 映射
     * map -- 接受Lambda,將元素轉換成其他形式或提取信息,接受一個函數作爲參數,
     * 該函數會被應用到每個元素上,並將其映射成一個新的元素。
     *
     * flatmap -- 接受一個函數做爲參數,將流中的每個值都轉換成另一個流,然後將所有流連接成一個流,
     */
    @Test
    public void test2(){
        empList 
                .stream()//創建流
                .map(employee -> employee.getName())//中間操作:映射
                .forEach(System.out::println);//終止流

    }

    @Test
    public void test3(){
        List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");
        Stream<Stream<Character>> streamStream = list
                .stream()
                .map(StreamTest3::getCharacter);//流中還是流


        streamStream
                .forEach(sm->sm
                        .forEach(System.out::println));


        System.out.println("-------------------------------------");

        list.stream()
                .flatMap(StreamTest3::getCharacter)//把兩個流,變成一個流返回
                .forEach(System.out::println);
    }
    public static Stream<Character> getCharacter(String str){
        List<Character> list = new ArrayList<>();
        for (char c : str.toCharArray()) {
            list.add(c);
        }
        return list.stream();
    }
}

(3)自然排序和定製排序

public class StreamTest4 {
    /**
     * 排序
     *
     * sorted -- 自然排序(Comparable)
     * sorted(Comparator com) -- 定製排序(Comparator)
     */

    @Test
    public void test1(){
        List<String> list =
                Arrays.asList("ddd", "ccc", "ggg", "bbb", "aaa");

        list.stream()
                .sorted()//自然排序
                .forEach(System.out::println);

        System.out.println("------------------------------");

       empList.stream()
                .sorted((e1,e2) -> {//定製排序
                    if(e1.getSalary()==e2.getSalary()) {
                        return e1.getName().compareTo(e2.getName());
                    }else {
                        return (int)(e1.getSalary()-e2.getSalary());
                    }
                }).forEach(System.out::println);
    }

}

Stream的終止操作

  • allMatch:檢查是否匹配所有元素 ,如果都匹配,則返回true
  • anyMatch:檢查是否至少匹配一個元素 ,如果有一個匹配,則返回true
  • noneMatch:檢查是否沒有匹配所有元素 ,如果是,則返回true
  • findFirst: 返回集合中的第一個元素
  • findAny:返回集合中,取到的任何一個對象
  • count:返回流中元素的總個數
  • max:返回流中最大值
  • min:返回流中最小值
List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");

boolean bbb = list.stream().allMatch(x -> x.equals("bbb"));
System.out.println("bbb="+bbb);

boolean bbb1 = list.stream().anyMatch(x -> x.equals("bbb"));
System.out.println("bbb1="+bbb1);

boolean bbb2 = list.stream().noneMatch(x -> x.equals("bbb"));
System.out.println("bbb2="+bbb2);

Optional<String> first = list.stream().findFirst();
System.out.println("first="+first.get());

long count = list.stream().count();
System.out.println("count="+count);

Optional<String> max = list.stream().max(Comparator.comparing(Function.identity()));
System.out.println("max="+max.get());

Optional<String> min = list.stream().min(Comparator.naturalOrder());
System.out.println("min="+min.get());

Optional<String> any = list.parallelStream().findAny();
System.out.println("any="+any.get());

結果如下:

bbb=false
bbb1=true
bbb2=false
first=aaa
count=4
max=ddd
min=aaa
any=ccc

還有功能比較強大的兩個終止操作 reduce和collect

(1)歸約reduce
reduce:(T identity,BinaryOperator)/reduce(BinaryOperator)-可以將流中元素反覆結合起來,得到一個值。


/**
 * 終止操作:
 *
 * 歸約
 * reduce(T identity, BinaryOperator) / reduce(BinaryOperator)
 *  -- 可以將流中的元素反覆結合起來,得到一個值
 */
@Test
public void test1(){
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
    Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
    System.out.println(reduce);

    Optional<Double> reduce1 = empList.stream()
            .map(Employee::getSalary)//現將salsry映射出來
            .reduce(Double::sum);//進行歸約求和
    System.out.println(reduce1.get());
}

(2)收集Collect

將流轉換成其他格式,接受一個Collector接口的實現,用於給Stream中元素做彙總的操作。

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

/**
 * 收集
 * collect
 * -- 將流轉換成其他的形式,接收一個Collector接口的實現,可以通過Collectors的實用類操作
 */
@Test
public void test2(){
    //收集姓名到列表
    List<String> collect = empList.stream()
            .map(Employee::getName)
            .collect(Collectors.toList());
    collect.forEach(System.out::println);
    System.out.println("-------------------------");

    //收集姓名到set
    Set<String> collect1 = empList.stream().map(Employee::getName)
            .collect(Collectors.toSet());
    collect1.forEach(System.out::println);
    System.out.println("--------------------------");

    //收集姓名到指定的數據結構
    LinkedHashSet<String> collect2 = empList.stream().map(Employee::getName)
            .collect(Collectors.toCollection(LinkedHashSet::new));
    collect2.forEach(System.out::println);
}
@Test
public void test3(){
    //總數
    Long collect = empList.stream()
            .collect(Collectors.counting());
    System.out.println(collect);
    System.out.println("---------------------------------");

    //平均
    Double collect1 = empList.stream().collect(Collectors.averagingDouble(Employee::getSalary));
    System.out.println(collect1);
    System.out.println("-------------------------------");

    //總和
    Double collect2 = empList.stream().collect(Collectors.summingDouble(Employee::getSalary));
    System.out.println(collect2);
    System.out.println("-----------------------");

    //最大值
    Optional<Employee> collect3 = empList.stream().collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
    System.out.println(collect3);
    System.out.println("-----------------------");
}

//分組
@Test
public void test4(){

      //單級分組
       Map<Integer, List<Employee>> collect = empList.stream().collect(Collectors.groupingBy(Employee::getAge));
       System.out.println(collect);
       System.out.println("----------------------");

       //多級分組
       Map<Integer, Map<String, List<Employee>>> collect1 = empList.stream().collect(Collectors.groupingBy(Employee::getAge, Collectors.groupingBy(e -> {
           if (e.getAge() < 20){
               return "少年";
           } else if (e.getAge() < 30){
               return "青年";
           } else{
               return "中年";
           }
       })));
       System.out.println(collect1);
       System.out.println("----------------------");

       //分區--滿足條件一個區,不滿足另一個區
       Map<Boolean, List<Employee>> collect2 = empList.stream().collect(Collectors.partitioningBy(e -> e.getSalary() > 6000));
       System.out.println(collect2);
       System.out.println("-----------------------");

       //收集各種統計數據
       DoubleSummaryStatistics collect3 = empList.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
       System.out.println(collect3+"-----------平均薪水"+collect3.getAverage());
       System.out.println("----------------------");

       //連接字符串
       String collect4 = empList.stream().map(Employee::getName).collect(Collectors.joining(",", "-----", "-----"));
       System.out.println(collect4);

}

5、Parallel並行流

jdk1.8並行流使用的是fork/join框架進行並行操作。

Fork/Join框架:在必要的情況下,將一個大任務,進行拆分(fork) 成若干個子任務(拆到不能再拆,這裏就是指我們制定的拆分的臨界值),再將一個個小任務的結果進行join彙總。

在這裏插入圖片描述

Fork/Join框架和傳統線程池的區別:
採用“工作竊取”模式(Working-stealing),即當執行新的任務時它可以將其拆分分成更小的任務執行,並將小任務加到線程隊列中,然後再從一個隨機線程的隊列中偷一個並把它放在自己的隊列中。

就比如兩個CPU上有不同的任務,這時候A已經執行完,B還有任務等待執行,這時候A就會將B隊尾的任務偷過來,加入自己的隊列中,對於傳統的線程,ForkJoin更有效的利用的CPU資源!

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

我們來看一下ForkJoin的實現:實現這個框架需要繼承RecursiveTask 或者 RecursiveAction ,RecursiveTask是有返回值的,相反Action則沒有

package ForkJionP;


import java.util.concurrent.RecursiveTask;

public class ForkJoinWork extends RecursiveTask<Long> {

    private Long start;//起始值
    private Long end;//結束值
    public static final  Long critical = 100000L;//臨界值

    public ForkJoinWork(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        //判斷是否是拆分完畢
        Long lenth = end - start;
        if(lenth<=critical){
            //如果拆分完畢就相加
            Long sum = 0L;
            for (Long i = start;i<=end;i++){
                sum += i;
            }
            return sum;
        }else {
            //沒有拆分完畢就開始拆分
            Long middle = (end + start)/2;//計算的兩個值的中間值
            ForkJoinWork right = new ForkJoinWork(start,middle);
            right.fork();//拆分,並壓入線程隊列
            ForkJoinWork left = new ForkJoinWork(middle+1,end);
            left.fork();//拆分,並壓入線程隊列

            //合併
            return right.join() + left.join();
        }
    }
}

測試:

package ForkJionP;

import org.junit.Test;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

public class ForkJoinWorkDemo {

    public  void test() {
    //ForkJoin實現
        long l = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();//實現ForkJoin 就必須有ForkJoinPool的支持
        ForkJoinTask<Long> task = new ForkJoinWork(0L,10000000000L);//參數爲起始值與結束值
        Long invoke = forkJoinPool.invoke(task);
        long l1 = System.currentTimeMillis();
        System.out.println("invoke = " + invoke+"  time: " + (l1-l));
        //invoke = -5340232216128654848  time: 76474
    }

    public void test2(){
  //普通線程實現
        Long x = 0L;
        Long y = 10000000000L;
        long l = System.currentTimeMillis();
        for (Long i = 0L; i <= y; i++) {
            x+=i;
        }
        long l1 = System.currentTimeMillis();
        System.out.println("invoke = " + x+"  time: " + (l1-l));
        //invoke = -5340232216128654848  time: 160939
    }
    @Test
    public void test3(){
   //Java 8 並行流的實現
        long l = System.currentTimeMillis();
        long reduce = LongStream.rangeClosed(0, 10000000000L).parallel().reduce(0, Long::sum);
        long l1 = System.currentTimeMillis();
        System.out.println("invoke = " + reduce+"  time: " + (l1-l));
        //invoke = -5340232216128654848  time: 15531
    }

}

我們觀察上面可以看出來執行10000000000L的相加操作各自執行完畢的時間不同。觀察到當數據很大的時候ForkJoin比普通線程實現有效的多,但是相比之下ForkJoin的實現實在是有點麻煩,這時候Java 8 就爲我們提供了一個並行流來實現ForkJoin實現的功能。可以看到並行流比自己實現ForkJoin還要快。

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

6、Optional類

Optional 類(java.util.Optional) 是一個容器類,代表一個值存在或不存在,原來用null 表示一個值不存在,現在Optional 可以更好的表達這個概念。可以快速的定位空指針異常,並且在一定程度上可以減少對參數非空檢驗的代碼量。

常用方法:

  • Optional.of(T t) : 創建一個Optional 實例
  • Optional.empty() : 創建一個空的Optional 實例
  • Optional.ofNullable(T t):若t 不爲null,創建Optional 實例,否則創建空實例
  • isPresent() : 判斷是否包含值
  • orElse(T t) : 如果調用對象包含值,返回該值,否則返回t
  • orElseGet(Supplier s) :如果調用對象包含值,返回該值,否則返回s 獲取的值
  • map(Function f): 如果有值對其處理,並返回處理後的Optional,否則返回Optional.empty()
  • flatMap(Function mapper):與map 類似,要求返回值必須是Optional
public class OptinalTest1 {
    //flatMap
    @Test
    public void test6(){
//        Optional<Employee> op = Optional.ofNullable(new Employee("小周","北京",9000));
        Optional<Employee> op = Optional.ofNullable(null);
        //flatMap,返回的必須是Optional容器,進一步避免空指針異常
        Optional<Double> optional = op.flatMap(e -> Optional.of(e.getSalary()));
        System.out.println(optional.orElse(new Double(10000)));
    }

    //map
    @Test
    public void test5(){
//        Optional<Employee> op = Optional.ofNullable(new Employee("小周","北京",9000));
        Optional<Employee> op = Optional.ofNullable(null);
        //有值返回map的操作,沒值返回Optional.empty()
        Optional<Double> salary = op.map(Employee::getSalary);
        System.out.println(salary.get());//如果傳入爲空,此時會報錯
    }

    //ORelse
    //orElseGet
    @Test
    public void test4(){
        Optional<Object> op = Optional.ofNullable(null);
        if(op.isPresent()){
            System.out.println(op.get());
        }else{
            op.orElse(new Employee());//如果沒值,傳入默認的值

            Object o = op.orElseGet(Employee::new);//函數式接口,可以寫更多
            System.out.println(o);
        }
    }

    //ofnullable
    @Test
    public void test3(){
         Optional<Object> op = Optional.ofNullable(null);
        if(!op.isPresent()){
            System.out.println(op.get());//仍然會報錯,NoSUchELEMEnt.exception
        } else{
            System.out.println("No Value");
        }
    }

    //empty
    @Test
    public void test2(){
        Optional<Object> op = Optional.empty();
        System.out.println(op.get());//也會報錯,NoSuchElement.Exception
    }

    //of 構建
    @Test
    public void test1(){
        Optional<Employee> op = Optional.of(new Employee());
        Employee employee = op.get();
        System.out.println(employee);

        //Optional<Object> op2 = Optional.of(null);//直接傳null會發生空指針異常
//        Object o = op2.get();
//        System.out.println(o);
    }
}

7、接口中的默認方法和靜態方法

在接口中可以使用default和static關鍵字來修飾接口中定義的普通方法

public interface Interface {
    default  String getName(){
        return "小李";
    }

    static String getName2(){
        return "小李";
    }
}

在JDK1.8中很多接口會新增方法,爲了保證1.8向下兼容,1.7版本中的接口實現類不用每個都重新實現新添加的接口方法,引入了default默認實現。

static的用法是直接用接口名去調方法即可。當一個類繼承父類又實現接口時,若後兩者方法名相同,則優先繼承父類中的同名方法,即“類優先”,如果實現兩個同名方法的接口,則要求實現類必須手動聲明默認實現哪個接口中的方法。

8、Date API

Java 8 在包java.time下包含了一組全新的時間日期API。新的日期API都是不可變的,更使用於多線程的使用環境中。新的日期API和開源的Joda-Time庫差不多,但又不完全一樣,下面的例子展示了這組新API裏最重要的一些部分:

Clock 時鐘

Clock類提供了訪問當前日期和時間的方法,Clock是時區敏感的,可以用來取代 System.currentTimeMillis() 來獲取當前的微秒數。某一個特定的時間點也可以使用Instant類來表示,Instant類也可以用來創建老的java.util.Date對象。

  Clock clock = Clock.systemDefaultZone();
  long millis = clock.millis();
  System.out.println(millis);
  System.out.println("------------------------");
  Instant instant = clock.instant();
  Date legacyDate = Date.from(instant);
  System.out.println(legacyDate);

Timezones 時區

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

LocalDate 本地日期

LocalDate 表示了一個確切的日期,比如 2014-03-11。該對象值是不可變的,用起來和LocalTime基本一致。下面的例子展示瞭如何給Date對象加減天/月/年。另外要注意的是這些對象是不可變的,操作返回的總是一個新實例。

public static void localDateTest() {

       // 根據年月日取日期,6月寫成6,
        LocalDate oldDate = LocalDate.of(2018, 6, 1);
        System.out.println(oldDate); //2018-06-01

        // 根據字符串取:默認格式yyyy-MM-dd
        LocalDate yesteday = LocalDate.parse("2018-06-06");
        System.out.println(yesteday);  //2018-06-06

        // 如果不是閏年 傳入29號也會報錯
        LocalDate.parse("2019-02-29");
    }

LocalDate常用轉化

    /**
     * 常用的日期轉換
     */
    public static void localDateTransferTest(){
       //今天:2019-03-29
        LocalDate today = LocalDate.now();
        // 取本月第1天: 2019-03-01
        LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth());
        // 取本月第2天:2019-03-02
        LocalDate secondDayOfThisMonth = today.withDayOfMonth(2);
        // 取本月最後一天:2019-03-31
        LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth());
        // 取本月最後一天的下一天:2019-04-01
        LocalDate firstDay = lastDayOfThisMonth.plusDays(1);
        // 取2019年3月第一個週三:2019-03-06
        LocalDate thirdMondayOf2019 = LocalDate.parse("2019-03-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.WEDNESDAY));
    }

LocalTime 本地時間

LocalTime 定義了一個沒有時區信息的時間,例如 晚上10點,或者 17:30:15。下面的例子使用前面代碼創建的時區創建了兩個本地時間。之後比較時間並以小時和分鐘爲單位計算兩個時間的時間差:

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 提供了多種工廠方法來簡化對象的創建,包括解析時間字符串。

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

LocalDateTime 本地日期時間

LocalDateTime 同時表示了時間和日期,相當於前兩節內容合併到一個對象上了。LocalDateTime和LocalTime還有LocalDate一樣,都是不可變的。LocalDateTime提供了一些能訪問具體字段的方法。

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

只要附加上時區信息,就可以將其轉換爲一個時間點Instant對象,Instant時間點對象可以很容易的轉換爲老式的java.util.Date。

Instant instant = sylvester
        .atZone(ZoneId.systemDefault())
        .toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate);  

格式化LocalDateTime和格式化時間和日期一樣的,除了使用預定義好的格式外,我們也可以自己定義格式:

DateTimeFormatter formatter =
                DateTimeFormatter
                        .ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime parsed = LocalDateTime.parse("2019-03-29 17:30:00", formatter);
String string = formatter.format(parsed);
System.out.println(string); //2019-03-29 17:30:00

和java.text.NumberFormat不一樣的是新版的DateTimeFormatter是不可變的,所以它是線程安全的。

文章中使用的 Employee類、集合empList 的定義如下:

public class Employee {

    private String name;
    private String address;
    private double salary;
    private int age;

    public Employee() {
    }

    public Employee(double salary) {
        this.salary = salary;
    }

    public Employee(String name, String address, double salary, int age) {
        this.name = name;
        this.address = address;
        this.salary = salary;
        this.age = age;
    }

    public Employee(String name, String address, double salary) {
        this.name = name;
        this.address = address;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", salary=" + salary +
                ", age=" + age +
                '}';
    }
}
List<Employee> empList = Arrays.asList(
            new Employee("小王", "蘇州", 5000, 22),
            new Employee("小趙", "廣州", 4000, 23),
            new Employee("小張", "大理", 6000, 50),
            new Employee("小李", "杭州", 7000, 50),
            new Employee("小花", "成都", 1000, 8)
    );
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章