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(); 
7
  • @mszymborski static methods are allowed in interfaces starting with Java SE 8. They just aren’t inherited by subtypes. Commented Apr 6, 2018 at 21:08
  • Possible duplicate of Is there a way to make sure classes implementing an Interface implement static methods? Commented Apr 6, 2018 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. Commented Apr 6, 2018 at 21:12
  • You need instance method to enforce certain behavior on your objects. Commented Apr 6, 2018 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 Commented Apr 6, 2018 at 21:31

2 Answers 2

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(); ... } 
Sign up to request clarification or add additional context in comments.

2 Comments

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
Done. I edited the answer above with a factory example.
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.

3 Comments

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?
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.
So I implement getDocParser in AbstractBuilder and all my Models should inherit from AbstractBuilder...? How do you declare and implement Model1.getDocParser(), for instance?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.