雖然好像慢了,但還是小小來記錄一下java 16到底有什麼具體的改變,方便我跟版本,畢竟9月就要發布java 17(LTS,長期穩定支援版本)了,總不能一直抱著祖傳java 8。

我就挑我覺得比較酷的來說,因為網路上的內容基本上都講得滿模糊的,所以乾脆直接看oracle的java document,再加入我自己的一點理解。

Record Classes

Record Classes的官方預設用途是被拿來實作簡單的"數據載體"的,基本上就是一個我覺得還算好用的語法糖。

具體功能大概是這樣。

record Rectangle(double length, double width) { }

開發者寫的這一段code等效於以下的程式碼。

public final class Rectangle {
private final double length;
private final double width;

public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}

double length() { return this.length; }
double width() { return this.width; }

// Implementation of equals() and hashCode(), which specify
// that two record objects are equal if they
// are of the same type and contain equal field values.
public boolean equals...
public int hashCode...

// An implementation of toString() that returns a string
// representation of all the record class's fields,
// including their names.
public String toString() {...}
}

可以從上面看到輸入的數據會被寫死在class創建的時候,並且自動生成equals,hashCode和toString function,我覺得不算驚艷,但還不錯,可以快速的來處理簡單得數據資料。

來講一下equals()跟hashCode()的用途,在java當中,==運算子比較的是物件實體或者說是記憶體位置是否相同,而equals()可以比較物件的值是否相同。

hashCode()則是會回傳一個將過hash函數處裡的int值,可以被用來做HashMap、HashSet等雜湊資料結構的索引,讓使用者可以以hashCode()快速實現相關結構。

另外不能宣告instance variables(non-static fields)或者是instance initializers。

record Rectangle(double length, double width) {

// Field declarations must be static:
BiFunction<Double, Double, Double> diagonal;

// Instance initializers are not allowed in records:
{
diagonal = (x, y) -> Math.sqrt(x*x + y*y);
}
}

類似這樣的code將不會被編譯。

但可以在Record Classes當中聲明instance methods,像是以下程式碼。

record Rectangle(double length, double width) {

// Nested record class
record RotationAngle(double angle) {
public RotationAngle {
angle = Math.toRadians(angle);
}
}

// Public instance method
public Rectangle getRotatedRectangleBoundingBox(double angle) {
RotationAngle ra = new RotationAngle(angle);
double x = Math.abs(length * Math.cos(ra.angle())) +
Math.abs(width * Math.sin(ra.angle()));
double y = Math.abs(length * Math.sin(ra.angle())) +
Math.abs(width * Math.cos(ra.angle()));
return new Rectangle(x, y);
}
}

Pattern Matching for instanceof

instanceof是java當中的保留關鍵字,用於確認變數型態,當相同時就返回true,不同就返回false。

Double d = 0;
if(d instanceof Double){//true
//做某件事情
}
if(d instanceof Boolean){//false
//做某件事情
}

大概就會像是這個樣子,可以降低程式的bug還有一些implements的子class分類會很好弄,像是以下這樣。

public interface Shape {
public static double getPerimeter(Shape shape) throws IllegalArgumentException {
if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
return 2 * r.length() + 2 * r.width();
} else if (shape instanceof Circle) {
Circle c = (Circle) shape;
return 2 * c.radius() * Math.PI;
} else {
throw new IllegalArgumentException("Unrecognized shape");
}
}
}

public class Rectangle implements Shape {
final double length;
final double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double length() { return length; }
double width() { return width; }
}

public class Circle implements Shape {
final double radius;
public Circle(double radius) {
this.radius = radius;
}
double radius() { return radius; }
}

而在java 16當中,有新的語法糖來簡化這個過程。

public static double getPerimeter(Shape shape)throws IllegalArgumentException {
if (shape instanceof Rectangle r) {
return 2 * r.length() + 2 * r.width();
} else if (shape instanceof Circle c) {
return 2 * c.radius() * Math.PI;
} else {
throw new IllegalArgumentExceptio("Unrecognized shape");
}
}

大概就是可以宣告一個變數並等同於shape以便後續使用。

Switch Expressions

Switch Expressions應該是我最喜歡的部分,他把switch弄得酷酷的。

public enum Day { SUNDAY, MONDAY, TUESDAY,
WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; }

// ...

int numLetters = 0;
Day day = Day.WEDNESDAY;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
numLetters = 6;
break;
case TUESDAY:
numLetters = 7;
break;
case THURSDAY:
case SATURDAY:
numLetters = 8;
break;
case WEDNESDAY:
numLetters = 9;
break;
default:
throw new IllegalStateException("Invalid day: " + day);
}
System.out.println(numLetters);

在java 16中把這種又長又冗的寫法改為以下這種,有夠香的啦!我前陣子寫計算機概論的作業的時候用的滿開心的。

Day day = Day.WEDNESDAY;    
System.out.println(
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
default -> throw new IllegalStateException("Invalid day: " + day);
}
);

而如果要寫更多(不只一行)操作的話,可以用{}把內容括號起來執行,例如以下。

int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> {
System.out.println(6);
yield 6;
}
case TUESDAY -> {
System.out.println(7);
yield 7;
}
case THURSDAY, SATURDAY -> {
System.out.println(8);
yield 8;
}
case WEDNESDAY -> {
System.out.println(9);
yield 9;
}
default -> {
throw new IllegalStateException("Invalid day: " + day);
}
};

封裝與其他部分

因為涉及到程式編寫層面較少,所以我把我覺得比較重要的改變拉出來集合成一個分類。

完整功能

  • java 16封裝了除了關鍵的內部API以外的所有內部方法,JDK默認為拒絕所有的非必要訪問,除非使用–add-opens打開特定套件。

  • 優化ZGC(java內部提供的垃圾記憶體回收機制),以多執行續處理,極大的降低延遲。

  • 可以將未使用的HotSpot VM metaspace 記憶體更快速的回傳給操作系統。

正在開發的項目和preview功能

  • Vector API : 在CPU架構上對向量運算做出優化,提供更高的性能。

  • Foreign Linker API : 把原本用來呼叫其他程式語言的JNI取代掉,該API提供靜態類型的純Java訪問,感覺比應該不會比JNI難用(?),我快受夠JNI了。

  • Sealed Classes : 可以限制那些class or interfaces可以extend or implement Sealed Classes和可以限制use of a superclass,目前還在預覽階段,但感覺是想未來逐漸取代掉class。

JDK內部改變

  • JDK 原程式碼開始使用C++ 14