Java8 的新特性:Lambda表達式、強大的 Stream API、全新時間日期 API、ConcurrentHashMap、MetaSpace。總得來講,Java8 的新特性使 Java 的運行速度更快、代碼更少、便於並行、最大化減小空指針異常。算法
本篇博客將以筆者的一些心得幫助你們快速理解lambda表達式和Stream API.後端
在IDE中,你是否遇到在寫如下列代碼時,被友情提示的狀況:數組
new Thread(new Runnable() { @Override public void run() { System.out.println("thread"); } });
這時候,咱們按一下快捷鍵,IDE自動幫咱們把代碼優化爲醬個樣子:微信
new Thread(() -> System.out.println("thread"));
這就是Java8的新特性:lambda表達式數據結構
借用引言中的示例,在調用new Thread的含參構造方法時,咱們經過匿名內部類的方式實現了Runnable對象,但其實有用的代碼只有System.out.println("thread");
這一句,而咱們卻要爲了這一句去寫這麼多行代碼。正是這個問題,纔有了Java8中的lambda表達式。那lambd表達式到底是如何簡化代碼的呢?app
先來看lambda表達式的語法:dom
() -> {}
() : 括號就是接口方法的括號,接口方法若是有參數,也須要寫參數。只有一個參數時,括號能夠省略。ide
-> : 分割左右部分的,沒啥好講的。函數
{} : 要實現的方法體。只有一行代碼時,能夠不加括號,能夠不寫return。學習
看了上面的解釋,也就不難理解IDE優化後的代碼了。不過看到這裏你也許意識到,若是接口中有多個方法時,按照上面的邏輯lambda表達式恐怕不行了。沒錯,lambda表達式只適用於函數型接口。說白了,函數型接口就是隻有一個抽象方法的接口。這種類型的接口還有一個對應的註解:@FunctionalInterface
爲了讓咱們在須要這種接口時再也不本身去建立,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表達式還有兩種簡化代碼的手段,它們是方法引用、構造引用。
方法引用是什麼呢?若是咱們要實現接口的方法與另外一個方法A相似,(這裏的相似是指參數類型與返回值部分相同),咱們直接聲明A方法便可。也就是,再也不使用lambda表達式的標準形式,改用高級形式。不管是標準形式仍是高級形式,都是lambda表達式的一種表現形式。
舉例:
Function function1 = (x) -> x; Function function2 = String::valueOf;
對比Function接口的抽象方法與String的value方法,能夠看到它們是相似的。
R apply(T t); public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); }
方法引用的語法:
對象::實例方法 類::靜態方法 類::實例方法
前兩個很容易理解,至關於對象調用實例方法,類調用靜態方法同樣。只是第三個須要特殊說明。
當出現以下這種狀況時:
Compare<Boolean> c = (a, b) -> a.equals(b);
用lambda表達式實現Compare接口的抽象方法,而且方法體只有一行,且該行代碼爲參數1調用方法傳入參數2。此時,就能夠簡化爲下面這種形式:
Compare<Boolean> c = String::equals;
也就是「類::實例方法」的形式。
值得一提的是,當參數b不存在時,該方式依舊適用。例如:
Function function1 = (x) -> x.toString(); Function function1 = Object::toString;
先來建立一個供給型接口對象:
Supplier<String> supplier = () -> new String();
在這個lammbda表達式中只作了一件事,就是返回一個新的Test對象,而這種形式能夠更簡化:
Supplier<String> supplier = String::new;
提煉一下構造引用的語法:
類名::new
當經過含參構造方法建立對象,而且參數列表與抽象方法的參數列表一致,也就是下面的這種形式:
Function1 function = (x) -> new String(x);
也能夠簡化爲:
Function1 function = String::new;
特殊點的數組類型:
Function<Integer,String[]> function = (x) -> new String[x];
能夠簡化爲:
Function<Integer,String[]> function = String[]::new;
上面並無給出太多的lambda實例,只是側重講了如何去理解lambda表達式。到這裏,不要懵。要記住lambda的本質:爲函數型接口的匿名實現進行簡化與更簡化。
所謂的簡化就是lambda的標準形式,所謂的更簡化是在標準形式的基礎上進行方法引用和構造引用。
方法引用是拿已有的方法去實現此刻的接口。
構造引用是對方法體只有一句new Object()的進一步簡化。
在我看來,學習lambda與學習Stream的聯繫就是由於在許多博客、文檔中對Stream API的講解大量使用lambda表達式,致使不學lambda表達式看不懂Stream API。
Stream 不是集合元素,它不是數據結構並不保存數據,它是有關算法和計算的,它更像一個高級版本的 Iterator。簡單來講,它的做用就是經過一系列操做將數據源(集合、數組)轉化爲想要的結果。
//Collection系的 stream() 和 parallelStream(); List<String> list = new ArrayList<>(); Stream<String> stream = list.stream(); Stream<String> stringStream = list.parallelStream(); //經過Arrays Stream<String> stream1 = Arrays.stream(new String[10]); //經過Stream Stream<Integer> stream2 = Stream.of(1, 2, 3); //無限流 //迭代 Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2); iterate.limit(10).forEach(System.out::println); //生成 Stream<Double> generate = Stream.generate(() -> Math.random()); generate.forEach(System.out::println);
多箇中間操做鏈接而成爲流水線,流水線不遇到終止操做是不觸發任何處理的,所爲又稱爲「惰性求值」。
list.stream() .map(s -> s + 1) //映射 .flatMap(s -> Stream.of(s)) //和map差很少,但返回類型爲Stream,相似list.add()和list.addAll()的區別 .filter(s -> s < 1000) //過濾 .limit(5) //限制 .skip(1) //跳過 .distinct() //去重 .sorted() //天然排序 .sorted(Integer::compareTo) //自定義排序
關於map方法,參數爲一個Function函數型接口的對象,也就是傳入一個參數返回一個對象。這個參數就是集合中的每一項。相似Iterator遍歷。其它的幾個操做思想都差很少。
執行上面的方法沒什麼用,由於缺乏終止操做。
list.stream().allMatch((x) -> x == 555); // 檢查是否匹配全部元素 list.stream().anyMatch(((x) -> x>600)); // 檢查是否至少匹配一個元素 list.stream().noneMatch((x) -> x>500); //檢查是否沒有匹配全部元素 list.stream().findFirst(); // 返回第一個元素 list.stream().findAny(); // 返回當前流中的任意一個元素 list.stream().count(); // 返回流中元素的總個數 list.stream().forEach(System.out::println); //內部迭代 list.stream().max(Integer::compareTo); // 返回流中最大值 Optional<Integer> min = list.stream().min(Integer::compareTo);//返回流中最小值 System.out.println("min "+min.get());
reduce (歸約):將流中元素反覆結合起來獲得一個值
Integer reduce = list.stream() .map(s -> (s + 1)) .reduce(0, (x, y) -> x + y); //歸約:0爲第一個參數x的默認值,x是計算後的返回值,y爲每一項的值。 System.out.println(reduce); Optional<Integer> reduce1 = list.stream() .map(s -> (s + 1)) .reduce((x, y) -> x + y); // x是計算後的返回值,默認爲第一項的值,y爲其後每一項的值。 System.out.println(reduce);
collect(收集):將流轉換爲其餘形式。須要Collectors類的一些方法。
//轉集合 Set<Integer> collect = list.stream() .collect(Collectors.toSet()); List<Integer> collect2 = list.stream() .collect(Collectors.toList()); HashSet<Integer> collect1 = list.stream() .collect(Collectors.toCollection(HashSet::new)); //分組 {group=[444, 555, 666, 777, 555]} Map<String, List<Integer>> collect3 = list.stream() .collect(Collectors.groupingBy((x) -> "group"));//將返回值相同的進行分組 System.out.println(collect3); //多級分組 {group={777=[777], 666=[666], 555=[555, 555], 444=[444]}} Map<String, Map<Integer, List<Integer>>> collect4 = list.stream() .collect(Collectors.groupingBy((x) -> "group", Collectors.groupingBy((x) -> x))); System.out.println(collect4); //分區 {false=[444], true=[555, 666, 777, 555]} Map<Boolean, List<Integer>> collect5 = list.stream() .collect(Collectors.partitioningBy((x) -> x > 500)); System.out.println(collect5); //彙總 DoubleSummaryStatistics collect6 = list.stream() .collect(Collectors.summarizingDouble((x) -> x)); System.out.println(collect6.getMax()); System.out.println(collect6.getCount()); //拼接 444,555,666,777,555 String collect7 = list.stream() .map(s -> s.toString()) .collect(Collectors.joining(",")); System.out.println(collect7); //最大值 Optional<Integer> integer = list.stream() .collect(Collectors.maxBy(Integer::compare)); System.out.println(integer.get());
關於Stream的其它用法推薦參考下源碼與API文檔。
本文已受權微信公衆號「後端技術精選」獨家發佈。