why does "Integer [ ] x = (Integer [ ]) new Object[100]; " compile?(为什么要编译“Integer[]x=(Integer[])new Object[100];”?)
转载作者:bug小助手更新时间:2023-10-26 20:44:48294
I compile:
我编写了:
Integer [ ] x = (Integer [ ]) new Object[100];
it succeeds. and I don't know Why?
它成功了。我不知道为什么
更多回答
Because you lied to the compiler.
因为你对编译器撒了谎。
Casting is a convenience Java has given you to make the Java compiler trust you. So, it compiles but you are trying to abuse the convenience given to you. Remember Karma 😊
Note that casting a literal expression happens at compile time: int i = (int)(1.0+2.0); This is different than casting a reference type as the OP has. So the OP might be asking why the reference type is different than the literal type.
There are a number of things going on here. Most of this is 'programming language archeology' - none of it makes sense and none of it is particularly consistent. If your aim is to learn the language better, there's a very simple rule you should apply to your java programming:
Do not use arrays. At all. For anything. Except perhaps arrays of primitive types (int[], long[], etcetera) for performance reasons. There is no point to them. They aren't faster than lists and have all sorts of wonky downsides. Including effectively broken equals and hashCode and toString implementations (in that they do unexpected things; the spec properly explains these things, but an API that does what the spec says it does, but the spec says bizarro unexpected things, well, I'll let the reader come up with a term for it. I trust it won't be a particularly nice term), they are this useless amalgamation of immutable-ish (they can neither grow nor shrink) but not actually immutable (you can set values), and more.
When casting arrays, assume that the compiler has no idea and you'll have to rely on runtime exceptions. Really, once you've reached this level (You're casting arrays), you need to back up and fix whatever got you into that mess.
There is really not much point in going any further if your aim is to be a good java coder.
如果你的目标是成为一名优秀的Java程序员,那么再往前走就没有多大意义了。
But, hey, you asked - so, let's put on our brown hat, grab a whip, play the Indiana Jones themesong, and go spelunkin' into the depths of java's distant past!
We need to cover the key problem first. An answer has already been posted by somebody who does not understand type systems, so, that's anecdotal proof this needs to be explained first.
我们需要首先解决关键问题。不了解类型系统的人已经发布了答案,因此,这是需要首先解释的轶事证据。
Java's type system is itself 'covariant'. That means: Any type is a fine substitution for any of its supertypes. If you have a method that requires that you pass it a Number parameter, you can pass an expression of type Integer, no problem.
However, once you move up to listy things (be it generics, or arrays) this does not work. They should be invariant - Only the exact type required will do and no other type can substitute.
That's because you can actually both writeandread from listy things and therein lies the problem. Here's a trivial example:
这是因为您实际上可以从列表中写入和读取内容,这就是问题所在。下面是一个简单的例子:
Integer i = 5; Number n = i; // this is fine.
Integer[] is = new Integer[10]; Number[] ns = is; // fine, too? right? Maybe? ns[0] = 5.5; // ... a double Integer firstElement = is[0]; // BOOM!
That last line shows the problem. Because basic java types are covariant and will remain so, the ns[0] = 5.5 line has to compile - I'm assigning a Double to a place that requires a Number. Nevertheless this leads to a problem on the last line.
Generics gets this right. The generics equivalent is this:
Generics做对了。泛型的等价形式是:
List<Integer> is = new ArrayList<Integer>(); List<Number> ns = is; // This does not compile!
And gives you the tools you need to make nice APIs; you can ask for covariance (List<? extends Number>, where a List<Integer>is acceptable, but the compiler prevents you from writing to this list to ensure the 'whoops there is a double in my list of integers' problem cannot occur), and even contravariance (List<? super Integer>, where you can pass a List<Integer>, or List<Number>, or a List<Object>. You can write to it (any integer - which works fine regardless of which of those 3 kinds of lists is actually passed in), but the compiler prevents you from reading. Or rather, returns Object, given that List in general guarantees that whatever it stores, it's at least Object).
Here's a good tip: When you read List<? extends Number> you may be translating that: "A list, containing things whose type is Number or subtypes of Number - you know, things whose type is.. something, that extends Number". That is wrong. You're supposed to read it as: "A list, limited to contain some type that I simply do not know here (see the question mark? That means 'don't know'). All I know is: That restriction is Number or some subtype there of. It could be a list that is only supposed to store integers. Could be Doubles. Could be any Number. No idea. Everything I do with this list must be valid regardless of whether that's a List<Number> or a List<Integer> or a List<Double> or any other List<X> where X is a subtype of Number. It has to work for ALL of them or it is not valid java and the compiler will refuse it. Hence why list.add() doesn't work here - there is no value that is both Double and Integer and Number and SomeSubTypeOfNumberYouHaventEvenWrittenYet. Well, except the literal null which indeed you can actually pass, the only way to call add at all.
Why? Well, generics is really complicated. The notion that you need 4 different types that all are List<Number> adjacent but subtly different is already quite complex:
List (raw/legacy type. Perhaps if java had had generics from v1.0 this would not be needed).
List<Number>
List<? extends Number>
List<? super Number>
and java was trying to 'keep things simple' so didn't add this. Generics didn't show up until java 1.5, over a decade later.
而Java试图让事情变得简单,所以没有添加这一点。十多年后,泛型直到Java 1.5才出现。
In some ways the designers of java were apparently (it's been a looong time, I haven't asked them) also not aware that the component type of a listy thing just cannot be covariant. Or, more likely, sometimes you want to write code that operates on arbitrary arrays and the language designers decided to just let the compiler get out of the way, turn java into more or less a dynamically typed (i.e. the compiler doesn't help you) situation where only the runtime exists to prevent problems from occurring.
Object is = new Integer[10]; Number[] numbers = (Number[]) is; is[0] = 5.5;
That code compiles, but when you run it.. the second line works, but the third line doesn't - fails with an ArrayStoreException. That's because arrays do know what their component type is, and thus the is[0] =.. assignment does check: Hey, what's the actual component type of the array object that the is variable is pointing at? Oh it's Integer[].. hmm, then, ArrayStoreException. It's a variant of ClassCastException in that sense.
Similarly, you can actually cast Object arrays to whatever array type you want, but the system is designed so that storing things into arrays might fail with an ArrayStoreException, but reading them never will. Because it's easier to reason about code when you know certain things aren't possible. Such as non-integers being in your Integer[] array.
To ensure that rule, when you cast an array, the runtime checks and won't let you if it means read operations could fail. Hence why arrays are covariant-ish:
Object xs = new Integer[100]; Number[] ns = (Number[]) xs; // allowed xs = new Number[100]; Integer[] is = (Integer[]) xs; // not allowed
Where 'not allowed' means: Usually compiles fine, but at runtime results in a ClassCastException.
其中‘不允许’意味着:通常编译得很好,但在运行时会导致ClassCastException异常。
The only reason it works that way is that the 'allowed' line is still broken (in that you'd expect an array of type Number[] to allow you to write any Number to it, whereas here this one doesn't and will fail with an ArrayStoreEx unless it's an Integer), but allowed because it can't break when reading from it (whatever is in that array, it's at least a Number. Because all Integers are Numbers and the only thing that array can store is Integer objects). Whereas the second is not allowed, because if it had been allowed, reading from it could cause problems. ArrayStoreException exists; ArrayReadException does not.
Why was the choice made this way? At some point we've arrived at a dead end. The answer to that is: "Because the spec says so" and if you want to know why that is the case, the answer is: "Because the designers at the time wanted it that way". Any further 'but.. why?' questions should be directed directly at James Gosling and co.
1/5th through this answer and I already could tell who the author was. Contravariance/Covariance/Invariance are key topics that are not easy to understand. Well stated, and from all of us, thanks for well educating us. 1+
I'm not sure this answers the OP's question. Why does that line compile? The compiler will evaluate int i = 1.0 + 2.0; at compile time and throw an error. But I'm pretty sure both the cast (Integer[]) and the new keyword are never evaluated by the compiler like a literal is. (But I didn't double check that.) So there is no choice: both new and the cast are only evaluated at runtime.
I guess in other words I'm interpreting the OP's question as asking "The type Object[] is right there, the cast (Integer[]) is right there, why can't the compiler evaluate that and spot the error immediately?" (Edit: but int i = (int)(1.0+2.0); is evaluated at compile time, so I guess casting references only happens at runtime, primitives can be evaluated at compile time; possibly this is also a source of confusion.)
@markspace The last line of the answer explains it: It's a one-word explanation: "Because". Spec says so. Doing it right is very complicated (see middle of answer: Co/Contra/Invariance), presumably java v1.0 design threw in the towel to try to make sense of it at compile time.
我是一名优秀的程序员,十分优秀!