【问题标题】:Extend class without adding any new fields扩展类而不添加任何新字段
【发布时间】:2018-11-21 01:00:05
【问题描述】:

假设我们有这样一个类:

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

【问题讨论】:

  • 在编译时?在运行时?为什么您认为字段是需要预防的问题?
  • 问题是为什么你需要Stew to extend Foo,在此之前你为什么需要barzoo 在Foo 中?您始终可以选择拥有一个不需要初始化父类属性的构造函数。这就是你要找的东西吗?
  • 如果你走注解路线,你可以创建一个注解处理器,如果你用相应的注解声明类中的任何字段,就会发出错误。这会在编译期间发生。
  • @AlexanderMills 我得到了问题陈述,只是想标记问题相关。另外,为什么在这种情况下Stew 甚至从设计角度扩展Foo
  • @AlexanderMills 不,我不知道。您可以在Foo 本身中生成提供的构造函数。如果这不可能,我宁愿先尝试解决这个问题。

标签: java json serialization java-8


【解决方案1】:

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

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

【讨论】:

    【解决方案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 应该可能返回一个不同的类型。

    【讨论】:

      【解决方案3】:

      您不能强制客户端代码具有没有字段的类,但您可以让序列化机制忽略它们。比如在使用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();
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-12-11
        • 1970-01-01
        • 1970-01-01
        • 2013-03-15
        • 2020-08-06
        • 1970-01-01
        • 2018-07-27
        • 2013-02-21
        相关资源
        最近更新 更多