何時使用primitive type與wrapper type?
技術人員在閱讀一些關於編碼效率的文章時,多少會看到要使用Java的primitive type或wrapper type之間的爭議。要求使用primitive type的原因不外乎是能夠盡可能的減少物件產生與管理時的系統資源overhead。例如如果服務需要處理大量的請求,而每個請求的處理過程中的數字或布林值都是對象的話,那麼影響到的資源使用確實可能是巨大的。所以問題是,我們在進行業務開發時使用的entity或DTO類中的字段究竟該使用primitive type或wrapper type呢?
優點:
* 允許null,因此在用戶複值時可以透過null來表示空白值、無值等字段的狀態。
* 以整數類來說,如果使用的值在-128到127之間的話,JVM使用的會是內存中的緩存對象而非新建對象,因此這個狀況下的overhead極低。
* Wrapper類中有包含多個便利方法,可以在lambda中使用,例如Integer::valueOf。
缺點:
* 不可變性(immutability):因為warpper class的實體是不可變的,所以任何對值的變動都需要建立新對象。
* 因為所有數字都是對象,因此存在對象處理的overhead,所有數值計算都會較慢
優點:
* 計算較快
缺點:
* 所有未被指定的值都會被預設為0,這在業務代碼中進行處理時可能會造成混淆。例如:現在看到某自斷的值是0,那麼是用戶選擇了0,還是沒有進行任何選擇?
所以,wrappre所帶來的一個比較明顯的差異,實際上也是因為這個差異造成了他比較適合作為DTO自斷值的原因就是「它允許null」。因為在序列化過程中,我們可以透過JSON序列化框架的設定來決定是否輸出該值,以減少序列化後對象的字段數量,並告知API的使用方:現在確實沒有這個值,而非給予0之後要求使用方進行其他判斷。
例如以下這個類其中有一個SSN字段,分別使用了int與Integer:
```java
class Person {
int SSN;
}
```
```java
class Person {
Integer SSN;
}
```
在第一段代碼中,你無法讓SSN置為「未設定」狀態。只要你new了這個對象,其中SSN的值就必定是0。因此如果其他人想要正確的使用SSN,那麼就必須先對該值進行有無初始化的判斷。
在第二段代碼中,我們則可以在沒使用SSN的狀況下讓他持續為null。即便我們不小心使用到了未初始化的SSN,我們得到的會是一個明確的NullPointerException而非隱晦不明的可能邏輯錯誤,這對開發這來說無非是更加友善的。
而在不需要可null (non-nullable) 的狀況下,primitive types則確實是比較好的選擇,畢竟他們可以更加有效率地進行運算了。而且,在函數中primitive type的變數和本地變數都是保存在stack中的,訪問速度比較快,對象變數則一樣在heap中,如以下的正反例:
反例:
```java
public final class Accumulator {
private double result = 0.0D;
public void addAll(@NonNull double[] values) {
for(double value : values) {
result += value;
}
}
...
}
```
正例:
```java
public final class Accumulator {
private double result = 0.0D;
public void addAll(@NonNull double[] values) {
double sum = 0.0D;
for(double value : values) {
sum += value;
}
result += sum;
}
...
}
```
2. https://stackoverflow.com/questions/1570416/when-to-use-wrapper-class-and-primitive-type
Wrapper class: java.lang.Integer
優點:
* 允許null,因此在用戶複值時可以透過null來表示空白值、無值等字段的狀態。
* 以整數類來說,如果使用的值在-128到127之間的話,JVM使用的會是內存中的緩存對象而非新建對象,因此這個狀況下的overhead極低。
* Wrapper類中有包含多個便利方法,可以在lambda中使用,例如Integer::valueOf。
缺點:
* 不可變性(immutability):因為warpper class的實體是不可變的,所以任何對值的變動都需要建立新對象。
* 因為所有數字都是對象,因此存在對象處理的overhead,所有數值計算都會較慢
Primitive type: int
優點:
* 計算較快
缺點:
* 所有未被指定的值都會被預設為0,這在業務代碼中進行處理時可能會造成混淆。例如:現在看到某自斷的值是0,那麼是用戶選擇了0,還是沒有進行任何選擇?
所以,wrappre所帶來的一個比較明顯的差異,實際上也是因為這個差異造成了他比較適合作為DTO自斷值的原因就是「它允許null」。因為在序列化過程中,我們可以透過JSON序列化框架的設定來決定是否輸出該值,以減少序列化後對象的字段數量,並告知API的使用方:現在確實沒有這個值,而非給予0之後要求使用方進行其他判斷。
例如以下這個類其中有一個SSN字段,分別使用了int與Integer:
```java
class Person {
int SSN;
}
```
```java
class Person {
Integer SSN;
}
```
在第一段代碼中,你無法讓SSN置為「未設定」狀態。只要你new了這個對象,其中SSN的值就必定是0。因此如果其他人想要正確的使用SSN,那麼就必須先對該值進行有無初始化的判斷。
在第二段代碼中,我們則可以在沒使用SSN的狀況下讓他持續為null。即便我們不小心使用到了未初始化的SSN,我們得到的會是一個明確的NullPointerException而非隱晦不明的可能邏輯錯誤,這對開發這來說無非是更加友善的。
而在不需要可null (non-nullable) 的狀況下,primitive types則確實是比較好的選擇,畢竟他們可以更加有效率地進行運算了。而且,在函數中primitive type的變數和本地變數都是保存在stack中的,訪問速度比較快,對象變數則一樣在heap中,如以下的正反例:
反例:
```java
public final class Accumulator {
private double result = 0.0D;
public void addAll(@NonNull double[] values) {
for(double value : values) {
result += value;
}
}
...
}
```
正例:
```java
public final class Accumulator {
private double result = 0.0D;
public void addAll(@NonNull double[] values) {
double sum = 0.0D;
for(double value : values) {
sum += value;
}
result += sum;
}
...
}
```
參考
1. https://stackoverflow.com/questions/48767012/should-i-use-a-wrapper-or-primitive-type-as-field-while-using-jackson2. https://stackoverflow.com/questions/1570416/when-to-use-wrapper-class-and-primitive-type
留言
張貼留言