Java装箱拆箱及String转型分析

八大基本类型都有对应的包装类boolean-Boolean,char-Charanter,byte-Byte,short-Short,int-Integer,long-Long,float-Float,double-Double,但包装类中使用缓存机制的就只有Byte、Short、Integer、Long、Character。

基本类型字面量

基本类型定义的变量,其值都是字面量,大小可知,生命周期可知。由于栈的存取速度比堆快,效率高,所以字面量就直接被存放于栈中。

装箱: 基本类型转换为包装类型,自动装箱是在编译阶段自动调用valurOf方法。

拆箱: 包装类型转换为基本类型,自动拆箱是在编译阶段自动调用xxxValue()方法。

缓存机制

包装类的缓存是通过其中的静态内部类来实现的。

Integer

Integer中实现缓存的静态内部类IntegerCache,实现如下:

private static class IntegerCache {
    // 缓存的最小值(固定)
    static final int low = -128;
    // 缓存的最大值(不固定,但有默认值)
    static final int high;
    // 缓存数组
    static final Integer cache[];

    static {
        // high value may be configured by property
        // 缓存的默认最大值
        int h = 127;
        // 读取配置
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                // 获取默认最大值和配置最大值中较大的值
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                // 最大值再小也不能小于127
                // 最大值+最小值的绝对值不能超出int范围,防止溢出
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;
        // 初始化缓存数组空间
        cache = new Integer[(high - low) + 1];
        int j = low;
        // 初始化缓存数组数据 -128~127
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

对于缓存的使用是在valueOf(int)方法中:

public static Integer valueOf(int i) {
    // 先判断I是否存在缓存数组中
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        // 如果存在则返回i在缓存数组中的位置
        return IntegerCache.cache[i + (-IntegerCache.low)];
    // 如果不存在,则直接通过i新建对象
    return new Integer(i);
}
    
Byte

Byte中实现缓存的静态内部类ByteCache,实现如下:

private static class ByteCache {
    private ByteCache(){}
    // 初始化缓存数组空间
    static final Byte cache[] = new Byte[-(-128) + 127 + 1];

    static {
        // 初始化缓存数组数据 -128~127
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Byte((byte)(i - 128));
    }
}
Character

Character中实现缓存的静态内部类CharacterCache,实现如下:

private static class CharacterCache {
    private CharacterCache(){}
    // 初始化缓存数组空间 128个
    static final Character cache[] = new Character[127 + 1];

    static {
    // 初始化缓存数组数据 0~127
        for (int i = 0; i < cache.length; i++)
            cache[i] = new Character((char)i);
    }
}
Short

Short中实现缓存的静态内部类ShortCache,实现如下:

private static class ShortCache {
    private ShortCache(){}
    // 初始化缓存数组空间
    static final Short cache[] = new Short[-(-128) + 127 + 1];

    static {
        // 初始化缓存数组数据 -128~127
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Short((short)(i - 128));
    }
}
Long

Short中实现缓存的静态内部类LongCache,实现如下:

    private static class LongCache {
        private LongCache(){}
        // 初始化缓存数组空间
        static final Long cache[] = new Long[-(-128) + 127 + 1];

        static {
            // 初始化缓存数组数据 -128~127
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Long(i - 128);
        }
    }
Float/Double

对于float和double,装箱操作都是新建对象:

public static Float valueOf(float f) {
    return new Float(f);
}
public static Double valueOf(double d) {
    return new Double(d);
}
Boolean

Boolean有点特殊,通过valueof(boolean)进行装箱操作的boolean值相同则其包装对象相同,:

/**
 * The {@code Boolean} object corresponding to the primitive
 * value {@code true}.
 */
public static final Boolean TRUE = new Boolean(true);

/**
 * The {@code Boolean} object corresponding to the primitive
 * value {@code false}.
 */
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

如上,这是因为valueOf(boolean)方法始终返回静态常量TRUE/FALSE。


下面以int为例分析装箱拆箱的内存情况。

例1

int a=3;
int b=3;
a=4;

执行第一句:首先在栈中创建变量为a的引用,然后在栈中找有没有字面值为3的地址,没找到就在栈中开辟一个空间存放3,然后将a指向值为3的空间的地址。

执行第二句:首先在栈中创建变量为b的引用,然后在栈中找有没有字面值为3的地址,找到有,则把b指向3的地址。

这时会出现a,b都会指向栈中3的地址的现象。

执行第三句,首先在栈中找有没有字面值为4的地址,没有则创建空间存放4,然后把a指向4的地址。


例2

Integer aI=new Integer3);
public Integer(int value) {
    this.value = value;
}

手动装箱,aI是实例,在栈中。new出来的是对象在堆中。栈中aI空间的值就是堆中对象的地址。对象内存中有一个value域,它的值为3。


例3

Integer aI=2;

编译优化:

Integer aI=Integer.valueOf(2);
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

自动装箱,调用Integer.valueOf(2)。i存在于valueOf(int)代码块中,是局部变量,首先会在栈中创建变量i的空间,并将2赋给i,也就是i的空间里存的是值2。

方法内部,判断2存在缓存池中,返回2在缓存池中的地址,所以aI的地址就是2在缓存池中的地址。

如果是:

Integer aI=128;

128不在缓存池中,调用构造方法新建对象。堆中对象内存中value域的值为128。aI的地址就是堆中这个对象的地址。


例4

int a=3;
Integer aI=a;

编译优化:

int a=3;
Integer aI=Integer.valueOf(a);

自动装箱,a变量在栈中有一个空间,a的地址指向栈中字面量为3的地址(没有则先创建)。

调用Integer.valueOf(a),i存在于valueOf(int)代码块中,是局部变量,首先会在栈中创建变量i的空间,并将a的值赋给i,也就是i的空间里存的是值3。之后和例2一样。


例5

Integer aI = 3;
int a = aI;

编译优化:

Integer aI = Integer.valueOf(3);;
int a = aI.intValue();

自动装箱、自动拆箱。我们只用关注第二行,第一行自动装箱前面已经分析过了。

第二行的aI实际会调用iniValue()方法来获取Integer类中的value值。前门我们分析到,aI的地址有两种情况:堆中对象地址或缓存池中值的地址。那么这里执行第二句代码时的内存是怎样的呢?
我们不妨再添加一行代码:

Integer aI = Integer.valueOf(3);;
int a = aI.intValue();
aI=null;

这样aI在栈中的空间就被释放了,但是对a却是没有任何影响的,所以执行第二句,就是将intValue()返回值存放在栈中a的内存空间,其实就是普通方法返回一个基本类型的值被基本类型变量接收而已。


调用Integer.valueOf(int a);

  • -128<=a<=127:则堆中不会新建对象(不会new),aI的地址就是缓存池中a的地址。
  • a<-128 || a>127:会在堆中创建对象(new),对象中的vaule值就是a,aI的地址就是堆中对象的地址。
  • 形参变量a和调用该方法的实参变量在内存上无关,形参变量a的值是对形参变量的值的拷贝。

String

String是不可变字符串,类声明由final修饰,它的内部通过字符数组常量存储字符串

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
    ...
}
字符串常量池

由于字符串的使用率非常高,内存分配同样也需要消耗时间和空间,JVM为了提高性能、减少内存开销,使用字符串常量池来进行优化,字符串常量池位于堆中。

每当创建字符串常量时,JVM会首先检查这个字符串是否存在常量池中,如果不存在则会实例化该字符串并将其放入常量池中,如果存在,那么就直接返回这个字符串在常量池中的实例引用。字符串常量池中不存在两个相同的字符串。

至此我们了解到,字符串实例引用的地址可能是在字符串常量池中,也可能是在堆中。

“+”连接符

对于字符串来说,“+”是作为连接符来使用的,而不是二元算术运算符。字符串对象可以使用“+”来连接其他对象,其他对象对通过调用toString()方法来和字符串对象进行连接。

字符串字面量

直接使用双引号括起来的一系列字符就是字符串字面量。字符串字面量是存储在堆中的字符串常量池中。

和字符串字面量拼接
拼接对象为字符串字面量或基本类型数据
String s="ab";
String s1="a"+"b";
String s2="a"+true; // boolean
String s3="a"+'c'; //char
String s4 = 10+"a"; // int
String s5="a"+10f; // float
String s6=10.0+"a"; // double
String s7="a"+10L; // long

String s8="a"+010; // octal
String s9="a"+0x10; // hex
.....

会被编译优化:

String s="ab";
String s1="ab";
String s2 = "atrue";
String s3 = "ac";
String s4 = "10a";
String s5 = "a10.0";
String s6 = "10.0a";
String s7 = "a10";
String s8 = "a8";
String s9 = "a16";

如上都会被编译器整合为一个字符串字面量,如果带进制数值,会先转化为10进制再拼接,像这样直接是字符串字面量的,实例的引用s*都是指向字符串常量池中的字符串对象地址。

拼接对象为引用类型数据
String s="a";

String s1 = s + true;
String s2 = s + 1;
String s3 = s + '1';
String s4 = s + "1";
String s5 = s + new Object();

会被编译器优化为以下形式:

(new StringBuilder()).append(s).append(xxx).toString();

所以是将“+”转化为StringBuilder对象的append(xxx)进行连接,而且每一个语句都会新建一个StringBuilder的实例。

StringBuilder和StringBuffer都是可变字符串,编译使用StringBuilder的原因是因为StringBuilder比StringBuffer效率更高。StringBuilder线程不安全,StringBuffer线程安全,因为在StringBuffer的每一个更新操作方法上都使用了synchronized进行加锁同步,单线程环境下相对于使用StringBuilder极大的降低了效率。

String.valueOf(xxx)

将不同数据类型通过String.valueOf(xxx)转换为String存在不同的形式。

String s=String.valueOf(boolean)
public static String valueOf(boolean b) {
    return b ? "true" : "false";
}

s实例地址指向字符串常量池中字符串对象地址。


String s=String.valueOf(char)
public static String valueOf(char c) {
    char data[] = {c};
    return new String(data, true);
}

s实例地址指向堆中字符串对象地址。


String s=String.valueOf(int)
public static String valueOf(int i) {
    return Integer.toString(i);
}

Integer.toString(int):

public static String toString(int i) {
    if (i == Integer.MIN_VALUE)
        return "-2147483648";
    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
    char[] buf = new char[size];
    getChars(i, size, buf);
    return new String(buf, true);
}

s实例地址指向堆中字符串对象地址。


String s=String.valueOf(long)
public static String valueOf(long l) {
    return Long.toString(l);
}

Integer.toString(long):

public static String toString(long i) {
    if (i == Long.MIN_VALUE)
        return "-9223372036854775808";
    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
    char[] buf = new char[size];
    getChars(i, size, buf);
    return new String(buf, true);
}

s实例地址指向堆中字符串对象地址。


String s=String.valueOf(float)
public static String valueOf(float f) {
    return Float.toString(f);
}

浮点型数据有点复杂,下面是具体调用:
Float.toString(float):

public static String toString(float f) {
    return FloatingDecimal.toJavaFormatString(f);
}

FloatingDecimal.toJavaFormatString(float):

public static String toJavaFormatString(float var0) {
    return getBinaryToASCIIConverter(var0).toJavaFormatString();
}

toJavaFormatString():

public String toJavaFormatString() {
    int var1 = this.getChars(this.buffer);
    return new String(this.buffer, 0, var1);
}

所以,s实例地址指向堆中字符串对象地址。


String s=String.valueOf(double)

double类型和float类型数据一样,s实例地址指向堆中字符串对象地址。


String s=String.valueOf(Object)
public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

obj.toString():

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

正在toString()中会进行字符串连接,上面我们说到,这样的字符串连接会被转化为通过StringBuilder的方式完成,会新建对象,所以即使相同的对象调用String.valueOf(Object)方法转化为String对象,两个s是不相同的,指向不同的对空间地址。


综上:

  • boolean类型转String通过String.valueOf(xxx)转String,s指向字符串常量池中的字符串对象地址;
  • char/char[]类型转String通过String.valueOf(xxx)转String,s指向中字符串对象地址;
  • 数值型数据通过String.valueOf(xxx)转String都会在堆中创建对象,s指向中字符串对象地址;
  • 引用型数据通过String.valueOf(xxx)转String都会在堆中创建对象,s指向中字符串对象地址;
new String(String)
String s = new String("abc");

s肯定指向堆中字符串对象地址。关于"abc"也会在字符串常量池中创建自己的地址。
这里创建了两个对象。

intern()

String类中的intern()是个本地方法:

/**
 * Returns a canonical representation for the string object.
 * <p>
 * A pool of strings, initially empty, is maintained privately by the
 * class {@code String}.
 * <p>
 * When the intern method is invoked, if the pool already contains a
 * string equal to this {@code String} object as determined by
 * the {@link #equals(Object)} method, then the string from the pool is
 * returned. Otherwise, this {@code String} object is added to the
 * pool and a reference to this {@code String} object is returned.
 * <p>
 * It follows that for any two strings {@code s} and {@code t},
 * {@code s.intern() == t.intern()} is {@code true}
 * if and only if {@code s.equals(t)} is {@code true}.
 * <p>
 * All literal strings and string-valued constant expressions are
 * interned. String literals are defined in section 3.10.5 of the
 * <cite>The Java™ Language Specification</cite>.
 *
 * @return  a string that has the same contents as this string, but is
 *          guaranteed to be from a pool of unique strings.
 */
public native String intern();

intern()方法的作用是返回该String对象的规范表示,也就是它的双引号形式,就是字面量。字面量存在于字符串常量池中。对于任何两个字符串s和t,s.intern() == t.intern()是true当且仅当s.equals(t)是true。也就是说字符串对象的值相等其调用intern()比较始终为true,因为intern()是将值从字符串常量池中直接返回的。

String s="abc";
String s1 = "a" + new String("bc");
String s2 = new String("abc");

System.out.println(s.intern()==s1.intern() && s1.intern()==s2.intern());

对于上面的s,s1,s2,不管是如何形式,只要它们的equals()比较为true,也就是值相等,s.intern()==s1.intern() && s1.intern()==s2.intern()永远成立。

案例

int a=127;
int b=128;
int c=-128;

Integer ia=127;
Integer ib=128;
Integer ic=-128;

Integer iaa=a;
Integer ibb=b;

Integer newa=new Integer(a);
Integer newb=new Integer(b);

Integer valueOfA = Integer.valueOf(a);
Integer valueOfB = Integer.valueOf(ib);

System.out.println(a=ia); // true
System.out.println(a=iaa);  // true
System.out.println(a=newa); // true
System.out.println(a=valueOfA); //true

System.out.println(ia=iaa); // true
System.out.println(ia=newa); // false
System.out.println(ia=valueOfA); // true

System.out.println(iaa=newa); // false
System.out.println(iaa=valueOfA); // true

System.out.println(newa=valueOfA); //false

System.out.println(b=ib); // true
System.out.println(b=ibb); // true
System.out.println(b=newb); // true
System.out.println(b=valueOfB); // true

System.out.println(ib=ibb); // false
System.out.println(ib=newb); // false
System.out.println(ib=valueOfB);// false

System.out.println(ibb=newb); // false
System.out.println(ibb=valueOfB);// false

System.out.println(newb=valueOfB);// false
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = "a" + "b";
String s5 = s1 + s2;
String s6 = new String(s3);
String s7 = new String("ab");
String s8 = String.valueOf(s4);
String s9 = String.valueOf(s5);

System.out.println(s1==s2);// false
System.out.println(s3==s4);// true
System.out.println(s3==s5);// false
System.out.println(s3==s6);// false
System.out.println(s3==s7);// false
System.out.println(s3==s8);// true
System.out.println(s3==s9);// false

System.out.println(s4==s5);// false
System.out.println(s4==s6);// false
System.out.println(s4==s7);// false
System.out.println(s4==s8);// true
System.out.println(s4==s9);// false

System.out.println(s5==s6);// false
System.out.println(s5==s7);// false
System.out.println(s5==s8);// false
System.out.println(s5==s9);// true

System.out.println(s6==s7);// false
System.out.println(s6==s8);// false
System.out.println(s6==s9);// false

System.out.println(s7==s8);// false
System.out.println(s7==s9);// false

System.out.println(s8==s9);// false
class Demo {
}
Demo demo = new Demo();
String s1=String.valueOf(demo);
String s2 = String.valueOf(demo);
String s3 = demo.toString();
String s4 = demo.toString();

System.out.println(s1==s2);// false
System.out.println(s1==s3);// false
System.out.println(s1==s4);// false
System.out.println(s3==s4);// false
class Test {
}
Test test1 = new Test();
Test test2 = new Test();

String s1 = "a";
String s2 = "b";
System.out.println(s1==s2); // false


String s3 = "a" + 1;
String s4 = s1 + 1;
System.out.println(s3==s4); // false


String s5 = s1 + test1;
String s6 = "a"+test1;
System.out.println(s5==s6); // false



String s7 = s1 + 1 + test1;
String s8 = s1 + "1" + test1;
System.out.println(s7==s8); // false



String s9 = test1 + 1 + s1; // Compilation error
String s10 = test1 + "1"+s1 ;
System.out.println(s9==s10);



String s11 = test1+test2; // Compilation error
String s12 = ""+test1+test2;
String s13 = test1+""+test2;
String s14 = test1 + test2 + ""; // Compilation error
System.out.println(s11==s12);
System.out.println(s12==s13); // false
System.out.println(s13==s14);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值