13

As of Java 8 you can have default or static methods implemented in Interfaces as the below

public interface DbValuesEnumIface<ID, T extends Enum<T>> { T fromId(ID id); ID getId(); static String getDescriptionKey(){ return "this is a test"; } } 

I would like to declare the above with the static method having a signature that uses bounds defined by the implementing classes since the method's implementation should be the same for all,with the only thing different should be the generics declared, as such:

public interface DbValuesEnumIface<ID, T extends Enum<T>> { public static T fromId(ID id) { if (id == null) { return null; } for (T en : T.values()) { if (en.getId().equals(id)) { return en; } } } ID getId(); String getDescriptionKey(); } ... public enum Statuses implements DbValuesEnumIface<Integer,Statuses> 

which breaks because T and ID are not static and cant be referenced from a static context.

So, how should the above be modified to compile successfully and if thats not possible, how the above should be implemented to achieve the desired purpose while avoiding code duplication within implementing classes .

5
  • 1
    Not completely sure if this question specifically needs to be about Java 8 and interfaces. The same rules apply for classes in general as well when it comes to static methods trying to use a type parameter defined for the class? Commented Nov 22, 2017 at 9:11
  • @CKing the ability to have static methods in interfaces is Java8 specific and the problem in hand occurred through using an interface with generics. So added tags accordingly. The ``` breaks because T and ID are not static and cant be referenced from a static context ``` should probably still occur if DbValuesEnumIface was a class I merely extended in a subclass though if thats what you mean. There may be some kinks unique to it being an interface in the example so thats why this info was added. Commented Nov 22, 2017 at 9:16
  • 1
    No kinks. Class or interface. Generic type parameters follow the same rules. Static is static and instance is instance. Moreover, T.values where T is a class or method type parameter (regardless of static) defies the syntax rules for the language. Please do post an example that considers the language rules specially when the question is not language agnostic? More importantly, a question that talks about compilation errors should surely eliminate all compilation errors not related to the direct question IMO. Commented Nov 22, 2017 at 9:24
  • 3
    Actually, there is an interface specific difference. For ordinary classes, you can invoke a static method of SuperClass like SubClass.method(…), raising the expectation that the parameterization of the inheritance (e.g. class SubClass extends SuperClass<SpecificType>) can be used, which disappointingly doesn’t work. In contrast, static methods of interfaces are not inherited in the first place. So you can’t do Statuses.fromId(…), but only DbValuesEnumIface.fromId(…), which obviously needs an explicit hint about Statuses being the class to be searched. Commented Nov 22, 2017 at 10:21
  • @Holger "static methods of interfaces are not inherited in the first place" Exactly. While you can use default methods (that one can argue lead to same problems) and Java can resolve conflicts, static methods are treated differently. Commented Nov 22, 2017 at 10:28

3 Answers 3

8

Since there is no relationship between static methods and the class’s type parameters, which describe how instances are parameterized, you have to make the static method generic on its own. The tricky part is to get the declarations right to describe all needed constraints. And, as this answer already explained, you need to add a Class parameter, as otherwise, the implementation has no chance to get hands on the actual type arguments:

public interface DbValuesEnumIface<ID, T extends Enum<T>> { public static <ID, T extends Enum<T> & DbValuesEnumIface<ID, T>> T fromId(ID id, Class<T> type) { if (id == null) { return null; } for (T en : type.getEnumConstants()) { if (en.getId().equals(id)) { return en; } } throw new NoSuchElementException(); } ID getId(); String getDescriptionKey(); } 

Note that the type parameters of the static method are independent from the class’s type parameter. You may consider giving them different names for clarity.

So now, given you enum Statuses implements DbValuesEnumIface<Integer,Statuses> example, you can use the method like Statuses status = DbValuesEnumIface.fromId(42, Statuses.class);


Note that for default methods, it is possible to access the actual type, as a method providing the enum type will be provided by the implementation. You only have to declare the presence of the method within the interface:

public interface DbValuesEnumIface<ID, T extends Enum<T>&DbValuesEnumIface<ID,T>> { public default T fromId(ID id) { if (id == null) { return null; } for (T en : getDeclaringClass().getEnumConstants()) { if (en.getId().equals(id)) { return en; } } throw new NoSuchElementException(); } //no needed to implement it, inherited by java.lang.Enum Class<T> getDeclaringClass(); ID getId(); String getDescriptionKey(); } 

However, the obvious disadvantage is that you need a target instance to invoke the method, i.e. Statuses status = Statuses.SOME_CONSTANT.fromId(42);

Sign up to request clarification or add additional context in comments.

9 Comments

hm.. but that means that the generics declared at the interface level and the method level and un-related right?
@Leon: yes. The only thing you can do, is to add a simple delegating method to each enum, e.g. add public static Statuses fromId(Integer id) { return DbValuesEnumIface.fromId(id, Statuses.class); } to Statuses, which is still better than repeating the entire implementation in each enum
@Eugene: right, the method’s type parameters are unrelated (that’s why I even suggested giving them different names); it’s the constraints, i.e. X extends Enum<X>&DbValuesEnumIface<ID,X> which enforce type arguments to have a compatible inheritance, as only enum types implementing DbValuesEnumIface with a proper parameterization can fulfill the constraints.
@Eugene: I just realized that there is a solution for default methods that doesn’t need the additional Class parameter (updated my answer), however, I don’t know whether having to invoke the method on an existing instance is better than providing a class literal as argument…
@Eugene: anyone can implement the interface, but no-one can provide a type for T that isn’t an enum and an appropriate implementation of this interface at the same time. So if you create an inappropriate interface, it would still have to point to an appropriate implementation of the interface for T. You can’t write bullet-proof software, but you shouldn’t even try. Guiding the implementors into the right direction is enough.
|
2

There is no easy way as far as I can tell, first you need to change your method to default, you can read more here of why you can't use generics in a static context.

But even if you change it to default things are still not going to work, since you need to pass an instance or class type of the enum to that method, something like this:

public default T fromId(ID id, Class<T> t) { if (id == null) { return null; } for (T en : t.getEnumConstants()) { // dome something } return null; } 

Now you are hitting another problem, inside fromId - the only thing that you know is that T extends an enum - not your enum may be, thus getId (which seems that your enums have) are simply not known by the compiler.

I don't know an easy way to make this work besides declaring an interface, like :

interface IID { public int getId(); } 

making your enum implement it:

static enum My implements IID { A { @Override public int getId() { // TODO Auto-generated method stub return 0; } }; } 

and change the declaration to:

public interface DbValuesEnumIface<ID, T extends Enum<My> & IID> 

2 Comments

Very thorough answer, I ll read the link provided as well as your suggestion. If nothing better comes up today I ll make sure to accept this answer. Thank you.
If you have the Class parameter, you already have what you need, there is no reason to change the method from static to default. You only need to declare that the Class must be an enum and implement the desired interface and ironically, you show how to do such things in your last line.
1

You can change from static to default and it will compile successfully.

default EvaluationStatuses fromId(Integer id)

2 Comments

My bad, I accidentally pasted the wrong method signature. Right one within the interface should be T fromId(ID id) . I did try to change static to default but it doesnt recognize the values() method in for (T en : T.values()) . You may be on to something here though, I ll look into this approach some more as well.
@Leon It doesn't recognise the values method because T is a type parameter and not a reference. This has got nothing to do with interfaces or default methods or static methods as such. Perhaps read up on generics syntax first and move on to more trickier topics such as this one?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.