通过未经检查的类型转换在 Java 中创建通用数组
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/17831896/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Creating generic array in Java via unchecked type-cast
提问by Markus A.
If I have a generic class Foo<Bar>
, I am not allowed to create an array as follows:
如果我有一个泛型类Foo<Bar>
,则不允许按如下方式创建数组:
Bar[] bars = new Bar[];
(This will cause the error "Cannot create a generic array of Bar").
(这将导致错误“无法创建 Bar 的通用数组”)。
But, as suggested by dimo414in an answer to this question (Java how to: Generic Array creation), I can do the following:
但是,正如dimo414在回答这个问题 (Java how to: Generic Array creation) 中所建议的那样,我可以执行以下操作:
Bar[] bars = (Bar[]) new Object[];
(This will "only" generate a warning: "Type safety: Unchecked cast from Object[] to Bar[]").
(这将“仅”生成一个警告:“类型安全:未检查从 Object[] 到 Bar[] 的强制转换”)。
In the comments responding to dimo414's answer, some people claim that using this construct can cause problems in certain situations and others say it's fine, as the only reference to the array is bars
, which is of the desired type already.
在回应dimo414答案的评论中,有些人声称使用此构造在某些情况下会导致问题,而其他人则表示这很好,因为对数组的唯一引用是bars
,它已经是所需的类型。
I'm a little confused in which cases this is OK and in which cases it can run me into trouble. The comments by newacctand Aaron McDaid, for example, seem to directly contradict each other. Unfortunately the comment stream in the original question simply ends with the unanswered "Why is this 'no longer correct'?", so I decided to make a new question for it:
我有点困惑在哪些情况下这是可以的,而在哪些情况下它会让我遇到麻烦。例如,newacct和Aaron McDaid的评论似乎直接相互矛盾。不幸的是,原始问题中的评论流只是以未回答的“为什么这'不再正确'?”结束,所以我决定为此提出一个新问题:
If the bars
-array only ever contains entries of type Bar
, could there still be any run-time issues when using the array or its entries? Or is the only danger, that at run-time I could technically cast the array to something else (like String[]
), which would then allow me to fill it with values of a type other than Bar
?
如果bars
-array 只包含 type 的条目Bar
,那么在使用数组或其条目时是否还会有任何运行时问题?或者是唯一的危险,在运行时我可以在技术上将数组强制转换为其他内容(例如String[]
),然后允许我用Bar
?以外的类型的值填充它。
I know I can use Array.newInstance(...)
instead, but I am specifically interested in the type-casting construct above, since, for example, in GWT the newInstance(...)
-option isn't available.
我知道我可以Array.newInstance(...)
改用,但我对上面的类型转换构造特别感兴趣,因为例如,在 GWT 中newInstance(...)
-option 不可用。
回答by newacct
Since I was mentioned in the question, I will chime in.
既然问题中提到了我,我就插话。
Basically, it will not cause any problems if you don't expose this array variable to the outside of the class. (kinda like, What happens in Vegas stays in Vegas.)
基本上,如果您不将此数组变量暴露给类的外部,它不会导致任何问题。(有点像,在维加斯发生的事情留在维加斯。)
The actual runtime type of the array is Object[]
. So putting it into a variable of type Bar[]
is effectively a "lie", since Object[]
is not a subtype of Bar[]
(unless Object
is Bar
). However, this lie is okay if it stays inside the class, since Bar
is erased to Object
inside the class. (The lower bound of Bar
is Object
in this question. In a case where the lower bound of Bar
is something else, replace all occurrences of Object
in this discussion with whatever that bound is.) However, if this lie gets somehow exposed to the outside (the simplest example is returning the bars
variable directly as type Bar[]
, then it will cause problems.
数组的实际运行时类型是Object[]
. 所以把它放入一个类型的变量Bar[]
实际上是一个“谎言”,因为Object[]
它不是Bar[]
(除非Object
是Bar
)的子类型。但是,这个谎言留在班级内部是可以的,因为它Bar
被擦除到Object
班级内部。(下界的Bar
是Object
在这个问题上,在下界的情况Bar
是另一回事,更换所有出现Object
在这次讨论与任何该界是。)但是,如果这个谎言被莫名其妙地暴露于外(最简单的示例bars
直接将变量作为 type返回Bar[]
,那么它会导致问题。
To understand what is really going on, it is instructive to look at the code with and without generics. Any generics program can be re-written into an equivalent non-generics program, simply by removing generics and inserting casts in the right place. This transformation is called type erasure.
要了解真正发生了什么,查看带有和不带有泛型的代码是有益的。任何泛型程序都可以重写为等效的非泛型程序,只需删除泛型并在正确的位置插入强制转换即可。这种转换称为类型擦除。
We consider a simple implementation of Foo<Bar>
, with methods for getting and setting particular elements in the array, as well as a method for getting the whole array:
我们考虑 的简单实现Foo<Bar>
,具有获取和设置数组中特定元素的方法,以及获取整个数组的方法:
class Foo<Bar> {
Bar[] bars = (Bar[])new Object[5];
public Bar get(int i) {
return bars[i];
}
public void set(int i, Bar x) {
bars[i] = x;
}
public Bar[] getArray() {
return bars;
}
}
// in some method somewhere:
Foo<String> foo = new Foo<String>();
foo.set(2, "hello");
String other = foo.get(3);
String[] allStrings = foo.getArray();
After type erasure, this becomes:
类型擦除后,这变成:
class Foo {
Object[] bars = new Object[5];
public Object get(int i) {
return bars[i];
}
public void set(int i, Object x) {
bars[i] = x;
}
public Object[] getArray() {
return bars;
}
}
// in some method somewhere:
Foo foo = new Foo();
foo.set(2, "hello");
String other = (String)foo.get(3);
String[] allStrings = (String[])foo.getArray();
So there are no casts inside the class anymore. However, there are casts in the calling code -- when getting one element, and getting the entire array. The cast to get one element should not fail, because the only things we can put into the array are Bar
, so the only things we can get out are also Bar
. However, the cast when getting the entire array, that will fail, since the array has actual runtime type Object[]
.
所以班级内不再有演员表了。但是,在调用代码中存在强制转换——获取一个元素和获取整个数组时。获取一个元素的转换不应该失败,因为我们唯一可以放入数组Bar
的东西是,所以我们唯一可以取出的东西也是Bar
。但是,获取整个数组时的转换将失败,因为数组具有实际的运行时类型Object[]
。
Written non-generically, what is happening and the problem become much more apparent. What is especially troubling is that the cast failure does not happen in the class where we wrote the cast in generics -- it happens in someone else's code that uses our class. And that other person's code is completely safe and innocent. It also does not happen at the time where we did our cast in the generics code -- it happens later, when someone calls getArray()
, without warning.
非一般地编写,正在发生的事情和问题变得更加明显。特别令人不安的是,转换失败不会发生在我们用泛型编写转换的类中——它发生在使用我们类的其他人的代码中。而那个人的代码是完全安全和无辜的。它也不会在我们在泛型代码中进行转换时发生——它发生在稍后,当有人调用 时getArray()
,没有警告。
If we didn't have this getArray()
method, then this class would be safe. With this method, it is unsafe. What characteristic makes it unsafe? It returns bars
as type Bar[]
, which depends on the "lie" we made earlier. Since the lie is not true, it causes problems. If the method had instead returned the array as type Object[]
, then it would be safe, since it does not depend on the "lie".
如果我们没有这个getArray()
方法,那么这个类就是安全的。用这种方法,是不安全的。什么特性使它不安全?它返回bars
为 type Bar[]
,这取决于我们之前制作的“谎言”。由于谎言是不真实的,它会引起问题。如果该方法将数组作为 type 返回Object[]
,那么它将是安全的,因为它不依赖于“谎言”。
People will tell you to not do such a cast like this, because it causes cast exceptions in unexpected places as seen above, not in the original place where the unchecked cast was. The compiler will not warn you that getArray()
is unsafe (because from its point of view, given the types you told it, it is safe). Thus it depends on the programmer to be diligent about this pitfall and not to use it in an unsafe way.
人们会告诉你不要做这样的转换,因为它会在上面看到的意想不到的地方导致转换异常,而不是在未经检查的转换所在的原始位置。编译器不会警告您这getArray()
是不安全的(因为从它的角度来看,鉴于您告诉它的类型,它是安全的)。因此,这取决于程序员是否认真对待这个陷阱,而不是以不安全的方式使用它。
However, I would argue that this is not a big concern in practice. Any well-designed API will never expose internal instance variables to the outside. (Even if there is a method to return the contents as an array, it would not return the internal variable directly; it would copy it, to prevent outside code from modifying the array directly.) So no method will be implemented like getArray()
is anyway.
但是,我认为这在实践中并不是一个大问题。任何设计良好的 API 都不会将内部实例变量暴露给外部。(即使有方法将内容作为数组返回,它也不会直接返回内部变量;它会复制它,以防止外部代码直接修改数组。)所以getArray()
无论如何都不会实现任何方法。
回答by Marko Topolnik
As opposed to lists, Java's array types are reified, which means that the runtime typeof Object[]
is distinct from String[]
. Therefore, when you write
相对于列表,Java的阵列类型物化,这意味着运行时类型的Object[]
,它不同于String[]
。因此,当你写
Bar[] bars = (Bar[]) new Object[];
you have created an array of runtime type Object[]
and have "cast" it to Bar[]
. I say "cast" within quotes because this is not a real checked-cast operation: it is just a compile-time directive which allows you to assign an Object[]
into a variable of type Bar[]
. Naturally, this opens the door to all kinds of runtime type errors. Whether it will actually create the errors is entirely up to your programming prowess and attentiveness. Therefore, if you feel up to it, then it is OK to do it; if you don't or this code is a part of a larger project with many developers, then it is a dangerous thing to do.
您已经创建了一个运行时类型数组Object[]
并将其“强制转换”为Bar[]
. 我在引号内说“强制转换”,因为这不是真正的检查强制转换操作:它只是一个编译时指令,它允许您将 an 分配给Object[]
类型为 的变量Bar[]
。自然地,这为各种运行时类型错误打开了大门。它是否会真正产生错误完全取决于您的编程能力和注意力。因此,如果您觉得可以,那么就可以做到;如果您不这样做,或者此代码是具有许多开发人员的大型项目的一部分,那么这是一件危险的事情。
回答by Markus A.
Ok, I've played with this construct for a bit and it can be a REAL mess.
好的,我已经玩了一段时间这个结构,它可能是一团糟。
I think the answer to my question is: Everything works fine as long as you always handle the array as generic. But as soon as you try to treat it in a non-generic way, there's trouble. Let me give a couple of examples:
我认为我的问题的答案是:只要您始终将数组作为通用来处理,一切都可以正常工作。但是,一旦您尝试以非通用方式对待它,就会遇到麻烦。让我举几个例子:
- Inside
Foo<Bar>
, I can create the array as shown and work with it just fine. This is because (if I understand correctly) the compiler "erases" theBar
-type and simply turns it intoObject
everywhere. So essentially insideFoo<Bar>
you are just handling anObject[]
, which is fine. But if you have a function like this inside
Foo<Bar>
, which provides access to the array:public Bar[] getBars(Bar bar) { Bar[] result = (Bar[]) new Object[1]; result[0] = bar; return result; }
you can run into some serious issues, if you use it somewhere else. Here are some examples of the craziness (most of it actually makes sense, but it seems crazy at first sight) you get:
String[] bars = new Foo<String>().getBars("Hello World");
will cause java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
for (String bar: new Foo<String>().getBars("Hello World"))
will also cause the same java.lang.ClassCastException
but
for (Object bar: new Foo<String>().getBars("Hello World")) System.out.println((String) bar);
works...
Here's one that doesn't make sense to me:
String bar = new Foo<String>().getBars("Hello World")[0];
will cause the java.lang.ClassCastException, too, even though I'm not assigning it to a String[] anywhere.
Even
Object bar = new Foo<String>().getBars("Hello World")[0];
will cause the same java.lang.ClassCastException!
Only
Object[] temp = new Foo<String>().getBars("Hello World"); String bar = (String) temp[0];
works...
and noneof these throw any compile-time errors, by the way.
Now, if you have another generic class, though:
class Baz<Bar> { Bar getFirstBar(Bar bar) { Bar[] bars = new Foo<Bar>().getBars(bar); return bars[0]; } }
The following works just fine:
String bar = new Baz<String>().getFirstBar("Hello World");
- 在里面
Foo<Bar>
,我可以创建如图所示的数组并使用它就好了。这是因为(如果我理解正确的话)编译器“擦除”了Bar
-type 并简单地将它变成了Object
任何地方。所以本质上Foo<Bar>
你只是在处理一个Object[]
,这很好。 但是如果你在里面有一个像这样的函数
Foo<Bar>
,它提供了对数组的访问:public Bar[] getBars(Bar bar) { Bar[] result = (Bar[]) new Object[1]; result[0] = bar; return result; }
如果你在其他地方使用它,你可能会遇到一些严重的问题。以下是一些疯狂的例子(其中大部分实际上是有道理的,但乍一看似乎很疯狂):
String[] bars = new Foo<String>().getBars("Hello World");
会导致java.lang.ClassCastException: [Ljava.lang.Object; 不能转换为 [Ljava.lang.String;
for (String bar: new Foo<String>().getBars("Hello World"))
也会导致同样的java.lang.ClassCastException
但
for (Object bar: new Foo<String>().getBars("Hello World")) System.out.println((String) bar);
作品...
这是一个对我来说没有意义的:
String bar = new Foo<String>().getBars("Hello World")[0];
也会导致java.lang.ClassCastException,即使我没有将它分配给 String[] 任何地方。
甚至
Object bar = new Foo<String>().getBars("Hello World")[0];
将导致相同的java.lang.ClassCastException!
仅有的
Object[] temp = new Foo<String>().getBars("Hello World"); String bar = (String) temp[0];
作品...
而没有这些抛出任何编译时错误,顺便说一句。
现在,如果你有另一个泛型类:
class Baz<Bar> { Bar getFirstBar(Bar bar) { Bar[] bars = new Foo<Bar>().getBars(bar); return bars[0]; } }
以下工作正常:
String bar = new Baz<String>().getFirstBar("Hello World");
Most of this makes sense, once you realize that, after type-erasure, the getBars(...)
-function actually returns an Object[]
, independent of Bar
. This is why you can't (at runtime) assign the return value to a String[]
without generating an exception, even if Bar
was set as String
. Why this prevents you to index into the array without first casting it back to an Object[]
beats me, though. The reason why things work fine in the Baz<Bar>
-class is that the Bar[]
there will also be turned into an Object[]
, independent of Bar
. Thus, this would be equivalent to casting the array to Object[]
, then indexing it, and then casting the returned entry back to String
.
大多数都是有道理的,一旦你意识到,在类型擦除之后,getBars(...)
- 函数实际上返回一个Object[]
,独立于Bar
。这就是为什么您不能(在运行时)将返回值分配给 aString[]
而不产生异常,即使Bar
设置为String
. 但是,为什么这会阻止您在不首先将其转换回Object[]
节拍的情况下对数组进行索引。之所以在Baz<Bar>
-class 中一切正常,是因为Bar[]
那里也会变成一个Object[]
,独立于Bar
. 因此,这相当于将数组转换为Object[]
,然后对其进行索引,然后将返回的条目转换回String
。
Overall, after seeing this, I'm definitely convinced that using this way to create arrays is a really bad idea, unless you don't ever return the array to anywhere outside your generic class. For my purposes, I'll be using a Collection<...>
instead of an array.
总的来说,在看到这个之后,我绝对相信使用这种方式来创建数组是一个非常糟糕的主意,除非你永远不会将数组返回到你的泛型类之外的任何地方。出于我的目的,我将使用 aCollection<...>
而不是数组。
回答by cyon
The cast:
演员阵容:
Bar[] bars = (Bar[]) new Object[];
is an operation that will happen at runtime. If the runtime type of Bar[]
is anything other than Object[]
then this will generate a ClassCastException
.
是将在运行时发生的操作。如果运行时类型Bar[]
不是Object[]
那么这将生成一个ClassCastException
.
Therefore, if you place bounds on Bar
as in <Bar extends Something>
it will fail. This is because the runtime type of Bar
will be Something
. If Bar
doesn't have any upper bounds then it's type will be erased to Object
and the compiler will generate all the relevant casts for placing objects into the array or for reading from it.
因此,如果您在Bar
as上放置边界,<Bar extends Something>
它将失败。这是因为 的运行时类型Bar
将是Something
. 如果Bar
没有任何上限,那么它的类型将被擦除,Object
并且编译器将生成所有相关的强制转换以将对象放入数组或从中读取。
If you try to assign bars
to something with a runtime type that isn't Object[]
(e.g String[] z = bars
) then the operation will fail. The compiler warns you about this usecase with the "unchecked cast" warning. So the following will fail even though it compiles with a warning:
如果您尝试分配bars
的运行时类型不是Object[]
(例如String[] z = bars
),则操作将失败。编译器使用“未经检查的强制转换”警告警告您有关此用例的信息。因此,即使编译时发出警告,以下内容也会失败:
class Foo<Bar> {
Bar[] get() {
return (Bar[])new Object[1];
}
}
void test() {
Foo<String> foo = new Foo<>();
String[] z = foo.get();
}
回答by m3th0dman
Everything works okay until you want to use something in that array as it would of type Bar
(or whatever type you initialize the generic class with) and not as an Object
which it really is. For example having the method:
一切正常,直到您想在该数组中使用某些类型Bar
(或您初始化泛型类所用的任何类型)而不是Object
它真正的类型。例如具有以下方法:
<T> void init(T t) {
T[] ts = (T[]) new Object[2];
ts[0] = t;
System.out.println(ts[0]);
}
seems to work okay for all types. If you change it to:
似乎适用于所有类型。如果将其更改为:
<T> T[] init(T t) {
T[] ts = (T[]) new Object[2];
ts[0] = t;
System.out.println(ts[0]);
return ts;
}
and call it with
并调用它
init("asdf");
it still works fine; but when you want to really use the real T[] array (which should be String[] in the above example):
它仍然可以正常工作;但是当你想真正使用真正的 T[] 数组时(在上面的例子中应该是 String[] ):
String[] strings = init("asfd");
then you have a problem, since Object[]
and String[]
are two distinct classes and what you have is Object[]
so a ClassCastException
is thrown.
那么你有一个问题,因为Object[]
和String[]
是两个不同的类,而你所拥有的就是Object[]
aClassCastException
被抛出。
The problem arises more quickly if you try a bounded generic type:
如果您尝试有界泛型类型,问题会更快出现:
<T extends Runnable> void init(T t) {
T[] ts = (T[]) new Object[2];
ts[0] = t;
System.out.println(ts[0]);
ts[0].run();
}
As a good practice, try avoiding using generics and arrays since they do not mix very well together.
作为一个好习惯,尽量避免使用泛型和数组,因为它们不能很好地混合在一起。