3

Let's say I have two classes

class A{
    int a;
    int getA(){
        return this.a;
    }
}

and

class B extends A{
    String b;
    String getB(){
        return this.b;
    }
}

I would like to have an enum, containing all possible values and and getters of inheritance tree (don't ask, I just want to).

enum ValueGetters{
    A("a", A::getA),
    B("b", B::getB);

    String parameterName;
    Function parameterGetter;
}

I have problem writing constructor for this class.

ValueGetters(String parameterName, Function parameterGetter){
    this.parameterName = parameterName;
    this.parameterGetter = parameterGetter;
}

returns error for both A and B (Error:(12, 14) java: incompatible types: invalid method reference).

The same situation is for ValueGetters(String parameterName, Function<?,?> parameterGetter)

For ValueGetters(String parameterName, Function<? extends A,?> parameterGetter) this gives error only for B, and if I try to overload constructor with ValueGetters(String parameterName, Function<? extends B,?> parameterGetter) I have new error that both types have the same type of erasure.

On the other hand ValueGetters(String parameterName, Object parameterGetter) returns error stating that Object is not a Function.

I have tried to define my own @FunctionalInterface but it gave me the same error, and overloading methods of @FunctionalInterface to accept both A and B apparently is not an option (or not an obvious one).

If anyone could propose solution to my problem I would greatly appreciate it.

EDIT & SOLUTION

I know it's not exactly an enum, but it works in the same way and provides functionality I needed:

  1. Returns getter
  2. Getter is type-sensitive
  3. Solution does not require any changes in the original class
  4. Works well with interfaces

...and now, the code:

public class ValueGetter<T extends A, R> {
    private String name;
    private Function<A,R> getter;

    private ValueGetter(String name, Function<A, R> getter){
        this.name = name;
        this.getter = getter;
    }

    private static <T extends A, R> ValueGetter create(String name, Function<T,R> valueGetter){
        return new ValueGetter(name, valueGetter);
    }

    public static ValueGetter<A, Integer> AA = create("a", A::getA);
    public static ValueGetter<B, Integer> BB = create("a", B::getB);

    public Function<T, R> getGetter(){
        return (Function<T, R>) this.getter;
    }
}

With this implementation

A a1 = new A(12345);
B b1 = new B(54321, "sdfghjk");
System.out.println(ValueGetter.AA.getGetter().apply(a1));
System.out.println(ValueGetter.AA.getGetter().apply(b1));
System.out.println(ValueGetter.BB.getGetter().apply(b1));

compiles and works, while line

System.out.println(ValueGetter.BB.getGetter().apply(a1));

gives compilation error apply(B) in function cannot be applied to (A)

Karol Pokomeda
  • 133
  • 2
  • 13

2 Answers2

1

One waty to implement it is using a singleton instance of each class:

class BB {  // Renamed B to BB to make it work with enum
    String b;
    private static final BB instance = new BB();


    public static BB getInstance() {
        return instance;
    }


    public String getB(){
        return this.b;
    }

    // Example usage with cast
    public String getB(Object b) {
        return ((BB) b).getB();
    }
}

public enum ValueGetters{
    B("b", BB.getInstance()::getB);

    String parameterName;
    Function parameterGetter;

    ValueGetters(String parameterName, Function parameterGetter){
        this.parameterName = parameterName;
        this.parameterGetter = parameterGetter;
    }
}

You could the use it like that:

ValueGetters.B.getGetter().apply(b);
syntagma
  • 23,346
  • 16
  • 78
  • 134
  • 2
    "non-static method references require object instance" - that's not correct. The method reference can be created using ``A::getA``. You do need an instance but only when you want to invoke the method. – f1sh May 18 '17 at 08:59
  • 1
    You completely missed my point; I have many instances of A and B and I want to use getters from this enum like `ValueGetters.A.getGetter().apply(instanceOfA)` and `ValueGetters.B.getGetter().apply(instanceOfB)`. You are referring to `A non-static method cannot be referenced from a static context` but this error is wrong(http://stackoverflow.com/questions/42200958/non-static-method-cannot-be-referenced-from-a-static-context-in-java-8-streams). I have used `A::getA`-like construction and it works. My problem is related to storing this type of reference in enum. – Karol Pokomeda May 18 '17 at 09:08
  • @KarolPokomeda I was referring to why non-static methods cannot be called without an instance. I have tested my solution and still think this solves the problem you are describing. I have used it in the following way: `ValueGetters.B.getGetter().apply(b)`. – syntagma May 18 '17 at 09:30
  • Sorry, I have to give you a credit. Your solution works but it is far from what i expected, as it requires severe modification of original class. And as @f1sh said, you need and instance only when you call. E.g. this code works perfectly: `A a1 = new A(12345);` `Function function = A::getA;` `System.out.println(function.apply(a1));` – Karol Pokomeda May 18 '17 at 10:19
  • Yes, I have modified my answer regarding having the instance of a class to apply the method. – syntagma May 18 '17 at 10:29
  • Without having to change your `A` and `B` classes, you could also create a static map from `String` (enum class name) to `Method`. – syntagma May 18 '17 at 10:41
  • With map I encounter exactly the same problems... I'm starting to believe that i want impossible. – Karol Pokomeda May 18 '17 at 11:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/144563/discussion-between-karol-pokomeda-and-). – Karol Pokomeda May 18 '17 at 12:33
1

You have two fundamental problems in your enum. First, there is a name clash. Your classes are named A and B and the enum constants are named A and B as well. In all contexts, where an identifier may refer to both, a class or a variable, Java will prefer the variable, hence, you are referring to the enum constants A and B which are in scope with your A::getA and B::getB.

Further, you are using the raw type Function, which doesn’t support such specific method references. One way to fix this, is

package somepackage;

import java.util.function.Function;

class A{
    int a;
    int getA(){
        return this.a;
    }
}
class B extends A{
    String b;
    String getB(){
        return this.b;
    }
}
enum ValueGetters{
    A("a", somepackage.A::getA),
    B("b", somepackage.B::getB);

    String parameterName;
    Function parameterGetter;

    <T> ValueGetters(String pName, Function<T,Object> f) {
        parameterName=pName;
        parameterGetter=f;
    }
}

Note that you need the classes to be in a named package to be able to disambiguate the names. There is no way to do that for the unnamed default package.

Further, the parameterGetter variable still has the raw type Function, as there is no way to express a common function type for your two functions having the signatures A -> Integer and B -> String. You can’t solve this with Generics here, as enums do not support having different type parameters at the constants.


You have already found the right solution, going from enum to an ordinary generic class, which may define differently typed constants. Naming the constants AA and BB instead of A and B also solves the name clash. But you shouldn’t use Function<A,R> when Function<T,R> is meant, further, you have copy&paste errors.

You can clean this up, simplifying your solution:

// you may also declare it as  public class ValueGetter<T extends A, R>,
// but there would be no actual benefit...
public final class ValueGetter<T, R> {
    private String name;
    private Function<T,R> getter;

    private ValueGetter(String name, Function<T, R> getter){
        this.name = name;
        this.getter = getter;
    }
    public static final ValueGetter<A, Integer> AA = new ValueGetter<>("a", A::getA);
    public static final ValueGetter<B, String>  BB = new ValueGetter<>("b", B::getB);

    public Function<T, R> getGetter() {
        return this.getter;
    }
    public String name() {
        return name;
    }
}
Holger
  • 285,553
  • 42
  • 434
  • 765