這篇文章主要講解了“ Java 16 有什么新特征”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“ Java 16 有什么新特征”吧!
創(chuàng)新互聯(lián)技術(shù)團(tuán)隊(duì)十載來致力于為客戶提供網(wǎng)站建設(shè)、做網(wǎng)站、品牌網(wǎng)站制作、成都營(yíng)銷網(wǎng)站建設(shè)、搜索引擎SEO優(yōu)化等服務(wù)。經(jīng)過多年發(fā)展,公司擁有經(jīng)驗(yàn)豐富的技術(shù)團(tuán)隊(duì),先后服務(wù)、推廣了1000多家網(wǎng)站,包括各類中小企業(yè)、企事單位、高校等機(jī)構(gòu)單位。
2021年3月16日,甲骨文正式發(fā)布了Java 16!想當(dāng)年JDK1.6新出的場(chǎng)景和歷歷在目,一瞬間,版本已經(jīng)變成了16,真正體會(huì)了一把什么叫做光陰似箭,滄海桑田。雖然目前大部分的場(chǎng)合,Java8還占著主導(dǎo)地位,但我猜想各位Javaer應(yīng)該對(duì)Java16的新特性也大有興趣吧!
看完之后我覺得這次更新還是很有意思的,我就精選幾個(gè)Java16的新特性,供大家一飽眼福!(大家可以自己建個(gè)項(xiàng)目用起來試試)
想想你是怎么用instanceof的吧,一個(gè)例子:
if (obj instanceof String) { String s = (String) obj; // grr... ... }
這代碼是不是讓人蛋疼,我都知道是個(gè)String了,還讓我強(qiáng)轉(zhuǎn)一下,該改進(jìn)一下啦~
if (obj instanceof String s) { //這里s隨你用了 從類型判斷,到變量定義,類型轉(zhuǎn)換,一氣呵成,爽不爽? }
進(jìn)一步的,還可以這么用,模式變量s在判斷條件里直接使用
if (obj instanceof String s && s.length() > 5) { flag = s.contains("jdk"); }
不過要小心,下面的用法是錯(cuò)誤的(原因就不多解釋啦):
if (obj instanceof String s || s.length() > 5) { // Error! ... }
我們對(duì)Java最大的意見是啥?當(dāng)然是太繁瑣了,一個(gè)簡(jiǎn)單的功能,繁重的語法要整出好幾十行,不急,改進(jìn)這就來了,看看新的Recodes類型吧!
假設(shè)你現(xiàn)在有這么一個(gè)類:
這是一個(gè)典型的不可變的數(shù)據(jù)對(duì)象,equals ()方法, hashCode()方法,toString()方法其實(shí)都是比較通用的。但是我們不得不為它多寫那么幾行代碼。雖然有IDE的鼎力協(xié)助,但是看上去還是不怎么爽(如果沒有IDE,更要哭了)。不過沒事Records來了!用Records來表示上面的類,你只需要:
record Point(int x, int y) { }
是不是特別簡(jiǎn)單,感覺心里特別爽?record類和普通的class不太一樣,它會(huì)幫你隱式生成一些字段和構(gòu)造函數(shù)。比如上面的record就會(huì)編譯成這樣:
record Point(int x, int y) { // 隱式生成字段 private final int x; private final int y; // 隱式生成構(gòu)造函數(shù),并帶上所有的參數(shù) Point(int x, int y) { this.x = x; this.y = y; } }
ZGC是The Z Garbage Collector,是JDK 11中推出的一款低延遲垃圾回收器,它嘗試解決GC之痛,也就是停頓。
同時(shí),它也將面向以TB為單位的超大規(guī)模的內(nèi)存。在Java 16中,ZGC的線程棧處理的眾多操作,從檢查點(diǎn)移動(dòng)到了并發(fā)階段,這樣意味著停頓更小了。(后面準(zhǔn)備出個(gè)zgc相關(guān)的文章,順便測(cè)一波這次的停頓優(yōu)化時(shí)長(zhǎng))
在Java虛擬機(jī)中,元空間用來保存一些類的元信息,并且,元空間中的數(shù)據(jù)是可以被垃圾回收的。但不幸的是,空閑的未被使用的元空間并不會(huì)歸還給操作系統(tǒng),這就導(dǎo)致了內(nèi)存浪費(fèi)。
這個(gè)新特性就是為了解決這個(gè)問題,它使得虛擬機(jī)可以從元空間中歸還未使用的內(nèi)存,從而更加有效得利用物理內(nèi)存。同時(shí)有一個(gè)新的虛擬機(jī)參數(shù)可以用來控制這種回收的執(zhí)行強(qiáng)度:-XX:MetaspaceReclaimPolicy=(balanced|aggressive|none)
Unix Domain套接字本身提供給了一套兼容于網(wǎng)絡(luò)編程,但是更加可靠、高效、安全的進(jìn)程間通信方式,它不需要經(jīng)過網(wǎng)絡(luò)協(xié)議棧,不需要打包拆包、計(jì)算校驗(yàn)和、維護(hù)序號(hào)和應(yīng)答等,只是將應(yīng)用層數(shù)據(jù)從一個(gè)進(jìn)程拷貝到另一個(gè)進(jìn)程。
在Java 16中,已經(jīng)可以直接使用這種套接字(Unix-domain (AF_UNIX),雖然叫做UNIX套接字,windows 10和Windows Server 2019也是可以使用的)了。為了支持Unix Domain套接字,新增了專門的java.net.UnixDomainSocketAddress類,下面看一下它的使用:
提供了一個(gè)新的打包工具jpackage,用來打包獨(dú)立的Java應(yīng)用程序。這個(gè)工具可以生成windows上的exe和msi,MacOS上的pkg和dmg,以及l(fā)inux上的deb和rpm。它很好的給用戶提供了一鍵式安裝Java程序的好方法。
比如,對(duì)于非模塊化的應(yīng)用,可以這么打包:
jpackage --name myapp --input lib --main-jar main.jar 或者 直接指定main class jpackage --name myapp --input lib --main-jar main.jar --main-class myapp.Main
對(duì)于模塊化的應(yīng)用:
jpackage --name myapp --module-path lib -m myapp 或者直接指定main class jpackage --name myapp --module-path lib -m myapp/myapp.Main
值對(duì)象,比如java.lang.Integer, java.lang.Double之類的不變對(duì)象,在廢棄構(gòu)造函數(shù)的基礎(chǔ)上,進(jìn)一步標(biāo)記為forRemoval(不要再使用它們的構(gòu)造函數(shù)了哦!)。
同時(shí),如果在值對(duì)象上進(jìn)行同步,將會(huì)被警告,比如:
Double d = 20.0; synchronized (d) { ... } // javac和hotsopt都會(huì)警告 Object o = d; synchronized (o) { ... } // HotSpot 會(huì)警告
在虛擬機(jī)層面,還提供了參數(shù)可以在虛擬機(jī)層面控制報(bào)錯(cuò)行為:
-XX:DiagnoseSyncOnValueBasedClasses=1 將這種同步行為視為致命錯(cuò)誤
-XX:DiagnoseSyncOnValueBasedClasses=2 打開日志,在控制臺(tái)和飛行記錄儀中記錄這種同步行為
對(duì)于一些JDK內(nèi)部的API,作出了更嚴(yán)格的限制。比如 com.sun.*, jdk.*, and org.*這些包里的API,從Java 16開始,默認(rèn)已經(jīng)禁止使用了。因此,鼓勵(lì)大家使用標(biāo)準(zhǔn)API,而不是內(nèi)部API(點(diǎn)擊這里查看可以替換的內(nèi)部API)。比如說下面這句代碼,在Java 16中將報(bào)錯(cuò):
System.out.println(sun.security.util.SecurityConstants.ALL_PERMISSION);
錯(cuò)誤示例:
Exception in thread "main" java.lang.IllegalAccessError: class Test (in unnamed module @0x5e481248) cannot access class sun.security.util.SecurityConstants (in module java.base) because module java.base does not export sun.security.util to unnamed module @0x5e481248
同時(shí),JDK還提供--illegal-access參數(shù)用來控制對(duì)內(nèi)部API的使用:
--illegal-access=permit 允許使用內(nèi)部API
--illegal-access=warn 允許使用內(nèi)部API,不過每次使用會(huì)得到一個(gè)警告
--illegal-access=debug 允許使用內(nèi)部API,會(huì)更詳細(xì)的打印每一個(gè)錯(cuò)誤的調(diào)用堆棧,用它你就可以找到你在哪里有不正確的調(diào)用,就可以修復(fù)那些不合適的使用
--illegal-access=deny 禁止使用內(nèi)部API
我們知道,像Go這樣的后起之秀,已經(jīng)在內(nèi)部使用了AVX指令,性能飆升。Java在這方面也不甘示弱,在Java 16中,向量API作為一個(gè)孵化項(xiàng)目,允許我們直接使用SIMD指令來提高性能(如果有效使用,這波就帶你起飛了)。
讓我們先一睹為快吧!
下面是一個(gè)簡(jiǎn)單的標(biāo)量計(jì)算:
void scalarComputation(float[] a, float[] b, float[] c) { for (int i = 0; i < a.length; i++) { c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f; } }
重點(diǎn)來了,使用AVX2帶你起飛:
//256位的向量浮點(diǎn)運(yùn)算 static final VectorSpeciesSPECIES = FloatVector.SPECIES_256; void vectorComputation(float[] a, float[] b, float[] c) { int i = 0; int upperBound = SPECIES.loopBound(a.length); for (; i < upperBound; i += SPECIES.length()) { // FloatVector va, vb, vc; var va = FloatVector.fromArray(SPECIES, a, i); var vb = FloatVector.fromArray(SPECIES, b, i); var vc = va.mul(va). add(vb.mul(vb)). neg(); vc.intoArray(c, i); } for (; i < a.length; i++) { c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f; } }
你是不是有抱怨過JNI太難用了?沒關(guān)系,JNI的進(jìn)化版來了,這就是外部鏈接器!它提供了一個(gè)靜態(tài)的,純Java的訪問本地native 代碼的方法,它將極大簡(jiǎn)化我們調(diào)用本地代碼的過程。
新的API:
LibraryLookup::ofDefault:返回被JVM加載的庫(kù),可以看到所有這些庫(kù)的符號(hào)
LibraryLookup::ofPath:加載指定路徑
LibraryLookup::ofLibrary :根據(jù)庫(kù)名,加載庫(kù)
下面代碼展示了,使用Java調(diào)用clang庫(kù)中的clang_getClangVersion()方法:
LibraryLookup libclang = LibraryLookup.ofLibrary("clang"); LibraryLookup.Symbol clangVersion = libclang.lookup("clang_getClangVersion");
另外一個(gè)重要的類是C鏈接器:
interface CLinker { MethodHandle downcallHandle(LibraryLookup.Symbol func, MethodType type, FunctionDescriptor function); MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function); }
downcallHandle()表示在Java中調(diào)用本地方法;upcallStub()方法在native方法中調(diào)用java代碼。
下面的代碼展示了如何在Java代碼中,調(diào)用C函數(shù)size_t strlen(const char *s):
MethodHandle strlen = CLinker.getInstance().downcallHandle( LibraryLookup.ofDefault().lookup("strlen"), MethodType.methodType(long.class, MemoryAddress.class), FunctionDescriptor.of(C_LONG, C_POINTER) );
上面代碼首先找到strlen符號(hào);然后描述它的簽名。最后使用downcallHandle()得到表示strlen()函數(shù)的MethodHandle對(duì)象;最后,就可以調(diào)用strlen()方法啦~
try (MemorySegment str = CLinker.toCString("Hello")) { long len = strlen.invokeExact(str.address()); // 5 }
反過來,也可以把Java函數(shù)作為參數(shù)傳遞給C函數(shù)進(jìn)行回調(diào):
比如有一個(gè)C函數(shù):
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
要調(diào)用這個(gè)C函數(shù),必須準(zhǔn)備一個(gè)函數(shù)指針compar,為了使用它,我們首先要得到它的MethodHandle:
MethodHandle qsort = CLinker.getInstance().downcallHandle( LibraryLookup.ofDefault().lookup("qsort"), MethodType.methodType(void.class, MemoryAddress.class, long.class, long.class, MemoryAddress.class), FunctionDescriptor.ofVoid(C_POINTER, C_LONG, C_LONG, C_POINTER) );
接著,用一個(gè)純Java類來實(shí)現(xiàn)compar函數(shù):
class Qsort { static int qsortCompare(MemoryAddress addr1, MemoryAddress addr2) { return MemoryAccess.getIntAtOffset(MemorySegment.ofNativeRestricted(), addr1.toRawLongValue()) - MemoryAccess.getIntAtOffset(MemorySegment.ofNativeRestricted(), addr2.toRawLongValue()); } }
然后需要一個(gè)MethodHandle指向上述函數(shù):
MethodHandle comparHandle = MethodHandles.lookup() .findStatic(Qsort.class, "qsortCompare", MethodType.methodType(int.class, MemoryAddress.class, MemoryAddress.class));
然后,使用upcallStub()方法,得到一個(gè)函數(shù)指針(應(yīng)該說是用Java描述的C函數(shù)指針):
MemorySegment comparFunc = CLinker.getInstance().upcallStub(comparHandle, FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER)); );
最后,把這個(gè)"C函數(shù)指針"傳給C函數(shù):
try (MemorySegment array = MemorySegment.allocateNative(4 * 10)) { array.copyFrom(MemorySegment.ofArray(new int[] { 0, 9, 3, 4, 6, 5, 1, 8, 2, 7 })); qsort.invokeExact(array.address(), 10L, 4L, comparFunc.address()); int[] sorted = array.toIntArray(); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] }
好了,這就是使用外部鏈接API來使用本地代碼的全過程,是不是很酷炫呢?
類的繼承是面向?qū)ο蟮囊粋€(gè)重要特性,但是濫用繼承對(duì)對(duì)象模型的建模也是非常不利的。對(duì)于這一點(diǎn),Java還有較大的改進(jìn)空間,密封類,正式對(duì)對(duì)象繼承的一種重大改進(jìn)。
首先,來看JDK內(nèi)部的一個(gè)例子:
package java.lang; abstract class AbstractStringBuilder { ... } public final class StringBuffer extends AbstractStringBuilder { ... } public final class StringBuilder extends AbstractStringBuilder { ... }
AbstractStringBuilder有兩個(gè)子類StringBuffer和StringBuilder。但是,我們的代碼卻無法繼承AbstractStringBuilder,因?yàn)锳bstractStringBuilder是包內(nèi)可見的,并不是public的。在很多場(chǎng)合,我們的對(duì)象模式其實(shí)并不希望徹底公開,我們有時(shí)候僅僅希望只有一些指定的類可以繼承,而不是可以任由繼承擴(kuò)展。這就是密封類的設(shè)計(jì)初衷。
密封類/接口在聲明的時(shí)候,就可以指定哪些類可以從這里繼承,比如:
package com.example.geometry; public abstract sealed class Shape permits Circle, Rectangle, Square { ... }
注意新關(guān)鍵字sealed,它表示被修飾的類或者接口是密封的,緊接著使用permits關(guān)鍵字,指定哪些子類可以繼承它,這里只有Circle,Rectangle和Square可以從Shape繼承(繼承類和密封類必須要在同一個(gè)模塊,如果在unamed模塊,就需要在同一個(gè)package)。
使用密封類,還有一些限制,比如:
1.子類必須是直接繼承,而不是間接的
2.子類必須說明如果處理得到的密封屬性,三選一,必選一個(gè):
子類標(biāo)記為final,一了百了
子類也作為sealed類,并做有限的繼承擴(kuò)展
子類申明為non-sealed,公開使用(這種情況,密封類的初衷就被打破了,繼承關(guān)系就不可控了)
下面的代碼說明了這3種情況:
package com.example.geometry; //使用final public final class Circle extends Shape { ... } //使用sealed public sealed class Rectangle extends Shape permits TransparentRectangle, FilledRectangle { ... } public final class TransparentRectangle extends Rectangle { ... } public final class FilledRectangle extends Rectangle { ... } //使用non-sealed public non-sealed class Square extends Shape { ... }
感謝各位的閱讀,以上就是“ Java 16 有什么新特征”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì) Java 16 有什么新特征這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!