JDK1.8新特性

JDK1.8新特性


總的來說,JDK在以下方面具有新特性:
1. 速度更快 – 紅黑樹
2. 代碼更少 – Lambda
3. 強大的Stream API – Stream
4. 便於並行 – Parallel
5. 最大化減少空指針異常 – Optional


1、HashMap中的紅黑樹

HashMap中鏈長度大於8時採取紅黑樹的結構存儲。

紅黑樹,除了添加,效率高於鏈表結構。


2、ConcurrentHashMap

Jdk1.7時隔壁級別CocnurrentLevel(鎖分段機制)默認爲16。

JDK1.8採取了CAS算法

Jdk1.8沒有永久區,取而代之的是MetaSpace元空間,用的是物理內存。


3、Lambda表達式

1、Lambda表達式的基礎語法:Java8引入了一個新的操作符“->”,該操作符成爲箭頭操作符或者Lambda操作符,箭頭操作符將Lambda表達式拆分成兩部分

左側:Lambda表達式的參數列表
右側:Lambda表達式中所需執行的功能,即Lambda體。

語法格式一:無參數,無返回值

Runnable r2 = () -> System.out.println("hello lambda");
        r2.run();

語法格式二:有一個參數,並且無返回值
(x) -> System.out.print(x);

語法格式三:若只有一個參數,小括號可以省略不寫
x -> System.out.print(x);

語法格式四:有兩個以上的參數,有返回值,並且Lambda體中有多條語句

        Comparator<Integer> c1 = (x, y) -> {
            System.out.print(Integer.compare(x, y)+"函數式接口");
            return Integer.compare(x, y);
        }  ;
        c1.compare(1, 2);

語法格式五:若Lambda體中只有一條語句,return和大括號都可以省略不寫

Comparator<Integer> c1 = (x,y) -> Integer.compare(x,y);

語法格式六:Lambda表達式的參數列表的數據類型可以省略不寫,因爲JVM編譯器可以通過上下文進行類型推斷出數據類型,既“類型推斷”。
(Integer x,Integer y) -> Integre.compare(x,y);

總結:
左右遇一括號省,左側推斷類型省, 能省則省。


2、Lambda表達式的函數式接口


一些基本的使用示例

List<Employee> list = Arrays.asList(
            new Employee("張三", "上海", 5000, 22),
            new Employee("李四", "北京", 4000, 23),
            new Employee("c五", "日本", 6000, 50),
            new Employee("b七", "香港", 7000, 50),
            new Employee("趙六", "紐約", 1000, 8)
    );

    /**
     *需求1:lambda表達式的使用:
     * 調用COllections.sort方法,通過定製排序比較兩個Employee(先按年齡比較,年齡相同按姓名比),使用
     * Lambda作爲參數傳遞。
     */
    @Test
    public void test1(){
        Collections.sort(list,(x,y)->{
            if(x.getAge()!=y.getAge())
                return Integer.compare(x.getAge(),y.getAge());
            else
                return x.getName().compareTo(y.getName());

        });

        for (Employee employee : list) {
            System.out.println(employee);
        }
    }

    /**
     * 需求2:
     * 1.聲明函數式接口,接口中聲明抽象方法,public String getvalue(String str();
     * 2.聲明類TestLambda,類中編寫方法使用接口作爲參數,講一個字符串轉換成大寫,並作爲方法的返回值。
     */
    @Test
    public void test2(){
        String str = getvalue("hello world", x -> x.toUpperCase());
        System.out.print(str);

    }
    public String getvalue(String str,MyFunction1 my){
        return my.getValue(str);
    }

    @FunctionalInterface
    public interface MyFunction1{
        public String getValue(String str);
    }


    /**
     * 需求3:
     * 1.聲明一個帶兩個泛型的函數式接口,泛型類型是<T,R>,T爲參數,R爲返回值。
     * 2.接口中聲明對應抽象方法
     * 3.在TestLambda類中聲明方法,使用接口作爲參數,計算兩個long型參數的和
     * 4.在計算兩個long型參數的乘積
     */
    @Test
    public void test3(){
        Long r = getR(25l,30l, (t1,t2) -> t1 * t2);
        System.out.print(r);

        Long r1 = getR(25l, 23l, (t1, t2) -> t1 + t2);
        System.out.print(r1);

    }
    public <T,R> R getR(T t1,T t2,MyFUnction2<T,R> mf){
        return mf.method(t1,t2);
    }

    public interface MyFUnction2<T,R>{
        public R method(T t1,T t2);
    }

Java8中的四大核心函數式接口

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

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

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

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

Lambda表達式的
一、方法引用
若Lambda體中的內容有方法已經實現了,我們可以使用“方法引用”(可以理解爲方法引用是Lambda表達式的另外一種表現形式)

主要語法:

對象::實例方法名

類::靜態方法名

類::實例方法名

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


二、構造器引用

格式:

ClassName::new

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

三、數組引用

格式:

Type[]::new


4、Stream流

一系列流水線式的中間操作。

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

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


操作的三個步驟:
1、創建一個流Stream
2、中間操作
3、終止操作


創建:

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

        //2. 通過Arrays中的靜態方法stream()獲取數組流
        Employee[] emps = new Employee[10];
        Stream<Employee> stream1 = Arrays.stream(emps);

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

        //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);
    }

中間操作:API

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

篩選和切片

   /**
    * 篩選和切片
    *
    * filter -- 接受Lambda,從流中排除某些元素
    * limit -- 截斷流,使其元素不超過某個給定數量
    * skip -- 跳過元素,返回一個扔掉了前n個元素的流,若流中元素不足n個,則返回一個空流,與limit互補。
    * distinct -- 去重,通過hashcode和equals去重。
    */

   List<Employee> list = Arrays.asList(
           new Employee("張三", "上海", 5000, 22),
           new Employee("李四", "北京", 4000, 23),
           new Employee("c五", "日本", 6000, 50),
           new Employee("b七", "香港", 7000, 50),
           new Employee("趙六", "紐約", 1000, 8)
   );

   @Test
   public void test1(){
       Stream<Employee> stream = list.stream();//創建流

       stream
               .filter(e -> e.getAge()>25)//過濾符合條件的流元素
               .limit(5)//只取5個
               .skip(4)//跳過4個
               .distinct()//去重,需重寫hashcode和equals方法
               .forEach(System.out::println);//終止操作,獲取流

   }

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

public class StreamTest3 {
    //中間操作

    /**
     * 映射
     * map -- 接受Lambda,將元素轉換成其他形式或提取信息,接受一個函數作爲參數,
     * 該函數會被應用到每個元素上,並將其映射成一個新的元素。
     *
     * flatmap -- 接受一個函數做爲參數,將流中的每個值都轉換成另一個流,然後將所有流連接成一個流,
     */

    List<Employee> emps = Arrays.asList(
            new Employee("張三", "上海", 5000, 22),
            new Employee("李四", "北京", 4000, 23),
            new Employee("c五", "日本", 6000, 50),
            new Employee("b七", "香港", 7000, 50),
            new Employee("趙六", "紐約", 1000, 8)
    );
    @Test
    public void test2(){
        emps
                .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)//大流中直接包含的是流元素,相當於add和addAll的區別。
                .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();
    }
}

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("------------------------------");

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

    }








    List<Employee> emps = Arrays.asList(
            new Employee("張三", "上海", 5000, 22),
            new Employee("李四", "北京", 4000, 23),
            new Employee("c五", "日本", 6000, 50),
            new Employee("b七", "香港", 6000, 50),
            new Employee("趙六", "紐約", 1000, 8)
    );

}

Stream的終止操作
- allMatch – 檢查是否匹配所有元素
- anyMatch – 檢查是否至少匹配一個元素
- noneMatch – 檢查是否沒有匹配所有元素
- findFirst – 返回第一個元素
- count – 返回流中元素的總個數
- max – 返回流中最大值
- min – 返回流中最小值


  @Test
  public void test1(){
      boolean b = emps.stream()
              .allMatch(emp -> emp.getStatus().equals(Status.FREE));
      System.out.println(b);

      boolean b1 = emps.stream().
              anyMatch(emp -> emp.getStatus().equals(Status.BUSY));
      System.out.println(b1);

      boolean b2 = emps.stream()
              .noneMatch(emp -> emp.getSalary() > 16000);
      System.out.println(b2);

      Optional<Employee> any = emps.parallelStream()//並行流
              .findAny();
      System.out.println(any.get());


  }

  List<Employee> emps = Arrays.asList(
          new Employee("張三",9000.00,25,Status.FREE),
          new Employee("李四",16000.00,22,Status.BUSY),
          new Employee("王五",15000.00,22,Status.FREE),
          new Employee("趙六",12000.00,23,Status.VOCATION),
          new Employee("孫七",8000.00,26,Status.FREE),
          new Employee("唐八",19000.00,24,Status.BUSY)
  );

終止操作:歸約reduce

map和reduce的連接通常稱爲map-reduce模式,因google用它進行網絡搜索而出名


/**
 * 終止操作:
 *
 * 歸約
 * 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 = emps.stream()
            .map(Employee::getSalary)//現將salsry映射出來
            .reduce(Double::sum);//進行歸約求和
    System.out.println(reduce1.get());
}

終止操作:收集Collect(很強大)
將流轉換成其他格式,接受一個Collector接口的實現,用於給Stream中元素做彙總的操作。

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

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

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

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

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

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

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

//分組
@Test
public void test4(){
    //單級分組
    Map<Status, List<Employee>> collect = emps.stream().collect(Collectors.groupingBy(Employee::getStatus));
    System.out.println(collect);
    System.out.println("----------------------");

    //多級分組
    Map<Status, Map<String, List<Employee>>> collect1 = emps.stream().collect(Collectors.groupingBy(Employee::getStatus, 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 = emps.stream().collect(Collectors.partitioningBy(e -> e.getSalary() > 8000));
    System.out.println(collect2);
    System.out.println("-----------------------");

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

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

}

一些練習:

@Test
public void test1(){
    /**
     * 需求1:
     *給定一個數字,如何返回一個有每個數的平方構成的列表?
     * 給定【1,2,3,4,5】,返回【1,4,9,16,25】
     */
    List<Integer> list = Arrays.asList(1,2,3,4,5);
    List<Integer> collect = list.stream().map(integer -> integer * integer).collect((Collectors.toList()));
    System.out.println(collect);

    /**
     * 需求2:
     * 用reduce和map數一數流中的元素個數
     */
    Optional<Integer> reduce = list.stream()
            .map(e -> 1)//巧妙之處
            .reduce(Integer::sum);
    System.out.println(reduce);
}

5、並行流

Fork/Join框架:
在必要的情況下,將一個大任務進行必要的拆分Fork成若干個小任務,再將小任務的運算結果進行Join彙總。

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

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

並行流就是把一個內容分成多個數據塊,並用不同的線程分別處理每個數據塊的流。
Java 8 中將並行進行了優化,我們可以很容易的對數據進行並行操作。Stream API 可以聲明性地通過parallel() 與sequential() 在並行流與順序流之間進行切換。

/**
 * 方式1:
 * 自己實現的ForkJoin
 */
@Test
public void test1(){
    Instant start = Instant.now();
    ForkJoinPool pool = new ForkJoinPool();
    ForkJoinTask<Long> task = new ForkJoinCalculate(0, 100000000L);

    Long sum = pool.invoke(task);
    Instant end = Instant.now();

    System.out.println(Duration.between(start,end).toMillis());
}
public class ForkJoinCalculate extends RecursiveTask<Long> {

    private long start;
    private long end;

    private static final long THRESHHOLD = 10000;

    public ForkJoinCalculate(long start, long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        long length = end - start;

        if(length <= THRESHHOLD){
            long sum = 0;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        }else{
            long middle = (start + end) / 2;

            ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
            left.fork();

            ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);
            right.fork();

            return left.join()+right.join();
        }
    }
}


/**
 * 方式2:
 * 直接使用for循環
 */
@Test
public void test2(){
    Instant start = Instant.now();

    Long sum = 0L;
    for (int i = 0; i <= 100000000L; i++) {
        sum += i;
    }


    Instant end = Instant.now();
    System.out.println(Duration.between(start,end).toMillis());

}

/**
 * 方式3:
 * JDK8的並行流實現。
 */
@Test
public void test3(){
    Instant start = Instant.now();

    long sum = LongStream.rangeClosed(0, 1000000000L)
            .parallel()//並行流
            //.sequential()//串行流
            .reduce(0, Long::sum);

    Instant end = Instant.now();

    System.out.println(Duration.between(start,end).toMillis());
}
//並行流將會充分使用多核的優勢,多線程並行執行,基數越大,效果越明顯。其底層還是Fork/Join框架。只不過SUN公司優化的更好,比自己實現更高效

6、Optional類

Optional<T> 類(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 {
    /**
     * 常用方法:
     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
     */

    //flatMap
    @Test
    public void test6(){
//        Optional<Employee> op = Optional.ofNullable(new Employee("jim","shanghai",9000));
        Optional<Employee> op = Optional.ofNullable(null);
        //flatMap,返回的必須是Optional容器,進一步避免空指針異常
        Optional<Integer> optional = op.flatMap(e -> Optional.of(e.getSalary()));
        System.out.println(optional.orElse(10000));
    }

    //map
    @Test
    public void test5(){
//        Optional<Employee> op = Optional.ofNullable(new Employee("jim","shanghai",9000));
        Optional<Employee> op = Optional.ofNullable(null);


        //有值返回map的操作,沒值返回Optional.empty()
        Optional<Integer> 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);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章