值类型可能产生多少个对象与值(通过源码和反编译class文件进行分析)

值类型可能产生多少个对象与值

原题的错误之处

可跳过本节直接去看“详细分析"章节里的题目。

原题:下面这段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版本。

自动装箱与拆箱的触发时机:

  1. 把值类型赋值给封装对象类型会触发装箱
  2. 把封装对象类型赋值给值类型会触发拆箱
  3. 值类型与封装类型使用运算符进行运算会触发拆箱

到这里我们得出本题的结论:

  1. 如果赋予变量a的值在缓存范围内,不会产生任何值或对象。
  2. 如果超出这个范围,且内存中没有此数值时,则会产生一个int类型数值。
  3. 触发自动装箱时,会创建一个Integer对象。

答案

所以题目在使用println方法时,会将值赋值给形参Integer a,又因为封装类型Integer也是对象,而main方法中的int a是基本类型,所以会触发装箱操作。

因此的答案是D,0个或2个:

  1. 0个。值在[-128,127]范围内直接返回缓存值,不会产生封装对象,不会产生数值常量。
  2. 2个。1个数值和1个对象
    1. 1个数值。若赋值为int a = 200;会产生一个200的数值。
    2. 1个对象。调用方法println时int与Integer交互,触发自动装箱,此时产生一个Integer类型的对象。

在运行方法时,如果栈帧的局部变量表存在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无缓存-
Booleantrue,false2

有缓存值的

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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值