1

Say I have these two methods:

<T extends MyClass> void method1(Class<T> a, ArrayList<T> b) {
    //whatever
}

<T extends MyClass> void method2(T c) {
    method1(c.getClass(), new ArrayList<T>()); //Here I get compile error: wrong argument type for the second parameter
}

While this other snippet returns no errors (only a warning for unchecked cast):

<T extends MyClass> void method1(Class<T> a, ArrayList<T> b) {
    //whatever
}

<T extends MyClass> void method2(T c) {
    method1((Class<T>)c.getClass(), new ArrayList<T>());
}

Can someone explain why I get the error in the first example, and what's the best way to deal with that?

alephz3r0
  • 99
  • 7
  • 1
    OT but nothing extends `String`. – user207421 Feb 11 '18 at 22:18
  • 3
    `c.getClass()` is of type `Class extends T>` so it doesn't match your `ArrayList` as parameters for `method1` – khelwood Feb 11 '18 at 22:21
  • 2
    For a value `c` of a generic type `T`, `c.getClass()` is not guaranteed to return `Class`, since `c` is not necessarily strictly of type `T` but rather of some (unknown) type that is assignable to `T`. (For that matter, `c` could even be null!) – Daniel Pryden Feb 11 '18 at 22:21
  • I can have my own class that extends `String` - anyway, that's a generic example to keep things simple :) – alephz3r0 Feb 11 '18 at 22:22
  • 4
    @alephz3r0 - `String` is `final` :/ – Oliver Charlesworth Feb 11 '18 at 22:23
  • @alephz3r0 to extend `String` you have to modify the internal JVM so it is not `final`. Or create a new class called `String`, or a have a generic type of that name. All options seem like a pretty bad idea. – Peter Lawrey Feb 11 '18 at 22:25
  • @OliverCharlesworth Alright, I will just use a generic `MyClass` for the sake of the example – alephz3r0 Feb 11 '18 at 22:27
  • @DanielPryden Thanks, that's the point, now I get it. So is there no way for calling `method1` only having `c` available as a parameter in `method2`? – alephz3r0 Feb 11 '18 at 22:36
  • 2
    You can make your method accept an `ArrayList extends T>`, but using a covariant type like that means you’ll only be able to get items _out_ of the list, and not put anything into it. More accurately, you’d be able to call methods on the list that have `T` in the return type, but not in an argument type. That’s the only way to guarantee type safety. – Mike Strobel Feb 11 '18 at 22:51
  • In working your question on my local environment, the issue I believe that you're facing can be answered in the above duplicate. Ultimately, this predicates on you not doing quite the right thing in getting the generic class back, which winds up throwing things out of whack. – Makoto Feb 11 '18 at 23:06
  • @MikeStrobel This isn't going to make any difference OP's code still won't compile and he already only can get items out of the list. – Oleg Feb 11 '18 at 23:11
  • Do you think I can ignore the unchecked cast warning in this specific case? I mean, the only possibility is that `method2` will be called like `method2(someObject)`, and assume that `someObject` is an object of class `SomeClass` (that extends `MyClass` of course). Hence, `someObject.getClass()` will return `SomeClass`, and `T` will be equal to `SomeClass` too. Can you make an example of how can things go wrong if I ignore the unchecked cast warning? – alephz3r0 Feb 12 '18 at 10:13
  • @khelwood: "`c.getClass()` is of type `Class extends T>`" No, actually, `.getClass()` returns type `Class extends |X|>` where `|X|` is the erasure of the type of the expression it's called on. The erasure of `T` (the type of `c`) is `MyClass`, so `c.getClass()` is actually of type `Class extends MyClass>`. – newacct Feb 25 '18 at 06:53

1 Answers1

1

public final native Class<?> getClass(); - this is code from Object class. Therfore you have warning, when do unsafe cast from Class<?> to Class<T>. You can give JVM more flexibility:

<T extends MyClass> void method1(Class<T> a, List<T> b) {
    //whatever
}

<T extends MyClass> void method2(T c) {
    method1(c.getClass(), new ArrayList<>());
}

There're no warning and compile error.

Oleg Cherednik
  • 17,377
  • 4
  • 21
  • 35
  • 4
    To be more precise: ([from documentation](https://docs.oracle.com/javase/9/docs/api/java/lang/Object.html#getClass--)) "The actual result type is Class extends |X|> where |X| is the erasure of the static type of the expression on which getClass is called." – Pshemo Feb 11 '18 at 22:41
  • This still erases to `Object`. You get back an `ArrayList` from that declaration. This is a situation where the developer is lulled into a false sense of security because the compiler hasn't squawked yet. If you actually try to *use* that array list, you can't. It looks like it converts to `ArrayList extends MyClass>`, which can only ever be read from. – Makoto Feb 11 '18 at 22:55
  • Do not forget, that at runtime, all `Class` and `List` become `Class extends Object>` and `List extends Object>` – Oleg Cherednik Feb 11 '18 at 23:07
  • Yes, this is a problem when the intent from the method signature being called with it is to ensure that it's *not* `Object`. – Makoto Feb 11 '18 at 23:11
  • @Makoto This does ensure type safety in `method1` the array is going to be `T extends MyClass` P.S. this has nothing to do with your duplicate. – Oleg Feb 11 '18 at 23:15