JAVA 1.8 新特性 Lamdba

JavaClub發表於2022-01-17

Java8 優勢:速度快、程式碼更少(增加了新的語法 Lambda 表示式)、強大的 Stream API、便於並行、最大化減少空指標異常 Optional;

一、Lambda 表示式

Lambda 是一個匿名函式,我們可以把 Lambda 表示式理解為是一段可以傳遞的程式碼(將程式碼像資料一樣進行傳遞)。可以取代大部分的匿名內部類,可以寫出更簡潔、更靈活的程式碼。尤其在集合的遍歷和其他集合操作中,可以極大地優化程式碼結構。作為一種更緊湊的程式碼風格,使 Java 的語言表達能力得到提升。JDK 也提供了大量的內建函式式介面供我們使用,使得 Lambda 表示式的運用更加方便、高效。
【1】從匿名類到 Lambda 的轉換:雖然使用 Lambda 表示式可以對某些介面進行簡單的實現,但並不是所有的介面都可以使用 Lambda 表示式來實現。Lambda 規定介面中只能有一個需要被實現的方法,不是規定介面中只能有一個方法。

jdk8 中有另一個新特性:default, 被 default 修飾的方法會有預設實現,不是必須被實現的方法,所以不影響 Lambda 表示式的使用。後續有專門的介紹。

//匿名類
Runnable runnable1 = new Runnable() {
    @Override
    public void run() {
        System.out.printf("Hello World!");
    }
};
/**
 *1.簡化引數型別,可以不寫引數型別,但是必須所有引數都不寫
 *2.簡化引數小括號,如果只有一個引數則可以省略引數小括號
 *3.簡化方法體大括號,如果方法條只有一條語句,則可以勝率方法體大括號(如下案例)
 *4.如果方法體只有一條語句,並且是 return 語句,則可以省略方法體大括號和rerun關鍵字:X x= a -> a+3;
 *Lambda 表示式展示:
 */
Runnable runnable2 = ()-> System.out.printf("Lambda 表示式");

【2】原來使用匿名內部類作為引數傳遞到 Lambda 表示式

//原來使用匿名內部類作為引數傳遞
TreeSet ts = new TreeSet<>(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return Integer.compare(o1.length(),o2.length());
    }
});
//Lambda 表示式作為引數傳遞
TreeSet<String> ts2 = new TreeSet<>((o1,o2)-> Integer.compare(o1.length(),o2.length()));

【3】Lambda 表示式語法:Lambda 表示式在 Java 語言中引入了一個新的語法元素和操作符。這個操作符為 “->” ,該操作符被稱為 Lambda 操作符或剪頭操作符。它將 Lambda 分為兩個部分:

■ 左側:指定了 Lambda 表示式需要的所有引數;
■ 右側:指定了 Lambda 體,即 Lambda 表示式要執行的功能;

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

Runnable runnable2 = ()-> System.out.printf("Lambda 表示式"); 

語法格式二Lambda 需要一個引數;

Consumer<String> fun = (args) -> System.out.printf(args);

語法格式三Lambda 只需要一個引數時,引數的小括號可以省略;

Consumer<String> fun = args -> System.out.printf(args);

語法格式四Lambda 需要兩個引數,並且有返回值;

BinaryOperator<Long> bo = (x,y)->{    System.out.printf("實現函式介面方法");    return x+y;};

語法格式五當 Lambda 體只有一條語句時,return 與大括號可以省略;

BinaryOperator<Long> bo = (x,y) -> x+y;

語法格式六資料型別可以省略,因為可由編譯器推斷得出,稱為“型別推斷”:根據上下文環境推斷引數型別;

BinaryOperator<Long> bo = (Long x,Long y)->{    
    System.out.printf("實現函式介面方法");    
    return x+y;
};

【4】遍歷集合: 可以呼叫集合的 forEach(Consumer<? super E> action) 方法,通過 lambda 表示式的方式遍歷集合中的元素。Consumer 介面是 jdk 為我們提供的一個函式式介面。

ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1,2,3,4,5);
//lambda表示式 方法引用
list.forEach(System.out::println);
list.forEach(element -> {
    if (element % 2 == 0) {
      System.out.println(element);
    }
});

【5】刪除集合:通過 removeIf(Predicate<? super E> filter) 方法來刪除集合中的某個元素,Predicate 也是 jdk 為我們提供的一個函式式介面,可以簡化程式的編寫。

ArrayList<Item> items = new ArrayList<>();
Collections.addAll(list, 1,2,3,4,5);
items.removeIf(ele -> ele.getId() == 3);

【6】集合內元素的排序:若要為集合內的元素排序,就必須呼叫 sort 方法,傳入比較器匿名內部類重寫 compare 方法,我們現在可以使用 lambda 表示式來簡化程式碼。

ArrayList<Item> list= new ArrayList<>();
Collections.addAll(list, 6,27,7,4,2);
list.sort((o1,o2) -> o1.getId() - o2.getId());

二、函式式介面


【1】只包含一個抽象方法的介面,稱為函式式介面;
【2】你可以通過 Lambda 表示式來建立該介面的物件。(若 Lambda 表示式丟擲一個受檢異常,那麼該異常需要在目標介面的抽象方法上進行宣告)。
【3】我們可以在任意函式式介面上使用 @FunctionalInterface 註解,這樣做可以檢查它是否是一個函式式介面,說明這個介面是一個函式式介面。

@FunctionalInterface
public interface MyLambda<T> {
    public T getValue(T t);
}
public String toUpperString(MyLambda<String> mf, String str){
    return mf.getValue(str);
}
//為了將 Lambda 表示式作為引數傳遞,接收 Lambda 表示式的引數型別必須是與 Lambda 表示式相容的函式式介面的型別
String newStr = toUpperString((str)-> str.toUpperCase(),"abcde");

【4】Java 內建四大核心函式式介面

函式式介面引數型別返回型別用途
Consumer<T> 消費型介面Tvoid對型別為T的物件應用操作,包含方法:void accept(T t);
Supplier<T> 供給型介面T返回型別為 T 物件,包含方法:T get();
Function<T, R> 函式型介面TR對型別為 T 的物件應用操作,並返回結果。結果時 R 型別的物件。包含方法:R apply(T t)
Predicate<T> 斷定型介面Tboolean確定型別為 T 的物件是否滿足某約束,並返回 Boolean 值,包含方法 Boolean test(T t)
public class Java8Tester {
   public static void main(String args[]){
      List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
      // Predicate<Integer> predicate1 = n -> n%2 == 0
      // n 是一個引數傳遞到 Predicate 介面的 test 方法
      // 如果 n%2 為 0 test 方法返回 true
      System.out.println("輸出所有偶數:");
      eval(list, n-> n%2 == 0 );
   }
   public static void eval(List<Integer> list, Predicate<Integer> predicate) {
      for(Integer n: list) {
         if(predicate.test(n)) {
            System.out.println(n + " ");
         }
      }
   }
}

三、方法引用與構造器引用


【1】方法引用當要傳遞給 Lambda 體的操作,已經有實現的方法了,可以使用方法引用!(實現抽象方法的引數列表,必須與方法引用中方法的引數列表保持一致!)方法引用:使用操作符 “::**” 將方法名和物件或類的名字分隔開來。如下三種主要使用情況:使用方法引用的時候需要保證引用方法的引數列表和返回值型別與我們當前所要實現的函式式介面方法的引數列表和返回值型別保持一致
①、物件::例項方法;②、類::靜態方法;③、類::例項方法**;

(x) -> System.out.println(x);
//等同於
System.out::println
BinaryOperator<Double> bo = (x,y) -> Math.pow(x,y);
//等同於
BinaryOperator<Double> bo = Math::pow;
compare((x,y) -> x.equals(y),"abcd","abcd");
//等同於
compare(String::equals,"abc","abc");(x) -> System.out.println(x);//等同於System.out::printlnBinaryOperator<Double> bo = (x,y) -> Math.pow(x,y);//等同於BinaryOperator<Double> bo = Math::pow;compare((x,y) -> x.equals(y),"abcd","abcd");//等同於compare(String::equals,"abc","abc");

注意:當需要引用方法的第一個引數是呼叫物件,並且第二個引數是需要引用方法的第二個引數(或無引數)時:

ClassName::methodName

【2】構造器引用: 格式: ClassName::new 與函式式介面相結合,自動與函式式介面中方法相容。可以把構造器引用賦值給定義的方法,與構造器引數列表要與介面中抽象方法的引數列表一致!呼叫哪個構造器取決於函式式介面中的方法形參的定義。我們通過兩種方式建立物件(無參和有參),具體如下:

//【案例一】無參構造器建立物件  使用Employee的無參構造器.
//使用 Supplier函式介面  因為 Supplier介面的抽象方法沒有入參 可以參考二中的函數語言程式設計.
Supplier<Employee> sup = Employee::new;
//【案例二】有參構造器建立物件  使用Employee的有參構造器.
//使用 Function函式介面  因為 Function介面的抽象方法有入參 可以參考二中的函數語言程式設計.
Function<Integer,Employee> fun = (n) -> new MyClass(n);
//構造器應用改造後:
Function<Integer,Employee> fun = MyClass::new;

【3】陣列引用: 同構造器引用,格式為 type[] :: new

Function<Integer,Integer[]> fun = (n) -> new Integer[n];
Function<Integer,Integer[]> fun = Integer[]::new;

四、Stream API


Java8 中有兩大最為重要的改變。第一個是 Lambda 表示式;另外一個則是 Stream API(java.util.stream.)。Stream 是 Java8 中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查詢、過濾和對映資料等操作。使用Stream API 對集合資料進行操作,就類似於使用 SQL 執行的資料庫查詢。也可以使用 Stream API 來並行執行操作。簡而言之,Stream API 提供了一種高效且易於使用的處理資料的方式。
【1】Stream 到底是什麼:**是資料渠道,用於運算元據源(集合、陣列等)所生成的元素序列。集合講的是資料,流講的是計算!
【2】注意:①、Stream 自己不會儲存資料。②、Stream 不會改變源物件。相反,他們會返回一個持有結果的新 Stream。③、Stream 操作是延遲執行的。這意味著他們會等到需要結果的時候才執行。
【3】**Stream 的操作三個步驟:①、建立 Stream 一個資料來源(如:集合、陣列),獲取一個流。 ②、中間操作:一箇中間操作鏈,對資料來源的資料進行處理。③、終止操作(終端操作):一個終止操作,執行中間操作鏈,併產生結果:
Java8 新特性

default Stream<E> stream()//返回一個順序流;
default Stream<E> parallelStream()//返回一個並行流;
//舉例
Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96};
numbers.stream().forEach(System.out::println);
});

【5】由陣列建立流:Java8 中的 Arrays 的靜態方法 stream() 可以獲取陣列流:static <T> Stream<T> stream(T[] array):返回一個流;過載形式,能夠處理對應基本型別的陣列:

public static IntStream stream(int[] array) ;
public static LongStream stream(long[] array) ;
public static DoubleStream stream(double[] array);
//舉例
Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96};
long count = Arrays.stream(array).filter(i->i>20).count();

【6】由值建立流:可以使用靜態方法 Stream.of(),通過顯示值建立一個流。它可以接收任意數量的引數。

public static<T> Stream<T> of(T... values) : 返回一個流
//舉例
Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96};
long count = Stream.of(array).filter(i->i>20).count();
long sum = Stream.of(12,77,59,3,654).filter(i->i>20).mapToInt(Integer::intValue).sum();

【7】由函式建立流:建立無限流,可以使用靜態方法 Stream.iterate() 和 Stream.generate(),建立無限流。

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f); //迭代
public static<T> Stream<T> generate(Supplier<T> s); //生成
//舉例 iterate
Stream<BigInteger> bigIntStream = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.TEN)).limit(10);
BigInteger[] bigIntArr = bigIntStream.toArray(BigInteger[]::new);
//輸出:[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
System.out.println(Arrays.toString(bigIntArr));
//舉例 generate
Stream<String> stream = Stream.generate(() -> "test").limit(10);
String[] strArr = stream.toArray(String[]::new);
//輸出 [test, test, test, test, test, test, test, test, test, test]
System.out.println(Arrays.toString(strArr));
Stream 的中間操作:多箇中間操作可以連線起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理!而在終止操作時一次性全部處理,稱為“惰性求值”。

【1】篩選與切片

方法描述
filter(Predicate p)接收 Lambda , 從流中排除某些元素。
distinct()篩選,通過流所生成元素的 hashCode() 和 equals() 去除重複元素。
limit(long maxSize)截斷流,使其元素不超過給定數量。
skip(long n)跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補。
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
for(Integer i:numbers){
    if(i>20){
        count++;
    }
}
//當使用 Stream 操作時  如下:
list.stream().filter(num -> num > 4).count();
//limit 使用:輸出 [1, 2, 3, 4] 方法會返回一個包含n個元素的新的流(若總長小於n則返回原始流)
List<Integer> afterLimit = list .stream().limit(4).collect(Collectors.toList());
//skip(n) 使用:輸出 [5, 6, 7, 8, 9] 與limit 正好相反,它會丟棄掉前面的n個元素。
List<Integer> afterSkip = list .stream().skip(4).collect(Collectors.toList());
//用limit和skip方法一起使用就可以實現日常的分頁功能
List<Integer> pageList = myList.stream().skip(pageNumber*pageSize)
                  .limit(pageSize).collect(Collectors.toList());

【2】 對映

方法描述
map(Function f)接收一個函式作為引數,該函式會被應用到每個元素上,並將其對映成一個新的元素。
mapToDouble(ToDoubleFunction f)接收一個函式作為引數,該函式會被應用到每個元素上,產生一個新的 DoubleStream。
mapToInt(ToIntFunction f)接收一個函式作為引數,該函式會被應用到每個元素上,產生一個新的 IntStream。
mapToLong(ToLongFunction f)接收一個函式作為引數,該函式會被應用到每個元素上,產生一個新的 LongStream。
flatMap(Function f)接收一個函式作為引數,將流中的每個值都換成另一個流,然後把所有流連線成一個流。
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
//將Integer型別轉換成String型別
List<String> afterString = integerList.stream().map(i->String.valueOf(i)).collect(Collectors.toList());

【3】 排序:上面介紹的流的轉換方法都是無狀態的。即從一個已經轉換的流中取某個元素時,結果並不依賴於之前的元素。除此之外還有兩個方法在轉換流時是需要依賴於之前流中的元素的。一個是 distinct方法一個是 sorted方法。distinct方法會根據原始流中的元素返回一個具有相同順序、去除了重複元素的流,這個操作顯然是需要記住之前讀取的元素。

方法描述
sorted()產生一個新流,其中按自然排序排序。
sorted(Comparator comp)產生一個新流,其中按比較器順序排序。
//distinct
List<Integer> list = Arrays.asList(70, 25, 38, 64, 25, 46, 7, 18, 9);
List<Integer> distinctList = list.stream().distinct().collect(Collectors.toList());
//sorted 
List<Integer> sortList = myTestList.stream()
                .sorted(Integer::compareTo).collect(Collectors.toList());

Stream 的聚合操作:終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
【1】查詢與匹配

方法描述
allMatch(Predicate p)檢查是否匹配所有元素
anyMatch(Predicate p)檢查是否至少匹配一個元素
noneMatch(Predicate p)檢查是否沒有匹配所有元素
findFirst()返回第一個元素終端操作會從流的流水線生成結果。其結果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
findAny()返回當前流中的任意元素
count()返回流中元素總數
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)內部迭代(使用 Collection 介面需要使用者去做迭代,稱為外部迭代。相反,Stream API 使用內部迭代——它幫你把迭代做了)
List<Integer> list = Arrays.asList(70, 125, 38, 64, 25, 46, 7, 18, 9);
//max 與 min 案例
Integer maxItem = list.stream().max(Integer::compareTo).get();
Integer minItem = list.stream().min(Integer::compareTo).get();
//findFirst:方法返回非空集合中的第一個值,它通常與filter方法結合起來使用。
Integer first = list.stream().filter(i->i>100).findFirst().get();
//findAny:可以在集合中只要找到任何一個所匹配的元素,就返回,此方法在對流並行執行時十分有效(任何片段中發現第一個匹配元素都會結束計算,序列流中和findFirst返回一樣)。
Integer anyItem = list.parallelStream().filter(i->i>100).findAny().get();
//anyMatch:可以判定集合中是否還有匹配的元素。返回結果是一個boolean型別值。noneMatch 相反。
boolean isHas = list.parallelStream().anyMatch(i->i>100);
boolean noHas = hearList.parallelStream().noneMatch(i->i>100);

【2】 歸約

reduce(T iden, BinaryOperator b)可以將流中元素反覆結合起來,得到一個值。返回 T
reduce(BinaryOperator b)可以將流中元素反覆結合起來,得到一個值。返回 Optional<T>
List<Integer> list = Arrays.asList(70, 125, 38, 64, 25, 46, 7, 18, 9);
//簡化一下,對元素長度進行求和。
sum = list.stream().map(Objects::toString).mapToInt(String::length).sum();

備註:map 和 reduce 的連線通常稱為 map-reduce 模式,因 Google 用它來進行網路搜尋而出名。

【3】收集:當處理完流之後,通常是想檢視一下結果,而不是將他們聚合為一個值。Collectorts 類為我們提供了常用的收集類的各個工廠方法。

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

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

方法返回型別作用
toListList<T>把流中元素收集到List
List<Employee> emps= list.stream().collect(Collectors.toList());
toSetSet<T>把流中元素收集到Set
Set<Employee> emps= list.stream().collect(Collectors.toSet());
toCollectionCollection<T>把流中元素收集到建立的集合
Collection<Employee>emps=list.stream().collect(Collectors.toCollection(ArrayList::new));
countingLong計算流中元素的個數
long count = list.stream().collect(Collectors.counting());
summingIntInteger對流中元素的整數屬性求和
inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingIntDouble計算流中元素Integer屬性的平均值
doubleavg= list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingIntIntSummaryStatistics收集流中Integer屬性的統計值。
IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joiningString連線流中每個字串
String str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxByOptional<T>根據比較器選擇最大值
Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minByOptional<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));
groupingByMap<K, List<T>>根據某屬性值對流分組,屬性為K,結果為V
Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus));
partitioningByMap<Boolean, List<T>>根據 true或 false進行分割槽
Map<Boolean,List<Emp>>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage));
//例如前面的例子用的要將一個流收集到一個List中,只需要這樣寫就可以。
List<Integer> thereList = hereList.stream().collect(Collectors.toList());
//收集到Set中可以這樣用
Set<Integer> thereSet = hereList.stream().collect(Collectors.toSet());
//將字流中的字串連線並收集起來。
String resultString = stringList.stream().collect(Collectors.joining());
//在將流中的字串連線並收集起來時,想在元素中介新增分隔符,傳遞個joining方法即可。
String resultString = stringList.stream().collect(Collectors.joining(","));
//總和、平均值,最大值,最小值
int sum = hereList.stream().collect(Collectors.summingInt(Integer::intValue));
Double ave = hereList.stream().collect(Collectors.averagingInt(Integer::intValue));
Integer max = hereList.stream().collect(Collectors.maxBy(Integer::compare)).get();
Integer min = hereList.stream().collect(Collectors.minBy(Integer::compare)).get();

常見面試題】:將結果集收集到 Map;當我們希望將集合中的元素收集到 Map中時,可以使用 Collectors.toMap方法。這個方法有兩個引數,用來生成 Map的 key和 value。例如將一個 Form 物件的 high作為鍵 width作為值:每個 toMap方法,都會有一個對應的 toConCurrentMap方法,用來生成一個併發Map。

//準備工作
List<Form> formList = Lists.newArrayList(
    new Form (3,4,5),
    new Form (6,5,3),
    new Form (77,43,55);
)
//Form物件的 high作為鍵 width作為值
Map<Integer,Integer> hwMap = formList.stream().collect(Collectors.toMap(Form::getHigh, Form::getWidth));
//但是通常還是以具體元素作為值的情況多,可以使用Function.identity()來獲取實際元素。
Map<Integer,Form> FormMap = formList.stream().collect(Collectors.toMap(Form::getHigh, Function.identity()));
//如果多個元素擁有相同的鍵,在收集結果時會丟擲java.lang.IllegalStateException異常。
//可以使用第三個引數來解決,第三個引數用來確定當出現鍵衝突時,該如何處理結果,如果當出現鍵衝突時只保留一個並且是保留已經存在的值時,就是如下方式。
Map<Integer,Form> rMap = formList.stream().collect(Collectors.toMap(Form::getHigh, Function.identity(),(nowValue,newValue)->nowValue));
//如果想指定生成的 Map型別,則還需要第三個引數。
TreeMap<Integer,Form> FormTreeMap = FormList.stream().collect(Collectors.toMap(Form::getHigh,
            Function.identity(),(nowValue,newValue)->newValue,TreeMap::new));
//******在一個集合中,對具有相同特性的值進行分組是一個很常見的功能,在Stream的API中也提供了相應的方法。
//根據 high 將 Form 進行分組
Map<Integer,List<Form>> groupMap = list .stream().collect(Collectors.groupingBy(Form::getHigh));
//當分類函式是一個返回布林值的函式時,流元素會被分為兩組列表:一組是返回true的元素集合,另一組是返回false的元素集合。這種情況適用 partitoningBy方法會比groupingBy更有效率。
Map<Boolean,List<Form>> partitionMap = list.stream().collect(Collectors.partitioningBy(form->form.getHigh()==22));
//例如我們將房間集合分為兩組,一組是高度為22的房間,另一組是其他房間。
//mapping方法會將結果應用到另一個收集器上。如下取出分組中寬度最大的寬度。
Map<Integer, Optional<Integer>> collect = list.stream().collect(Collectors.groupingBy(Form::getHigh,
                Collectors.mapping(Form::getWidth,Collectors.maxBy(Comparator.comparing(Integer::valueOf)))));
//*****groupingBy是支援多級分組的。例如第一級我們將按照高度分組,第二級按照寬度分組。
Map<Integer,Map<Integer,List<Form>>> multistageMap = list.stream().
        collect(Collectors.groupingBy(Form::getHigh,Collectors.groupingBy(Form::getWidth)));

並行流與序列流:並行流就是把一個內容分成多個資料塊,並用不同的執行緒分別處理每個資料塊的流。Java8 中將並行進行了優化,我們可以很容易的對資料進行並行操作。Stream API 可以宣告性地通過 parallel() 與 sequential() 在並行流與順序流之間進行切換。但是並行流在使用的時候也是需要注意的。首先,必須是一個並行流,只要在終止方法執行時,流處於並行模式,那麼所有的流操作就都會並行執行。

Stream.of(roomList).parallel();

parallel 方法可以將任意的序列流轉換為一個並行流。其次要確保傳遞給並行流操作的函式是執行緒安全的。

//下面這個例子中的程式碼就是錯誤的,傳遞給並行流的操作並不是執行緒安全的。可以改為AtomicInteger的物件陣列來作為計數器。
int[] words = new int[23];
Stream.of(roomList).parallel().forEach(s->{
     if(s.size()<10){
           words[s.size()]++;
     }
});

我們使在處理集合資料量較大的時候才能體現出並行流的優勢,並且目的是為了在保證執行緒安全的情況下,提升效率,利用多核 CPU的資源。使用 Stream 的 API時,在遍歷或處理流的過程中當引用外部變數的時候會預設的將變數當成 fianl變數來處理。所以有些同學就會覺得在遍歷的過程中取不出來集合的索引。其實可以換一種思想可以只遍歷集合索引,然後在遍歷中取值。

IntStream.range(0,roomList.size()).forEach(i->{       System.out.println(roomList.get(i));});

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

Fork/Join 框架與傳統執行緒池的區別:採用 “工作竊取”模式(work-stealing):當執行新的任務時它可以將其拆分分成更小的任務執行,並將小任務加到執行緒佇列中,然後再從一個隨機執行緒的佇列中偷一個並把它放在自己的佇列中。相對於一般的執行緒池實現,fork/join 框架的優勢體現在對其中包含的任務的處理方式上.在一般的執行緒池中,如果一個執行緒正在執行的任務由於某些原因
無法繼續執行,那麼該執行緒會處於等待狀態。而在 fork/join 框架實現中,如果某個子問題由於等待另外一個子問題的完成而無法繼續執行。那麼處理該子問題的執行緒會主動尋找其他尚未執行的子問題來執行。這種方式減少了執行緒的等待時間,提高了效能。

五、介面中的預設方法與靜態方法


Java8 中允許介面中包含具有具體實現的方法,該方法稱為“預設方法”,預設方法使用 default 關鍵字修飾。例如:

interface MyFunc<T>{
    T func(int a);
    default String getName(){
        return "Hello Java8!";
    }
}

介面預設方法的 ”類優先” 原則:若一個介面中定義了一個預設方法,而另外一個父類或介面中又定義了一個同名的方法時。
【1】選擇父類中的方法。如果一個父類提供了具體的實現,那麼介面中具有相同名稱和引數的預設方法會被忽略。
【2】介面衝突。如果一個父介面提供一個預設方法,而另一個介面也提供了一個具有相同名稱和引數列表的方法(不管方法
是否是預設方法),那麼必須覆蓋該方法來解決衝突 。
【介面預設方法的”類優先”原則】

interface MyFunc{
    default String getName(){
        return "Hello Java8!";
    }
}
interface Named(){
    default String getName(){
        return "Hello atguigu!";
    }
}
class MyClass implements MyFunc,Named{
    public String getName(){
        return Named.super.getName();
    }
}

【介面中的靜態方法】:Java8 中,介面中允許新增靜態方法;

interface Named{
    public Integer myFunc();
    default String getName(){
        return "Hello atguigu!";
    }
    static void show(){
        System.out.println("Hello Lambda!");
    }
}

六、新時間日期 API


【1】LocalDate、LocalTime、LocalDateTime:的例項是不可變的物件,分別表示使用 ISO-8601 日期系統的日期、時間、日期和時間。它提供了簡單的日期或時間,並不包含當前的時間資訊。也不包含與時區相關的資訊。

方法描述示例
now()靜態方法,根據當前時間建立物件LocalDate localDate = LocalDate.now();LocalTime localTime = LocalTime.now();LocalDateTime localDateTime = LocalDateTime.now()
of()靜態方法,根據指定日期/時間建立物件LocalDate localDate = LocalDate.of(2016, 10, 26); LocalTime localTime = LocalTime.of(02, 22, 56); LocalDateTime localDateTime = LocalDateTime.of(2016, 10, 26, 12, 10, 55);
plusDays,plusWeeks, plusMonths, plusYears向當前LocalDate 物件新增幾天、幾周、幾個月、幾年
minusDays, minusWeeks,minusMonths, minusYears從當前 LocalDate 物件減去幾天、幾周、幾個月、幾年
plus, minus新增或減少一個 Duration 或 Period
withDayOfMonth, withDayOfYear, withMonth,withYear將月份天數、年份天數、月份、年份修改為指定的值並返 回新的LocalDate 物件
getDayOfMonth獲得月份天數(1-31)
getDayOfYeargetDayOfYear 獲得年份天數(1-366)
getDayOfWeek獲得星期幾(返回一個 DayOfWeek 列舉值)
getMonth獲得月份, 返回一個 Month 列舉值
getMonthValue獲得月份(1-12)
getYear獲得年份
until獲得兩個日期之間的 Period 物件,或者指定 ChronoUnits 的數字
isBefore, isAfter比較兩個 LocalDate
isLeapYear判斷是否是閏年

【2】Instant 時間戳:**用於 “時間戳” 的運算。它是以 Unix 元年(傳統的設定為UTC時區1970年1月1日午夜時分)開始所經歷的描述進行運算;
【3】**Duration 和 Period:Duration:用於計算兩個“時間”間隔;Period:用於計算兩個“日期”間隔;

Instant instant_1 = Instant.now();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
Instant instant_2 = Instant.now();
Duration duration = Duration.between(instant_1, instant_2);
System.out.println(duration.toMillis());
// 執行結果:1000
LocalTime localTime_1 = LocalTime.now();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
LocalTime localTime_2 = LocalTime.now();
System.out.println(Duration.between(localTime_1, localTime_2).toMillis());
// 執行結果:1000
LocalDate localDate_1 = LocalDate.of(2018,9, 9);
LocalDate localDate_2 = LocalDate.now();
Period period = Period.between(localDate_1, localDate_2);
System.out.println(period.getYears());      // 執行結果:1
System.out.println(period.getMonths());     // 執行結果:1
System.out.println(period.getDays());       // 執行結果:18

【4】日期的操縱: TemporalAdjuster:時間校正器。有時我們可能需要獲取例如:將日期調整到“下個週日”等操作。TemporalAdjusters:該類通過靜態方法提供了大量的常用 TemporalAdjuster 的實現。例如獲取下個週日:

LocalDate nextSunday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));//獲取下個週日
LocalDateTime localDateTime1 = LocalDateTime.now();
System.out.println(localDateTime1);     // 2019-10-27T14:19:56.884
// 獲取這個第一天的日期
System.out.println(localDateTime1.with(TemporalAdjusters.firstDayOfMonth()));            // 2019-10-01T14:22:58.574
// 獲取下個週末的日期
System.out.println(localDateTime1.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)));   

【5】解析與格式化:java.time.format.DateTimeFormatter 類:該類提供了三種格式化方法:
● 預定義的標準格式;
● 語言環境相關的格式;
● 自定義的格式;

DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ISO_DATE;
LocalDateTime localDateTime = LocalDateTime.now();
String strDate1 = localDateTime.format(dateTimeFormatter1);
System.out.println(strDate1);
// 執行結果:2019-10-27
// Date -> String
DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd  HH:mm:ss");
String strDate2 = dateTimeFormatter2.format(localDateTime);
System.out.println(strDate2);
// 執行結果:2019-10-27  14:36:11
// String -> Date
LocalDateTime localDateTime1 = localDateTime.parse(strDate2, dateTimeFormatter2);
System.out.println(localDateTime1);
// 執行結果:2019-10-27T14:37:39

【6】時區的處理:Java8 中加入了對時區的支援,帶時區的時間為分別為:ZonedDate、ZonedTime、ZonedDateTime 其中每個時區都對應著 ID,地區ID都為 “{區域}/{城市}”的格式;例如 :Asia/Shanghai 等;
● ZoneId:該類中包含了所有的時區資訊;
● getAvailableZoneIds():可以獲取所有時區時區資訊;
● of(id):用指定的時區資訊獲取 ZoneId 物件;

// 獲取所有的時區
Set<String> set = ZoneId.getAvailableZoneIds();
// 通過時區構建LocalDateTime
LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("America/El_Salvador"));
System.out.println(localDateTime1);
// 2019-10-27T00:46:21.268
// 以時區格式顯示時間
LocalDateTime localDateTime2 = LocalDateTime.now();
ZonedDateTime zonedDateTime1 = localDateTime2.atZone(ZoneId.of("Africa/Nairobi"));
System.out.println(zonedDateTime1);
// 2019-10-27T14:46:21.273+03:00[Africa/Nairobi]

【7】與傳統日期處理的轉換

To 遺留類From 遺留類
java.time.Instant java.util.DateDate.from(instant)date.toInstant()
java.time.Instant java.sql.TimestampTimestamp.from(instant)timestamp.toInstant()
java.time.ZonedDateTime java.util.GregorianCalendarGregorianCalendar.from(zonedDateTime)cal.toZonedDateTime()
java.time.LocalDate java.sql.TimeDate.valueOf(localDate)date.toLocalDate()
java.time.LocalTime java.sql.TimeDate.valueOf(localDate)date.toLocalTime()
java.time.LocalDateTime java.sql.TimestampTimestamp.valueOf(localDateTime)timestamp.toLocalDateTime()
java.time.ZoneId java.util.TimeZoneTimezone.getTimeZone(id)timeZone.toZoneId()
java.time.format.DateTimeFormatter java.text.DateFormatformatter.toFormat()

七、其他新特性


【1】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 Java8Tester {
   public static void main(String args[]){
      Java8Tester java8Tester = new Java8Tester();
      Integer value1 = null;
      Integer value2 = new Integer(10);
      // Optional.ofNullable - 允許傳遞為 null 引數
      Optional<Integer> a = Optional.ofNullable(value1);
      // Optional.of - 如果傳遞的引數是 null,丟擲異常 NullPointerException
      Optional<Integer> b = Optional.of(value2);
      System.out.println(java8Tester.sum(a,b));
   }
   public Integer sum(Optional<Integer> a, Optional<Integer> b){
      // Optional.isPresent - 判斷值是否存在
      System.out.println("第一個引數值存在: " + a.isPresent());
      System.out.println("第二個引數值存在: " + b.isPresent());
      // Optional.orElse - 如果值存在,返回它,否則返回預設值
      Integer value1 = a.orElse(new Integer(0));
      //Optional.get - 獲取值,值需要存在
      Integer value2 = b.get();
      return value1 + value2;
   }
}
//輸出結果:
第一個引數值存在: false
第二個引數值存在: true
10

【2】重複註解與型別註解:Java8 對註解處理提供了兩點改進,可重複的註解及可用於型別的註解。如下自定義可重複註解:使用 @Repeatable 元註解,引數為可重複註解的容器。

@Repeatable(MyAnnotations.class)
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "java8";
}
/**
 * 容器類
 */
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RepetitionAnnotations {
    RepetitionAnnotation[] value();
}

測試方法

public class AnnotationTest {
    @Test
    public void t1() throws Exception {
        Class<AnnotationTest> clazz = AnnotationTest.class;
        Method method = clazz.getMethod("show");
        // 獲取方法上的註解
        RepetitionAnnotation[] ras = method.getAnnotationsByType(RepetitionAnnotation.class);
        for (RepetitionAnnotation repetitionAnnotation : ras) {
            System.out.println(repetitionAnnotation.value());
        }
    }
    @RepetitionAnnotation("Hello")
    @RepetitionAnnotation("World")
    public void show() {
    }
}

型別註解

//就是向 @Target 新增一種型別 TYPE_PARAMETER
@Repeatable(RepetitionAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RepetitionAnnotation {
    String value() default "ling";
}
//使用
@RepetitionAnnotation("Hello")
@RepetitionAnnotation("World")
public void show(@RepetitionAnnotation String str) {}

文章來源:https://javajgs.com/archives/...

看到這裡今天的分享就結束了,如果覺得這篇文章還不錯,來個分享、點贊、在看三連吧,讓更多的人也看到~

歡迎關注個人公眾號 「JavaClub」,定期為你分享一些技術乾貨。

相關文章