八大基本类型都有对应的包装类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 Integer(3);
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);