值类型可能产生多少个对象与值
原题的错误之处
可跳过本节直接去看“详细分析"章节里的题目。
原题:下面这段java代码,当 T 分别是引用类型和值类型的时候,分别产生了多少个 T对象和T类型的值()
T t = new T();(值类型时:T t;)
Func(t);
Func 定义如下:
public void Func(T t) { }
A.1 1
B.2 1
C.2 2
D.1 2
值类型就是基本类型。这道题考察的知识点很有意思,但题目答案是错的。
正确答案应该是,值类型产生了0个或1个数值类型。
产生了1个数值类型
数值类型:
public static void main(String[] args) {
int a = 200;
println(a);
}
static void println(int a){}
反编译class文件:
public static void main(java.lang.String[]);
Code:
0: sipush 200
3: istore_1
4: iload_1
5: invokestatic #7 // Method println:(I)V
8: return
static void println(int);
Code:
0: return
sipush将16位带符号整数压入栈,istore_1将int类型值存入局部变量1,iload_1从局部变量1中装载int类型值,然后调用println方法。
整个过程只产生了0个对象和1个数值类型。
产生了0个数值类型
public static void main(String[] args) {
int a = 1;
println(a);
}
static void println(int a){}
反编译class文件:
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: istore_1
2: iload_1
3: invokestatic #7 // Method println:(I)V
6: return
static void println(int);
Code:
0: return
iconst_1 将int类型常量1压入栈,istore_1将int类型值存入局部变量1,iload_1从局部变量1中装载int类型值,然后调用println方法。
在我的运行环境中,0-5的数值都将取常量值。
这里是从常量池中取出了常量1,整个过程产生了0个对象和0个数值类型。
详细分析
我们先换个题目:
若下面这段java代码中的a变量能够为任意int类型的值,可能产生多少个对象与值:
public class Example {
public static void main(String[] args) {
int a = // Any integer value
println(a);
}
static void println(Integer a) {}
}
A. 0个
B. 1个
C. 1个或2个
D. 0个或2个
Integer类部分源码
现在开始解析这道题。后面要讲到自动装箱与拆箱,所以先来看看Integer.valueOf
的原码:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer[] cache;
static {
int h = 127;
// ... // 已将从虚拟机参数中获取high值的代码省略
high = h;
// ... // 初始化cache的代码也省略掉了
}
}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
在没有设置JVM参数的情况下,Integer会将[-128,127]范围的值,缓存成一个Integer数组。
如果超出这个范围,在触发自动装箱与拆箱时,Java会调用Integer.valueOf(200)
方法来将其转为对象。
所以范围内的值使用valueOf
会得到同一个对象,而范围之外的值使用valueOf
每次都会新建一个Integer
对象。
Integer类会通过value常量缓存其地址:
private final int value;
public Integer(int value) {
this.value = value;
}
这里提到一点,赋值给成员常量private final int value
这一操作会将这个值取出存入到运行时常量池中。
自动装箱与拆箱
首先明确一点:封装类型也是对象。那JDK1.5引入了自动装箱与拆箱机制会在什么时候触发?
当你只写了一个int a = 200
在main方法里的时候,是不会触发自动装箱的。反编译class文件:
public static void main(java.lang.String[]);
Code:
0: sipush 200
3: istore_1
4: return
但如果涉及到与对象的操作,把数值的值抛给对象类型变量(或者说封装类型)时会触发自动装箱:
public static void main(String[] args) {
int a = 200;
Integer b = 100;
Integer c = a;
}
反编译class文件:
public static void main(java.lang.String[]);
Code:
0: sipush 200
3: istore_1
4: bipush 100
6: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: astore_2
10: iload_1
11: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
14: astore_3
15: return
bipush 将一个8位带符号整数压入栈。astore_2 将引用类型值存入局部变量3。
依此反推,把封装类型对象给基本类型时会触发拆箱:
public static void main(String[] args) {
Integer c = 200;
int a = c;
}
反编译class文件:
public static void main(java.lang.String[]);
Code:
0: sipush 200
3: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: astore_1
7: aload_1
8: invokevirtual #13 // Method java/lang/Integer.intValue:()I
11: istore_2
12: return
aload_1 从局部变量1中装载引用类型值。
看来的确如此,在把c的值给a时触发了拆箱操作,调用了Integer.intValue
方法。
除此之外,值类型与对象类型进行交互时也会触发拆箱,包括但不限于使用+,-,*,/,==
等运算符:
public static void main(String[] args) {
Integer a = 200;
boolean b = (a == 200);
}
反编译class文件:
public static void main(java.lang.String[]);
Code:
0: sipush 200
3: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: astore_1
7: aload_1
8: invokevirtual #13 // Method java/lang/Integer.intValue:()I
11: sipush 200
14: if_icmpne 21
17: iconst_1
18: goto 22
21: iconst_0
22: istore_2
23: return
if_icmpne 21
如果两个int类型值不相等,则跳转到21行。goto 无条件跳转。这里注意,在这个虚拟机中true用0表示,false用1表示。
可以看出Integer a = 200
时触发了装箱,a == 200
触发了拆箱。
但基本类型之间的交互不会触发自动装箱和拆箱:
public static void main(String[] args) {
int a = 200;
boolean b = (a == 200);
}
反编译class文件:
public static void main(java.lang.String[]);
Code:
0: sipush 200
3: istore_1
4: iload_1
5: sipush 200
8: if_icmpne 15
11: iconst_1
12: goto 16
15: iconst_0
16: istore_2
17: return
结论
在JDK1.8版本。
自动装箱与拆箱的触发时机:
- 把值类型赋值给封装对象类型会触发装箱
- 把封装对象类型赋值给值类型会触发拆箱
- 值类型与封装类型使用运算符进行运算会触发拆箱
到这里我们得出本题的结论:
- 如果赋予变量a的值在缓存范围内,不会产生任何值或对象。
- 如果超出这个范围,且内存中没有此数值时,则会产生一个int类型数值。
- 触发自动装箱时,会创建一个Integer对象。
答案
所以题目在使用println
方法时,会将值赋值给形参Integer a
,又因为封装类型Integer
也是对象,而main方法中的int a
是基本类型,所以会触发装箱操作。
因此的答案是D,0个或2个:
- 0个。值在[-128,127]范围内直接返回缓存值,不会产生封装对象,不会产生数值常量。
- 2个。1个数值和1个对象
- 1个数值。若赋值为
int a = 200;
会产生一个200的数值。 - 1个对象。调用方法println时int与Integer交互,触发自动装箱,此时产生一个Integer类型的对象。
- 1个数值。若赋值为
在运行方法时,如果栈帧的局部变量表存在200的数值就不会产生新的200数值,此时值产生了一个Integer类型的对象。本体没有绕复杂的弯子,所以是产生1个数值和1个对象。
牛刀小试
那你能够解析下面这道题了吗:
int a = 200;
Integer a1 = a;
Integer a2 = a;
System.out.println(a == a1);// true
System.out.println(a1 == a2);// false
int b = 100;
Integer b1 = b;
Integer b2 = b;
System.out.println(b1 == b2);// true
封装类型的valueOf源码
既然都讲到valueOf了,那不妨把封装类型的valueOf源码都翻出来瞧瞧。
类型 | 缓存范围 | 缓存大小 |
---|---|---|
Byte | [-128,127] | 256 |
Character | [0,127] | 128 |
Short | [-128,127] | 256 |
Integer | [-128,127] | 256 |
Long | [-128,127] | 256 |
Double | 无缓存 | - |
Float | 无缓存 | - |
Boolean | true,false | 2 |
有缓存值的
Byte
偏移量offset是因为byte取值范围从-128开始,而缓存数组索引从0位开始。
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
Character
char取值范围是0-65,535
private final char value;
public Character(char value) {
this.value = value;
}
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
Short
private final short value;
public Short(short value) {
this.value = value;
}
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
if (sAsInt >= -128 && sAsInt <= 127) { // must cache
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}
Long
private final long value;
public Long(long value) {
this.value = value;
}
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
Boolean
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
private final boolean value;
public Boolean(boolean value) {
this.value = value;
}
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
没缓存值的
Double
private final double value;
public Double(double value) {
this.value = value;
}
public static Double valueOf(double d) {
return new Double(d);
}
Float
private final float value;
public Float(float value) {
this.value = value;
}
public static Float valueOf(float f) {
return new Float(f);
}