JAVA版本總結(jié)

給自己總結(jié)一個(gè)所有JAVA版本更新的核心內(nèi)容總結(jié):

JAVA 8?

JAVA 8在java更新的歷史中是一個(gè)非常重要的一個(gè)版本,引入函數(shù)式編程使得java編程更為強(qiáng)大,主要更新的內(nèi)容:

? ?1.Lambda表達(dá)式

Lambda表達(dá)式(也叫做閉包)是Java 8中最大的也是期待已久的變化。它允許我們將一個(gè)函數(shù)當(dāng)作方法的參數(shù)(傳遞函數(shù)),或者說把代碼當(dāng)作數(shù)據(jù),這是每個(gè)函數(shù)式編程者熟悉的概念。很多基于JVM平臺(tái)的語言一開始就支持Lambda表達(dá)式,但是Java程序員沒有選擇,只能使用匿名內(nèi)部類來替代Lambda表達(dá)式。

Lambda表達(dá)式的設(shè)計(jì)被討論了很久,而且花費(fèi)了很多的功夫來交流。不過最后取得了一個(gè)折中的辦法,得到了一個(gè)新的簡(jiǎn)明并且緊湊的Lambda表達(dá)式結(jié)構(gòu)。最簡(jiǎn)單的Lambda表達(dá)式可以用逗號(hào)分隔的參數(shù)列表、->符號(hào)和功能語句塊來表示。示例如下:

Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

請(qǐng)注意到編譯器會(huì)根據(jù)上下文來推測(cè)參數(shù)的類型,或者你也可以顯示地指定參數(shù)類型,只需要將類型包在括號(hào)里。舉個(gè)例子:

Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );

如果Lambda的功能語句塊太復(fù)雜,我們可以用大括號(hào)包起來,跟普通的Java方法一樣,如下:

String separator = ",";

Arrays.asList( "a", "b", "d" ).forEach(

? ? ( String e ) -> System.out.print( e + separator ) );

Lambda表達(dá)式可能會(huì)引用類的成員或者局部變量(會(huì)被隱式地轉(zhuǎn)變成final類型),下面兩種寫法的效果是一樣的:

String separator = ",";

Arrays.asList( "a", "b", "d" ).forEach(

? ? ( String e ) -> System.out.print( e + separator ) );

final String separator = ",";

Arrays.asList( "a", "b", "d" ).forEach(

? ? ( String e ) -> System.out.print( e + separator ) );

Lambda表達(dá)式可能會(huì)有返回值,編譯器會(huì)根據(jù)上下文推斷返回值的類型。如果lambda的語句塊只有一行,不需要return關(guān)鍵字。下面兩個(gè)寫法是等價(jià)的:

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {

? ? int result = e1.compareTo( e2 );

? ? return result;

} );

語言的設(shè)計(jì)者們思考了很多如何讓現(xiàn)有的功能和lambda表達(dá)式友好兼容。于是就有了函數(shù)接口這個(gè)概念。函數(shù)接口是一種只有一個(gè)方法的接口,像這樣地,函數(shù)接口可以隱式地轉(zhuǎn)換成lambda表達(dá)式。

java.lang.Runnable 和java.util.concurrent.Callable是函數(shù)接口兩個(gè)最好的例子。但是在實(shí)踐中,函數(shù)接口是非常脆弱的,只要有人在接口里添加多一個(gè)方法,那么這個(gè)接口就不是函數(shù)接口了,就會(huì)導(dǎo)致編譯失敗。Java 8提供了一個(gè)特殊的注解@FunctionalInterface來克服上面提到的脆弱性并且顯示地表明函數(shù)接口的目的(java里所有現(xiàn)存的接口都已經(jīng)加上了@FunctionalInterface)。讓我們看看一個(gè)簡(jiǎn)單的函數(shù)接口定義:

@FunctionalInterface

public interface Functional {

? ? void method();

}

我們要記住默認(rèn)的方法和靜態(tài)方法(下一節(jié)會(huì)具體解釋)不會(huì)違反函數(shù)接口的約定,例子如下:

@FunctionalInterface

public interface FunctionalDefaultMethods {

? ? void method();

? ? default void defaultMethod() {

? ? }

}

支持Lambda是Java 8最大的賣點(diǎn),他有巨大的潛力吸引越來越多的開發(fā)人員轉(zhuǎn)到這個(gè)開發(fā)平臺(tái)來,并且在純Java里提供最新的函數(shù)式編程的概念。

? ? 2.新的日期API

Java 8引入了新的日期時(shí)間API(JSR 310)改進(jìn)了日期時(shí)間的管理。日期和時(shí)間管理一直是Java開發(fā)人員最痛苦的問題。java.util.Date和后來的java.util.Calendar一點(diǎn)也沒有改變這個(gè)情況(甚至讓人們更加迷茫)。

因?yàn)樯厦孢@些原因,產(chǎn)生了Joda-Time,可以替換Java的日期時(shí)間API。Joda-Time深刻影響了 Java 8新的日期時(shí)間API,Java 8吸收了Joda-Time?的精華。新的java.time包包含了所有關(guān)于日期、時(shí)間、日期時(shí)間、時(shí)區(qū)、Instant(跟日期類似但精確到納秒)、duration(持續(xù)時(shí)間)和時(shí)鐘操作的類。設(shè)計(jì)這些API的時(shí)候很認(rèn)真地考慮了這些類的不變性(從java.util.Calendar吸取的痛苦教訓(xùn))。如果需要修改時(shí)間對(duì)象,會(huì)返回一個(gè)新的實(shí)例。

讓我們看看一些關(guān)鍵的類和用法示例。第一個(gè)類是Clock,Clock使用時(shí)區(qū)來訪問當(dāng)前的instant, date和time。Clock類可以替換System.currentTimeMillis()TimeZone.getDefault().

1// Get the system clock as UTC offset

2final?Clock clock = Clock.systemUTC();

3System.out.println( clock.instant() );

4System.out.println( clock.millis() );

控制臺(tái)輸出如下:

2014-04-12T15:19:29.282Z

1397315969360

其他類我們看看LocalTime和LocalDate。LocalDate只保存有ISO-8601日期系統(tǒng)的日期部分,有時(shí)區(qū)信息,相應(yīng)地,LocalTime只保存ISO-8601日期系統(tǒng)的時(shí)間部分,沒有時(shí)區(qū)信息。LocalDate和LocalTime都可以從Clock對(duì)象創(chuàng)建。

01// Get the local date and local time

02final?LocalDate date = LocalDate.now();

03final?LocalDate dateFromClock = LocalDate.now( clock );

04?

05System.out.println( date );

06System.out.println( dateFromClock );

07?

08// Get the local date and local time

09final?LocalTime time = LocalTime.now();

10final?LocalTime timeFromClock = LocalTime.now( clock );

11?

12System.out.println( time );

13System.out.println( timeFromClock );

控制臺(tái)輸出如下:

2014-04-12

2014-04-12

11:25:54.568

15:25:54.568

LocalDateTime類合并了LocalDate和LocalTime,它保存有ISO-8601日期系統(tǒng)的日期和時(shí)間,但是沒有時(shí)區(qū)信息。讓我們看一個(gè)簡(jiǎn)單的例子。

1// Get the local date/time

2final?LocalDateTime datetime = LocalDateTime.now();

3final?LocalDateTime datetimeFromClock = LocalDateTime.now( clock );

4?

5System.out.println( datetime );

6System.out.println( datetimeFromClock );

輸出如下:

2014-04-12T11:37:52.309

2014-04-12T15:37:52.309

如果您需要一個(gè)類持有日期時(shí)間和時(shí)區(qū)信息,可以使用ZonedDateTime,它保存有ISO-8601日期系統(tǒng)的日期和時(shí)間,而且有時(shí)區(qū)信息。讓我們看一些例子:

1// Get the zoned date/time

2final?ZonedDateTime zonedDatetime = ZonedDateTime.now();

3final?ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );

4final?ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of(?"America/Los_Angeles"?) );

5?

6System.out.println( zonedDatetime );

7System.out.println( zonedDatetimeFromClock );

8System.out.println( zonedDatetimeFromZone );

輸出如下:

2014-04-12T11:47:01.017-04:00[America/New_York]

2014-04-12T15:47:01.017Z

2014-04-12T08:47:01.017-07:00[America/Los_Angeles]

最后讓我們看看Duration類,Duration持有的時(shí)間精確到納秒。它讓我們很容易計(jì)算兩個(gè)日期中間的差異。讓我們來看一下:

1// Get duration between two dates

2final?LocalDateTime from = LocalDateTime.of(?2014, Month.APRIL,?16,?0,?0,?0?);

3final?LocalDateTime to = LocalDateTime.of(?2015, Month.APRIL,?16,?23,?59,?59?);

4?

5final?Duration duration = Duration.between( from, to );

6System.out.println(?"Duration in days: "?+ duration.toDays() );

7System.out.println(?"Duration in hours: "?+ duration.toHours() );

上面的例子計(jì)算了兩個(gè)日期(2014年4月16日和2014年5月16日)之間的持續(xù)時(shí)間(基于天數(shù)和小時(shí))輸出如下:

Duration in days: 365

Duration in hours: 8783

對(duì)于Java 8的新日期時(shí)間的總體印象還是比較積極的。一部分是因?yàn)橛薪?jīng)歷實(shí)戰(zhàn)的Joda-Time的基礎(chǔ),還有一部分是因?yàn)槿掌跁r(shí)間終于被認(rèn)真對(duì)待而且聽取了開發(fā)人員的聲音。

? ? 3.引入Optional

著名的NullPointerException是引起系統(tǒng)失敗最常見的原因。很久以前Google Guava項(xiàng)目引入了Optional作為解決空指針異常的一種方式,不贊成代碼被null檢查的代碼污染,期望程序員寫整潔的代碼。受Google Guava的鼓勵(lì),Optional現(xiàn)在是Java 8庫的一部分。

Optional只是一個(gè)容器,它可以保存一些類型的值或者null。它提供很多有用的方法,所以沒有理由不顯式地檢查null。請(qǐng)參照java 8的文檔查看詳細(xì)信息。

讓我們看看兩個(gè)Optional用法的小例子:一個(gè)是允許為空的值,另外一個(gè)是不允許為空的值。

1Optional< String > fullName = Optional.ofNullable(?null?);

2System.out.println(?"Full Name is set? "?+ fullName.isPresent() );???????

3System.out.println(?"Full Name: "?+ fullName.orElseGet( () ->?"[none]"?) );

4System.out.println( fullName.map( s ->?"Hey "?+ s +?"!"?).orElse(?"Hey Stranger!"?) );

如果Optional實(shí)例有非空的值,方法?isPresent()?返回true否則返回false。方法orElseGet提供了回退機(jī)制,當(dāng)Optional的值為空時(shí)接受一個(gè)方法返回默認(rèn)值。map()方法轉(zhuǎn)化Optional當(dāng)前的值并且返回一個(gè)新的Optional實(shí)例。orElse方法和orElseGet類似,但是它不接受一個(gè)方法,而是接受一個(gè)默認(rèn)值。上面代碼運(yùn)行結(jié)果如下:

Full Name is set? false

Full Name: [none]

Hey Stranger!

讓我們大概看看另外一個(gè)例子。

1Optional< String > firstName = Optional.of(?"Tom"?);

2System.out.println(?"First Name is set? "?+ firstName.isPresent() );???????

3System.out.println(?"First Name: "?+ firstName.orElseGet( () ->?"[none]"?) );

4System.out.println( firstName.map( s ->?"Hey "?+ s +?"!"?).orElse(?"Hey Stranger!"?) );

5System.out.println();

輸出如下:

First Name is set? true

First Name: Tom

Hey Tom!

? ? 4.使用Base64

? ? 5.接口的默認(rèn)方法和靜態(tài)方法

Java 8增加了兩個(gè)新的概念在接口聲明的時(shí)候:默認(rèn)和靜態(tài)方法。默認(rèn)方法和Trait有些類似,但是目標(biāo)不一樣。默認(rèn)方法允許我們?cè)诮涌诶锾砑有碌姆椒?,而不?huì)破壞實(shí)現(xiàn)這個(gè)接口的已有類的兼容性,也就是說不會(huì)強(qiáng)迫實(shí)現(xiàn)接口的類實(shí)現(xiàn)默認(rèn)方法。

默認(rèn)方法和抽象方法的區(qū)別是抽象方法必須要被實(shí)現(xiàn),默認(rèn)方法不是。作為替代方式,接口可以提供一個(gè)默認(rèn)的方法實(shí)現(xiàn),所有這個(gè)接口的實(shí)現(xiàn)類都會(huì)通過繼承得倒這個(gè)方法(如果有需要也可以重寫這個(gè)方法),讓我們來看看下面的例子:

01private?interface?Defaulable {

02????// Interfaces now allow default methods, the implementer may or

03????// may not implement (override) them.

04????default?String notRequired() {

05????????return?"Default implementation";

06????}

07}

08?

09private?static?class?DefaultableImpl?implements?Defaulable {

10}

11?

12private?static?class?OverridableImpl?implements?Defaulable {

13????@Override

14????public?String notRequired() {

15????????return?"Overridden implementation";

16????}

17}

接口Defaulable使用default關(guān)鍵字聲明了一個(gè)默認(rèn)方法notRequired(),類DefaultableImpl實(shí)現(xiàn)了Defaulable接口,沒有對(duì)默認(rèn)方法做任何修改。另外一個(gè)類OverridableImpl重寫類默認(rèn)實(shí)現(xiàn),提供了自己的實(shí)現(xiàn)方法。

Java 8 的另外一個(gè)有意思的新特性是接口里可以聲明靜態(tài)方法,并且可以實(shí)現(xiàn)。例子如下:

1private?interface?DefaulableFactory {

2????// Interfaces now allow static methods

3????static?Defaulable create( Supplier< Defaulable > supplier ) {

4????????return?supplier.get();

5????}

6}

下面是把接口的靜態(tài)方法和默認(rèn)方法放在一起的示例(::new?是構(gòu)造方法引用,后面會(huì)有詳細(xì)描述):

1public?static?void?main( String[] args ) {

2????Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new?);

3????System.out.println( defaulable.notRequired() );

4?

5????defaulable = DefaulableFactory.create( OverridableImpl::new?);

6????System.out.println( defaulable.notRequired() );

7}

控制臺(tái)的輸出如下:

Default implementation

Overridden implementation

JVM平臺(tái)的接口的默認(rèn)方法實(shí)現(xiàn)是很高效的,并且方法調(diào)用的字節(jié)碼指令支持默認(rèn)方法。默認(rèn)方法使已經(jīng)存在的接口可以修改而不會(huì)影響編譯的過程。java.util.Collection中添加的額外方法就是最好的例子:stream(),parallelStream(),forEach(),removeIf()

雖然默認(rèn)方法很強(qiáng)大,但是使用之前一定要仔細(xì)考慮是不是真的需要使用默認(rèn)方法,因?yàn)樵趯蛹?jí)很復(fù)雜的情況下很容易引起模糊不清甚至變異錯(cuò)誤。

? ? 6.新增方法引用格式

方法引用提供了一個(gè)很有用的語義來直接訪問類或者實(shí)例的已經(jīng)存在的方法或者構(gòu)造方法。結(jié)合Lambda表達(dá)式,方法引用使語法結(jié)構(gòu)緊湊簡(jiǎn)明。不需要復(fù)雜的引用。

下面我們用Car?這個(gè)類來做示例,Car這個(gè)類有不同的方法定義。讓我們來看看java 8支持的4種方法引用。

01public?static?class?Car {

02????public?static?Car create(?final?Supplier< Car > supplier ) {

03????????return?supplier.get();

04????}?????????????

05?

06????public?static?void?collide(?final?Car car ) {

07????????System.out.println(?"Collided "?+ car.toString() );

08????}

09?

10????public?void?follow(?final?Car another ) {

11????????System.out.println(?"Following the "?+ another.toString() );

12????}

13?

14????public?void?repair() {

15????????System.out.println(?"Repaired "?+?this.toString() );

16????}

17}

第一種方法引用是構(gòu)造方法引用,語法是:Class::new,對(duì)于泛型來說語法是:Class<T >::new,請(qǐng)注意構(gòu)造方法沒有參數(shù):

1final?Car car = Car.create( Car::new?);

2final?List< Car > cars = Arrays.asList( car );

第二種方法引用是靜態(tài)方法引用,語法是:Class::static_method請(qǐng)注意這個(gè)靜態(tài)方法只支持一個(gè)類型為Car的參數(shù)。

1cars.forEach( Car::collide );

第三種方法引用是類實(shí)例的方法引用,語法是:Class::method請(qǐng)注意方法沒有參數(shù)。

1cars.forEach( Car::repair );

最后一種方法引用是引用特殊類的方法,語法是:instance::method,請(qǐng)注意只接受Car類型的一個(gè)參數(shù)。

1final?Car police = Car.create( Car::new?);

2cars.forEach( police::follow );

運(yùn)行這些例子我們將會(huì)在控制臺(tái)得到如下信息(Car的實(shí)例可能會(huì)不一樣):?

Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

關(guān)于方法引用更多的示例和詳細(xì)信息,請(qǐng)參考官方文檔

? ? 7.新增Stream類

新增加的Stream API?(java.util.stream)引入了在Java里可以工作的函數(shù)式編程。這是目前為止對(duì)java庫最大的一次功能添加,希望程序員通過編寫有效、整潔和簡(jiǎn)明的代碼,能夠大大提高生產(chǎn)率。

Stream API讓集合處理簡(jiǎn)化了很多(我們后面會(huì)看到不僅限于Java集合類)。讓我們從一個(gè)簡(jiǎn)單的類Task開始來看看Stream的用法。

01public?class?Streams {

02private?enum?Status {

03OPEN, CLOSED

04};

05?

06private?static?final?class?Task {

07private?final?Status status;

08private?final?Integer points;

09?

10Task(?final?Status status,?final?Integer points ) {

11this.status = status;

12this.points = points;

13}

14?

15public?Integer getPoints() {

16return?points;

17}

18?

19public?Status getStatus() {

20return?status;

21}

22?

23@Override

24public?String toString() {

25return?String.format(?"[%s, %d]", status, points );

26}

27}

28}

Task類有一個(gè)分?jǐn)?shù)的概念(或者說是偽復(fù)雜度),其次是還有一個(gè)值可以為OPEN或CLOSED的狀態(tài).讓我們引入一個(gè)Task的小集合作為演示例子:

1final?Collection< Task > tasks = Arrays.asList(

2????new?Task( Status.OPEN,?5?),

3????new?Task( Status.OPEN,?13?),

4????new?Task( Status.CLOSED,?8?)

5);

第一個(gè)問題是所有的開放的Task的點(diǎn)數(shù)是多少?在java 8 之前,通常的做法是用foreach迭代。但是Java8里頭我們會(huì)用Stream。Stream是多個(gè)元素的序列,支持串行和并行操作。

1// Calculate total points of all active tasks using sum()

2final?long?totalPointsOfOpenTasks = tasks

3????.stream()

4????.filter( task -> task.getStatus() == Status.OPEN )

5????.mapToInt( Task::getPoints )

6????.sum();

7?????????

8System.out.println(?"Total points: "?+ totalPointsOfOpenTasks );

控制臺(tái)的輸出將會(huì)是:

Total points: 18

上面代碼執(zhí)行的流程是這樣的,首先Task集合會(huì)被轉(zhuǎn)化為Stream表示,然后filter操作會(huì)過濾掉所有關(guān)閉的Task,接下來使用Task::getPoints?方法取得每個(gè)Task實(shí)例的點(diǎn)數(shù),mapToInt方法會(huì)把Task Stream轉(zhuǎn)換成Integer Stream,最后使用Sum方法將所有的點(diǎn)數(shù)加起來得到最終的結(jié)果。

在我們看下一個(gè)例子之前,我們要記住一些關(guān)于Stream的說明。Stream操作被分為中間操作和終點(diǎn)操作。

中間操作返回一個(gè)新的Stream。這些中間操作是延遲的,執(zhí)行一個(gè)中間操作比如filter實(shí)際上不會(huì)真的做過濾操作,而是創(chuàng)建一個(gè)新的Stream,當(dāng)這個(gè)新的Stream被遍歷的時(shí)候,它里頭會(huì)包含有原來Stream里符合過濾條件的元素。

終點(diǎn)操作比如說forEach或者sum會(huì)遍歷Stream從而產(chǎn)生最終結(jié)果或附帶結(jié)果。終點(diǎn)操作執(zhí)行完之后,Stream管道就被消費(fèi)完了,不再可用。在幾乎所有的情況下,終點(diǎn)操作都是即時(shí)完成對(duì)數(shù)據(jù)的遍歷操作。

Stream的另外一個(gè)價(jià)值是Stream創(chuàng)造性地支持并行處理。讓我們看看下面這個(gè)例子,這個(gè)例子把所有task的點(diǎn)數(shù)加起來。

1// Calculate total points of all tasks

2final?double?totalPoints = tasks

3???.stream()

4???.parallel()

5???.map( task -> task.getPoints() )?// or map( Task::getPoints )

6???.reduce(?0, Integer::sum );

7????

8System.out.println(?"Total points (all tasks): "?+ totalPoints );

這個(gè)例子跟上面那個(gè)非常像,除了這個(gè)例子里使用了parallel()方法?????? 并且計(jì)算最終結(jié)果的時(shí)候使用了reduce方法。

輸出如下:

Total points (all tasks): 26.0

經(jīng)常會(huì)有這個(gè)一個(gè)需求:我們需要按照某種準(zhǔn)則來對(duì)集合中的元素進(jìn)行分組。Stream也可以處理這樣的需求,下面是一個(gè)例子:

1// Group tasks by their status

2final?Map< Status, List< Task > > map = tasks

3????.stream()

4????.collect( Collectors.groupingBy( Task::getStatus ) );

5System.out.println( map );

控制臺(tái)的輸出如下:

{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}

讓我們來計(jì)算整個(gè)集合中每個(gè)task分?jǐn)?shù)(或權(quán)重)的平均值來結(jié)束task的例子。

01// Calculate the weight of each tasks (as percent of total points)

02final?Collection< String > result = tasks

03????.stream()?// Stream< String >

04????.mapToInt( Task::getPoints )?// IntStream

05????.asLongStream()?// LongStream

06????.mapToDouble( points -> points / totalPoints )?// DoubleStream

07????.boxed()?// Stream< Double >

08????.mapToLong( weigth -> (?long?)( weigth *?100?) )?// LongStream

09????.mapToObj( percentage -> percentage +?"%"?)?// Stream< String>

10????.collect( Collectors.toList() );?// List< String >

11?????????

12System.out.println( result );

控制臺(tái)輸出如下:

[19%, 50%, 30%]

最后,就像前面提到的,Stream API不僅僅處理Java集合框架。像從文本文件中逐行讀取數(shù)據(jù)這樣典型的I/O操作也很適合用Stream API來處理。下面用一個(gè)例子來應(yīng)證這一點(diǎn)。

1final?Path path =?new?File( filename ).toPath();

2try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {

3????lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );

4}

Stream的方法onClose返回一個(gè)等價(jià)的有額外句柄的Stream,當(dāng)Stream的close()方法被調(diào)用的時(shí)候這個(gè)句柄會(huì)被執(zhí)行。

Stream API、Lambda表達(dá)式還有接口默認(rèn)方法和靜態(tài)方法支持的方法引用,是Java 8對(duì)軟件開發(fā)的現(xiàn)代范式的響應(yīng)。

? ? 8.注解相關(guān)的改變

? ? 9.支持并行(parallel)數(shù)組

Java 8新增加了很多方法支持并行的數(shù)組處理。最重要的大概是parallelSort()這個(gè)方法顯著地使排序在多核計(jì)算機(jī)上速度加快。下面的小例子演示了這個(gè)新的方法(parallelXXX)的行為。

01</pre>

02

03?

04import?java.util.Arrays;

05import?java.util.concurrent.ThreadLocalRandom;

06?

07public?class?ParallelArrays {

08????public?static?void?main( String[] args ) {

09????????long[] arrayOfLong =?new?long?[?20000?];???????

10?

11????????Arrays.parallelSetAll( arrayOfLong,

12????????????index -> ThreadLocalRandom.current().nextInt(?1000000?) );

13????????Arrays.stream( arrayOfLong ).limit(?10?).forEach(

14????????????i -> System.out.print( i +?" "?) );

15????????System.out.println();

16?

17????????Arrays.parallelSort( arrayOfLong );

18????????Arrays.stream( arrayOfLong ).limit(?10?).forEach(

19????????????i -> System.out.print( i +?" "?) );

20????????System.out.println();

21????}

22}</pre>

23<pre>

這一小段代碼使用parallelSetAll()t方法填充這個(gè)長度是2000的數(shù)組,然后使用parallelSort()排序。這個(gè)程序輸出了排序前和排序后的10個(gè)數(shù)字來驗(yàn)證數(shù)組真的已經(jīng)被排序了。示例可能的輸出如下(請(qǐng)注意這些數(shù)字是隨機(jī)產(chǎn)生的)

Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378

Sorted: 39 220 263 268 325 607 655 678 723 793

? ? 10.對(duì)并發(fā)類(Concurrency)的擴(kuò)展。

在新增Stream機(jī)制與lambda的基礎(chǔ)之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法來支持聚集操作。同時(shí)也在java.util.concurrent.ForkJoinPool類中加入了一些新方法來支持共有資源池(common pool)

新增的java.util.concurrent.locks.StampedLock類提供一直基于容量的鎖,這種鎖有三個(gè)模型來控制讀寫操作(它被認(rèn)為是不太有名的java.util.concurrent.locks.ReadWriteLock類的替代者)。

在java.util.concurrent.atomic包中還增加了下面這些類:

DoubleAccumulator

DoubleAdder

LongAccumulator

LongAdder

JAVA 9

Java 9 發(fā)布于 2017 年 9 月 22 日,帶來了很多新特性,其中最主要的變化是已經(jīng)實(shí)現(xiàn)的模塊化系統(tǒng)。

1 模塊系統(tǒng):模塊是一個(gè)包的容器,Java 9 最大的變化之一是引入了模塊系統(tǒng)(Jigsaw 項(xiàng)目)。

2 REPL (JShell):交互式編程環(huán)境。

JAVA 10

1. 局部變量類型推斷

局部變量類型推斷可以說是Java 10中最值得注意的特性,這是Java語言開發(fā)人員為了簡(jiǎn)化Java應(yīng)用程序的編寫而采取的又一步,如下圖所示。

局部變量類型推斷將引入"var"關(guān)鍵字,也就是你可以隨意定義變量而不必指定變量的類型

局部變量類型推薦僅限于如下使用場(chǎng)景:

局部變量初始化

for循環(huán)內(nèi)部索引變量

傳統(tǒng)的for循環(huán)聲明變量

Java官方表示,它不能用于以下幾個(gè)地方:

方法參數(shù)

構(gòu)造函數(shù)

參數(shù)方法返回

類型字段捕獲表達(dá)式(或任何其他類型的變量聲明)

2 JEP304,統(tǒng)一的垃圾回收接口。

3 JEP307,G1 垃圾回收器的并行完整垃圾回收,實(shí)現(xiàn)并行性來改善最壞情況下的延遲。

JAVA 11

1 本地變量類型推斷

2、字符串加強(qiáng)

Java 11 增加了一系列的字符串處理方法,如以下所示。

// 判斷字符串是否為空白" ".isBlank(); // true// 去除首尾空格" Javastack ".strip(); // "Javastack"http:// 去除尾部空格?" Javastack ".stripTrailing(); // " Javastack"http:// 去除首部空格?" Javastack ".stripLeading(); // "Javastack "http:// 復(fù)制字符串"Java".repeat(3); // "JavaJavaJava"http:// 行數(shù)統(tǒng)計(jì)"A\nB\nC".lines().count(); // 3

3、集合加強(qiáng)

自 Java 9 開始,Jdk 里面為集合(List/ Set/ Map)都添加了of和copyOf方法,它們兩個(gè)都用來創(chuàng)建不可變的集合,來看下它們的使用和區(qū)別。

示例1:

var list = List.of("Java", "Python", "C");var copy = List.copyOf(list);System.out.println(list == copy); // true

示例2:

var list = new ArrayList<String>();var copy = List.copyOf(list);System.out.println(list == copy); // false

示例1和2代碼差不多,為什么一個(gè)為true,一個(gè)為false?

來看下它們的源碼:

static <E> List<E> of(E... elements) {switch (elements.length) { // implicit null check of elements?case 0:?return ImmutableCollections.emptyList();?case 1:?return new ImmutableCollections.List12<>(elements[0]);?case 2:?return new ImmutableCollections.List12<>(elements[0], elements[1]);?default:?return new ImmutableCollections.ListN<>(elements);?}}static <E> List<E> copyOf(Collection<? extends E> coll) {?return ImmutableCollections.listCopy(coll);}static <E> List<E> listCopy(Collection<? extends E> coll) {?if (coll instanceof AbstractImmutableList && coll.getClass() != SubList.class) {?return (List)coll;?} else {?return (List)List.of(coll.toArray());?}}

可以看出copyOf方法會(huì)先判斷來源集合是不是AbstractImmutableList類型的,如果是,就直接返回,如果不是,則調(diào)用of創(chuàng)建一個(gè)新的集合。

示例2因?yàn)橛玫?new 創(chuàng)建的集合,不屬于不可變AbstractImmutableList類的子類,所以copyOf方法又創(chuàng)建了一個(gè)新的實(shí)例,所以為false.

注意:使用 of 和 copyOf 創(chuàng)建的集合為不可變集合,不能進(jìn)行添加、刪除、替換、排序等操作,不然會(huì)報(bào)

java.lang.UnsupportedOperationException異常。

上面演示了 List 的 of 和 copyOf 方法,Set 和 Map 接口都有。

4、Stream 加強(qiáng)

Stream 是 Java 8 中的新特性,Java 9 開始對(duì) Stream 增加了以下 4 個(gè)新方法。

增加單個(gè)參數(shù)構(gòu)造方法,可為null

Stream.ofNullable(null).count(); // 0

增加 takeWhile 和 dropWhile 方法

Stream.of(1, 2, 3, 2, 1).takeWhile(n -> n < 3)?.collect(Collectors.toList()); // [1, 2]

從開始計(jì)算,當(dāng) n < 3 時(shí)就截止。

Stream.of(1, 2, 3, 2, 1).dropWhile(n -> n < 3)?.collect(Collectors.toList()); // [3, 2, 1]

這個(gè)和上面的相反,一旦 n < 3 不成立就開始計(jì)算。

3)iterate重載

這個(gè) iterate 方法的新重載方法,可以讓你提供一個(gè) Predicate (判斷條件)來指定什么時(shí)候結(jié)束迭代。

如果你對(duì) JDK 8 中的 Stream 還不熟悉,可以看之前分享的這一系列教程。

5、Optional 加強(qiáng)

Opthonal 也增加了幾個(gè)非??岬姆椒?,現(xiàn)在可以很方便的將一個(gè) Optional 轉(zhuǎn)換成一個(gè) Stream, 或者當(dāng)一個(gè)空 Optional 時(shí)給它一個(gè)替代的。

Optional.of("javastack").orElseThrow(); // javastackOptional.of("javastack").stream().count(); // 1Optional.ofNullable(null).or(() -> Optional.of("javastack"))?.get(); // javastack

6、InputStream 加強(qiáng)

InputStream 終于有了一個(gè)非常有用的方法:transferTo,可以用來將數(shù)據(jù)直接傳輸?shù)?OutputStream,這是在處理原始數(shù)據(jù)流時(shí)非常常見的一種用法,如下示例。

var classLoader = ClassLoader.getSystemClassLoader();var inputStream = classLoader.getResourceAsStream("javastack.txt");var javastack = File.createTempFile("javastack2", "txt");try (var outputStream = new FileOutputStream(javastack)) {inputStream.transferTo(outputStream);}

7、HTTP Client API

這是 Java 9 開始引入的一個(gè)處理 HTTP 請(qǐng)求的的孵化 HTTP Client API,該 API 支持同步和異步,而在 Java 11 中已經(jīng)為正式可用狀態(tài),你可以在

java.net

包中找到這個(gè) API。

來看一下 HTTP Client 的用法:

var request = HttpRequest.newBuilder().uri(URI.create("https://javastack.cn"))?.GET()?.build();var client = HttpClient.newHttpClient();// 同步HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());System.out.println(response.body());// 異步client.sendAsync(request, HttpResponse.BodyHandlers.ofString())?.thenApply(HttpResponse::body)?.thenAccept(System.out::println);

上面的.GET()可以省略,默認(rèn)請(qǐng)求方式為 Get!

更多使用示例可以看這個(gè) API,后續(xù)有機(jī)會(huì)再做演示。

現(xiàn)在 Java 自帶了這個(gè) HTTP Client API,我們以后還有必要用 Apache 的 HttpClient 工具包嗎?

8、化繁為簡(jiǎn),一個(gè)命令編譯運(yùn)行源代碼

看下面的代碼。

// 編譯javac Javastack.java// 運(yùn)行java Javastack

在我們的認(rèn)知里面,要運(yùn)行一個(gè) Java 源代碼必須先編譯,再運(yùn)行,兩步執(zhí)行動(dòng)作。而在未來的 Java 11 版本中,通過一個(gè)java命令就直接搞定了,如以下所示。

javaJavastack.java

JAVA 12

1、Shenandoah:低暫停時(shí)間的 GC(實(shí)驗(yàn)性功能)

新增了一個(gè)名為 Shenandoah 的?GC 算法,通過與正在運(yùn)行的 Java 線程同時(shí)進(jìn)行 evacuation 工作來減少 GC 暫停時(shí)間。使用 Shenandoah 的暫停時(shí)間與堆大小無關(guān),這意味著無論堆是 200 MB 還是 200 GB,都將具有相同的暫停時(shí)間。

2、微基準(zhǔn)測(cè)試套件

JDK 源碼中新增了一套微基準(zhǔn)測(cè)試套件,使開發(fā)人員可以輕松運(yùn)行現(xiàn)有的微基準(zhǔn)測(cè)試并創(chuàng)建新的基準(zhǔn)測(cè)試。

3、Switch 表達(dá)式(預(yù)覽功能)

擴(kuò)展了 switch 語句,使其不僅可以作為語句(statement),還可以作為表達(dá)式(expression),并且兩種寫法都可以使用傳統(tǒng)的 switch 語法,或者使用簡(jiǎn)化的“case L ->”模式匹配語法作用于不同范圍并控制執(zhí)行流。這些更改將簡(jiǎn)化日常編碼工作,并為 switch 中的模式匹配(JEP 305)做好準(zhǔn)備。

4、JVM 常量 API

引入 API 對(duì)關(guān)鍵類文件和運(yùn)行時(shí)工件建模,特別是可從常量池加載的常量。在新的 java.lang.invoke.constant 包中定義了一系列基于值的符號(hào)引用(JVMS 5.1)類型,它們能夠描述每種可加載常量。

符號(hào)引用以純?nominal 形式描述可加載常量,與類加載或可訪問性上下文區(qū)分開。有些類可以作為自己的符號(hào)引用(例如 String),而對(duì)于可鏈接常量,定義了一系列符號(hào)引用類型(ClassDesc、MethodTypeDesc、MethodHandleDesc 和 DynamicConstantDesc),它們包含描述這些常量的 nominal 信息。

5、只保留一個(gè) AArch64 實(shí)現(xiàn)

刪除了與 arm64 相關(guān)的所有源,同時(shí)保留 32 位 ARM 實(shí)現(xiàn)和 64 位 aarch64。

JDK 中存在兩套?64 位 ARM 實(shí)現(xiàn),主要存在于 src/hotspot/cpu/arm 和 open/src/hotspot/cpu/aarch64 目錄。兩者都實(shí)現(xiàn)了?aarch64,現(xiàn)在將只保留后者,刪除由?Oracle 提供的 arm64。這將使貢獻(xiàn)者將他們的精力集中在單個(gè) 64 位 ARM 實(shí)現(xiàn)上,并消除維護(hù)兩套實(shí)現(xiàn)所需的重復(fù)工作。

6、默認(rèn)類數(shù)據(jù)共享歸檔文件

針對(duì) 64 位平臺(tái),使用默認(rèn)類列表增強(qiáng) JDK 構(gòu)建過程以生成類數(shù)據(jù)共享(class data-sharing,CDS)檔。

7、可中止的 G1 Mixed GC

如果 G1 Mixed GC 存在超出暫停目標(biāo)的可能性,則使其可中止。

8、G1 及時(shí)返回未使用的已分配內(nèi)存

增強(qiáng) G1 GC,在空閑時(shí)自動(dòng)將 Java 堆內(nèi)存返回給操作系統(tǒng)。為了實(shí)現(xiàn)向操作系統(tǒng)返回最大內(nèi)存量的目標(biāo),G1 將在應(yīng)用程序不活動(dòng)期間定期執(zhí)行或觸發(fā)并發(fā)周期以確定整體 Java 堆使用情況。這將導(dǎo)致它自動(dòng)將 Java 堆的未使用部分返回給操作系統(tǒng)。而在用戶控制下,可以可選地執(zhí)行完整的 GC,以使返回的內(nèi)存量最大化。

JAVA 13

1、Dynamic CDS Archives

這一特性是在JEP310:Application Class-Data Sharing 基礎(chǔ)上擴(kuò)展而來的,Dynamic CDS Archives中的CDS指的就是Class-Data Sharing。

那么,這個(gè)JEP310是個(gè)啥東西呢?

我們知道在同一個(gè)物理機(jī)/虛擬機(jī)上啟動(dòng)多個(gè)JVM時(shí),如果每個(gè)虛擬機(jī)都單獨(dú)裝載自己需要的所有類,啟動(dòng)成本和內(nèi)存占用是比較高的。所以Java團(tuán)隊(duì)引入了CDS的概念,通過把一些核心類在每個(gè)JVM間共享,每個(gè)JVM只需要裝載自己的應(yīng)用類,啟動(dòng)時(shí)間減少了,另外核心類是共享的,所以JVM的內(nèi)存占用也減少了。

CDS 只能作用于 Boot Class Loader 加載的類,不能作用于 App Class Loader 或者自定義的 Class Loader 加載的類。

在 Java 10 中,則將 CDS 擴(kuò)展為 AppCDS,顧名思義,AppCDS 不止能夠作用于 Boot Class Loader了,App Class Loader 和自定義的 Class Loader 也都能夠起作用,大大加大了 CDS 的適用范圍。也就說開發(fā)自定義的類也可以裝載給多個(gè)JVM共享了。

Java 10中包含的JEP310的通過跨不同Java進(jìn)程共享公共類元數(shù)據(jù)來減少了內(nèi)存占用和改進(jìn)了啟動(dòng)時(shí)間。

但是,JEP310中,使用AppCDS的過程還是比較復(fù)雜的,需要有三個(gè)步驟:

1、決定要 Dump 哪些 Class

2、將類的內(nèi)存 Dump 到歸檔文件中

3、使用 Dump 出來的歸檔文件加快應(yīng)用啟動(dòng)速度

這一次的JDK 13中的JEP 350 ,在JEP310的基礎(chǔ)上,又做了一些擴(kuò)展。允許在Java應(yīng)用程序執(zhí)行結(jié)束時(shí)動(dòng)態(tài)歸檔類,歸檔類將包括默認(rèn)的基礎(chǔ)層 CDS(class data-sharing)存檔中不存在的所有已加載的應(yīng)用程序類和庫類。

也就是說,在Java 13中再使用AppCDS的時(shí)候,就不在需要這么復(fù)雜了。

2、ZGC: Uncommit Unused Memory

在討論這個(gè)問題之前,想先問一個(gè)問題,JVM的GC釋放的內(nèi)存會(huì)還給操作系統(tǒng)嗎?

GC后的內(nèi)存如何處置,其實(shí)是取決于不同的垃圾回收器的。因?yàn)榘褍?nèi)存還給OS,意味著要調(diào)整JVM的堆大小,這個(gè)過程是比較耗費(fèi)資源的。

在JDK 11中,Java引入了ZGC,這是一款可伸縮的低延遲垃圾收集器,但是當(dāng)時(shí)只是實(shí)驗(yàn)性的。并且,ZGC釋放的內(nèi)存是不會(huì)還給操作系統(tǒng)的。

而在Java 13中,JEP 351再次對(duì)ZGC做了增強(qiáng),本次 ZGC 可以將未使用的堆內(nèi)存返回給操作系統(tǒng)。之所以引入這個(gè)特性,是因?yàn)槿缃裼泻芏鄨?chǎng)景中內(nèi)存是比較昂貴的資源,在以下情況中,將內(nèi)存還給操作系統(tǒng)還是很有必要的:

1、那些需要根據(jù)使用量付費(fèi)的容器

2、應(yīng)用程序可能長時(shí)間處于空閑狀態(tài)并與許多其他應(yīng)用程序共享或競(jìng)爭(zhēng)資源的環(huán)境。

3、應(yīng)用程序在執(zhí)行期間可能有非常不同的堆空間需求。例如,啟動(dòng)期間所需的堆可能大于稍后在穩(wěn)定狀態(tài)執(zhí)行期間所需的堆。

3、Reimplement the Legacy Socket API

使用易于維護(hù)和調(diào)試的更簡(jiǎn)單、更現(xiàn)代的實(shí)現(xiàn)替換 java.net.Socket 和 java.net.ServerSocket API。

java.net.Socket和java.net.ServerSocket的實(shí)現(xiàn)非常古老,這個(gè)JEP為它們引入了一個(gè)現(xiàn)代的實(shí)現(xiàn)?,F(xiàn)代實(shí)現(xiàn)是Java 13中的默認(rèn)實(shí)現(xiàn),但是舊的實(shí)現(xiàn)還沒有刪除,可以通過設(shè)置系統(tǒng)屬性jdk.net.usePlainSocketImpl來使用它們。

運(yùn)行一個(gè)實(shí)例化Socket和ServerSocket的類將顯示這個(gè)調(diào)試輸出。這是默認(rèn)的(新的):

4、Switch Expressions (Preview)

在JDK 12中引入了Switch表達(dá)式作為預(yù)覽特性。JEP 354修改了這個(gè)特性,它引入了yield語句,用于返回值。這意味著,switch表達(dá)式(返回值)應(yīng)該使用yield, switch語句(不返回值)應(yīng)該使用break。

在以前,我們想要在switch中返回內(nèi)容,還是比較麻煩的,一般語法如下:

inti;

switch(x)?{

case"1":

i=1;

break;

case"2":

i=2;

break;

default:

????????i?=?x.length();

break;

}

在JDK13中使用以下語法:

int?i?=switch(x)?{

case"1"->?1;

case"2"->?2;

default->?{

int?len?=?args[1].length();

yieldlen;

????}

};

或者

inti?=switch(x)?{

case"1":yield1;

case"2":yield2;

default:?{

intlen?=?args[1].length();

yieldlen;

????}

};

在這之后,switch中就多了一個(gè)關(guān)鍵字用于跳出switch塊了,那就是yield,他用于返回一個(gè)值。和return的區(qū)別在于:return會(huì)直接跳出當(dāng)前循環(huán)或者方法,而yield只會(huì)跳出當(dāng)前switch塊。

5、Text Blocks (Preview)

在JDK 12中引入了Raw String Literals特性,但在發(fā)布之前就放棄了。這個(gè)JEP在引入多行字符串文字(text block)在意義上是類似的。

text block,文本塊,是一個(gè)多行字符串文字,它避免了對(duì)大多數(shù)轉(zhuǎn)義序列的需要,以可預(yù)測(cè)的方式自動(dòng)格式化字符串,并在需要時(shí)讓開發(fā)人員控制格式。

我們以前從外部copy一段文本串到Java中,會(huì)被自動(dòng)轉(zhuǎn)義,如有一段以下字符串:

Hello,?Hollis

將其復(fù)制到Java的字符串中,會(huì)展示成以下內(nèi)容:

"

"?+

"

"?+

"

Hello,?Hollis

"?+

"

"?+

"

";

即被自動(dòng)進(jìn)行了轉(zhuǎn)義,這樣的字符串看起來不是很直觀,在JDK 13中,就可以使用以下語法了:

"""

Hello,?Hollis

"""

;

使用"""作為文本塊的開始符合結(jié)束符,在其中就可以放置多行的字符串,不需要進(jìn)行任何轉(zhuǎn)義。看起來就十分清爽了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容