我知道,当Java编译器编译泛型类型时,它会执行类型擦除,并从代码中删除对泛型的所有引用,我的ArrayList<Cheesecake>
就变成了ArrayList
。
我没有明确答案的问题是,缺少类型(因此是强制性的类型转换)是否会导致速度减慢。换句话说,如果我使用的是标准Java编译器和Oracle:中的标准JVM 1.7
- 字节码是否包含类型转换
- 如果是,它是否包括运行时检查,以检查其类型是否正确
- 转换本身是否需要相当长的时间
- 如果让我自己的
CheesecakeList
类看起来与ArrayList
完全相同,只是让所有的Object
变成Cheesecake
,我会得到任何东西吗(同样只是一个假设,我对拥有更大二进制文件的任何副作用都不感兴趣,因为我的代码中有更多的类或重复)
我对类型擦除引起的任何其他问题都不感兴趣,只是一个比我更了解JVM的人给出的简单答案
我最感兴趣的是这个例子中的成本:
List<Cheesecake> list = new ArrayList<Cheesecake>();
for (int i = 0; i < list.size(); i++)
{
// I know this gets converted to ((Cheesecake)list.get(i)).at(me)
list.get(i).eat(me);
}
与相比,循环内铸造是否昂贵和/或重要
CheesecakeList list = new CheesecakeList();
for (int i = 0; i < list.size(); i++)
{
//where the return of list.get() is a Cheesecake, therefore there is no casting.
list.get(i).eat(me);
}
免责声明:这主要是一个学术好奇心的问题。我真的怀疑类型转换是否存在任何重大的性能问题,如果我在代码中发现性能错误,那么消除对它们的需求甚至不是我要做的五件事之一。如果你读这篇文章是因为你确实有性能问题,那就帮自己一个忙,启动一个探查器,找出瓶颈在哪里。如果你真的相信这是类型转换,那么只有这样,你才应该尝试以某种方式优化它。
短篇小说:是的,有一个类型检查。这是证据-
给定以下类别:
// Let's define a generic class.
public class Cell<T> {
public void set(T t) { this.t = t; }
public T get() { return t; }
private T t;
}
public class A {
static Cell<String> cell = new Cell<String>(); // Instantiate it.
public static void main(String[] args) {
// Now, let's use it.
cell.set("a");
String s = cell.get();
System.out.println(s);
}
}
A.main()
编译成的字节码(通过javap -c A.class
反编译)如下:
public static void main(java.lang.String[]);
Code:
0: getstatic #20 // Field cell:Lp2/Cell;
3: ldc #22 // String a
5: invokevirtual #24 // Method p2/Cell.set:(Ljava/lang/Object;)V
8: getstatic #20 // Field cell:Lp2/Cell;
11: invokevirtual #30 // Method p2/Cell.get:()Ljava/lang/Object;
14: checkcast #34 // class java/lang/String
17: astore_1
18: getstatic #36 // Field java/lang/System.out:Ljava/io/PrintStream;
21: aload_1
22: invokevirtual #42 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
25: return
}
正如您在偏移量14
中所看到的,cell.get()
的结果经过类型检查以验证它确实是一个字符串:
14: checkcast #34 // class java/lang/String
这确实会导致一些运行时惩罚。然而,由于JVM已经得到了很好的优化,因此其影响可能很小。
长话短说:
您将如何实现这样一个CheesecakeList
类?这个类不是要定义一个数组来容纳元素吗?请注意,对数组的每次赋值都会引发隐藏的类型检查。因此,您不会获得想象中的那么多(尽管您的程序可能会执行更多的读取操作而不是写入操作,因此Cheesecake[]
数组会给您带来一些好处)。
一句话:不要过早地进行优化。
最后的评论。人们通常认为类型擦除意味着Cell<String>
被编译成Cell<Object>
。这不是真的。擦除仅适用于泛型类/方法的定义。它不适用于这些类/方法的使用站点。
换句话说,类Cell<T>
被编译,就好像它被写为Cell<Object>
一样。如果T
具有上界(比如Number
),则它被编译为Cell<Number>
。在代码的其他地方,通常是类型为Cell
类(如Cell<String> myCell
)的实例化的变量/参数,不应用擦除。myCell
属于Cell<String>
类型这一事实保留在类文件中。这允许编译器正确地键入检查程序。
类型检查在编译时完成。如果你这样做:
List<Cheesecake> list = new ArrayList<Cheesecake>();
则可以在编译时检查泛型类型。这将擦除到:
List list = new ArrayList();
这与任何其他上行广播(例如Object o = new Integer(5);
)没有什么不同。
如果您将ArrayList
的JDK源代码克隆到CheesecakeList
中,并将ArrayList
中出现的所有Object
更改为Cheesecake
,则结果(假设您做得正确)将是一个可以在应用程序中使用的类,并且与使用ArrayList<Cheesecake>
相比,只需少执行一次checkcast
操作。
也就是说,checkcast
操作非常快,尤其是当您检查的对象属于命名类而不是子类时。它的存在对于稍微打乱JITC优化可能比实际执行成本更重要。
字节码是否包含类型转换?
是的。类型擦除在本教程中简要介绍
如果是,它是否包括运行时检查,以检查其类型是否正确?
没有。只要没有任何警告,就可以在编译时保证类型安全。然而,有一些桥接方法如以上教程中所述使用
[编辑:澄清"否"意味着编译器没有因为使用泛型而插入的额外运行时检查。强制转换期间的任何运行时检查仍在执行]
转换本身是否需要相当长的时间?
你所说的转换是指铸造吗?如果是,那么即使你没有仿制药,情况也是如此
会让我自己的
CheesecakeList
类看起来和ArrayList
完全一样吗。。。
总的来说,这不是一个简单的问题,当然也不能不考虑其他因素。首先,我们不再讨论泛型了。你在问一个专业课是否会比ArrayList
更快假设是的,拥有一个可以避免施法的特殊类应该会更快。但真正的问题是你应该关心它吗?
要回答最后一个问题,你必须具体。每个案例都不一样。例如,我们谈论的对象有多少?数百万?重复呼叫?额外的时间在节目的表演中值得注意吗?你计时了吗?我们期望我们的数据在未来如何扩展?等等
也不要忘记编译器是进化的。未来的版本可能会进行优化,自动解决性能问题(如果你能接受它的话),而如果你坚持使用一个无用的代码库,它可能会在项目存在的时间内一直存在。
因此,我的建议是:a)确保你在这一特定领域有问题,否则不要试图超越编译器或语言本身。b)为你的代码计时,为你想要实现的目标设定一些标准(例如,可以接受多少延迟,我必须获得什么等),c)只有当你确信自己走在正确的轨道上时,才能继续。没人能告诉你。
希望对有所帮助