為什麼說列舉更佔記憶體,列舉原理是什麼?
為什麼說列舉更佔記憶體,列舉原理是什麼?
從以前學習java 開始就聽說列舉很佔記憶體,然後老版Android開發指南文章也指出,列舉通常需要比靜態常量多兩倍的記憶體。你應該嚴格避免在android上使用列舉。那麼究竟為什麼說列舉更佔記憶體呢?本文就是通過這種方法來分析列舉為什麼佔記憶體的,而不是說拒絕列舉。
關於 Enum
Enum
一般用來表示一組相同型別可列舉的常量。如性別、日期、月份、顏色等。對這些屬性用常量的好處是顯而易見的,不僅可以保證單例,且比較時候可以用 ”==”
來替換 equals
。
基本使用
看看列舉的基本使用
public enum Color {
RED , BLUE,GREEN,BLACK ;
}
原理分析
通常會使用javap -c Color.class
反編譯class
檔案,檢視生成的位元組碼,如下:
Compiled from "Color.java"
public final class org.fast.clean.Color extends java.lang.Enum<org.fast.clean.Color> {
public static final org.fast.clean.Color RED;
public static final org.fast.clean.Color BLUE;
public static final org.fast.clean.Color GREEN;
public static final org.fast.clean.Color BLACK;
public static org.fast.clean.Color[] values();
Code:
0: getstatic #1 // Field $VALUES:[Lorg/fast/clean/Color;
3: invokevirtual #2 // Method "[Lorg/fast/clean/Color;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Lorg/fast/clean/Color;"
9: areturn
public static org.fast.clean.Color valueOf(java.lang.String);
Code:
....
9: areturn
static {};
Code:
0: new #4 // class org/fast/clean/Color
3: dup
4: ldc #7 // String RED
6: iconst_0
7: invokespecial #8 // Method "<init>":(Ljava/lang/
....
10: putstatic #9 // Field RED:Lorg/fast/clean/Color;
13: new #4 // class org/fast/clean/Color
16: dup
17: ldc #10 // String BLUE
83: return
}
org.fast.clean.Color
類被編譯成繼承自繼承自java.lang.Enum
的final
類,其中的成員變數也是final常量,它們都不能被修改的。
下面使用jad 反編譯生成jad原始碼檔案, 檔案顯示每個方法的位元組碼的實際作用,與原始碼做出對比。
public final class Color extends Enum
{
public static Color[] values()
{
return (Color[])$VALUES.clone();
}
public static Color valueOf(String s)
{
return (Color)Enum.valueOf(org/fast/clean/Color, s);
}
private Color(String s, int i)
{
super(s, i);
}
public static final Color RED;
public static final Color BLUE;
public static final Color GREEN;
public static final Color BLACK;
private static final Color $VALUES[];
static
{
RED = new Color("RED", 0);
BLUE = new Color("BLUE", 1);
GREEN = new Color("GREEN", 2);
BLACK = new Color("BLACK", 3);
$VALUES = (new Color[] {
RED, BLUE, GREEN, BLACK
});
}
}
通過比較位元組碼和原始碼的區別,我們可以知道為什麼列舉確實比簡單的靜態變數佔用的記憶體要更多。
Android 簡單替代列舉的方法
嚴格來講,這個使用方法是有很多缺陷的,但是使用下面的方法就能滿足需求的話,那麼用Java Enum
是會帶來各種更大的開銷。
- 方法一:使用介面變數
介面變數預設都是public static final
的,個人理解介面只是對一類事物的屬性和行為更高層次的抽象。對修改關閉,對擴充套件(不同的實現implements
)開放,介面是對開閉原則的一種體現。
public interface ErrorCode {
int ERROR_MANUAL_EXP = 100;
int ERROR_MANUAL_BACK = 101;
}
使用javap Color.class
反編譯class
檔案,檢視生成的位元組碼,如下:
Compiled from "ErrorCode.java"
interface org.fast.clean.ErrorCode {
public static final int ERROR_MANUAL_EXP;
public static final int ERROR_MANUAL_BACK;
}
可以看出就是一個public static final
的靜態變數。
- 方法二:使用support-annotations 註解庫
從Android Support Library19.1
版本開始引入了一個新的註解庫,使用 com.android.support:support-annotations
,這個官方的註解支援庫中包含了許多很好的註解,可以幫助我們在編譯的時候就找到錯誤。IntDef
和 StringDef
是包含在庫中的兩個關於常量的註解,我們可以用來代替列舉其中包括了很多有用的元註解,可以用來修飾程式碼,如@NonNull
,@StringRes
,@IntDef
,@StringDef
等等
下面我們使用@IntDef
來替代列舉,方法如下
public static final int RED = 0;
public static final int BLUE = 1;
public static final int GREEN = 2;
@IntDef({RED, BLUE, GREEN})
@Retention(RetentionPolicy.SOURCE)
public @interface Colors {
}
gradle 依賴:
dependencies { compile ‘com.android.support:support-annotations:24.2.0’ }
等等還有其他方法。
列舉單例
列舉在單例的使用也是很平常的
public enum SingleTon {
INSTANCE;
}
//單例物件的獲取:
SingleTon instance = SingleTon.INSTANCE;
通過檢視java.lang.Enum
原始碼如下:
/**
* prevent default deserialization
*/
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can`t deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can`t deserialize enum");
}
對於單例是否安全,主要考慮以下兩方面:序列化和反序列方面、執行緒安全方面。
- 對於序列化和反序列化,因為每一個列舉型別和列舉變數在JVM中都是唯一的,即Java在序列化和反序列化列舉時做了特殊的規定,列舉的
writeObject
、readObject
、readObjectNoData
、writeReplace
和readResolve
等方法是被編譯器禁用的,因此也不存在實現序列化介面後呼叫readObject
會破壞單例的問題。 - 對於執行緒安全方面,類似於普通的餓漢模式,通過在第一次呼叫時的靜態初始化建立的物件是執行緒安全的。
所以使用列舉也是一種比較好的單例模式,通過反編譯我們知道,缺點就是不能夠繼承,因為final
了。
通過上面的分析列舉,我們只是討論為什麼說列舉更佔記憶體,列舉原理是什麼,而不是讓我們拒絕使用列舉,因為就幾個列舉,讓應用記憶體開銷大,那就實在是太可怕了。。。更重要的是通過比較位元組碼和原始碼,我們可以發現很多的問題,一個很重要的作用就是了解很多編譯器內部的工作機制。
原文地址http://www.bieryun.com/2974.html
相關文章
- Rust 列舉類是什麼Rust
- 什麼是常見缺陷列舉 (CWE)
- 為什麼建議你使用列舉?
- 講講怎麼列舉MmMapViewInSystemSpace分配的記憶體View記憶體
- 舉例說明什麼是IIFEs?它有什麼好處?
- 什麼是覆蓋?有什麼作用?請舉例說明。
- 為什麼使用列舉作為配置項(enum as configuration)是反開發模式的模式
- 7.1 實現程式記憶體塊列舉記憶體
- Java列舉:為什麼它是單例模式的最佳選擇?Java單例模式
- Swift列舉關聯值的記憶體探究Swift記憶體
- Java記憶體模型是什麼,為什麼要有Java記憶體模型,Java記憶體模型解決了什麼問題?Java記憶體模型
- 什麼是閉包?舉個例子
- 為什麼我牆裂建議大家使用列舉來實現單例。單例
- 為什麼我們要學習DMAIC?—舉例說明AI
- 什麼是Java記憶體模型?Java記憶體模型
- 什麼是Java記憶體模型Java記憶體模型
- Java 列舉、JPA 和 PostgreSQL 列舉JavaSQL
- 列舉
- 列舉和列舉的取值範圍
- 什麼是框架?為什麼說 Angular 是框架?框架Angular
- 什麼叫執行緒安全,舉例說明。執行緒
- Python記憶體檢視是什麼Python記憶體
- 為什麼遊戲與列車是天作之合?遊戲
- 你知道什麼是二、三級域名嗎?舉例說明下
- 舉例理解什麼是程式,執行緒執行緒
- SQL Server為什麼這麼耗記憶體SQLServer記憶體
- C# 中的“智慧列舉”:如何在列舉中增加行為C#
- Java記憶體模型FAQ(一) 什麼是記憶體模型Java記憶體模型
- MongoDB 如何使用記憶體?為什麼記憶體滿了?MongoDB記憶體
- MongoDB如何使用記憶體?為什麼記憶體滿了?MongoDB記憶體
- java稀疏陣列是什麼Java陣列
- 《RabbitMQ》什麼是死信佇列MQ佇列
- 什麼是訊息佇列?佇列
- Enumeration列舉
- 列舉類
- Java列舉Java
- scala 列舉
- 我在大廠做 CR——為什麼建議使用列舉來替換布林值