3

I have an enum and POJO classes in JAVA. In enum class, each enum value matches with variables of POJO classes... And then I want to create a relationship between two classes.

Enum Class:

public enum MyEnum
{
   FIELD1,
   FIELD2,
   FIELD3,
   ...
}

POJO Class:

public class MyPojo
{
   private String field1;
   private String field2_not_ref;
   private String field3;
   ...
}

Then, when I try to match these fields of two classes, I've implemented code like:

public String matchMethod(MyEnum myenum)
{
  switch(myenum)
  {
   case FIELD1:
      return field1;
   case FIELD2:
      return field2_not_ref;
    ...
  }
}

I think that it is not a good/clear solution. Is there any suggestion?

Andronicus
  • 25,419
  • 17
  • 47
  • 88
Sha
  • 921
  • 17
  • 46
  • When do you set the values of fields1,2,3 ? – azro Jan 16 '20 at 18:48
  • @azro, In my main method, ...myMainMethod(MyPojo myPojo, MyEnum myenum) { ... Object matchResult = myPojo.matchMethod(myEnum); ... } – Sha Jan 16 '20 at 18:56
  • Do I see it right, that you want the POJO fields to be variable? Or should these fields be immutable? I mean, does the Mapping hat to be applied for chaning values in `MyPojo`? – Jan Held Feb 09 '20 at 14:44
  • 1
    @JanHeld, question is so clear. I need to match enum fields with POJO fields. As a result, I will use it like; `new MyPOJO.getFieldByEnum(myEnum) -> return matched field`; This is not related with immutable/mutable things. – Sha Feb 09 '20 at 15:20

5 Answers5

6

To avoid reflection, which might be slow and make things more concise, than with defining method on an enum, you can create an enum with a field that is a function consuming MyPojo and returning String:

public enum MyEnum {
    FIELD1(MyPojo::getField1),
    FIELD2(MyPojo::getField2),
    FIELD3(MyPojo::getField3);

    private final Function<MyPojo, String> getField;

    MyEnum(Function<MyPojo, String> getField) {
        this.getField = getField;
    }
}

Then you can call it in the following way:

public static void main(String[] args) {
    MyPojo myPojo = new MyPojo("f1", "f2", "f3");
    System.out.println(MyEnum.FIELD2.getGetField().apply(myPojo));
}

If you don't want to use it as a Function variable with apply method, you can create a single function on en enum, that does it:

public enum MyEnum {
    FIELD1(MyPojo::getField1),
    FIELD2(MyPojo::getField2),
    FIELD3(MyPojo::getField3);

    private final Function<MyPojo, String> getField;

    MyEnum(Function<MyPojo, String> getField) {
        this.getField = getField;
    }

    String getFieldFromMyPojo(MyPojo myPojo) { return getField.apply(myPojo); }
}

And invoke it like that:

public static void main(String[] args) {
    MyPojo myPojo = new MyPojo("f1", "f2", "f3");
    System.out.println(MyEnum.FIELD2.getFieldFromMyPojo(myPojo));
}

Getters and setters were omitted for brevity.

Andronicus
  • 25,419
  • 17
  • 47
  • 88
  • I vote for the second alternative, with the apply internal to the enum. – Jonathan Rosenne Feb 11 '20 at 21:35
  • Wasn't the OP's whole idea to NOT call MyEnum.FIELD2 or other specific enum type each time? Even the switch presented by @Sha was much more generic than this approach. – Piotr Niewinski Feb 12 '20 at 19:19
  • @PiotrNiewinski so you're saying, that the whole switch in the question is a better approach than simply writing `myenum.getFieldFromMyPojo(myPojo)`? – Andronicus Feb 13 '20 at 08:03
  • @Andronicus not at all, I am saying that it may suggest that OP was looking for more generic answer, at least signature of `matchMethod` suggests so – Piotr Niewinski Feb 13 '20 at 08:37
  • Hmm. "And then I want to create a relationship between two classes." Made me feel free to design enum to be aware of the entity. – Andronicus Feb 13 '20 at 10:06
  • @Andronicus how to set the class fields based on enum value? – vinter Jan 01 '22 at 12:30
2

There is one more way:

Define an abstract method in enum and override for every enum field:

public enum MyEnum {
    FIELD1 {
        @Override
        public String getFromPojo(MyPojo myPojo) {
            return myPojo.getField1();
        }
    },
    FIELD2 {
        @Override
        public String getFromPojo(MyPojo myPojo) {
            return myPojo.getField2();
        }
    },
    FIELD3 {
        @Override
        public String getFromPojo(MyPojo myPojo) {
            return myPojo.getField3();
        }
    };

    public abstract String getFromPojo(MyPojo myPojo);
}

For class MyPojo define getters for fields:

Define also method matchMethod in a way that it will handle the responsibility to the enum (if you wish, this is not mandatory, since enum can resolve the field by itself).

public class MyPojo {

    private String field1;
    private String field2;
    private String field3;

    public MyPojo(String field1, String field2, String field3) {
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
    }

    public String getField1() {
        return field1;
    }

    public String getField2() {
        return field2;
    }

    public String getField3() {
        return field3;
    }

    public String matchMethod(MyEnum myEnum) {
        return myEnum.getFromPojo(this);
    }
}

Now in "main" method you can use the following approach:

 MyPojo p1 = new MyPojo("p1", "p2", "p3");
 MyPojo p2 = new MyPojo("k1", "k2", "k3");

 System.out.println(MyEnum.FIELD1.getFromPojo(p1));
 System.out.println(MyEnum.FIELD2.getFromPojo(p1));
 System.out.println(MyEnum.FIELD3.getFromPojo(p1));

 System.out.println(MyEnum.FIELD1.getFromPojo(p2));
 System.out.println(MyEnum.FIELD2.getFromPojo(p2));
 System.out.println(MyEnum.FIELD3.getFromPojo(p2));

 // in addition, if you've defined 'matchMethod' on POJO
 System.out.println(p1.matchMethod(MyEnum.FIELD1));
 System.out.println(p1.matchMethod(MyEnum.FIELD2));
 System.out.println(p1.matchMethod(MyEnum.FIELD3));

 System.out.println(p2.matchMethod(MyEnum.FIELD1));
 System.out.println(p2.matchMethod(MyEnum.FIELD2));
 System.out.println(p2.matchMethod(MyEnum.FIELD3));     

This prints:

p1
p2
p3
k1
k2
k3
// and optionally...
p1
p2
p3
k1
k2
k3
Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
  • 1
    This doesnt look compatible with SOLID Prensiple (Open for extension close for modification.) and Couple-Cohesion? so to call POJO class from enum, doesnt look good choice for me. – Sha Feb 09 '20 at 11:53
  • It all depends on your use-case, currently MyPojo and MyEnum are already coupled: Remove FIELD3 from the enum, you'll have to change the POJO; add a new field to Pojo, probably you'll also have to change `MyEnum` . I've tried to facilitate java syntax capabilities, all other solutions will include a level of indirection: Map and so forth. The choice is yours of course – Mark Bramnik Feb 09 '20 at 12:19
2

You can use reflection. Here is an example:

public String matchMethod(MyEnum myenum, Map<MyEnum, String> enumToFieldMap) throws NoSuchFieldException, IllegalAccessException {
    String customFieldName = enumToFieldMap.get(myenum);
    if (customFieldName == null) { // custom field name not found, use default mapping
        return (String) this.getClass().getDeclaredField(myenum.name().toLowerCase()).get(this);
    } // custom field name found in config
    return (String) this.getClass().getDeclaredField(customFieldName).get(this);
}

public String matchMethod(MyEnum myEnum) throws NoSuchFieldException, IllegalAccessException {
    return matchMethod(myEnum, Collections.EMPTY_MAP);
}

There are some drawbacks of using reflection like type safety or traceability, however in this case I think I would choose this option.

Another, much more flexible option is to use reflection combined with custom annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyEnumRef {
    MyEnum value();
}

And common interface:

public interface Pojo {
}

Declaring new Pojo(s) becomes much simpler and cleaner now, and more readable too (at least for some people). It is also very obvious where the actual mapping (configuration) is done.

public class MyPojo implements Pojo {
    @MyEnumRef(MyEnum.FIELD1)
    private String field1;
    @MyEnumRef(MyEnum.FIELD2)
    private String field2;
    @MyEnumRef(MyEnum.FIELD3)
    private String field3;
}

public class MyOtherPojo implements Pojo {
    @MyEnumRef(MyEnum.FIELD1)
    private String field1;
    @MyEnumRef(MyEnum.FIELD2)
    private String field2;
}

One simple method to rule them all:

public String matchMethod(MyEnum myEnum, Pojo myPojo) throws IllegalAccessException {
        for (Field field : myPojo.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(MyEnumRef.class) && field.getAnnotation(MyEnumRef.class).value() == myEnum) {
                field.setAccessible(true);
                return (String) field.get(myPojo);
            }
        }
        return "";
    }

It doesn't matter which implementation of Pojo you use. There is no overhead when adding new Pojos. Example:

private void run() throws IllegalAccessException {
    System.out.println(">>" + matchMethod(MyEnum.FIELD2, new MyPojo("f1", "f2", "f3")));
    System.out.println(">>" + matchMethod(MyEnum.FIELD1, new MyOtherPojo("o1", "o2")));
}
Piotr Niewinski
  • 1,298
  • 2
  • 15
  • 27
1

This solution only works for static fields, since enums are always static!

One way I can imagine is the following:

public enum MyEnum
{
   private String field;

   public String getField()
   { return this.field; }


   MyEnum(String field)
   {
      this.field = field;
   }

   FIELD1(field1),
   FIELD2(field2),
   FIELD3(field3),
   ...
}

you can even make it generic if you want:

public enum MyEnum<T>
{
   private T field;

   public T getField()
   { return this.field; }


   MyEnum(T field)
   {
      this.field = field;
   }

   FIELD1(field1),
   FIELD2(field2),
   FIELD3(field3),
   ...
}
lue
  • 449
  • 5
  • 16
  • you are completely right. This only works for static fields.... I corrected the answer – lue Jan 16 '20 at 19:34
1

Here is yet another way, it does have one level of indirection (registry), but doesn't modify an Enum.

This requires Java 8+, because it uses lambda expressions:

import java.util.EnumMap;
import java.util.function.Function;

enum MyEnum {
  FIELD1, FIELD2, FIELD3;
}

class MyPojo {
  private String field1, field2, field3;

  public MyPojo(String f1, String f2, String f3) {
    this.field1 = f1;
    this.field2 = f2;
    this.field3 = f3;
  }
  private static EnumMap<MyEnum, Function<MyPojo, String>> registry = new EnumMap(MyEnum.class);
  static {
    registry.put(MyEnum.FIELD1, p -> p.field1);
    registry.put(MyEnum.FIELD2, p -> p.field2);
    registry.put(MyEnum.FIELD3, p -> p.field3);
  }

  public String matchMethod(MyEnum e) {
    return registry.get(e).apply(this);
  }
}

class Main {
  public static void main(String[] args) {
    MyPojo p1 = new MyPojo("a", "b", "c");
    MyPojo p2 = new MyPojo("x", "y", "z");
    System.out.println(p1.matchMethod(MyEnum.FIELD1));
    System.out.println(p1.matchMethod(MyEnum.FIELD2));
    System.out.println(p1.matchMethod(MyEnum.FIELD3));

    System.out.println(p2.matchMethod(MyEnum.FIELD1));
    System.out.println(p2.matchMethod(MyEnum.FIELD2));
    System.out.println(p2.matchMethod(MyEnum.FIELD3));
  }
}

This prints:

a
b
c
x
y
z
Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97