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我得到了问题陈述,只是想标记与问题相关的内容。还有,为什么在这种情况下是