【问题标题】:Interface to enforce generic static method in Java在 Java 中强制执行通用静态方法的接口
【发布时间】:2018-09-16 23:07:33
【问题描述】:

我有一个 Java 类 Model,它为我的远程数据库中的一些数据建模。我希望我项目中的所有数据模型都能够从 Map<String, Object> 实例提供构建器(实际上,我正在使用带有 Firestore 的 SnapshotParser<Model> 解析器,但我只会在每个模型中调用 getData()) .这应该类似于:

public class Model {
    private String name;
    public Model(String name) { this.name = name; }
    public static SnapshotParser<Model> getDocParser() {
        return docSnapshot -> {
            Map<String, Object> data = docSnapshot.getData();
            return new Model(data.getOrDefault("name", "John Doe"));
        };
    }
}

请注意,我将有几个模型(Model2、Model3...),它们也需要提供这样的接口。为了强制执行此行为,我为我的模型类创建了一个 DocParserSupplier 泛型类来实现:

public interface DocParserSupplier<T> {
    static SnapshotParser<T> getDocParser();
}

这不起作用有两个原因(Android Studio 告诉我):

  • static 接口的方法必须有一个默认实现。不知道T,我做不到。
  • 我收到“T 无法在静态上下文中引用”错误。

如果从上面的界面中删除 static 关键字,我可以做我想做的,但它需要我创建一个 Model 的实际实例来获取解析器。它会起作用,但如果方法是静态的,它会更有意义。

有没有 Java 方法可以做我想做的事?

编辑:我的具体用例是将RecyclerViews 与我数据库中的文档进行匹配。构造FirestoreRecyclerOptions对象需要解析器将键值数据转换为Model:

FirestoreRecyclerOptions<Model1> fro1 = new FirestoreRecyclerOptions.Builder<Model1>()
            .setQuery(query1, Model1.getDocParser())
            .build();
FirestoreRecyclerOptions<Model2> fro2 = new FirestoreRecyclerOptions.Builder<Model2>()
            .setQuery(query2, Model2.getDocParser())
            .build();

【问题讨论】:

  • @mszymborski static 方法允许在从 Java SE 8 开始的接口中使用。它们只是不被子类型继承。
  • 两件事 - 既然你提到了 Android Studio,我将把 android 标签放在这里。第二:为什么你需要这是一个静态方法?这有一种味道,因为您希望每种解析器类型都不同,因此您可能应该寻找一些具体定义的方法。
  • 您需要实例方法来对您的对象强制执行某些行为。
  • @Makoto 解析器的行为取决于类本身,而不是实例;我需要它是静态的,但在我的用例中,我需要解析器而不需要Model 的实际实例。 @Bhesh Gurung 我不明白你的评论

标签: java android generics interface static


【解决方案1】:

接口强制执行实例的行为,因此对具有该行为的任何对象的引用都可以以类型安全的方式传递。另一方面,静态方法不属于对象的任何特定实例。类名本质上只是一个命名空间。如果要强制执行行为,则必须在某处创建实例(或使用反射,如果绝对有必要确保类具有特定的静态方法)。

除非这个系统要开放扩展,其他人可以定义他们自己的模型,否则我会说完全放弃 DocParserSupplier 接口并像现在一样调用静态方法,或者将它们分解为工厂接口+ 实施。工厂选项很好,因为您可以用返回虚拟解析器进行测试的假实现替换生产实现。

编辑:文档解析器工厂

public interface DocParserFactory {
    SnapshotParser<Model1> getModel1Parser();
    SnapshotParser<Model2> getModel2Parser();
    ...
    SnapshotParser<Model1> getModelNParser();
}

...

// The implementation of each getModelXParser method
class DocParserFactoryImpl {

    SnapshotParser<Model1> getModel1Parser() {
        return docSnapshot -> {
            Map<String, Object> data = docSnapshot.getData();
            return new Model(data.getOrDefault("name", "John Doe"))};
    }

    ...

}

...

private DocParserFactory docParserFactory;

// You can inject either the real instance (DocParserFactoryImpl) or a 
// test instance which returns dummy parsers with predicable results 
// when you construct this object. 
public ThisObject(DocParserFactory docParserFactory) {
    this.docParserFactory = docParserFactory;
}

...

// Your code
public void someMethod() {

    ...

    FirestoreRecyclerOptions<Model1> fro1 = new 
    FirestoreRecyclerOptions.Builder<Model1>()
        .setQuery(query1, docParserFactory.getModel1Parser())
        .build();
    FirestoreRecyclerOptions<Model2> fro2 = new 
    FirestoreRecyclerOptions.Builder<Model2>()
        .setQuery(query2, docParserFactory.getModel2Parser())
        .build();

    ...

}

【讨论】:

  • 你能展示你描述的模式的小例子吗?以及它们如何应用于我当前的场景?我不熟悉你描述的概念
  • 完成。我用工厂示例编辑了上面的答案。
【解决方案2】:

这与静态或非静态关系不大,因为如果不以一种或另一种方式传递类型参数,就无法创建泛型对象的实例。其实前几天我也回答过一个类似的问题,当时有人想用枚举来获取所需的构建器。

简而言之,如果不以某种形式传递T,就无法编写方法&lt;T extends AbstractBuilder&gt; T builder(final SomeNonGenericObject object)(或者,在本例中为&lt;T extends AbstractBuilder&gt; T builder())。即使它在运行时有意义,但如果你不告诉它是哪个泛型类型,编译器将无法确定使用哪种泛型类型。

在 Java 8 中,您可以通过方法引用优雅地解决这个问题。我对 Android 了解不多,但我相信你还在使用 Java 6,所以这行不通。

无论如何,你可以有如下的东西:

public <T extends AbstractBuilder> T builder(final Supplier<T> object) {
    return supplier.get();
}

final Supplier<AbstractBuilder> model1BuilderSupplier = Model1Builder::new;
builder(model1BuilerSupplier)
    .setQuery(query1, Model1.getDocParser())
    .build();

这不是你想要的,但你尝试的方式是行不通的。

【讨论】:

  • 我可以在接口中声明静态方法,所以我想我使用的是 Java 8。你能描述一下方法引用 ... err.. 方法吗?还是你举的例子?
  • 基本上,我正在做的是结合使用抽象工厂设计模式和对new 运算符的引用。因此,当您调用供应商的get() 方法时——并且使用lambdas,它可以被隐式调用——相应AbstractBuilder 的new 运算符。这里我明确地返回它,因为使用 lambda 表达式没有意义。方法引用是通过:: 运算符完成的。最好阅读它,因为它可能会令人困惑。我在其他地方也读到过Supplier 的技术。
  • 所以我在AbstractBuilder 中实现getDocParser 并且我所有的Models 都应该继承自AbstractBuilder...?例如,您如何声明和实现Model1.getDocParser()?
猜你喜欢
  • 2012-04-24
  • 2012-02-11
  • 2014-06-02
  • 1970-01-01
  • 2021-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多