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

时间:2018-11-21 01:00:05

标签: java json serialization java-8

说我们有一个像这样的课程:

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有任何新字段。我想让任何使用此文件的开发人员都知道,任何其他字段都将被忽略(或不会被识别)等。

3 个答案:

答案 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();