说我们有一个像这样的课程:
class Bar {
boolean b;
}
class Foo {
String zoo;
Bar bar;
}
然后有一个扩展Foo的类:
class Stew extends Foo {
public Stew(Bar b, String z){
this.bar = b;
this.zoo = z;
}
}
我的问题是-有什么方法可以防止Stew
拥有Foo
中没有的任何非方法字段?换句话说,我不希望Stew
有任何字段,我只是希望Stew
实现一个构造函数,也许一个或两个方法。
也许我可以使用一个注释,可以做到这一点吗?
类似的东西:
@OnlyAddsMethods
class Stew extends Foo {
public Stew(Bar b, String z){
this.bar = b;
this.zoo = z;
}
}
目的-我要将Stew
序列化为JSON,但我不希望Stew有任何新字段。我想让任何使用此文件的开发人员都知道,任何其他字段都将被忽略(或不会被识别)等。
答案 0 :(得分:3)
Java语言没有提供阻止子类添加字段的内置方法。
您也许可以编写注释处理器(本质上是Java编译器的插件)来强制执行此类注释,或者使用反射api来检查超类构造函数或单元测试中的子类字段声明。前者提供编译时支持,甚至可能提供IDE支持,但是比后者更难实现。
后者可能看起来像这样:
public class Super {
protected Super() {
for (Class<?> c = getClass(); c != Super.class; c = c.getSuperClass()) {
if (c.getDeclaredFields().length > 0) {
throw new IllegalApiUseException();
}
}
}
}
您可能希望允许使用静态字段,并添加更好的错误消息。
答案 1 :(得分:2)
那是一个奇怪的功能。
例如,您可以使用javac
处理器在编译时进行检查或在运行时进行反射,但这是一个奇怪的选择。
一种更好的方法是更改设计。
委托通常比继承更好。
所以,我们可以传递给没有状态的构造函数。 enum
是完美匹配。它可能具有全局状态,但不幸的是,您确实无法进行检查。
interface FooStrategy {
MyRet fn(Foo foo, MyArg myArg);
}
public final class Foo<S extends Enum<S> & FooStrategy> {
private final S strategy;
private String zoo;
private Bar bar;
public Foo(S strategy, Bar bar, String zoo) {
this.strategy = strategy;
this.bar = bar;
this.zoo = zoo;
}
// For any additional methods the enum class may provide.
public S strategy() {
return strategy;
}
public MyRet fn(Foo foo, MyArg myArg) {
return strategy.fn(this, myArg);
}
...
}
您可以为策略Foo
使用不同的接口(和对象),它们可能不相同。
另外,strategy
应该返回不同的类型。
答案 2 :(得分:1)
您不能强迫客户端代码具有没有字段的类,但是可以使序列化机制忽略它们。例如,当使用Gson时,此策略
class OnlyFooBar implements ExclusionStrategy {
private static final Class<Bar> BAR_CLASS = Bar.class;
private static final Set<String> BAR_FIELDS = fieldsOf(BAR_CLASS);
private static final Class<Foo> FOO_CLASS = Foo.class;
private static final Set<String> FOO_FIELDS = fieldsOf(FOO_CLASS);
private static Set<String> fieldsOf(Class clazz) {
return Arrays.stream(clazz.getDeclaredFields())
.map(Field::getName)
.collect(Collectors.toSet());
}
@Override
public boolean shouldSkipField(FieldAttributes f) {
String field = f.getName();
Class<?> clazz = f.getDeclaringClass();
return !(BAR_CLASS.equals(clazz) && BAR_FIELDS.contains(field)
|| FOO_CLASS.equals(clazz) && FOO_FIELDS.contains(field));
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
在Gson中使用时,将忽略除必填字段之外的所有其他字段:
Gson gson = new GsonBuilder().setPrettyPrinting()
.addSerializationExclusionStrategy(new OnlyFooBar())
.create();