9

just found a line of code that I do not quite understand.

List<String>[] stringLists = new List[1];

This line can pass the compilation on my AndroidStudio IDE though with warning. It seems to violate the basic rule of Object Oriented language: "the super class object can be instantiated with child class instance, but not vice versa."

String[] ss = new Object[1];  // won't compile

We know that List is super type of any generic List types, such as List<String> in this case. And as array is covariant, so I think List[] type is super of List<String>[] type. Why a List<String>[] can be instantiated with a List[]?

7
  • 6
    Welcome to the wonderful world of type erasure. Commented Jan 18, 2016 at 22:43
  • yeah, I know erasure works at execution time, but should't the code in my example fails at compilation time? Commented Jan 18, 2016 at 22:48
  • I get Type safety: The expression of type List[] needs unchecked conversion to conform to List<String>[] in Eclipse, which is the correct warning. You've only instantiated the array at this point, and it contains only a null pointer. No Lists have been created yet. Commented Jan 18, 2016 at 23:31
  • 2
    The rule of thumb is that if you ever use a raw type (i.e. List and not List<?>), your intent is to bypass the post-Java-1.5 type system when it comes to type constraints between generic types. Do note that List is not the same as List<Object>! There is no meaningful relation between a raw type and a generic type, and casting between the two is something the Java language allows for the sake of compatibility. You should avoid raw types completely, unless absolutely necessary. Commented Jan 19, 2016 at 0:53
  • 1
    @SLaks - Type erasure isn't really the answer to this. Type erasure is the process by which the compiler generates bytecode for a runtime that can't handle parameterised types at a low level; it doesn't answer why that assignment is allowed at the language level. It's performed only after the code is checked for type safety, and isn't really relevant to a question about the language. Commented Jan 19, 2016 at 1:13

4 Answers 4

5

I think the JLS, §4.8, "Raw Types" answers your actual question of why the assignment is valid:

The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of generics into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.

It's for compatibility with pre-generics code. Generally if you're going for type-safety, you shouldn't use arrays of parameterised types at all, it would've been better if the author wrote:

List<List<String>> lists = new ArrayList<>();

Your assumption about a rule of type safety being violated is mistaken. You're saying that List is not a subtype of List<String>. However, in the Java type system, the answer to the question: "Is List a subtype of List<String>?" is neither "Yes", nor is it "No." It's "this question cannot be answered."

(It's also probably not correct to conflate "types" and "classes." There is only one List class, but List<String> and List<Object> are different types. Bonus: List<String> is not a subtype of List<Object>.)


To add a bit more detail about what's going on, the JLS explains what conversions are allowed in a variable assignment in §5.2, "Assignment Contexts". Notably, the list ends with:

If, after the conversions listed above have been applied, the resulting type is a raw type (§4.8), an unchecked conversion (§5.1.9) may then be applied.

The latter link to §5.1.9, "Unchecked Conversions", after some formalisms explaining what an unchecked conversion is, reiterates the rationale (emphasis mine):

Unchecked conversion is used to enable a smooth interoperation of legacy code, written before the introduction of generic types, with libraries that have undergone a conversion to use genericity (a process we call generification). In such circumstances (most notably, clients of the Collections Framework in java.util), legacy code uses raw types (e.g. Collection instead of Collection<String>). Expressions of raw types are passed as arguments to library methods that use parameterized versions of those same types as the types of their corresponding formal parameters.

Such calls cannot be shown to be statically safe under the type system using generics. Rejecting such calls would invalidate large bodies of existing code, and prevent them from using newer versions of the libraries. This in turn, would discourage library vendors from taking advantage of genericity. To prevent such an unwelcome turn of events, a raw type may be converted to an arbitrary invocation of the generic type declaration to which the raw type refers. While the conversion is unsound, it is tolerated as a concession to practicality. An unchecked warning is issued in such cases.

The official story is indeed that unchecked conversions from raw type to parameterised type were added to the language deliberately, despite being potentially unsafe, and are only marked with a compilation warning for compatibility reasons. (Java very much tries to ensure that code that compiled in version X never fails to compile or ceases to be useful in version X+1.)

Sign up to request clarification or add additional context in comments.

8 Comments

yeah, @millimoose I think you are right, the rule of type safety just does not apply for this special case, thanks!
@L14967 - Well, it does kiiind of apply. The assignment is unsafe, in that it's impossible to ensure that it is safe. (Which would require knowing the type signature of the right-hand-side.) Unfortunately compatibility had to be solved somehow, and the designers of Java decided to introduce the concept of "raw type" to express the way the type system works in legacy code.
@L14967 - You can compare and contrast to C#, which went the other way entirely: variables and objects can only ever be of specific types. (Even though something similar to "raw types" exists in C#, "generic type definitions", they're mostly used when doing reflection.) This of course lead to other parts of the design being different: C#'s generic collections are different classes entirely than the old non-generic ones. It's also close to impossible to retrofit generics into legacy frameworks; a notable example would be ASP.NET Web Forms and its repeaters as an ugly pain point.
thanks for the information. well, I am not sure if either way is good or not. Personally I do not like the idea of raw type. It is kind of breaking a hole of the well-designed type hierarchy. Anyway, compatibility is ALWAYS the headache we have to face in reality. :-)
I find it ironic that you down vote my answer which quotes a passage from JLS 4.3 that refers to the very same passage in JLS 4.8 that forms the basis of your own answer.
|
2

Generics in Java are just syntactic sugar in compilation time. In execution time, both lists are List<Object>

4 Comments

but at compiling time, the type should be List[] and List<String>[], I think.
While true, the fact that generics are implemented by erasure in Java 8 (and earlier) does nothing to solve the OP's issue. The Java compiler has the responsibility for type safety with generics, and yet this code compiles (never mind that generic array creation in Java is not type safe).
All of a programming language is syntactic sugar at compilation time. This is a pretty meaningless statement. It doesn't explain why a construct is or is not allowed.
Besides, they're clearly not List<Object> at execution time, because no such information is attached to objects then, and it doesn't really make sense to conflate compile-time types with runtime classes (as in objects loaded from .class files that contain the compiled implementation of a type). At runtime, the lists will be Lists, except not the "raw type", because the very concept of a raw type is meaningless at runtime.
2

Compiling in Eclipse I get

Type safety: The expression of type List[] needs unchecked conversion to conform to List<String>[]

which is the correct warning. Trying

List<String>[] sl = new ArrayList<String>[1];

I get

Cannot create a generic array of ArrayList<String>

In your code you are able to instantiate a non-generic array of List and assign it to List<String>[] with an unchecked conversion.

Comments

0

This line can pass the compilation on my AndroidStudio IDE though with warning. It seems to violate the basic rule of Object Oriented language: "the super class object can be instantiated with child class instance, but not vice versa."

The line:

List<String>[] stringLists = new List[1];

It seems to me that the above should not compile. Acknowledging that arrays in Java are covariant, I nevertheless don't think it is reasonable to conclude that List<String> is a super class of its raw type.

I was able to find this in the JLS, sec 4.3:

There are contexts in the Java programming language where a generic class or interface name is used without providing type arguments. Such contexts do not involve the use of raw types (§4.8). Rather, they are contexts where type arguments are unnecessary for, or irrelevant to, the meaning of the generic class or interface.

Perhaps, for the purpose of the above assignment, the raw type is allowed because the "type arguments are unnecessary for... the meaning of the generic class or interface." Once the raw type is assigned to stringLists, it must be a List<String>. Admittedly unexpected, if true.

6 Comments

I can't find this in the JLS, the whole quote seems odd to me. Clearly the OP's context involves the use of a raw type, so some other part of the spec would apply. The OP's code compiles because expressions involving raw types are treated specially in the compiler, and possibly non-type-safe assignments are allowed. List<String> is not a super class of List, nor vice versa, they do not have that sort of relationship at all. The "raw type" vs. "parameterized type" relationship is something completely unrelated to the superclass / subclass one.
@millimoose: well, I assure you that I didn't make the quote up, if that was the basis of your down vote. FWIW, "and possibly non-type-safe assignments are allowed" is a resynthesis of my answer (without explication) in any case.
It wasn't that, but everything else I mentioned. The fact that the quote doesn't apply because a raw type is, in fact being used. That you used this to justify why the raw type is allowed, when it's not because the type parameters are irrelevant. On the contrary, they are very relevant, because that assignment is potentially not type-safe, hence the warning. And your conclusion that there's a type relationship between a raw type and a parameterised type because arrays are covariant is also false.
In fact, the last part should be the other way around - if the raw type was a subtype of the parameterised type - which it isn't - then, and only then would array covariance imply that "array of the raw type" is a subtype of "array of the parameterised type." The subtyping relationship between the array element types is necessary precondition for covariance.
Reminding that List<String>.class is illegal while List.class is legal, I guess @millimoose is correct, "there is no meaningful relationship between a raw type and a generic type." So the OP rule does not apply for this type of assignment. Anyway, the compiler has tried it best by giving a warning. I do not know JLS implements the generics, but it is the most reasonable conclusion, I think.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.