給自己總結一個所有JAVA版本更新的核心內(nèi)容總結:
JAVA 8?
JAVA 8在java更新的歷史中是一個非常重要的一個版本捏萍,引入函數(shù)式編程使得java編程更為強大旷余,主要更新的內(nèi)容:
? ?1.Lambda表達式
Lambda表達式(也叫做閉包)是Java 8中最大的也是期待已久的變化。它允許我們將一個函數(shù)當作方法的參數(shù)(傳遞函數(shù))薪鹦,或者說把代碼當作數(shù)據(jù)掌敬,這是每個函數(shù)式編程者熟悉的概念。很多基于JVM平臺的語言一開始就支持Lambda表達式池磁,但是Java程序員沒有選擇奔害,只能使用匿名內(nèi)部類來替代Lambda表達式。
Lambda表達式的設計被討論了很久地熄,而且花費了很多的功夫來交流华临。不過最后取得了一個折中的辦法,得到了一個新的簡明并且緊湊的Lambda表達式結構端考。最簡單的Lambda表達式可以用逗號分隔的參數(shù)列表雅潭、->符號和功能語句塊來表示揭厚。示例如下:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
請注意到編譯器會根據(jù)上下文來推測參數(shù)的類型,或者你也可以顯示地指定參數(shù)類型扶供,只需要將類型包在括號里筛圆。舉個例子:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
如果Lambda的功能語句塊太復雜,我們可以用大括號包起來椿浓,跟普通的Java方法一樣太援,如下:
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
? ? ( String e ) -> System.out.print( e + separator ) );
Lambda表達式可能會引用類的成員或者局部變量(會被隱式地轉變成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表達式可能會有返回值色乾,編譯器會根據(jù)上下文推斷返回值的類型。如果lambda的語句塊只有一行主巍,不需要return關鍵字笋敞。下面兩個寫法是等價的:
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;
} );
語言的設計者們思考了很多如何讓現(xiàn)有的功能和lambda表達式友好兼容唧垦。于是就有了函數(shù)接口這個概念。函數(shù)接口是一種只有一個方法的接口液样,像這樣地振亮,函數(shù)接口可以隱式地轉換成lambda表達式。
java.lang.Runnable 和java.util.concurrent.Callable是函數(shù)接口兩個最好的例子鞭莽。但是在實踐中坊秸,函數(shù)接口是非常脆弱的,只要有人在接口里添加多一個方法澎怒,那么這個接口就不是函數(shù)接口了褒搔,就會導致編譯失敗。Java 8提供了一個特殊的注解@FunctionalInterface來克服上面提到的脆弱性并且顯示地表明函數(shù)接口的目的(java里所有現(xiàn)存的接口都已經(jīng)加上了@FunctionalInterface)喷面。讓我們看看一個簡單的函數(shù)接口定義:
@FunctionalInterface
public interface Functional {
? ? void method();
}
我們要記住默認的方法和靜態(tài)方法(下一節(jié)會具體解釋)不會違反函數(shù)接口的約定星瘾,例子如下:
@FunctionalInterface
public interface FunctionalDefaultMethods {
? ? void method();
? ? default void defaultMethod() {
? ? }
}
支持Lambda是Java 8最大的賣點,他有巨大的潛力吸引越來越多的開發(fā)人員轉到這個開發(fā)平臺來惧辈,并且在純Java里提供最新的函數(shù)式編程的概念琳状。
? ? 2.新的日期API
Java 8引入了新的日期時間API(JSR 310)改進了日期時間的管理。日期和時間管理一直是Java開發(fā)人員最痛苦的問題盒齿。java.util.Date和后來的java.util.Calendar一點也沒有改變這個情況(甚至讓人們更加迷茫)念逞。
因為上面這些原因,產(chǎn)生了Joda-Time边翁,可以替換Java的日期時間API翎承。Joda-Time深刻影響了 Java 8新的日期時間API,Java 8吸收了Joda-Time?的精華符匾。新的java.time包包含了所有關于日期叨咖、時間、日期時間、時區(qū)甸各、Instant(跟日期類似但精確到納秒)仰剿、duration(持續(xù)時間)和時鐘操作的類。設計這些API的時候很認真地考慮了這些類的不變性(從java.util.Calendar吸取的痛苦教訓)痴晦。如果需要修改時間對象南吮,會返回一個新的實例。
讓我們看看一些關鍵的類和用法示例誊酌。第一個類是Clock部凑,Clock使用時區(qū)來訪問當前的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() );
控制臺輸出如下:
2014-04-12T15:19:29.282Z
1397315969360
其他類我們看看LocalTime和LocalDate碧浊。LocalDate只保存有ISO-8601日期系統(tǒng)的日期部分涂邀,有時區(qū)信息,相應地箱锐,LocalTime只保存ISO-8601日期系統(tǒng)的時間部分比勉,沒有時區(qū)信息。LocalDate和LocalTime都可以從Clock對象創(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 );
控制臺輸出如下:
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
LocalDateTime類合并了LocalDate和LocalTime浩聋,它保存有ISO-8601日期系統(tǒng)的日期和時間,但是沒有時區(qū)信息臊恋。讓我們看一個簡單的例子衣洁。
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
如果您需要一個類持有日期時間和時區(qū)信息,可以使用ZonedDateTime抖仅,它保存有ISO-8601日期系統(tǒng)的日期和時間坊夫,而且有時區(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持有的時間精確到納秒环凿。它讓我們很容易計算兩個日期中間的差異。讓我們來看一下:
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() );
上面的例子計算了兩個日期(2014年4月16日和2014年5月16日)之間的持續(xù)時間(基于天數(shù)和小時)輸出如下:
Duration in days: 365
Duration in hours: 8783
對于Java 8的新日期時間的總體印象還是比較積極的放吩。一部分是因為有經(jīng)歷實戰(zhàn)的Joda-Time的基礎智听,還有一部分是因為日期時間終于被認真對待而且聽取了開發(fā)人員的聲音。
? ? 3.引入Optional
著名的NullPointerException是引起系統(tǒng)失敗最常見的原因屎慢。很久以前Google Guava項目引入了Optional作為解決空指針異常的一種方式瞭稼,不贊成代碼被null檢查的代碼污染,期望程序員寫整潔的代碼腻惠。受Google Guava的鼓勵,Optional現(xiàn)在是Java 8庫的一部分欲虚。
Optional只是一個容器集灌,它可以保存一些類型的值或者null。它提供很多有用的方法,所以沒有理由不顯式地檢查null欣喧。請參照java 8的文檔查看詳細信息腌零。
讓我們看看兩個Optional用法的小例子:一個是允許為空的值,另外一個是不允許為空的值唆阿。
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實例有非空的值益涧,方法?isPresent()?返回true否則返回false。方法orElseGet提供了回退機制驯鳖,當Optional的值為空時接受一個方法返回默認值闲询。map()方法轉化Optional當前的值并且返回一個新的Optional實例。orElse方法和orElseGet類似浅辙,但是它不接受一個方法扭弧,而是接受一個默認值。上面代碼運行結果如下:
Full Name is set? false
Full Name: [none]
Hey Stranger!
讓我們大概看看另外一個例子记舆。
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.接口的默認方法和靜態(tài)方法
Java 8增加了兩個新的概念在接口聲明的時候:默認和靜態(tài)方法鸽捻。默認方法和Trait有些類似,但是目標不一樣泽腮。默認方法允許我們在接口里添加新的方法御蒲,而不會破壞實現(xiàn)這個接口的已有類的兼容性,也就是說不會強迫實現(xiàn)接口的類實現(xiàn)默認方法诊赊。
默認方法和抽象方法的區(qū)別是抽象方法必須要被實現(xiàn)删咱,默認方法不是。作為替代方式豪筝,接口可以提供一個默認的方法實現(xiàn)痰滋,所有這個接口的實現(xiàn)類都會通過繼承得倒這個方法(如果有需要也可以重寫這個方法),讓我們來看看下面的例子:
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關鍵字聲明了一個默認方法notRequired()续崖,類DefaultableImpl實現(xiàn)了Defaulable接口敲街,沒有對默認方法做任何修改。另外一個類OverridableImpl重寫類默認實現(xiàn)严望,提供了自己的實現(xiàn)方法多艇。
Java 8 的另外一個有意思的新特性是接口里可以聲明靜態(tài)方法,并且可以實現(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)方法和默認方法放在一起的示例(::new?是構造方法引用峻黍,后面會有詳細描述):
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}
控制臺的輸出如下:
Default implementation
Overridden implementation
JVM平臺的接口的默認方法實現(xiàn)是很高效的,并且方法調用的字節(jié)碼指令支持默認方法拨匆。默認方法使已經(jīng)存在的接口可以修改而不會影響編譯的過程姆涩。java.util.Collection中添加的額外方法就是最好的例子:stream(),parallelStream(),forEach(),removeIf()
雖然默認方法很強大,但是使用之前一定要仔細考慮是不是真的需要使用默認方法惭每,因為在層級很復雜的情況下很容易引起模糊不清甚至變異錯誤骨饿。
? ? 6.新增方法引用格式
方法引用提供了一個很有用的語義來直接訪問類或者實例的已經(jīng)存在的方法或者構造方法。結合Lambda表達式,方法引用使語法結構緊湊簡明宏赘。不需要復雜的引用绒北。
下面我們用Car?這個類來做示例,Car這個類有不同的方法定義察署。讓我們來看看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}
第一種方法引用是構造方法引用,語法是:Class::new贴汪,對于泛型來說語法是:Class<T >::new脐往,請注意構造方法沒有參數(shù):
1final?Car car = Car.create( Car::new?);
2final?List< Car > cars = Arrays.asList( car );
第二種方法引用是靜態(tài)方法引用,語法是:Class::static_method請注意這個靜態(tài)方法只支持一個類型為Car的參數(shù)嘶是。
1cars.forEach( Car::collide );
第三種方法引用是類實例的方法引用钙勃,語法是:Class::method請注意方法沒有參數(shù)。
1cars.forEach( Car::repair );
最后一種方法引用是引用特殊類的方法聂喇,語法是:instance::method辖源,請注意只接受Car類型的一個參數(shù)。
1final?Car police = Car.create( Car::new?);
2cars.forEach( police::follow );
運行這些例子我們將會在控制臺得到如下信息(Car的實例可能會不一樣):?
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
關于方法引用更多的示例和詳細信息希太,請參考官方文檔
? ? 7.新增Stream類
新增加的Stream API?(java.util.stream)引入了在Java里可以工作的函數(shù)式編程克饶。這是目前為止對java庫最大的一次功能添加,希望程序員通過編寫有效誊辉、整潔和簡明的代碼矾湃,能夠大大提高生產(chǎn)率。
Stream API讓集合處理簡化了很多(我們后面會看到不僅限于Java集合類)堕澄。讓我們從一個簡單的類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類有一個分數(shù)的概念(或者說是偽復雜度),其次是還有一個值可以為OPEN或CLOSED的狀態(tài).讓我們引入一個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);
第一個問題是所有的開放的Task的點數(shù)是多少蛙紫?在java 8 之前拍屑,通常的做法是用foreach迭代。但是Java8里頭我們會用Stream坑傅。Stream是多個元素的序列僵驰,支持串行和并行操作。
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 );
控制臺的輸出將會是:
Total points: 18
上面代碼執(zhí)行的流程是這樣的唁毒,首先Task集合會被轉化為Stream表示蒜茴,然后filter操作會過濾掉所有關閉的Task,接下來使用Task::getPoints?方法取得每個Task實例的點數(shù)浆西,mapToInt方法會把Task Stream轉換成Integer Stream粉私,最后使用Sum方法將所有的點數(shù)加起來得到最終的結果。
在我們看下一個例子之前室谚,我們要記住一些關于Stream的說明毡鉴。Stream操作被分為中間操作和終點操作崔泵。
中間操作返回一個新的Stream秒赤。這些中間操作是延遲的猪瞬,執(zhí)行一個中間操作比如filter實際上不會真的做過濾操作,而是創(chuàng)建一個新的Stream入篮,當這個新的Stream被遍歷的時候陈瘦,它里頭會包含有原來Stream里符合過濾條件的元素。
終點操作比如說forEach或者sum會遍歷Stream從而產(chǎn)生最終結果或附帶結果潮售。終點操作執(zhí)行完之后痊项,Stream管道就被消費完了,不再可用酥诽。在幾乎所有的情況下鞍泉,終點操作都是即時完成對數(shù)據(jù)的遍歷操作。
Stream的另外一個價值是Stream創(chuàng)造性地支持并行處理肮帐。讓我們看看下面這個例子咖驮,這個例子把所有task的點數(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 );
這個例子跟上面那個非常像训枢,除了這個例子里使用了parallel()方法?????? 并且計算最終結果的時候使用了reduce方法托修。
輸出如下:
Total points (all tasks): 26.0
經(jīng)常會有這個一個需求:我們需要按照某種準則來對集合中的元素進行分組。Stream也可以處理這樣的需求恒界,下面是一個例子:
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 );
控制臺的輸出如下:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
讓我們來計算整個集合中每個task分數(shù)(或權重)的平均值來結束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 );
控制臺輸出如下:
[19%, 50%, 30%]
最后,就像前面提到的十酣,Stream API不僅僅處理Java集合框架涩拙。像從文本文件中逐行讀取數(shù)據(jù)這樣典型的I/O操作也很適合用Stream API來處理。下面用一個例子來應證這一點耸采。
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返回一個等價的有額外句柄的Stream兴泥,當Stream的close()方法被調用的時候這個句柄會被執(zhí)行。
Stream API洋幻、Lambda表達式還有接口默認方法和靜態(tài)方法支持的方法引用郁轻,是Java 8對軟件開發(fā)的現(xiàn)代范式的響應。
? ? 8.注解相關的改變
? ? 9.支持并行(parallel)數(shù)組
Java 8新增加了很多方法支持并行的數(shù)組處理文留。最重要的大概是parallelSort()這個方法顯著地使排序在多核計算機上速度加快好唯。下面的小例子演示了這個新的方法(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方法填充這個長度是2000的數(shù)組燥翅,然后使用parallelSort()排序骑篙。這個程序輸出了排序前和排序后的10個數(shù)字來驗證數(shù)組真的已經(jīng)被排序了。示例可能的輸出如下(請注意這些數(shù)字是隨機產(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.對并發(fā)類(Concurrency)的擴展森书。
在新增Stream機制與lambda的基礎之上靶端,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法來支持聚集操作谎势。同時也在java.util.concurrent.ForkJoinPool類中加入了一些新方法來支持共有資源池(common pool)
新增的java.util.concurrent.locks.StampedLock類提供一直基于容量的鎖,這種鎖有三個模型來控制讀寫操作(它被認為是不太有名的java.util.concurrent.locks.ReadWriteLock類的替代者)杨名。
在java.util.concurrent.atomic包中還增加了下面這些類:
DoubleAccumulator
DoubleAdder
LongAccumulator
LongAdder
JAVA 9
Java 9 發(fā)布于 2017 年 9 月 22 日脏榆,帶來了很多新特性,其中最主要的變化是已經(jīng)實現(xiàn)的模塊化系統(tǒng)台谍。
1 模塊系統(tǒng):模塊是一個包的容器须喂,Java 9 最大的變化之一是引入了模塊系統(tǒng)(Jigsaw 項目)。
2 REPL (JShell):交互式編程環(huán)境趁蕊。
JAVA 10
1. 局部變量類型推斷
局部變量類型推斷可以說是Java 10中最值得注意的特性坞生,這是Java語言開發(fā)人員為了簡化Java應用程序的編寫而采取的又一步,如下圖所示掷伙。
局部變量類型推斷將引入"var"關鍵字是己,也就是你可以隨意定義變量而不必指定變量的類型
局部變量類型推薦僅限于如下使用場景:
局部變量初始化
for循環(huán)內(nèi)部索引變量
傳統(tǒng)的for循環(huán)聲明變量
Java官方表示,它不能用于以下幾個地方:
方法參數(shù)
構造函數(shù)
參數(shù)方法返回
類型字段捕獲表達式(或任何其他類型的變量聲明)
2 JEP304任柜,統(tǒng)一的垃圾回收接口卒废。
3 JEP307,G1 垃圾回收器的并行完整垃圾回收乘盼,實現(xiàn)并行性來改善最壞情況下的延遲升熊。
JAVA 11
1 本地變量類型推斷
2、字符串加強
Java 11 增加了一系列的字符串處理方法绸栅,如以下所示级野。
// 判斷字符串是否為空白" ".isBlank(); // true// 去除首尾空格" Javastack ".strip(); // "Javastack"http:// 去除尾部空格?" Javastack ".stripTrailing(); // " Javastack"http:// 去除首部空格?" Javastack ".stripLeading(); // "Javastack "http:// 復制字符串"Java".repeat(3); // "JavaJavaJava"http:// 行數(shù)統(tǒng)計"A\nB\nC".lines().count(); // 3
3、集合加強
自 Java 9 開始粹胯,Jdk 里面為集合(List/ Set/ Map)都添加了of和copyOf方法蓖柔,它們兩個都用來創(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代碼差不多况鸣,為什么一個為true,一個為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方法會先判斷來源集合是不是AbstractImmutableList類型的,如果是竹观,就直接返回镐捧,如果不是,則調用of創(chuàng)建一個新的集合臭增。
示例2因為用的 new 創(chuàng)建的集合懂酱,不屬于不可變AbstractImmutableList類的子類,所以copyOf方法又創(chuàng)建了一個新的實例誊抛,所以為false.
注意:使用 of 和 copyOf 創(chuàng)建的集合為不可變集合列牺,不能進行添加、刪除拗窃、替換瞎领、排序等操作泌辫,不然會報
java.lang.UnsupportedOperationException異常。
上面演示了 List 的 of 和 copyOf 方法九默,Set 和 Map 接口都有震放。
4、Stream 加強
Stream 是 Java 8 中的新特性荤西,Java 9 開始對 Stream 增加了以下 4 個新方法澜搅。
增加單個參數(shù)構造方法伍俘,可為null
Stream.ofNullable(null).count(); // 0
增加 takeWhile 和 dropWhile 方法
Stream.of(1, 2, 3, 2, 1).takeWhile(n -> n < 3)?.collect(Collectors.toList()); // [1, 2]
從開始計算邪锌,當 n < 3 時就截止。
Stream.of(1, 2, 3, 2, 1).dropWhile(n -> n < 3)?.collect(Collectors.toList()); // [3, 2, 1]
這個和上面的相反癌瘾,一旦 n < 3 不成立就開始計算觅丰。
3)iterate重載
這個 iterate 方法的新重載方法,可以讓你提供一個 Predicate (判斷條件)來指定什么時候結束迭代妨退。
如果你對 JDK 8 中的 Stream 還不熟悉妇萄,可以看之前分享的這一系列教程。
5咬荷、Optional 加強
Opthonal 也增加了幾個非彻诰洌酷的方法,現(xiàn)在可以很方便的將一個 Optional 轉換成一個 Stream, 或者當一個空 Optional 時給它一個替代的幸乒。
Optional.of("javastack").orElseThrow(); // javastackOptional.of("javastack").stream().count(); // 1Optional.ofNullable(null).or(() -> Optional.of("javastack"))?.get(); // javastack
6懦底、InputStream 加強
InputStream 終于有了一個非常有用的方法:transferTo,可以用來將數(shù)據(jù)直接傳輸?shù)?OutputStream罕扎,這是在處理原始數(shù)據(jù)流時非常常見的一種用法聚唐,如下示例。
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 開始引入的一個處理 HTTP 請求的的孵化 HTTP Client API杆查,該 API 支持同步和異步,而在 Java 11 中已經(jīng)為正式可用狀態(tài)臀蛛,你可以在
java.net
包中找到這個 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()可以省略,默認請求方式為 Get浊仆!
更多使用示例可以看這個 API客峭,后續(xù)有機會再做演示。
現(xiàn)在 Java 自帶了這個 HTTP Client API氧卧,我們以后還有必要用 Apache 的 HttpClient 工具包嗎桃笙?
8、化繁為簡沙绝,一個命令編譯運行源代碼
看下面的代碼搏明。
// 編譯javac Javastack.java// 運行java Javastack
在我們的認知里面鼠锈,要運行一個 Java 源代碼必須先編譯,再運行星著,兩步執(zhí)行動作购笆。而在未來的 Java 11 版本中,通過一個java命令就直接搞定了虚循,如以下所示同欠。
javaJavastack.java
JAVA 12
1、Shenandoah:低暫停時間的 GC(實驗性功能)
新增了一個名為 Shenandoah 的?GC 算法横缔,通過與正在運行的 Java 線程同時進行 evacuation 工作來減少 GC 暫停時間铺遂。使用 Shenandoah 的暫停時間與堆大小無關,這意味著無論堆是 200 MB 還是 200 GB茎刚,都將具有相同的暫停時間襟锐。
2、微基準測試套件
JDK 源碼中新增了一套微基準測試套件膛锭,使開發(fā)人員可以輕松運行現(xiàn)有的微基準測試并創(chuàng)建新的基準測試粮坞。
3、Switch 表達式(預覽功能)
擴展了 switch 語句初狰,使其不僅可以作為語句(statement)莫杈,還可以作為表達式(expression),并且兩種寫法都可以使用傳統(tǒng)的 switch 語法奢入,或者使用簡化的“case L ->”模式匹配語法作用于不同范圍并控制執(zhí)行流筝闹。這些更改將簡化日常編碼工作,并為 switch 中的模式匹配(JEP 305)做好準備俊马。
4丁存、JVM 常量 API
引入 API 對關鍵類文件和運行時工件建模,特別是可從常量池加載的常量柴我。在新的 java.lang.invoke.constant 包中定義了一系列基于值的符號引用(JVMS 5.1)類型解寝,它們能夠描述每種可加載常量。
符號引用以純?nominal 形式描述可加載常量艘儒,與類加載或可訪問性上下文區(qū)分開聋伦。有些類可以作為自己的符號引用(例如 String),而對于可鏈接常量界睁,定義了一系列符號引用類型(ClassDesc觉增、MethodTypeDesc、MethodHandleDesc 和 DynamicConstantDesc)翻斟,它們包含描述這些常量的 nominal 信息逾礁。
5、只保留一個 AArch64 實現(xiàn)
刪除了與 arm64 相關的所有源访惜,同時保留 32 位 ARM 實現(xiàn)和 64 位 aarch64嘹履。
JDK 中存在兩套?64 位 ARM 實現(xiàn)腻扇,主要存在于 src/hotspot/cpu/arm 和 open/src/hotspot/cpu/aarch64 目錄。兩者都實現(xiàn)了?aarch64砾嫉,現(xiàn)在將只保留后者幼苛,刪除由?Oracle 提供的 arm64。這將使貢獻者將他們的精力集中在單個 64 位 ARM 實現(xiàn)上焕刮,并消除維護兩套實現(xiàn)所需的重復工作舶沿。
6、默認類數(shù)據(jù)共享歸檔文件
針對 64 位平臺配并,使用默認類列表增強 JDK 構建過程以生成類數(shù)據(jù)共享(class data-sharing括荡,CDS)檔。
7荐绝、可中止的 G1 Mixed GC
如果 G1 Mixed GC 存在超出暫停目標的可能性一汽,則使其可中止。
8低滩、G1 及時返回未使用的已分配內(nèi)存
增強 G1 GC,在空閑時自動將 Java 堆內(nèi)存返回給操作系統(tǒng)岩喷。為了實現(xiàn)向操作系統(tǒng)返回最大內(nèi)存量的目標恕沫,G1 將在應用程序不活動期間定期執(zhí)行或觸發(fā)并發(fā)周期以確定整體 Java 堆使用情況。這將導致它自動將 Java 堆的未使用部分返回給操作系統(tǒng)纱意。而在用戶控制下婶溯,可以可選地執(zhí)行完整的 GC,以使返回的內(nèi)存量最大化偷霉。
JAVA 13
1迄委、Dynamic CDS Archives
這一特性是在JEP310:Application Class-Data Sharing 基礎上擴展而來的,Dynamic CDS Archives中的CDS指的就是Class-Data Sharing类少。
那么叙身,這個JEP310是個啥東西呢?
我們知道在同一個物理機/虛擬機上啟動多個JVM時硫狞,如果每個虛擬機都單獨裝載自己需要的所有類信轿,啟動成本和內(nèi)存占用是比較高的。所以Java團隊引入了CDS的概念残吩,通過把一些核心類在每個JVM間共享财忽,每個JVM只需要裝載自己的應用類,啟動時間減少了泣侮,另外核心類是共享的即彪,所以JVM的內(nèi)存占用也減少了。
CDS 只能作用于 Boot Class Loader 加載的類活尊,不能作用于 App Class Loader 或者自定義的 Class Loader 加載的類隶校。
在 Java 10 中琼蚯,則將 CDS 擴展為 AppCDS,顧名思義惠况,AppCDS 不止能夠作用于 Boot Class Loader了遭庶,App Class Loader 和自定義的 Class Loader 也都能夠起作用,大大加大了 CDS 的適用范圍稠屠。也就說開發(fā)自定義的類也可以裝載給多個JVM共享了峦睡。
Java 10中包含的JEP310的通過跨不同Java進程共享公共類元數(shù)據(jù)來減少了內(nèi)存占用和改進了啟動時間。
但是权埠,JEP310中榨了,使用AppCDS的過程還是比較復雜的,需要有三個步驟:
1攘蔽、決定要 Dump 哪些 Class
2龙屉、將類的內(nèi)存 Dump 到歸檔文件中
3、使用 Dump 出來的歸檔文件加快應用啟動速度
這一次的JDK 13中的JEP 350 满俗,在JEP310的基礎上转捕,又做了一些擴展。允許在Java應用程序執(zhí)行結束時動態(tài)歸檔類唆垃,歸檔類將包括默認的基礎層 CDS(class data-sharing)存檔中不存在的所有已加載的應用程序類和庫類五芝。
也就是說,在Java 13中再使用AppCDS的時候辕万,就不在需要這么復雜了枢步。
在討論這個問題之前渐尿,想先問一個問題醉途,JVM的GC釋放的內(nèi)存會還給操作系統(tǒng)嗎?
GC后的內(nèi)存如何處置砖茸,其實是取決于不同的垃圾回收器的隘擎。因為把內(nèi)存還給OS,意味著要調整JVM的堆大小渔彰,這個過程是比較耗費資源的嵌屎。
在JDK 11中,Java引入了ZGC恍涂,這是一款可伸縮的低延遲垃圾收集器宝惰,但是當時只是實驗性的。并且再沧,ZGC釋放的內(nèi)存是不會還給操作系統(tǒng)的尼夺。
而在Java 13中,JEP 351再次對ZGC做了增強,本次 ZGC 可以將未使用的堆內(nèi)存返回給操作系統(tǒng)淤堵。之所以引入這個特性寝衫,是因為如今有很多場景中內(nèi)存是比較昂貴的資源,在以下情況中拐邪,將內(nèi)存還給操作系統(tǒng)還是很有必要的:
1慰毅、那些需要根據(jù)使用量付費的容器
2、應用程序可能長時間處于空閑狀態(tài)并與許多其他應用程序共享或競爭資源的環(huán)境扎阶。
3汹胃、應用程序在執(zhí)行期間可能有非常不同的堆空間需求。例如东臀,啟動期間所需的堆可能大于稍后在穩(wěn)定狀態(tài)執(zhí)行期間所需的堆着饥。
3、Reimplement the Legacy Socket API
使用易于維護和調試的更簡單惰赋、更現(xiàn)代的實現(xiàn)替換 java.net.Socket 和 java.net.ServerSocket API宰掉。
java.net.Socket和java.net.ServerSocket的實現(xiàn)非常古老,這個JEP為它們引入了一個現(xiàn)代的實現(xiàn)×薇簦現(xiàn)代實現(xiàn)是Java 13中的默認實現(xiàn)轨奄,但是舊的實現(xiàn)還沒有刪除,可以通過設置系統(tǒng)屬性jdk.net.usePlainSocketImpl來使用它們流部。
運行一個實例化Socket和ServerSocket的類將顯示這個調試輸出戚绕。這是默認的(新的):
4、Switch Expressions (Preview)
在JDK 12中引入了Switch表達式作為預覽特性枝冀。JEP 354修改了這個特性,它引入了yield語句,用于返回值。這意味著琉苇,switch表達式(返回值)應該使用yield, switch語句(不返回值)應該使用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中就多了一個關鍵字用于跳出switch塊了户辱,那就是yield,他用于返回一個值糙臼。和return的區(qū)別在于:return會直接跳出當前循環(huán)或者方法庐镐,而yield只會跳出當前switch塊。
在JDK 12中引入了Raw String Literals特性必逆,但在發(fā)布之前就放棄了。這個JEP在引入多行字符串文字(text block)在意義上是類似的。
text block名眉,文本塊粟矿,是一個多行字符串文字,它避免了對大多數(shù)轉義序列的需要损拢,以可預測的方式自動格式化字符串陌粹,并在需要時讓開發(fā)人員控制格式。
我們以前從外部copy一段文本串到Java中福压,會被自動轉義掏秩,如有一段以下字符串:
Hello,?Hollis
將其復制到Java的字符串中,會展示成以下內(nèi)容:
"
"?+
"
"?+
"
Hello,?Hollis
"?+
"
"?+
"
";
即被自動進行了轉義隧膏,這樣的字符串看起來不是很直觀哗讥,在JDK 13中,就可以使用以下語法了:
"""
Hello,?Hollis
"""
;
使用"""作為文本塊的開始符合結束符胞枕,在其中就可以放置多行的字符串杆煞,不需要進行任何轉義「海看起來就十分清爽了决乎。