new String(“abc”)创建几个对象;Integer缓存[-128,127]、自动拆箱装箱
最近做了几个笔试题
花了不少时间靠着记忆一边分析一边做,事后拉到一起梳理一下,先运行结果
// 对于 String
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2); // true
String str3 = new String("abc");
String str4 = new String("abc");
System.out.println(str3 == str4); // false
System.out.println(str1 == str3); // false
// 对于 Integer
Integer i1 = 100; //装箱Integer.valueOf(100) :直接拿到缓存的值,所以 i1 和 i2 地址一样
Integer i2 = 100;
Integer i3 = 200; //装箱Integer.valueOf(200) :new Integer(200),两次new出不一样的地址
Integer i4 = 200;
System.out.println(i1 == i2); // true
System.out.println(i3 == i4); // false
System.out.println(i1.equals(i2)); // true
System.out.println(i3.equals(i4)); // true
System.out.println(i4 == 200); // true
System.out.println(i4.equals(200)); // true
结论
对于 new 关键字。总是会在堆内存创建一个对象,并将引用地址赋值。两次创建的对象地址总不一样。
同时适用new String(任意字符串) 和 new Integer(任意大小)。
- 对于 new String(“abc”); 会在常量池创建(如无),还会在堆中创建(总是)。
- 对于 new Integer(i); 总是会在堆中创建新对象。不论大小,绕过了valueOf(i),不会取缓存值。
对于字面值创建。
- 对于 String str1 = “abc”; 会在常量池创建(如无),存在则直接常量池获取。
- 对于 Integer i1 = i; 总是会自动装箱,即编译为 Integer i1 = valueOf(i),而valueOf(i)方法中:
i 属于 [-128,127] ,将会从缓存中获取。
i 不属于,将会 new Integer(i); 在堆中创建新对象。
String
涉及字符串常量池
- String str1 = “abc”; // 字面值方式创建
/*
* 字面值的方式创建一个字符串时,JVM首先会去字符串池中查找是否存在"abc"这个对象
* 不存在,则在字符串池中创建"abc"这个对象,然后将池中"abc"这个对象的地址赋给str1
* 如果存在,则直接将池中"abc"这个对象的地址赋给str2。所以str1、str2同一个地址
* */
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2); // true
- new String(“abc”); // new 创建
/*
* new关键字新建一个字符串对象时,JVM首先会去字符串池中查找是否存在"abc"这个对象
* 不存在,则在字符串池中创建"abc"这个对象,然后再在堆中创建一个"abc"字符串对象,然后将堆中这对象的地址赋给str3
* 如果存在,直接在 堆 中创建一个"abc"字符串对象,然后将堆中这对象的地址赋给str4。所以str3、str4 不是同一个地址
* */
String str3 = new String("abc");
String str4 = new String("abc");
System.out.println(str3 == str4); // false
new String(“abc”)创建几个对象,分两种情况:
- 对于 new 关键字,总会创建一个对象;常量池中不存在"abc"这个对象,则会在常量池创建"abc"对象。所以创建两个对象。
- 对于 new 关键字,总会创建一个对象;常量池中存在"abc"这个对象,则会直接引用。 所以只创建一个对象。
Integer
Integer 涉及到
- 缓存 [-128,127]
- 使用Integer integer = i 实际上会自动装箱 :编译为 Integer integer = Integer.valueOf(i); 根据源码得知, 装箱的过程是先判断缓存,否则就 new Integer(i)
- 使用 if(integer == i ) 实际上自动拆箱 int i = integer.intValue(); 拆箱为基础类型int后才能使用==做比较
装箱
int i = 100;
Integer integer = i; //自动装箱,自动编译为 Integer integer = Integer.valueOf(i)
装箱的过程,查看 Integer 源码
Integer.valueOf(i) :先判断i是否在缓存范围[-128,127]中,存在则取缓存值,不存在再 new Integer(i);
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
拆箱
Integer integer = new Integer(100);
int k = integer.intValue(); //手动拆箱
int kk = integer; //自动拆箱,编译为 integer.intValue();
int kkk = new Integer(500); //自动拆箱,编译为 new Integer(500).intValue();
if (integer == 100){}; // true //自动拆箱 integer.intValue() == 100;
if (integer.equals(100)){}; // true // equals已经覆写,自动拆箱出value再==比较
拆箱就是直接拿出被包装的value
public int intValue() {
return value;
}
再来分析结果
首先明确
-
== 比较引用地址。
-
Object的equals也是使用 == ,但常用的很多类都自己覆写equals后比较值。
其中 Integer 源码:
@Override
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
所以对Integer使用equals比较的都是值,拆箱后的int value。
- Integer i1 = 100; // 涉及自动装箱
Integer i1 = 100; // 编译为valueOf(100),但 100有缓存,直接返回缓存地址。所以i1和i2同一个缓存地址
Integer i2 = 100;
Integer i3 = 200; // 编译为valueOf(200),但 200无缓存,于是 new 创建。所以i3和i4不同的堆内存地址
Integer i4 = 200;
System.out.println(i1 == i2); // true
System.out.println(i3 == i4); // false
System.out.println(i1.equals(i2)); // true
System.out.println(i3.equals(i4)); // true 覆写equals,拆箱再比较值
- Integer i5 = new Integer(100); // 直接new 绕过了使用缓存
Integer i5 = new Integer(100); // new 绕过了使用[-128,127]缓存,new 会在堆上创建,然后将堆中对象的地址赋
Integer i6 = new Integer(100);
Integer i7 = new Integer(200);
Integer i8 = new Integer(200);
System.out.println(i5 == i6); // false
System.out.println(i7 == i8); // false
System.out.println(i1 == i5); // false i1是从缓存拿,i5是从堆内存拿
System.out.println(i1 == i2 + 0); // true
扩展开来
[-128,127] 如何缓存起来的
— 享元模式。
在 Integer 源码中有静态内部类,静态内部类中有 静态代码块。
当静态内部类加载时,静态块执行,则循环中的对象跟随内部类加载一起创建。
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// ...
for(int k = 0; k < cache.length; k++){
cache[k] = new Integer(j++);
}
}
}
静态内部类加载何时被加载
- 类加载时:类中静态资源(类变量,静态方法,静态代码块)一起被加载。但静态内部类不加载,内部类更不加载。
- 静态内部类:只有当静态内部类中的静态资源被调用,才能触发静态内部类的加载。
- 所以 [-128,127] 何时缓存的:当第一次任何调用Integer.IntegerCache.[静态资源]时(如Integer.valueOf(i)),触发IntegerCache类加载,再触发静态代码块加载,将[low,high]全部new 创建到cache[]。
这个cache[] 不就是个单例的数组吗。
静态内部类创建单例模式
public class SingleTon{
private SingleTon(){}
private static class SingleTonHolder{
private static SingleTon INSTANCE = new SingleTon();
}
public static SingleTon getInstance(){
return SingleTonHolder.INSTANCE;
}
}
对于垃圾回收来说:
Integer i = 100;
i = null;
这里虽然i被赋予null,但它之前指向的是cache中的new Integer(100)对象,而cache没有被赋null,所以Integer(100)这个对象还是存在;然而如果这里是1000的话就符合回收的条件;其他的包装类也是。
其他包装类的缓存:
Boolean:(全部缓存)
Byte:[-128,127] (全部)
Character[0,127]
Short[-128,127]
Long[-128,127]
Integer[-128,127]
Float(没有缓存)
Doulbe(没有缓存)