1

I have a Java class Model which models some data from my remote database. I want all data models in my project to be able to supply a builder from a Map<String, Object> instance (in practice, I'm working with SnapshotParser<Model> parsers with Firestore, but I'll just call getData() in every model). This should look something like:

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

Note that I'll have several models (Model2, Model3...) which will also be required to provide such an interface. To enforce this behavior, I created a DocParserSupplier generic class for my model classes to implement:

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

This doesn't work for two reasons (as Android Studio informs me):

  • static methods of interfaces must have a default implementation. I can't do that without knowing T.
  • I get the "T cannot be referenced in static context" error.

If remove the static keyword from the above interface, I can do what I want but it would require I create an actual instance of the Model to get the parser. It would work but it makes more sense if the method is static.

Is there a Java way to do what I want?

EDIT: My specific use case is in matching RecyclerViews to documents in my database. Constructing the FirestoreRecyclerOptions object requires a parser to convert key-value data to a 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();
Dori
  • 1,035
  • 1
  • 12
  • 22
  • @mszymborski `static` methods are allowed in interfaces starting with Java SE 8. They just aren’t inherited by subtypes. – Logan Apr 06 '18 at 21:08
  • Possible duplicate of [Is there a way to make sure classes implementing an Interface implement static methods?](https://stackoverflow.com/questions/2689312/is-there-a-way-to-make-sure-classes-implementing-an-interface-implement-static-m) – Logan Apr 06 '18 at 21:10
  • 1
    Two things - since you mentioned Android Studio I'll throw the android tag in here. Second: why do you *need* this to be a static method? This has a smell about it in that you should likely look to have some concretely defined approach given that you expect it to be different for each parser type. – Makoto Apr 06 '18 at 21:12
  • You need instance method to enforce certain behavior on your objects. – Bhesh Gurung Apr 06 '18 at 21:16
  • @Makoto the parser's behavior depends on the class itself, not an instance; I don't _need_ it to be static but in my use cases I need the parser without needing an actual instance of `Model`. @Bhesh Gurung I didn't understand your comment – Dori Apr 06 '18 at 21:31
  • So multiple @mentions don't work; only I saw that one. Could you provide a use example of your parser method in two or three more classes so I could get a better feel for it? – Makoto Apr 06 '18 at 21:34
  • @Makoto edited :) – Dori Apr 06 '18 at 21:41

2 Answers2

1

Interfaces enforce behavior of instances, so that references to any object which has that behavior can be passed around in a type-safe way. Static methods on the other hand, don't belong to any particular instance of an object; the class name is essentially just a namespace. If you want to enforce behavior, you will have to create an instance somewhere (or use reflection, if it is absolutely necessary to ensure a class has a particular static method).

Unless this system is going to be opened up for extension, where others can define their own models, I would say ditch the DocParserSupplier interface altogether and call the static methods exactly as you are now, or factor them out into a factory interface + implementation. The factory option is nice because you can replace the production implementation with a fake implementation that returns dummy parsers for tests.

Edit: Doc Parser Factory

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

    ...

}
Jason Carlson
  • 398
  • 2
  • 4
  • Could you show small examples for the patterns you described? And how they apply to my current scenario? I'm not familiar with the concepts you described – Dori Apr 07 '18 at 07:43
  • Done. I edited the answer above with a factory example. – Jason Carlson Apr 09 '18 at 18:14
0

It's not so much to do with static or non-static, as it is with the fact that you cannot create an instance of a generic object without passing the type parameter(s) one way or another. In fact, I answered a similar question a few days ago, when somebody wanted to use enums to get the required builder.

In short, you cannot write a method <T extends AbstractBuilder> T builder(final SomeNonGenericObject object) (or, in this case, <T extends AbstractBuilder> T builder()) without passing T in some form. Even though it will make sense at runtime, the compiler can't figure out what generic type to use if you don't tell it which one it is.

In Java 8, you can solve this elegantly with method references. I don't know much about Android, but I believe you're still on Java 6 there, so this wouldn't work.

Anyway, you can have something like the following:

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

It's not exactly what you want, but the way you're trying to go about it will not work.

SeverityOne
  • 2,476
  • 12
  • 25
  • I can declare static methods in interfaces, so I think I'm using Java 8. Could you describe the method-references ... err.. method? Or is that the example you gave? – Dori Apr 07 '18 at 08:08
  • Basically, what I'm doing is using the Abstract Factory design pattern in combination with a reference to the `new` operator. So when you invoke the `get()` method of the supplier –and with lambdas, it can be invoked implicitly– the `new` operator of the respective `AbstractBuilder`. Here I'm returning it explicitly, because there's no point in using a lambda expression. The method reference is done via the `::` operator. It's best to read up on it, because it can be confusing. The technique with the `Supplier` I read elsewhere as well. – SeverityOne Apr 07 '18 at 08:37
  • So I implement `getDocParser` in `AbstractBuilder` and all my `Model`s should inherit from `AbstractBuilder`...? How do you declare and implement `Model1.getDocParser()`, for instance? – Dori Apr 07 '18 at 08:43