日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

《Java8实战》笔记(01):为什么要关心Java8

發布時間:2023/12/13 java 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《Java8实战》笔记(01):为什么要关心Java8 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Java 8 新特性:

  • Stream API
  • 向方法傳遞代碼的技巧
  • 接口中的默認方法

助記:

//Java8主要有哪些新特性?//1.Stream API 2.接口的默認實現 3.方法晉升一級公民 inventory.stream().filter((Apple a) -> a.getWeight() > 150).collect(Collectors.toList());

在Java 8之前:

//對inventory中的蘋果按照重量進行排序 Collections.sort(inventory, new Comparator<Apple>() {public int compare(Apple a1, Apple a2){return a1.getWeight().compareTo(a2.getWeight());} });

在Java 8之后:

//給庫存排序,比較蘋果重量,讀起來言簡意賅 inventory.sort(Collectors.comparing(Apple::getWeight));

Java 8提供了一個新的API(稱為“流”,Stream),它支持許多處理數據的并行操作,其思路和在數據庫查詢語言中的思路類似——用更高級的方式表達想要的東西,而由“實現”(在這里是Streams庫)來選擇最佳低級執行機制。這樣就可以避免用synchronized編寫代碼,這一代碼不僅容易出錯,而且在多核CPU上執行所需的成本也比你想象的要高。(更高效利用多核CPU)

從有點修正主義的角度來看,在Java 8中加入Streams可以看作把另外兩項擴充加入Java 8的直接原因:把代碼傳遞給方法的簡潔方式(方法引用、Lambda)和接口中的默認方法

如果僅僅“把代碼傳遞給方法”看作Streams的一個結果,那就低估了它在Java 8中的應用范圍。它提供了一種新的方式,這種方式簡潔地表達了行為參數化。比方說,你想要寫兩個只有幾行代碼不同的方法,那現在你只需要把不同的那部分代碼作為參數傳遞進去就可以了。采用這種編程技巧,代碼會更短、更清晰,也比常用的復制粘貼更不容易出錯。

Java 8里面將代碼傳遞給方法的功能(同時也能夠返回代碼并將其包含在數據結構中)還讓我們能夠使用一整套新技巧,通常稱為函數式編程。一言以蔽之,這種被函數式編程界稱為函數的代碼,可以被來回傳遞并加以組合,以產生強大的編程語匯。

Java 怎么還在變

Java 在編程語言生態系統中的位置

流處理

流是一系列數據項,一次只生成一項。程序可以從輸入流中一個一個讀取數據項,然后以同樣的方式將數據項寫入輸出流。一個程序的輸出流很可能是另一個程序的輸入流。

舉一個例子:Unix的cat命令會把兩個文件連接起來創建一個流,tr會轉換流中的字符,sort會對流中的行進行排序,而tail -3則給出流的最后三行。Unix命令行允許這些程序通過管道(|)連接在一起,命令如下

cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3

基于這一思想,Java 8在java.util.stream中添加了一個Stream API;Stream就是一系列T類型的項目。你現在可以把它看成一種比較花哨的迭代器。Stream API的很多方法可以鏈接起來形成一個復雜的流水線,就像先前例子里面鏈接起來的Unix命令一樣。

推動這種做法的關鍵在于,現在你可以在一個更高的抽象層次上寫Java 8程序了:思路變成了把這樣的流變成那樣的流(就像寫數據庫查詢語句時的那種思路),而不是一次只處理一個項目。另一個好處是,Java 8可以透明地把輸入的不相關部分拿到幾個CPU內核上去分別執行你的Stream操作流水線——這是幾乎簡單易行的并行,用不著去費勁搞Thread了

用行為參數化把代碼傳遞給方法

方法當作參數傳入方法

Java8前只能傳基本類型,對象類型,不能單純存入方法

并行與共享的可變數據

一般情況下這就意味著,寫代碼時不能訪問共享的可變數據。這些函數有時被稱為“純函數”或“無副作用函數”或“無狀態函數”,

并行只有在假定你的代碼的多個副本可以獨立工作時才能進行。但如果要寫入的是一個共享變量或對象,這就行不通了:如果兩個進程需要同時修改這個共享變量怎么辦?

Java 8的流實現并行比Java現有的線程API更容易,因此,盡管可以使用synchronized來打破“不能有共享的可變數據”這一規則,但這相當于是在和整個體系作對,因為它使所有圍繞這一規則做出的優化都失去意義了。在多個處理器內核之間使用synchronized,其代價往往比你預期的要大得多,因為同步迫使代碼按照順序執行,而這與并行處理的宗旨相悖。

這兩個要點(沒有共享的可變數據,將方法和函數即代碼傳遞給其他方法的能力)是函數式編程范式的基石。與此相反,在命令式編程范式中,你寫的程序則是一系列改變狀態的指令。

“不能有共享的可變數據”的要求意味著,一個方法是可以通過它將參數值轉換為結果的方式完全描述的;換句話說,它的行為就像一個數學函數,沒有可見的副作用。

Java 需要演變

你之前已經見過了Java的演變。例如,引入泛型,使用List而不只是List,可能一開始都挺煩人的。但現在你已經熟悉了這種風格和它所帶來的好處,即在編譯時能發現更多錯誤,且代碼更易讀,因為你現在知道列表里面是什么了。

其他改變讓普通的東西更容易表達,比如,使用for-each循環而不用暴露Iterator里面的套路寫法。

Java 8中的主要變化反映了它開始遠離常側重改變現有值的經典面向對象思想,而向函數式編程領域轉變,在大面上考慮做什么(例如,創建一個值代表所有從A到B低于給定價格的交通線路)被認為是頭等大事,并和如何實現(例如,掃描一個數據結構并修改某些元素)區分開來。

請注意,如果極端點兒來說,傳統的面向對象編程和函數式可能看起來是沖突的。但是我們的理念是獲得兩種編程范式中最好的東西,這樣你就有更大的機會為任務找到理想的工具了。(取長補短)

語言需要不斷改進以跟進硬件的更新或滿足程序員的期待。要堅持下去,Java必須通過增加新功能來改進,而且只有新功能被人使用,變化才有意義。所以,使用Java 8,你就是在保護你作為Java程序員的職業生涯

Java 中的函數

編程語言中的函數一詞通常是指方法,尤其是靜態方法;這是在數學函數,也就是沒有副作用的函數之外的新含義。

Java 8中新增了函數——值的一種新形式。有了它,Java 8可以進行多核處理器上的并行編程

想想Java程序可能操作的值吧。首先有原始值,比如42(int類型)和3.14(double類型)。其次,值可以是對象(更嚴格地說是對象的引用)。獲得對象的唯一途徑是利用new,也許是通過工廠方法或庫函數實現的;對象引用指向類的一個實例。例子包括"abc"(String類型),new Integer(1111)(Integer類型),以及new HashMap<Integer,String>(100)的結果——它顯然調用了HashMap的構造函數,甚至數組也是對象。

編程語言的整個目的就在于操作值,要是按照歷史上編程語言的傳統,這些值因此被稱為一等值(或一等公民,這個術語是從20世紀60年代美國民權運動中借用來的)

編程語言中的其他結構也許有助于我們表示值的結構,但在程序執行期間不能傳遞,因而是二等公民(Java中如方法和類等)。

用方法來定義類很不錯,類還可以實例化來產生值,但方法和類本身都不是值。這又有什么關系呢?

人們發現,在運行時傳遞方法能將方法變成一等公民。這在編程中非常有用,因此Java 8的設計者把這個功能加入到了Java中。

方法和Lambda 作為一等公民

Scala和Groovy等語言的實踐已經證明,讓方法等概念作為一等值可以擴充程序員的工具庫,從而讓編程變得更容易。

Java 8的第一個新功能是方法引用

MethodArgument

Java 8 之前:

File[] hiddenFiles = new File(".").listFiles(new FileFilter() {public boolean accept(File file) {return file.isHidden();} });

Java 8 之后:

File[] hiddenFiles = new File(".").listFiles(File::isHidden);

Java 8的方法引用::語法(即“把這個方法作為值”)將其傳給listFiles方法,也開始用函數代表方法了。一個好處是,你的代碼現在讀起來更接近問題的陳述了。方法不再是二等值了。

與用對象引用傳遞對象類似(對象引用是用new創建的),在Java 8里寫下File::isHidden的時候,創建了一個方法引用,同樣可以傳遞它


Lambda——匿名函數

除了允許(命名)函數成為一等值外,Java 8還體現了更廣義的將函數作為值的思想,包括Lambda(或匿名函數)。比如,你現在可以寫(int x) -> x + 1,表示“調用時給定參數x,就返回x + 1值的函數”。

你可能會想這有什么必要呢?因為你可以在MyMathsUtils類里面定義一個add1方法,然后寫MyMathsUtils::add1嘛!確實是可以,但要是你沒有方便的方法和類可用,新的Lambda語法更簡潔。

傳遞代碼:一個例子

FilteringApples

需求

假設你有一個Apple類,它有一個getColor方法,還有一個變量inventory保存著一個Apples的列表。你可能想要選出所有的綠蘋果,并返回一個列表。

Java 8之前的寫法:

public static List<Apple> filterGreenApples(List<Apple> inventory) {List<Apple> result = new ArrayList<>();for (Apple apple : inventory) {if ("green".equals(apple.getColor())) {result.add(apple);}}return result; }

另一個新需求

可能想要選出重量超過150克的蘋果

public static List<Apple> filterHeavyApples(List<Apple> inventory) {List<Apple> result = new ArrayList<>();for (Apple apple : inventory) {if (apple.getWeight() > 150) {result.add(apple);}}return result; }

上面有代碼重復,重構的氣味出現


Java 8會把條件代碼作為參數傳遞進去,這樣可以避免filter方法出現重復的代碼

public static boolean isGreenApple(Apple apple) {return "green".equals(apple.getColor()); }public static boolean isHeavyApple(Apple apple) {return apple.getWeight() > 150; }public static List<Apple> filterApples(List<Apple> inventory, java.util.function.Predicate<Apple> p) {List<Apple> result = new ArrayList<>();for (Apple apple : inventory) {if (p.test(apple)) {result.add(apple);}}return result; }

要用它的話,可以寫成

filterApples(inventory, FilteringApples::isGreenApple);filterApples(inventory, FilteringApples::isHeavyApple);

什么是謂詞Predicate?

在數學上常常用來代表一個類似函數的東西,它接受一個參數值,并返回true或false

從傳遞方法到Lambda

把方法作為值來傳遞顯然很有用,但要是為類似于isHeavyApple和isGreenApple這種可能只用一兩次的短方法寫一堆定義很煩人。

filterApples(inventory, (Apple a) -> "green".equals(a.getColor()));//orfilterApples(inventory, (Apple a) -> a.getWeight() > 150 );//orfilterApples(inventory, (Apple a) -> a.getWeight() < 80|| "brown".equals(a.getColor()) );

都不需要為只用一次的方法寫定義;代碼更干凈、更清晰,因為你用不著去找自己到底傳遞了什么代碼。

但要是Lambda的長度多于幾行(它的行為也不是一目了然)的話,那你還是應該用方法引用來指向一個有描述性名稱的方法,而不是使用匿名的Lambda。你應該以代碼的清晰度為準繩。

Java 8的設計師幾乎可以就此打住了,要是沒有多核CPU,可能他們真的就到此為止了。

我們迄今為止談到的函數式編程竟然如此強大,在后面你更會體會到這一點。本來,Java加上filter和幾個相關的東西作為通用庫方法就足以讓人滿意了,比如

static <T> Collection<T> filter(Collection<T> c, Predicate<T> p);

filterApples(inventory, (Apple a) -> a.getWeight() > 150);

就可以直接調用庫方法filter

inventory.stream().filter((Apple a) -> a.getWeight() > 150).collect(Collectors.toList())

Stream

幾乎每個Java應用都會制造和處理集合。但集合用起來并不總是那么理想。

需求

Java 8前

比方說,你需要從一個列表中篩選金額較高的交易,然后按貨幣分組。你需要寫一大堆套路化的代碼來實現這個數據處理命令。

Map<Currency, List<Transaction>> transactionsByCurrencies = new HashMap<>();for (Transaction transaction : transactions) {if(transaction.getPrice() > 1000){Currency currency = transaction.getCurrency();List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);if (transactionsForCurrency == null) {transactionsForCurrency = new ArrayList<>();transactionsByCurrencies.put(currency,transactionsForCurrency);}transactionsForCurrency.add(transaction);} }

Java 8后

import static java.util.stream.Collectors.toList; Map<Currency, List<Transaction>> transactionsByCurrencies = transactions.stream().filter((Transaction t) -> t.getPrice() > 1000).collect(groupingBy(Transaction::getCurrency));

和Collection API相比,Stream API處理數據的方式非常不同。用集合的話,你得自己去做迭代的過程。你得用for-each循環一個個去迭代元素,然后再處理元素。我們把這種數據迭代的方法稱為外部迭代

相反,有了Stream API,根本用不著操心循環的事情。數據處理完全是在庫內部進行的。我們把這種思想叫作內部迭代

使用流的好處——更高效利用多核CPU

使用集合的另一個頭疼的地方是,想想看,要是你的交易量非常龐大,你要怎么處理這個巨大的列表呢?單個CPU根本搞不定這么大量的數據,但你很可能已經有了一臺多核電腦。理想的情況下,你可能想讓這些CPU內核共同分擔處理工作,以縮短處理時間。理論上來說,要是你有八個核,那并行起來,處理數據的速度應該是單核的八倍。

傳統上是利用synchronized關鍵字,但是要是用錯了地方,就可能出現很多難以察覺的錯誤。Java 8基于Stream的并行提倡很少使用synchronized的函數式編程風格,它關注數據分塊而不是協調訪問

多線程并非易事

問題在于,通過多線程代碼來利用并行(使用先前Java版本中的Thread API)并非易事。

譬如:線程可能會同時訪問并更新共享變量

因此,如果沒有協調好,數據可能會被意外改變。相比一步步執行的順序模型,這個模型不太好理解。

下圖就展示了如果沒有同步好,兩個線程同時向共享變量sum加上一個數時,可能出現的問題。

Java 8也用Stream API(java.util.stream)解決了這兩個問題:集合處理時的套路和晦澀,以及難以利用多核。

這樣設計的第一個原因是,有許多反復出現的數據處理模式,類似于前一節所說的filterApples或SQL等數據庫查詢語言里熟悉的操作,如果在庫中有這些就會很方便:根據標準篩選數據(比如較重的蘋果),提取數據(例如抽取列表中每個蘋果的重量字段),或給數據分組(例如,將一個數字列表分組,奇數和偶數分別列表)等。

第二個原因是,這類操作常常可以并行化

例如,如下圖所示,在兩個CPU上篩選列表,可以讓一個CPU處理列表的前一半,第二個CPU處理后一半,這稱為分支步驟(1)。CPU隨后對各自的半個列表做篩選(2)。最后(3),一個CPU會把兩個結果合并起來

現在最好記得,Collection主要是為了存儲和訪問數據,而Stream則主要用于描述對數據的計算

// 順序處理 List<Apple> heavyApples3 = inventory.stream().filter((Apple a) -> a.getWeight() > 150).collect(Collectors.toList()); System.out.println(heavyApples3);// 并行處理 List<Apple> heavyApples4 = inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150).collect(Collectors.toList()); System.out.println(heavyApples4);

Java中的并行與無共享可變狀態

大家都說Java里面并行很難,而且和synchronized相關的玩意兒都容易出問題。那Java 8里面有什么“靈丹妙藥”呢?事實上有兩個。

首先,庫會負責分塊,即把大的流分成幾個小的流,以便并行處理。

其次,流提供的這個幾乎免費的并行,只有在傳遞給filter之類的庫方法的方法不會互動(比方說有可變的共享對象)時才能工作。

但是其實這個限制對于程序員來說挺自然的,舉個例子,我們的Apple::isGreenApple就是這樣。確實,雖然函數式編程中的函數的主要意思是“把函數作為一等值”,不過它也常常隱含著第二層意思,即“執行時在元素之間無互動”。

默認方法

Java 8中加入默認方法主要是為了支持庫設計師,讓他們能夠寫出更容易改進的接口

這一方法很重要,因為你會在接口中遇到越來越多的默認方法,但由于真正需要編寫默認方法的程序員相對較少,而且它們只是有助于程序改進,而不是用于編寫任何具體的程序


譬如

List<Apple> heavyApples1 = inventory.stream().filter((Apple a) -> a.getWeight() > 150).collect(Collectors.toList());List<Apple> heavyApples2 = inventory.parallelStream().filter((Apple a) -> a.a.getWeight() > 150).collect(Collectors.toList());

這里有個問題:在Java 8之前,List并沒有stream或parallelStream方法,它實現的Collection接口也沒有,因為當初還沒有想到這些方法嘛!可沒有這些方法,這些代碼就不能編譯

換作你自己的接口的話,最簡單的解決方案就是讓Java 8的設計者把stream方法加入Collection接口,并加入ArrayList類的實現。

可要是這樣做,對用戶來說就是噩夢了。有很多的替代集合框架都用Collection API實現了接口。但給接口加入一個新方法,意味著所有的實體類都必須為其提供一個實現。語言設計者沒法控制Collections所有現有的實現,這下你就進退兩難了:你如何改變已發布的接口而不破壞已有的實現呢?

Java 8的解決方法就是——接口如今可以包含實現類沒有提供實現的方法簽名
了!那誰來實現它呢?缺失的方法主體隨接口提供了(因此就有了默認實現),而不是由實現類提供。

這就給接口設計者提供了一個擴充接口的方式,而不會破壞現有的代碼。Java 8在接口聲明中使用新的default關鍵字來表示這一點。

例如,在Java 8里,你現在可以直接對List調用sort方法。它是用Java 8 List接口中如下所示的默認方法實現的,它會調用Collections.sort靜態方法:

default void sort(Comparator<? super E> c) {Collections.sort(this, c); }

這意味著List的任何實體類都不需要顯式實現sort,而在以前的Java版本中,除非提供了sort的實現,否則這些實體類在重新編譯時都會失敗。

眾所周知,一個類可以實現多個接口,那么,如果在好幾個接口里有多個默認實現,是否意味著Java中有了某種形式的多重繼承?Java 8用一些限制來避免出現類似于C++中臭名昭著的菱形繼承問題。

來自函數式編程的其他好思想

Java 8里有一個Optional類,如果你能一致地使用它的話,就可以幫助你避免出現NullPointer異常。它是一個容器對象,可以包含,也可以不包含一個值。Optional中有方法來明確處理值不存在的情況,這樣就可以避免NullPointer異常了。換句話說,它使用類型系統, 允許你表明我們知道一個變量可能會沒有值。


(結構)模式匹配

模式匹配看作switch的擴展形式

總結

以上是生活随笔為你收集整理的《Java8实战》笔记(01):为什么要关心Java8的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。