环境: JDK8+kotlin 1.1.0
构建工具:Maven 3
项目中有一个定义报警类型的enum类最开始使用java编写, 后来改成了kotlin. IDE和编译都没有提示错误, 运行时却发现错误.ClassNotFoundException, Could not initialize class.
原来是kotlin使用companion代替static, 而enum类是在类初始化时就初始化实例, 如果在enum实例中引入到了companion object中的字段, ide和编译器都不会提示错误, 但是运行那么就会导致enum类初始化失败. 因为初始化实例时, companion object还没有初始化. 引用其字段, 导致NPE, 类初始化失败, 导致ClassNotFoundException, Could not initialize class.
Java代码
public enum AlarmType {
MEMORY("memory", AlarmType.CONTAINER_TYPE, true),
CPU("cpu", AlarmType.CONTAINER_TYPE, true);
public static final int DEFAULT_TYPE = 0;
public static final int CONTAINER_TYPE = 1;
public static final int DOCKER_DAEMON_TYPE = 2;
public static final int AGENT_TYPE = 3;
private String type;
private int generalType;
AlarmType(String type, int generalType) {
this.type = type;
this.generalType = generalType;
}
}
kotlin代码
kotlin中没有static的概念, 由companion object来代替. 我将以上的代码修改为kotlin时为以下的代码.
enum class AlarmType(val type: String, val generalType: Int) {
MEMORY("memory", AlarmType.CONTAINER_TYPE),
CPU("cpu", AlarmType.CONTAINER_TYPE);
companion object {
public val DEFAULT_TYPE = 0
public val CONTAINER_TYPE = 1
public val DOCKER_DAEMON_TYPE = 2
public val AGENT_TYPE = 3
}
}
kotlin字节码反编译
IDE没有报错, 编译也没有报错. 运行时却发现不能运行. 报ClassNotFoundException异常, Could not initialize class. 我将kotlin生成的class字节码用jd-gui反编译看到以下代码.(适度精简)
由1和2处的代码, 可知, 在enum类中, 实例的初始化是在类加载完后, static代码块中运行, 因为在kotlin中使用companion object代替static, 代码从Companion中取值来实例化enum的实例时, 却发现Companion对象为空, 空指针错误, 进而导致无法初始化类, 导致ClassNotFoundException.
public enum AlarmType {
private final String type;
public static final Companion Companion;
private static final int DEFAULT_TYPE = 0;
private static final int CONTAINER_TYPE = 1;
private static final int DOCKER_DAEMON_TYPE = 2;
private static final int AGENT_TYPE = 3;
static {
//enum类是在静态代码块中就初始化实例. 因为kotlin中, 使用companion替代static, 所以, 下面用了Companion对象. 在类初始化时, 导致NPE, 导致类不能初始化
AlarmType[] tmp6_2 = new AlarmType[2]; //1
tmp6_2[0] =
(MEMORY = new AlarmType("MEMORY", 0, "memory", Companion.getCONTAINER_TYPE()));
AlarmType[] tmp32_6 = tmp6_2;
tmp32_6[1] =
(CPU = new AlarmType("CPU", 1, "cpu", Companion.getCONTAINER_TYPE()));
$VALUES = tmp32_6;
//在初始化实例之后, 才初始化Companion对象. 导致出问题
Companion = new Companion(null); //2
}
private final int generalType;
protected AlarmType(String type, int generalType) {
this.type = type;
this.generalType = generalType;
}
public static final class Companion {
public final int getDEFAULT_TYPE() {
return AlarmType.access$getDEFAULT_TYPE$cp();
}
public final int getCONTAINER_TYPE() {
return AlarmType.access$getCONTAINER_TYPE$cp();
}
public final int getDOCKER_DAEMON_TYPE() {
return AlarmType.access$getDOCKER_DAEMON_TYPE$cp();
}
public final int getAGENT_TYPE() {
return AlarmType.access$getAGENT_TYPE$cp();
}
}
}
解决方案
改回java, 或者将companion object移动到另外一个类中. 这样enum初始化类执行static代码时就不会引用未初始化的Companion了.
总结
新语言还是有坑, 谷歌上都没有搜到这个错误. 如果是不应该这样用, 那么IDE和编译器就应该提示错误, 而不是等到运行时才报错.