能否通过Interface扩展Enum以支持新增选项?Minecraft Forge API开发疑问
实现可扩展的Minecraft Forge数据类型系统
核心问题说明
Java枚举类本身是不可继承的,所以无法直接让其他模组给你的枚举添加新选项。要实现可扩展,得用「接口抽象+注册中心」的方案替代传统枚举的单一实现。
具体实现步骤
1. 定义数据类型接口
先把原有枚举的行为抽象成接口,让所有数据类型(包括你的原生类型和其他模组的自定义类型)都实现这个接口:
import java.util.function.Function; public interface DataType<T> { // 返回唯一标识,用于区分不同类型 String getId(); // 将字符串解析为对应类型的对象 T parse(String value); // 将对象序列化为字符串 String serialize(T value); }
2. 改造原有枚举实现接口
把你原来的枚举改成实现上面的DataType接口,保留原有逻辑:
import java.util.Date; import java.time.LocalDate; import java.util.function.Function; public enum VanillaDataType implements DataType<Object> { BOOLEAN("boolean", Boolean::parseBoolean, Object::toString), DATE("date", s -> new Date(Long.parseLong(s)), d -> String.valueOf(((Date)d).getTime())), DOUBLE("double", Double::parseDouble, Object::toString), FLOAT("float", Float::parseFloat, Object::toString), INTEGER("integer", Integer::parseInt, Object::toString), LOCAL_DATE("local_date", LocalDate::parse, Object::toString); private final String id; private final Function<String, Object> parser; private final Function<Object, String> serializer; VanillaDataType(String id, Function<String, Object> parser, Function<Object, String> serializer) { this.id = id; this.parser = parser; this.serializer = serializer; } @Override public String getId() { return id; } @Override public Object parse(String value) { return parser.apply(value); } @Override public String serialize(Object value) { return serializer.apply(value); } }
3. 实现数据类型注册中心
创建一个全局注册中心,用来管理所有注册的DataType实例,包括你的原生枚举和其他模组的自定义类型:
import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; public class DataTypeRegistry { private static final Map<String, DataType<?>> REGISTRY = new HashMap<>(); // 初始化时注册所有原生枚举类型 static { for (VanillaDataType type : VanillaDataType.values()) { register(type); } } // 对外暴露的注册方法,其他模组调用这个方法添加自定义类型 public static void register(DataType<?> dataType) { if (REGISTRY.containsKey(dataType.getId())) { throw new IllegalArgumentException("数据类型ID " + dataType.getId() + " 已存在!"); } REGISTRY.put(dataType.getId(), dataType); } // 根据ID获取对应的数据类型 public static DataType<?> getById(String id) { return REGISTRY.get(id); } // 获取所有已注册的数据类型 public static Collection<DataType<?>> getAllTypes() { return Collections.unmodifiableCollection(REGISTRY.values()); } }
4. 其他模组扩展自定义类型
其他模组开发者只需要实现DataType接口,然后在模组初始化阶段调用注册方法即可:
import java.math.BigDecimal; public class BigDecimalDataType implements DataType<BigDecimal> { @Override public String getId() { return "big_decimal"; } @Override public BigDecimal parse(String value) { return new BigDecimal(value); } @Override public String serialize(BigDecimal value) { return value.toPlainString(); } } // 在模组的初始化事件中注册(比如FMLCommonSetupEvent) // @SubscribeEvent // public void onCommonSetup(FMLCommonSetupEvent event) { // DataTypeRegistry.register(new BigDecimalDataType()); // }
关于接口扩展枚举的问题
Java中无法通过接口直接扩展枚举的选项,因为枚举类默认继承java.lang.Enum,而Java不支持多继承,枚举类只能实现接口,但不能被其他类继承。不过上面的方案正是利用接口的抽象能力,把枚举的行为和实现解耦,让自定义类型和原生枚举站在同一层级,间接实现了「扩展枚举选项」的效果。
Forge开发注意事项
- 注册时机:其他模组应该在
FMLCommonSetupEvent或更晚的初始化事件中注册自定义类型,避免在模组加载早期注册导致的问题。 - 线程安全:Forge的模组初始化是单线程执行的,所以注册中心无需额外加锁,但如果有动态注册需求,需要自行保证线程安全。
内容的提问来源于stack exchange,提问作者CuteKitsuneForever
相关产品推荐
相关产品推荐

