简体   繁体   中英

Enum static Method being called from Generic class

I want to make a refactoring and want to create a generic class for avoiding duplicate code. We have many XXXCriteriaValidator in our project and we want to make one only unique class to substitute them all.

The problem is one line where this class calls for a static method from an Enum. Here you will see. This is more or less what I'mtrying to achieve:

public class GenericCriteriaValidator<T extends ¿SomeKindOfEnumInterface?> 
                                               implements CriterionVisitor {

    protected Errors errors;

    public Errors getErrors() {
        return this.errors;
    }

    /* 
     * Some code around here 
     */

    protected void doVisit(final PropertyCriterion criterion) {
        if (criterion == null) {
            this.errors.reject("error.criterion.null");
        } else {
            if (criterion.getOperator() == null) {
                this.errors.reject("error.operator.null");
            }

            // Validates property (exception thrown if not exists)
            T.fromString(criterion.getName()); // The problem is this call here!!
                                               // Not saying this compiles, just looking
                                               // how to do something equivalent
        }
    }
}

T is always a differente Enum. The typical enum is like this:

public enum ContactCriteria implements CriteriaInterface<ContactCriteria>  {
                                       // ^ This interface is added by me
                                       //   for the enum being called in the previous                           class

    CONTACT_ID("this.id"),
    CONTACT_COMPANY_ID("this.companyId"),
    CONTACT_NAME("this.name"),
    CONTACT_EMAIL("this.email"),
    CONTACT_PHONE_NUMBER("this.phoneNumber"),
    CONTACT_ORDER("this.order"),

    private final String alias;

    ContactCriteria(final String alias) {
        this.alias = alias;
    }

    public String getAlias() {
        return this.alias;
    }

    public static ContactCriteria fromString(final String name) {
        ContactCriteria result = null;

        if (name != null) {
            result = Enum.valueOf(ContactCriteria.class, name);
        }
        return result;
    }

    public ContactCriteria returnThis() {
        return this;
    }

}

Finally, I'm looking for making an interface for the first class to accept the fromString method of T. I suppose it should be similar to:

public interface CriteriaInterface<T> {
    static T fromString(String name);
    // ^ This static is important
}

I haven't found none post or strategy for making something similar with an Enum. I know the Enum can implement an interface, but don't know how to get it.

Please help. Thanks in advance

You should start with that a static method is not allowed in Java interface.

The concept behind interfaces strongly disagree with static elements as they belong to class not to object.

So if you have a static method in a enum is just a container that is assigned to but you should not connect it by any other relations.

What is bad here is the design, you try to use enum to something that the are not dedicated on in the way you should not that why you struggle so much.

The question is if a enum instance is an CriteriaInterface then why is should provide it self by name.

Enum contains definition of "constants" that can represent an interface but can not be generic. That why enum can implement interface.

To express that you can define a interface

interface Messanger {
  String getMessage();
} 

And try to apply it to enum

enum Messages {
 INFO
 WARNING;
}

You have two options,

First, create a field that will be

   enum Messages implements Messanger {
     INFO,
     WARNING;

     private String message;

     @Override
     public String getMessage() {
          return message;
     }
}

Then you have to add the constructor to set the field

   enum Messages implements Messanger {
     INFO("Info"),  //We create an instance of class as we call the constructor 
     WARNING("Warnig") //We create an instance of class as we call the constructor 
     ;

     private final String message;

     public Message(String message) {
       this.messsage = message;
     }

     @Override
     public String getMessage() {
          return message;
     }
}

As we declare the instances inside the body of the enum you must provide all information required to create it. Assuming that enum would allow generic this is the place where you should declare it.

If the static method is on your CriteriaInterface , shouldn't you do

CriteriaIntervace.fromString("")

since static methods belong to a class (in this case CriteriaIntervace ) instead of to an object?

You can't put static methods in an interface, the generics etc have no direct bearing on this. Interfaces define the methods of an instance of an object, static methods are not part of the interface of an instance, they are part of the interface of the class.

The easiest work around will be to provide a factory object to the GenericCriteriaValidator or make it abstract and provide an:

abstract T getEnum(String name);

Each implementation can then implement getEnum for the enum it is using.

Well, generally speaking, the generic type is erased and you have no other chance than explicitly telling the GenericCriteriaValidator what kind of validation logic it should apply. You might want to abstract the receiving of some type away and use a factory pattern for that what would allow you to define an interface for the fromString method.

This would result in something like this:

public interface CriteriaInterface<T> {
  static class Factory<U> {
    U fromString(String name);
  }
}

However, I do not quite see the benefit of that in your example. Simply require an instance of CriteriaInterface<T> as a constructor argument to your GenericCriteriaValidator and define some sort of validate method in this interface.

However, if you really, really want to avoid this, there is a solution. It is possible to read the generic type of the super class of some other class (this is rather hacky, requires reflection and I would not recommend it, but some libraries love this approach). This requires you to always declare an anonymous subclass when using your generic class:

class GenericCriteriaValidator<T extends Enum<?>> implements CriterionVisitor {

  private final Method criteria;

  public GenericCriteriaValidator() {
    ParameterizedType parameterizedType = (ParameterizedType) getClass()
       .getGenericSuperclass();
    try {
      criteria = ((Class<?>) parameterizedType.getActualTypeArguments()[0])
          .getMethod("fromString", String.class);
      criteria.setAccessible(true);
    } catch (NoSuchMethodException e) {
      throw new IllegalArgumentException(e);
    }
  }

  @SuppressWarning("unchecked")
  private CriteriaInterface<?> invokeFromString(String value) {
    try {
      return (CriteriaInterface<?>) criteria.invoke(null, value);
    } catch (IllegalAccessException e) {
      throw new IllegalStateException(e);
    } catch (InvocationTargetException e) {
      throw new IllegalArgumentException(e);
    }
  }

  // Your other code goes here.

}

Be aware that you need to instantiate your GenericCriteriaValidator as an anonymous subclass:

new GenericCriteriaValidator<ContactCriteria>() { }; // mind the braces!

As I said. I do not find this intuitive and it is most certainly not the "Java way", but you might still want to consider it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM