8

I have following class:

public class Publisher<T> {

    private static final Class[] SUPPORTED_CLASSES = new Class[]{T1.class, T2.class};

    public Publisher() {
        if(Arrays.asList(SUPPORTED_CLASSES).contains(T)) { // error: expression expected!
            System.out.println("Class not supported!");
        }
    }
}

How can I check if class parameter conforms to the implementation?
In the above example I cannot use class parameter T as a parameter.

FazoM
  • 4,777
  • 6
  • 43
  • 61
  • Check this thread https://stackoverflow.com/questions/3403909/get-generic-type-of-class-at-runtime – Veselin Davidov Oct 19 '17 at 12:22
  • 3
    Why do you want this, if I may ask? – MC Emperor Oct 19 '17 at 12:24
  • @MCEmperor I wanted to check if class is supported and log warning message if necessary – FazoM Oct 19 '17 at 12:37
  • 1
    What you are trying to do is an abuse of generics. Generics are a compile time feature to ensure type safety, but you are trying to enforce at runtime something that cannot be verified at compile time. Why is it necessary to restrict the allowed type parameters? – cpp beginner Oct 19 '17 at 12:38
  • 4
    Why are you not simply using a `S` that is only defined in `T1` and `T2`, use an interface `IPublisher`, add it to both class and then `Publish` ... you can remove that list and this condition. You know that `T` will be an instance of `IPublisher`. – AxelH Oct 19 '17 at 12:38
  • Agreed, or T extends S & IPublisher if the methods of S are needed. – cpp beginner Oct 19 '17 at 12:39
  • @AxelH charms of legacy code ;) – FazoM Oct 19 '17 at 12:41
  • @FazoM you can use wrappers if you don't have too many class to support ... even if this could get messy really quick I guess. – AxelH Oct 19 '17 at 12:42
  • I've edited question to get rid of `T extends S` which was confusing – FazoM Oct 19 '17 at 12:50

3 Answers3

13

Why this doesn't work

You are trying to access a generic type at runtime, which does not work in this case, because of type erasure.

How to fix

The simplest way to fix this is to take a Class<T> in the constructor, which will give you the type at run time, you can then check if the List contains the value you have been given.

Example code

public Publisher(Class<T> clazz) {
    if(!SUPPORTED_CLASSES.contains(clazz)) {
        System.out.println("Class not supported!");
    }
}

Possible issues

Your code does not currently support subtypes, which may cause issues, unless you are ok with this (you may work on Lists, but not necessarily ArrayLists), this does beak the LSP though.

jrtapsell
  • 6,719
  • 1
  • 26
  • 49
  • I'd say that an even more fundamental reason why the OP's code doesn't work is that type arguments such as `T` designate *types*, not objects. In particular, they are not the same as and do not represent objects of type `java.lang.Class`, such as the ones the OP's array contains. Types are not valid ordinary arguments. – John Bollinger Oct 19 '17 at 19:25
  • LSP is only a problem if OP's class is supposed to be covariant in T. If it's contra- or invariant, this is an entirely legal (if weird and non-OOP) way of doing things. – Kevin Oct 19 '17 at 20:30
6

Though some other answers are quite good, I would like to propose another workaround:

You could create an empty interface MyInterface, and have all the classes in your list implement this interface. Then, you can change your class declaration to:

public class Publisher<T extends S, MyInterface>

which will achieve your purpose.

Turtle
  • 1,626
  • 16
  • 26
  • 1
    This is a better solution if you have control of all of the classes you need to support, but if you need to support classes you haven't written you may need to do it a different way. – jrtapsell Oct 19 '17 at 12:40
  • 1
    @jrtapsell in the worst case, if you don't have to support too many class, using a bridge for each class you need (a simple wrapper in fact) implementing the interface – AxelH Oct 19 '17 at 12:41
  • When your solution only works with classes which implement a specific interface, you don't need that list of compatible classes anymore and can just declare the class as `public class Publisher` – Philipp Oct 19 '17 at 18:32
4

You'll need to pass the class into the constructor:

public Publisher(Class<T> clazz) {
    if(!SUPPORTED_CLASSES.contains(clazz)) {
        System.out.println("Class not supported!");
    }
}

because T isn't available at runtime: exactly the same code will be executed for new Publisher<T1>() and new Publisher<T3>().

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487