问题 Java Generics的类型擦除会导致完全类型转换吗?
我知道当Java编译器编译泛型类型时,它会执行类型擦除并从代码中删除对泛型的所有引用 ArrayList<Cheesecake>
变得只是一个 ArrayList
。
我不需要明确答案的问题是,缺少类型(因此强制类型转换)是否会导致速度变慢。换句话说,如果我使用标准的Java编译器和Oracle的标准JVM 1.7:
- 字节码是否包含类型转换?
- 如果是,是否包括运行时检查以检查其类型是否正确?
- 转换本身是否需要非常重要的时间?
- 会做我自己的
CheesecakeList
看起来与...相同的类 ArrayList
,只是与所有的 Object
s转向 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);
}
免责声明:这主要是学术好奇的问题。我真的怀疑类型转换有任何重大的性能问题,如果我在代码中发现了性能错误,那么删除对它们的需求甚至不会是我要做的前五件事之一。如果你正在阅读这篇文章是因为你有性能问题请自己帮个忙,然后启动一个分析器并找出瓶颈的位置。如果你真的相信它的类型转换,那么你应该尝试以某种方式优化它。
1245
2017-07-19 17:20
起源
答案:
短篇小说:是的,有一种类型检查。这是证明 -
鉴于以下类别:
// 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
类?这个类不会定义一个数组来保存元素吗?请注意,对阵列的每个任务都会产生隐藏的类型检查。所以你不会获得你想象的那么多(尽管你的程序可能会执行比写操作更多的读操作,所以a Cheesecak[]
数组会给你一些东西)。
底线:不要过早优化。
最后评论。人们通常认为类型擦除意味着 Cell<String>
编译成 Cell<Object>
。这不是真的。擦除仅适用于泛型类/方法的定义。它不适用于这些类/方法的使用地点。
换句话说,班级 Cell<T>
被编译为好像它被写成 Cell<Object>
。如果 T
有一个不受限制的(比如说 Number
)然后它被编译成 Cell<Number>
。在代码中的其他位置,通常是变量/参数,其类型是实例化的 Cell
上课(如 Cell<String> myCell
),擦除不适用。这个事实 myCell
是类型 Cell<string>
保存在类文件中。这允许编译器正确地检查程序。
15
2017-07-19 17:57
答案:
短篇小说:是的,有一种类型检查。这是证明 -
鉴于以下类别:
// 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
类?这个类不会定义一个数组来保存元素吗?请注意,对阵列的每个任务都会产生隐藏的类型检查。所以你不会获得你想象的那么多(尽管你的程序可能会执行比写操作更多的读操作,所以a Cheesecak[]
数组会给你一些东西)。
底线:不要过早优化。
最后评论。人们通常认为类型擦除意味着 Cell<String>
编译成 Cell<Object>
。这不是真的。擦除仅适用于泛型类/方法的定义。它不适用于这些类/方法的使用地点。
换句话说,班级 Cell<T>
被编译为好像它被写成 Cell<Object>
。如果 T
有一个不受限制的(比如说 Number
)然后它被编译成 Cell<Number>
。在代码中的其他位置,通常是变量/参数,其类型是实例化的 Cell
上课(如 Cell<String> myCell
),擦除不适用。这个事实 myCell
是类型 Cell<string>
保存在类文件中。这允许编译器正确地检查程序。
15
2017-07-19 17:57
类型检查在编译时完成。如果你这样做:
List<Cheesecake> list = new ArrayList<Cheesecake>();
然后可以在编译时检查泛型类型。这将抹去:
List list = new ArrayList();
这与其他任何上传都没有什么不同(例如 Object o = new Integer(5);
)。
1
2017-07-19 17:25
如果您使用了JDK源代码 ArrayList
,你克隆了它 CheesecakeList
,并改变了所有 Object
发生在 ArrayList
至 Cheesecake
,结果(假设你做得正确)将是一个你可以在你的应用程序中使用并且需要少一个的类 checkcast
操作,使用 ArrayList<Cheesecake>
。
那说,一个 checkcast
操作非常快,特别是如果您正在检查指定类的对象而不是子类。对于略微扰乱JITC优化而言,它的存在可能比实际执行成本更重要。
0
2017-07-19 18:10
字节码是否包含类型转换?
是。简要描述了类型擦除 本教程
如果是,是否包括运行时检查以检查其类型是否正确?
不可以。只要您没有任何警告,编译时就可以保证类型安全。
但是有一些 桥梁方法 如上面的教程中所述使用
[编辑: 澄清“否”意味着没有 额外 由于使用泛型而由编译器插入的运行时检查。仍在执行铸造期间的任何运行时检查]
转换本身是否需要非常重要的时间?
通过转换你的意思是铸造?如果是的话,即使你没有泛型也会出现这种情况
会做我自己的 CheesecakeList
看起来与...相同的类 ArrayList
,...
一般来说,这不是一个简单的问题,当然也不会考虑其他因素。首先,我们不再讨论泛型。你问的是一个专门的课程是否比一个快 ArrayList
。 可以想像 是的,有一个特殊的类可以避免强制转换应该更快。但真正的问题是 你应该关心它吗??
要回答最后一个问题,您必须具体。每个案例都不同。例如,我们谈论了多少个对象?百万?重复通话?该计划的表现是否需要额外的时间?你有时间吗?我们如何期望我们的数据在未来扩展?等等
另外不要忘记编译器的发展。未来的版本可能会有一个优化,可以自动修复性能问题(如果你可以使用它,那就是),而如果你坚持使用一个无用的代码库,它可能会留在项目中,只要它存在。
所以我的建议是:a)确定你在这个特定领域遇到问题,否则不要试图超越编译器或语言本身b)为你的代码计时,设定你想要实现的标准(例如延迟多少)是可以接受的,我必须得到的东西等)和c)只有当你确信自己走在正确的轨道上时才进行。没有人能告诉你。
希望有所帮助
0
2017-07-19 18:04