Java 扩展类而不添加任何新字段

Java 扩展类而不添加任何新字段,java,json,serialization,java-8,Java,Json,Serialization,Java 8,假设我们有这样一节课: class Bar { boolean b; } class Foo { String zoo; Bar bar; } public class Super { protected Super() { for (Class<?> c = getClass(); c != Super.class; c = c.getSuperClass()) { if (c.getDeclaredFields().

假设我们有这样一节课:

class Bar {
  boolean b;
}

class Foo {
  String zoo;
  Bar bar;
}
public class Super {
    protected Super() {
        for (Class<?> c = getClass(); c != Super.class; c = c.getSuperClass()) {
            if (c.getDeclaredFields().length > 0) {
                throw new IllegalApiUseException();
            }
        }
    }
}
然后我们有一个扩展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;
   }
}

purpose-我将把
stud
序列化为JSON,但我不希望stud有任何新字段。我想让任何处理此文件的开发人员都知道,任何附加字段都将被忽略(或无法识别)等等。

Java语言没有内置方法来阻止子类添加字段

您可以编写注释处理器(本质上是java编译器的插件)来强制执行这样的注释,或者使用反射api来检查超类构造函数或单元测试中的子类字段声明。前者提供编译时支持,甚至可能提供IDE支持,但比后者更难实现

后者可能看起来像这样:

class Bar {
  boolean b;
}

class Foo {
  String zoo;
  Bar bar;
}
public class Super {
    protected Super() {
        for (Class<?> c = getClass(); c != Super.class; c = c.getSuperClass()) {
            if (c.getDeclaredFields().length > 0) {
                throw new IllegalApiUseException();
            }
        }
    }
}
公共类超级{
受保护超级(){
对于(类c=getClass();c!=Super.Class;c=c.getSuperClass()){
如果(c.getDeclaredFields().length>0){
抛出新的IllegalApiUseException();
}
}
}
}

您可能希望允许静态字段,并添加更好的错误消息。

Java语言没有提供内置的方法来阻止子类添加字段

您可以编写注释处理器(本质上是java编译器的插件)来强制执行这样的注释,或者使用反射api来检查超类构造函数或单元测试中的子类字段声明。前者提供编译时支持,甚至可能提供IDE支持,但比后者更难实现

后者可能看起来像这样:

class Bar {
  boolean b;
}

class Foo {
  String zoo;
  Bar bar;
}
public class Super {
    protected Super() {
        for (Class<?> c = getClass(); c != Super.class; c = c.getSuperClass()) {
            if (c.getDeclaredFields().length > 0) {
                throw new IllegalApiUseException();
            }
        }
    }
}
公共类超级{
受保护超级(){
对于(类c=getClass();c!=Super.Class;c=c.getSuperClass()){
如果(c.getDeclaredFields().length>0){
抛出新的IllegalApiUseException();
}
}
}
}

您可能希望允许静态字段,并添加更好的错误消息。

这将是一个奇怪的特性

例如,您可以使用
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);
    }
    ...
}
接口策略{
MyRet-fn(Foo-Foo,MyArg-MyArg);
}
公开期末班{
民营企业战略;
私人动物园;
私人酒吧;
公共食物(S策略、酒吧、串动物园){
这个。策略=策略;
这个.bar=bar;
this.zoo=动物园;
}
//对于enum类可能提供的任何其他方法。
公共服务策略(){
回报策略;
}
公共MyRet fn(Foo-Foo,MyArg-MyArg){
返回策略.fn(this,myArg);
}
...
}
您可以为策略使用不同的接口(和对象)来处理
Foo
,它们可能不应该相同


另外,
策略
可能会返回不同的类型

这将是一个奇怪的特性

例如,您可以使用
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);
    }
    ...
}
接口策略{
MyRet-fn(Foo-Foo,MyArg-MyArg);
}
公开期末班{
民营企业战略;
私人动物园;
私人酒吧;
公共食物(S策略、酒吧、串动物园){
这个。策略=策略;
这个.bar=bar;
this.zoo=动物园;
}
//对于enum类可能提供的任何其他方法。
公共服务策略(){
回报策略;
}
公共MyRet fn(Foo-Foo,MyArg-MyArg){
返回策略.fn(this,myArg);
}
...
}
您可以为策略使用不同的接口(和对象)来处理
Foo
,它们可能不应该相同


另外,
策略
可能会返回不同的类型

不能强制客户机代码包含没有字段的类,但可以让序列化机制忽略它们。例如,在使用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时,此策略

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;
    }
}

在编译时?在运行时?为什么你认为字段是一个需要预防的问题?问题是为什么你需要<代码> Stuw来扩展Fo< <代码>,在这之前为什么你需要在FoO中使用<代码> Bar < /C>和<代码>动物园< /代码>?您总是可以选择拥有一个不需要初始化父类属性的构造函数。这就是您要寻找的吗?如果您选择注释路径,您可以创建一个注释处理器,如果您在类中声明了具有相应注释的任何字段,则该处理器会发出错误。编译时会发生这种情况。@AlexanderMills我得到了问题陈述,只是想标记与问题相关的内容。还有,为什么在这种情况下是