5

I have a class that looks like the following:

class FooClassAnalyser<T extends Foo> extends ClassAnalyser<T>

(where ClassAnalyser is an abstract base class for a number of concrete implementations; and FooClassAnalyser is a concrete implementation that's specialised for the case in which T extends Foo). It has a constructor that looks like this:

FooClassAnalyser(Class<T> classToAnalyse)

In another class, I have a static factory method for ClassAnalysers that calls an appropriate constructor depending on the type of classToAnalyse:

static <U> ClassAnalyser<U> constructClassAnalyser(Class<U> classToAnalyse)

The functionality I want is to check to see if U instanceof Foo, then construct a FooClassAnalyser and return it if it is.

However, I can't find a way to fit this within Java's type system. Type erasure means that we can't do anything clever with U directly. However, the fact that we pass classToAnalyse in as an argument makes it possible to test to see if U instanceof Foo via using reflection:

if (Foo.class.isAssignableFrom(classToAnalyse))

My problem is that unlike instanceof, this "instanceof via reflection" isn't visible to Java's type system. In particular, passing classToAnalyse directly as an argument to FooClassAnalyser's constructor fails with a type mismatch because Java doesn't know that classToAnalyse is actually a Class<U extends Foo>.

The best solution I've found so far is to use an unchecked cast to make classToAnalyse a Class<? extends Foo> (it is actually checked, but Java's unaware that it's checked). That at least makes it possible to pass it as an argument to new FooClassAnalyser, and get a FooClassAnalyser<?> object in return. The problem, however, is that this doesn't then convert back into a ClassAnalyser<U>, because Java doesn't recognise that casting classToAnalyse to have a different generic bound nonetheless doesn't change the fact that the Class object is still the same object (and thus is still a Class<U>); in other words, all Java can see is a FooClassAnalyser<?> that it doesn't recognise is also a FooClassAnalyser<U>, and thus converting back requires another unchecked cast. The result is code that compiles and runs, but with numerous warnings about type safety.

Most of the other things I've attempted have been syntax errors (e.g. a variable of type Class<U extends Foo> can't be declared directly; Java doesn't parse that properly). It should be noted that I don't actually have an object of type U at any point; I'm attempting to analyse the class itself, and thus only have Class<U> objects to work with.

Is it possible to write code like this in a type-safe way?

smithaiw
  • 107
  • 6
  • 1
    For what it's worth, I don't think you can do any better than what you're doing already (with unchecked casts). It always gets gnarly when you do _anything_ slightly more advanced with Java generics. – kaqqao Jun 12 '17 at 11:48
  • 3
    As a side note, you don't have to use an unchecked cast to `Class extends Foo>`. You can use [`clazz.asSubclass(Foo.class)`](http://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#asSubclass-java.lang.Class-). – Radiodef Jun 12 '17 at 16:22
  • 1
    @Radiodef: yes, but `clazz.asSubclass(Foo.class)` returns a `Class extends Foo>` which has lost the knowledge about ``. You can use it to construct a `FooClassAnalyser extends Foo>` safely without warnings, but have no clean way of returning it as `ClassAnalyser`… – Holger Jun 12 '17 at 16:25
  • @Holger Yeah, that's why it's a side note. – Radiodef Jun 12 '17 at 16:26
  • This is an older question, so I am not sure if this is a solved issue or not, but shouldn't the reflection package have a method for calling the method or constructor you need by passing in the reflected generic type? I know c# can do this (https://stackoverflow.com/a/325161/1026459), which is why I asked. – Travis J Mar 26 '19 at 02:40
  • @TravisJ: I don't need to call a method or constructor at all, just to cast an object. I've been using the solution of "two unchecked casts" for a while; but it's ugly, I asked this question hoping that there was a better one that actually respected Java's type system, but it seems like there isn't. As a side note, calling `Method()` in Java, with `T` not a compile-time constant, is very easy due to type erasure; you just call `Method()` (with no angle brackets) and get a compile-time warning, but it works. – smithaiw Mar 26 '19 at 12:16

1 Answers1

0

My problem is that unlike instanceof, this "instanceof via reflection" isn't visible to Java's type system. In particular, passing classToAnalyse directly as an argument to FooClassAnalyser's constructor fails with a type mismatch because Java doesn't know that classToAnalyse is actually a Class<U extends Foo>.

This will happen as long as you're passing a Class<U> to constructClassAnalyser. You didn't provide us with much code, so I can't write an explicit solution for you; however, it would be a lot more simple if you can find a way to pass U to constructClassAnalyser instead of Class<U>.

static <U> ClassAnalyser<U> constructClassAnalyser(U objectToAnalyse)

To check its class, you can use objectToAnalyse#getClass; you can also use instanceof to verify that U extends Foo if objectToAnalyse instanceof Foo returns true, and then cast it to its respective class if required.

Jacob G.
  • 28,856
  • 5
  • 62
  • 116
  • The whole point of a `ClassAnalyser` is that it analyses a class; it doesn't necessarily have an object of that class available. So this solution won't work, and this doesn't really answer the question. (Even if we did have an object of the class available, though, I'm not convinced that you could get rid of the second unchecked cast in my current solution, although it'd be fairly easy to see how you could get rid of the first.) – smithaiw Jun 12 '17 at 02:43
  • If you have the class, just make a new instance of it using `Class#newInstance` – Jacob G. Jun 12 '17 at 02:44
  • That requires the class to have an accessible constructor, and also requires appropriate arguments to be found for the constructor. Additionally, the constructor might have side effects. (I still don't see how it would solve the initial problem, though.) – smithaiw Jun 12 '17 at 02:47
  • So make one! You're only using it to check the type anyway – Jacob G. Jun 12 '17 at 02:48
  • (This conversation should really be taken to chat, but I don't have enough reputation yet.) As far as I can tell, you're suggesting fixing a type inference issue via creating a new no-argument constructor for every class we might potentially need to analyse (which automatically limits the method to only analysing classes that we *can* edit, and which could logically have a no-argument constructor; how would you initialize `final` fields that aren't meant to be nullable?). That seems very inefficient, as well as not being general enough to solve the original problem. – smithaiw Jun 12 '17 at 02:52