Why calling method with generic return on a generic class is considered unsafe by javac?
Asked Answered
C

4

16

Consider the following code:

public class Main {
    public static class NormalClass {
        public Class<Integer> method() {
            return Integer.class;
        }
    }

    public static class GenericClass<T> {
        public Class<Integer> method() {
            return Integer.class;
        }
    }

    public static void main(String... args) {
        NormalClass safeInstance = new NormalClass();
        Class<Integer> safeValue = safeInstance.method();

        GenericClass unsafeInstance = new GenericClass();
        Class<Integer> unsafeValue = unsafeInstance.method();
    }
}

If I compile it with:

$ javac -Xlint:unchecked Main.java 

It returns:

Main.java:16: warning: [unchecked] unchecked conversion
        Class<Integer> unsafeValue = unsafeInstance.method();
                                                          ^
  required: Class<Integer>
  found:    Class
1 warning

Please note that only the generic method is considered unsafe, even if no generic type is referenced on the return type.

Is this a javac bug? Or there is a deeper reason for this I'm not taking into account?

Czardas answered 2/5, 2015 at 16:58 Comment(6)
I don't know the reason, but it only happens if the generic class where the method is declared is a raw type.Lame
I am getting the error on GenericClass unsafeInstance = new GenericClass(); instead. This is because you instantiated a generic type with no type argument.Antabuse
Because when you choose to use raw types, you're basically saying to the compiler that you want to be in a non-typesafe, legacy mode. Don't use raw types. Read #2770821Isobaric
@Lame Yes, and I would find completely normal if the return type had a reference to the generic type (that would be undefined), but it doesn't.Czardas
@JBNizet I'm aware I shouldn't use raw types. But there is always legacy code. My question is more about why it does give a warning even if there is no generic type involved.Czardas
Because, when you use GenericClass as a raw type, you set javac in raw type, legacy mode for this type, and javac ignores all generic types in Generic class. It thus sees the method as public Class method() instead of public Class<Integer> method().Isobaric
C
12

Raw types were allowed to ensure compatibility with code written before generics were introduced. Raw types work by simply ignoring all type information from all method arguments and return types, even type information that is not related to the type parameter of the class. This can lead to strange results, as you have found. But it gets even stranger than this. For example, this compiles.

public class Main {

    public static class GenericClass<T> {
        public void foo(Class<Integer> clazz) {
        }
    }

    public static void main(String... args) {
        GenericClass unsafeInstance = new GenericClass();
        unsafeInstance.foo(String.class);
    }
}
Culliton answered 2/5, 2015 at 17:24 Comment(1)
...but it doesn't if you specify the generic type parameter at instantiation time, although this does not affect method foo. Really cool example!Leffen
C
3

The type of a constructor (§8.8), instance method (§8.4, §9.4), or non-static field (§8.3) M of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

Java Language Specification

Chiclayo answered 2/5, 2015 at 17:58 Comment(2)
Even though I accepted the other answer, I think this answer also deserves more upvotes, because it cites the specification, that clarifies this was a specific design decision, not an unintended side effect of the implementation.Czardas
Also 4.6: "Type erasure also maps the signature of a method to a signature that has no parameterized types. [...] the return type of a method also undergoes erasure if the method's signature is erased".Flagstaff
U
2

In order to be compatible with Java 1.4 compiler assumes that if you drop the generic arguments on instance type declaration, then you work with the special version of the class where no generics exist at all. And it issues a warning if you mix a Java 1.4 non-generic code with Java 1.5+ generic code. That's easier than trying to figure out whether generic return type of your method is actually independent from parameters. You can always @SuppressWarning if you don't like it.

Uneven answered 2/5, 2015 at 17:25 Comment(0)
G
0

This might have to do with the instantiation of GenericClass which is a parameterized type and hence type arguments are needed in it. The warning vanishes if we do something like the following

GenericClass<String> unsafeInstance = new GenericClass<String>();
                          OR
GenericClass<?> unsafeInstance = new GenericClass();

As per my point of view, the reference "unsafeInstance" refers to a class which is generic in nature as opposed to "safeInstance". Thus the compiler may want the type information be associated to the reference before any method is called using it in code.

Gammy answered 2/5, 2015 at 19:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.