枚举注解替换枚举
java 虚拟机内存分配
java 内存区域可分为
- 方法区 存放虚拟机加载的类信息,常量,静态变量等数据。
- 虚拟机栈 java 方法执行的内存模型:每个方法在执行的时候创建的栈帧,包括存储局部变量表,操作数栈,动态链接,方法出口等信息。
- 本地方法栈 主要与Native相关
- 堆 存放对象实例。
- 程序计数器 当前线程执行的字节码行号指示器。
java 数据类型占内存大小
java 数据类型分为基本数据类型和引用数据类型。
在32位系统上基本数据类型,本文中中的所有内存空间大小都在在32位系统上面。占用的内存大小为
基本数据类型 | 内存大小 |
---|---|
byte | 1 字节 |
short | 2 字节 |
char | 2 字节 |
int | 4 字节 |
float | 4 字节 |
double | 8 字节 |
long | 8 字节 |
boolean | 1 字节或者 4字节 |
在Java 中引用数据类型 String 也是一个类。简化源码为
public final class String{
private final char value[];
private int hash; //4 字节
private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
}
因此一个空的String 大小为 12(Header) + 4(char[] reference) + 4(int) + 4(Padding) = 24 bytes
关于枚举的使用
Enum需要占用比较大的内存空间,如果对内存空间敏感,请谨慎使用。
google官方文档有提到
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android
个人在目前google官方的文档没有找到这句话,但是网络都有转发这就话,姑且认为互联网都是有记忆的,认为这句话是正确的吧。同时查看android 的源码,与枚举有关的变量,都使用枚举注解,比如说设置控件是否可见,使用的也是枚举注解。因此认为google关键在Android应用上面尽量少用枚举,并且使用枚举注解替代枚举。
// 设置控件是否可见
public void setVisibility(@Visibility int visibility) {
setFlags(visibility, VISIBILITY_MASK);
}
// 设置布局的布局方向
public void setOrientation(@OrientationMode int orientation){}
枚举占用的内存空间
关于枚举占用的内存空间,先理解枚举的实现原理。
-
定义枚举
public enum FirstWeek { Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday }
-
编译枚举类
// 编译命令 javac FirstWeek.java
-
反编译
// 反编译命令 javap -p FirstWeek.class Compiled from "FirstWeek.java" // 类用final 修饰,保证类不能被继承 public final class com.chenxum.dragon.utils.FirstWeek extends java.lang.Enum<com.chenxum.dragon.utils.FirstWeek> { // 定义枚举实例 public static final com.chenxum.dragon.utils.FirstWeek Monday; public static final com.chenxum.dragon.utils.FirstWeek Tuesday; public static final com.chenxum.dragon.utils.FirstWeek Wednesday; public static final com.chenxum.dragon.utils.FirstWeek Thursday; public static final com.chenxum.dragon.utils.FirstWeek Friday; public static final com.chenxum.dragon.utils.FirstWeek Saturday; public static final com.chenxum.dragon.utils.FirstWeek Sunday; private static final com.chenxum.dragon.utils.FirstWeek[] $VALUES; public static com.chenxum.dragon.utils.FirstWeek[] values(); public static com.chenxum.dragon.utils.FirstWeek valueOf(java.lang.String); // 私有的构造函数, 原因是不能通过new 来实例化这个类 private com.chenxum.dragon.utils.FirstWeek(); static { // 调用父类的构造函数 MONDAY = new FirstWeek("MONDAY", 0); TUESDAY = new Day("TUESDAY", 1); WEDNESDAY = new Day("WEDNESDAY", 2); THURSDAY = new Day("THURSDAY", 3); FRIDAY = new Day("FRIDAY", 4); SATURDAY = new Day("SATURDAY", 5); SUNDAY = new Day("SUNDAY", 6); $VALUES = (new Day[] { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }); } }
枚举占用内存大小分析
FirstWeek成员变量有枚举的实例引用和一个数组。一个实例引用占用4个字节,一个数组引用也占有4个字节。
class FirstWeek{ public static final com.chenxum.dragon.utils.FirstWeek Monday; public static final com.chenxum.dragon.utils.FirstWeek Tuesday; public static final com.chenxum.dragon.utils.FirstWeek Wednesday; public static final com.chenxum.dragon.utils.FirstWeek Thursday; public static final com.chenxum.dragon.utils.FirstWeek Friday; public static final com.chenxum.dragon.utils.FirstWeek Saturday; public static final com.chenxum.dragon.utils.FirstWeek Sunday; private static final com.chenxum.dragon.utils.FirstWeek[] $VALUES; }
引用指向的实例对象也要占用额外的内存空间。枚举实例引用指向一个枚举。一个枚举的成员变量有name和ordinal。参见简化源码源码
class Enum{ private final String name; private final int ordinal; }
一个枚举类占用空间大小为 对象头8个字节+引用4个字节+(String 引用)4个字节+(int 型数据)+4个字节 = 20字节。
但是name是字符串,因此String 本身也需要占用空间。一个String 占用的空间为 24+n * 2;
对枚举类计算内存空间
class FirstWeek{ // 对象引用4个字节 public static final com.chenxum.dragon.utils.FirstWeek Monday; public static final com.chenxum.dragon.utils.FirstWeek Tuesday; public static final com.chenxum.dragon.utils.FirstWeek Wednesday; public static final com.chenxum.dragon.utils.FirstWeek Thursday; public static final com.chenxum.dragon.utils.FirstWeek Friday; public static final com.chenxum.dragon.utils.FirstWeek Saturday; public static final com.chenxum.dragon.utils.FirstWeek Sunday; // 数组引用对象4个字节,由于数组存放的是上面的引用实例,因此 占用空间 4* n private static final com.chenxum.dragon.utils.FirstWeek[] $VALUES; }
因此不计算枚举内部枚举引用指向的实例占用的空间,一个带有N个枚举值的枚举占用的空间为 n *4 + n *4 + 4;由于每个枚举引用对象的枚举实例含有一个String 对象,当枚举的命名非常长的时候,占用的内存空间是非常可怕的。
枚举注解
枚举注解类定义
@IntDef({
SecondWeek.Monday,
SecondWeek.Tuesday,
SecondWeek.Wednesday,
SecondWeek.Thursday,
SecondWeek.Friday,
SecondWeek.Saturday,
SecondWeek.Sunday,
})
public @interface SecondWeek {
int Monday = 1;
int Tuesday = 2;
int Wednesday = 3;
int Thursday = 4;
int Friday = 5;
int Saturday = 6;
int Sunday = 7;
}
在这里无法使用java 自带的命令进行编译 javac SecondWeek.java,报错无法找到IntDef,这个注解了。因此使用IDE编译后的Class文件反编译回java文件
反编译后为
// javap -p -c SecondWeek.class
Compiled from "SecondWeek.java"
public interface com.chenxum.dragon.utils.SecondWeek extends java.lang.annotation.Annotation {
public static final int Monday;
public static final int Tuesday;
public static final int Wednesday;
public static final int Thursday;
public static final int Friday;
public static final int Saturday;
public static final int Sunday;
}
从反编译后的文件可以看出,这是存在语法问题的,final没有赋值,因此继续反编译,将汇编指令也反编译处理。
// javap -v classxx,不仅会输出行号、本地变量表信息、反编译汇编代码,还会输出当前类用到的常量池等信息
Classfile /H:/dragon/app/build/intermediates/javac/debug/classes/com/chenxum/dragon/utils/SecondWeek.class
Last modified 2020-5-20; size 403 bytes
MD5 checksum 895b02673e1cde9588d38aaf51a999f8
Compiled from "SecondWeek.java"
public interface com.chenxum.dragon.utils.SecondWeek extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Class #22 // com/chenxum/dragon/utils/SecondWeek
#2 = Class #23 // java/lang/Object
#3 = Class #24 // java/lang/annotation/Annotation
#4 = Utf8 Monday
#5 = Utf8 I
#6 = Utf8 ConstantValue
#7 = Integer 1
#8 = Utf8 Tuesday
#9 = Integer 2
#10 = Utf8 Wednesday
#11 = Integer 3
#12 = Utf8 Thursday
#13 = Integer 4
#14 = Utf8 Friday
#15 = Integer 5
#16 = Utf8 Saturday
#17 = Integer 6
#18 = Utf8 Sunday
#19 = Integer 7
#20 = Utf8 SourceFile
#21 = Utf8 SecondWeek.java
#22 = Utf8 com/chenxum/dragon/utils/SecondWeek
#23 = Utf8 java/lang/Object
#24 = Utf8 java/lang/annotation/Annotation
{
public static final int Monday;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 1
public static final int Tuesday;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 2
public static final int Wednesday;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 3
public static final int Thursday;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 4
public static final int Friday;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 5
public static final int Saturday;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 6
public static final int Sunday;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 7
}
SourceFile: "SecondWeek.java"
这样就可以确定,确实是赋值了。
枚举注解占用空间
枚举注解占用的空间比较好计算,带有N个枚举值的注解类占用的空间为 n *4 。
总结
单纯考虑内存空间的情况下,枚举值为n。枚举占用的空间为 4 *n +4 *n +4 = 8 * n +4。(没有将String计算在内)。枚举注解占用的空间为 n * 4 。所以google 官方说Enums often require more than twice as much memory as static constants.
。
当我们定义枚举,只是为了编码方便,将枚举值在入参,switch中使用,完全可以使用枚举注解替代。枚举注解已有这些功能。
当然枚举还有其他功能,这些功能是无法使用枚举注解进行替换的。
-
情况1,android 目前只提供了枚举注解@StringDef和@IntDef,如果枚举值,不是int类型或者String 类型,则无法使用android 官方提供的枚举注解。
-
情况2。在某些情况下,自定义枚举,将多个变量有机的结合起来,这时候无法使用枚举注解。比如说常用的将错误码和错误描述结合起来,这时候使用枚举方便。
enum TransState{ STATE1(1,"交易成功"), STATE2(1,"交易失败"); private int code; private String message; TransState(int code, String message) { this.code = code; this.message = message; } }
- 情况3。因为枚举实现了Serializable,说明枚举本身是可以序列化的,如果枚举值需要保存在介质中或者通过Intent进行传递,使用枚举方便。
参考博客连接:
https://www.jianshu.com/p/6052cd4ea9ae