Java 反射 - 枚举生成
Java reflection - enum generate
我制作的程序可以使用反射生成 class、enum 和接口的源代码,但我在生成 Enum 时遇到问题。
我的EumTestclass
public enum EnumTest{
a,b;
private String r;
private EnumTest(){
}
private void some(){
}
public int[] some2(int[] b){
return b;
}
}
生成枚举文件的方法
private void generateEnum(Class<?> cls,PrintWriter writer) {
this.writePackage(cls, writer);
this.writeAnnotations(writer, cls.getDeclaredAnnotations());
writer.write(Modifier.toString(cls.getModifiers())+ " enum " + cls.getSimpleName());
this.writeImplementation(cls, writer);
writer.write("{");
this.writeNewLine(writer);
Object[] cons = cls.getEnumConstants();
for (int i = 0; i < cons.length; i++) {
writer.write(cons[i].toString());
if(i != cons.length - 1)
writer.write(",");
}
writer.write(";");
this.writeNewLine(writer);
this.writeFields(cls, writer);
this.writeConstructors(cls, writer);
this.writeMethods(cls,writer);
writer.write("}");
}
结果是这个新枚举
package metaprogramovanie.test;
public final enum EnumTest{
a,b;
private java.lang.String r;
private static final metaprogramovanie.test.EnumTest[] $VALUES;
private EnumTest(java.lang.String arg0,int arg1){
}
public static metaprogramovanie.test.EnumTest[] values(){
return null;
}
public static metaprogramovanie.test.EnumTest valueOf(java.lang.String arg0){
return null;
}
private void some(){
}
public int daco(int arg0){
return 0;
}
}
如您所见,存在一些错误。例如 Modifier generate FINAL 和 enum 不能是 final,接下来还有更多的方法和字段,构造函数有参数 ...
构造函数生成
private void writeConstructors(Class<?> cls, PrintWriter writer){
Constructor[] cons = cls.getDeclaredConstructors();
for (Constructor constructor : cons) {
this.writeAnnotations(writer, constructor.getDeclaredAnnotations());
writer.write(Modifier.toString(constructor.getModifiers()) + " " + cls.getSimpleName());
this.writeParameters(writer,constructor.getParameters());
writer.write("{");
this.writeNewLine(writer);
writer.write("}");
this.writeNewLine(writer);
}
}
字段生成
private void writeFields(Class<?> cls, PrintWriter writer){
Field[] atr = cls.getDeclaredFields();
for (Field field : atr) {
if(field.isEnumConstant()){
System.out.println("JE");
}
else{
this.writeAnnotations(writer, field.getDeclaredAnnotations());
writer.write(Modifier.toString(field.getModifiers())+" " + field.getType().getTypeName()+ " " + field.getName());
if(Modifier.isStatic(field.getModifiers())){
try{
Object value = field.get(null);
writer.write(" = " + this.getFieldValue(field));
}catch(IllegalAccessException ex){
}
}
writer.write(";");
this.writeNewLine(writer);
}
}
this.writeNewLine(writer);
}
方法生成
private void writeMethods(Class<?> cls, PrintWriter writer){
Method[] methods = cls.getDeclaredMethods();
for (Method met : methods) {
this.writeAnnotations(writer, met.getDeclaredAnnotations());
writer.write(Modifier.toString(met.getModifiers())+" "+met.getReturnType().getTypeName() +" "+ met.getName());
this.writeParameters(writer, met.getParameters());
writer.write("{");
this.writeNewLine(writer);
if(!met.getReturnType().equals(Void.TYPE)){
this.writeMethodBody(writer,met);
}
this.writeNewLine(writer);
writer.write("}");
this.writeNewLine(writer);
}
this.writeNewLine(writer);
}
如果您有任何想法如何获得可以编译的枚举。
这里解释了为什么你会得到所有额外的东西。
枚举是使用编译器合成的大量代码实现的,因此 "behind the scenes" 它们的行为与 Java 语言规范指定的一样,不需要对 JVM 进行彻底更改。
Java Language Specification 表示,例如:
- 一个
enum
是隐含的 final
除非有一个带有主体的常量。
- 任何
enum
隐式声明方法 values()
和 valueOf(String)
.
因此,您获得了 EnumTest
的 final
修饰符,并且获得了两个额外的方法。
此外,编译器会合成一些代码以帮助有效地实现一些所需的东西。例如,要复制并通过方法 values()
编辑的数组的源放置在一个名为 $VALUES
的合成字段中。它在枚举的初始化时被填满,然后每当调用values()
时就可以复制它。
此外,为了正确初始化常量,编译器向每个构造函数添加了两个隐藏参数。所以如果你有一个空的构造函数,它在幕后实际上有两个参数——常量的名称和它的序号。如果您有一个带一个参数的构造函数,那么在幕后它将有 3 个参数。
当您拥有自己的常量时,会创建额外的合成代码。
编译器完成的所有这些后期实现都没有记录在 JLS 中,基本上由任何编译器决定如何实现。只要枚举作为编译器生成的任何代码的结果正确运行,它就可以自由地以它选择的任何方式生成它。
解决您遇到的一些问题的技巧:
不使用 Modifier.toString(cls.getModifiers())
打印 enum
修饰符,而是使用
Modifier.toString(cls.getModifiers() & ~ Modifier.FINAL & ~ Modifier.ABSTRACT)
这从输出中排除了 final
修饰符以及 abstract
修饰符,如果您向 enum
添加抽象方法(并处理常量体,这是你到目前为止还没有做过的事情)。
- 使用
Field.isSynthetic()
方法检查某个字段是否被编译器合成,不输出该方法returntrue
的字段。这将摆脱 $VALUES
字段。
values()
和 valueOf(String)
这两种方法不被认为是合成的,如果您对它们使用 Method.isSynthetic()
,它将 return 错误。所以你只需要检查你在枚举中找到的每个方法。如果是这两种方法中的一种,签名和return类型在JLS中指定,则跳过不输出。
棘手的部分是摆脱每个构造函数的两个附加参数。基本上,当你遍历构造函数参数时,你可以跳过前两个参数。但是,请记住,这仅适用于 Oracle 编译器生成的代码,在未来版本或任何其他编译器中无法保证!
我制作的程序可以使用反射生成 class、enum 和接口的源代码,但我在生成 Enum 时遇到问题。
我的EumTestclass
public enum EnumTest{
a,b;
private String r;
private EnumTest(){
}
private void some(){
}
public int[] some2(int[] b){
return b;
}
}
生成枚举文件的方法
private void generateEnum(Class<?> cls,PrintWriter writer) {
this.writePackage(cls, writer);
this.writeAnnotations(writer, cls.getDeclaredAnnotations());
writer.write(Modifier.toString(cls.getModifiers())+ " enum " + cls.getSimpleName());
this.writeImplementation(cls, writer);
writer.write("{");
this.writeNewLine(writer);
Object[] cons = cls.getEnumConstants();
for (int i = 0; i < cons.length; i++) {
writer.write(cons[i].toString());
if(i != cons.length - 1)
writer.write(",");
}
writer.write(";");
this.writeNewLine(writer);
this.writeFields(cls, writer);
this.writeConstructors(cls, writer);
this.writeMethods(cls,writer);
writer.write("}");
}
结果是这个新枚举
package metaprogramovanie.test;
public final enum EnumTest{
a,b;
private java.lang.String r;
private static final metaprogramovanie.test.EnumTest[] $VALUES;
private EnumTest(java.lang.String arg0,int arg1){
}
public static metaprogramovanie.test.EnumTest[] values(){
return null;
}
public static metaprogramovanie.test.EnumTest valueOf(java.lang.String arg0){
return null;
}
private void some(){
}
public int daco(int arg0){
return 0;
}
}
如您所见,存在一些错误。例如 Modifier generate FINAL 和 enum 不能是 final,接下来还有更多的方法和字段,构造函数有参数 ...
构造函数生成
private void writeConstructors(Class<?> cls, PrintWriter writer){
Constructor[] cons = cls.getDeclaredConstructors();
for (Constructor constructor : cons) {
this.writeAnnotations(writer, constructor.getDeclaredAnnotations());
writer.write(Modifier.toString(constructor.getModifiers()) + " " + cls.getSimpleName());
this.writeParameters(writer,constructor.getParameters());
writer.write("{");
this.writeNewLine(writer);
writer.write("}");
this.writeNewLine(writer);
}
}
字段生成
private void writeFields(Class<?> cls, PrintWriter writer){
Field[] atr = cls.getDeclaredFields();
for (Field field : atr) {
if(field.isEnumConstant()){
System.out.println("JE");
}
else{
this.writeAnnotations(writer, field.getDeclaredAnnotations());
writer.write(Modifier.toString(field.getModifiers())+" " + field.getType().getTypeName()+ " " + field.getName());
if(Modifier.isStatic(field.getModifiers())){
try{
Object value = field.get(null);
writer.write(" = " + this.getFieldValue(field));
}catch(IllegalAccessException ex){
}
}
writer.write(";");
this.writeNewLine(writer);
}
}
this.writeNewLine(writer);
}
方法生成
private void writeMethods(Class<?> cls, PrintWriter writer){
Method[] methods = cls.getDeclaredMethods();
for (Method met : methods) {
this.writeAnnotations(writer, met.getDeclaredAnnotations());
writer.write(Modifier.toString(met.getModifiers())+" "+met.getReturnType().getTypeName() +" "+ met.getName());
this.writeParameters(writer, met.getParameters());
writer.write("{");
this.writeNewLine(writer);
if(!met.getReturnType().equals(Void.TYPE)){
this.writeMethodBody(writer,met);
}
this.writeNewLine(writer);
writer.write("}");
this.writeNewLine(writer);
}
this.writeNewLine(writer);
}
如果您有任何想法如何获得可以编译的枚举。
这里解释了为什么你会得到所有额外的东西。
枚举是使用编译器合成的大量代码实现的,因此 "behind the scenes" 它们的行为与 Java 语言规范指定的一样,不需要对 JVM 进行彻底更改。
Java Language Specification 表示,例如:
- 一个
enum
是隐含的final
除非有一个带有主体的常量。 - 任何
enum
隐式声明方法values()
和valueOf(String)
.
因此,您获得了 EnumTest
的 final
修饰符,并且获得了两个额外的方法。
此外,编译器会合成一些代码以帮助有效地实现一些所需的东西。例如,要复制并通过方法 values()
编辑的数组的源放置在一个名为 $VALUES
的合成字段中。它在枚举的初始化时被填满,然后每当调用values()
时就可以复制它。
此外,为了正确初始化常量,编译器向每个构造函数添加了两个隐藏参数。所以如果你有一个空的构造函数,它在幕后实际上有两个参数——常量的名称和它的序号。如果您有一个带一个参数的构造函数,那么在幕后它将有 3 个参数。
当您拥有自己的常量时,会创建额外的合成代码。
编译器完成的所有这些后期实现都没有记录在 JLS 中,基本上由任何编译器决定如何实现。只要枚举作为编译器生成的任何代码的结果正确运行,它就可以自由地以它选择的任何方式生成它。
解决您遇到的一些问题的技巧:
不使用
Modifier.toString(cls.getModifiers())
打印enum
修饰符,而是使用Modifier.toString(cls.getModifiers() & ~ Modifier.FINAL & ~ Modifier.ABSTRACT)
这从输出中排除了
final
修饰符以及abstract
修饰符,如果您向enum
添加抽象方法(并处理常量体,这是你到目前为止还没有做过的事情)。- 使用
Field.isSynthetic()
方法检查某个字段是否被编译器合成,不输出该方法returntrue
的字段。这将摆脱$VALUES
字段。 values()
和valueOf(String)
这两种方法不被认为是合成的,如果您对它们使用Method.isSynthetic()
,它将 return 错误。所以你只需要检查你在枚举中找到的每个方法。如果是这两种方法中的一种,签名和return类型在JLS中指定,则跳过不输出。
棘手的部分是摆脱每个构造函数的两个附加参数。基本上,当你遍历构造函数参数时,你可以跳过前两个参数。但是,请记住,这仅适用于 Oracle 编译器生成的代码,在未来版本或任何其他编译器中无法保证!