问题描述
给定以下方法:
Long getLong() {
...
}
如果我调用它并将返回值分配给这样的变量:
long abc = getLong();
会生成一个 Long
对象然后转换为 long 还是 Java 足够聪明以避免生成中间的 Long
对象?或者它实际上可能取决于 getLong()
的实现?
我问这个的原因是包装对象的大小通常比相应的原始类型大小大得多。如果我必须多次调用此方法,并且每次都需要为 Long
对象分配内存,则程序最终会消耗比实际需要更多的内存,从而触发更多的 GC 周期。
此外,我如何验证执行 long abc = getLong()
时发生的确切步骤(基本上也在寻找有关如何获得上述问题的答案的指导)?
解决方法
也许测试您的问题的最简单方法是编写一个小示例,然后运行 Java 反汇编程序。
假设我们有这门课:
public class Unboxing {
public static void main(String[] args) {
long l = getLong();
}
public static Long getLong() {
return 10L;
}
}
然后我们可以编译它 (javac Unboxing.java
),一旦编译,我们就可以反汇编它 (javap -c -s Unboxing
) 以查看它的字节码并了解 JVM 在幕后做了什么。
public class Unboxing {
public Unboxing();
descriptor: ()V
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
Code:
0: invokestatic #7 // Method getLong:()Ljava/lang/Long;
3: invokevirtual #13 // Method java/lang/Long.longValue:()J
6: lstore_1
7: return
public static java.lang.Long getLong();
descriptor: ()Ljava/lang/Long;
Code:
0: ldc2_w #19 // long 10l
3: invokestatic #21 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
6: areturn
}
您可以在代码中看到它获取了一个 Long
对象,然后在其上调用了它的 longValue
方法:
0: invokestatic #7 // Method getLong:()Ljava/lang/Long;
3: invokevirtual #13 // Method java/lang/Long.longValue:()J
然后将其存储到变量 l
(lstore_1
) 中。
所以,这可能会回答您的问题。
,您问:“会生成一个 Long 对象然后转换为 long 还是 Java 足够聪明以避免生成中间 Long 对象?或者它实际上可能取决于 getLong() 的实现?”
Java 足够聪明,可以保护您免于射中自己的脚,因此它会创建一个 Long 对象,正如方法签名所请求的那样,然后如果您的代码将其分配给 long
,它将取消装箱它。必须严格遵守开发人员必须从 Long
返回 getLong()
的任何原因。装箱/拆箱侧重于可读性而不是性能。如果您只想在整个代码中使用 long getLong()
,则严格要求创建一个新的 long
方法,这通常会更快。
对于另一个问题,您只需要使用调试器并使用调试器提供的“进入”工具,尝试使用 Eclipse、IntelliJ 或 NetBeans,仅举几例
,很可能你没有必要或理由去关心。
是的,正如其他人所说,当一个方法返回一个 Long
对象时,它就会返回一个 Long
对象。或 null
,但这里似乎并非如此。但是,如果该对象是在返回之前在方法中创建的,那么它是一个非常短暂的对象。垃圾收集器处理如此有效,因此空间分配不太可能伤害您。由于您一次只有一 (1) 个 Long
对象,因此您最终不会消耗大量内存。
在 Edwin Dalorzo 的回答和许多其他情况下,Long
对象甚至根本不会被分配和垃圾收集,并且不需要比原始 long
多的内存。怎么来的? Long
范围从 -127 到 128 的对象是预先分配和共享的。所以当 Edwin 的方法返回 10L
时,实际上只是返回了对预分配对象的引用。返回时不进行分配。
也就是说,如果该方法从不返回 null
,出于可读性原因,返回一个对象是一个糟糕的设计。它应该返回一个原语。如果在某些情况下没有要返回的 long 值,可以考虑返回一个 OptionalLong
。这些不是预先分配的,但也是短暂的,因此由垃圾收集器有效地处理。