面试官:new String(“abc“)创建几个对象;Integer缓存[-128,127]、自动拆箱装箱

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(任意大小)。

  1. 对于 new String(“abc”); 会在常量池创建(如无),还会在堆中创建(总是)。
  2. 对于 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

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(没有缓存)

Integer 可以通过jvm参数修改缓存范围(low固定-128,high不能超过127),其它包装类都不可修改。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值