面试题:new Integer(112)和Integer.valueOf(112)的区别

面试官考察点猜想

这道题,考察的是对Integer这个对象原理的理解,关于这道题的变体有很多,我们会一一进行分析。

理解这道题,对于实际开发过程中防止出现意想不到的Bug很有用,建议大家认真思考和解读。

背景知识详解

关于Integer的实现

Integer是int的一个封装类,它的构造实现如下。

    /**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value; /**
* Constructs a newly allocated {@code Integer} object that
* represents the specified {@code int} value.
*
* @param value the value to be represented by the
* {@code Integer} object.
*/
public Integer(int value) {
this.value = value;
}

Integer中定义了一个int类型的value属性。由于该属性是final类型,因此需要通过构造方法来赋值。这个逻辑非常简单,没有太多要关注得。

结论: 当通过new关键字构建一个Integer实例时,和所有普通对象的实例化相同,都是在堆内存地址中分配一块空间。

Integer.valueOf

Integer.valueOf方法,是把一个字符串转换为Integer类型,该方法定义如下

public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}

这个方法调用另外一个重载方法,该方法定义如下。

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

从这段代码中发现,如果i的值是在IntegerCache.lowIntegerCache.high这个区间范围,则通过下面这段代码返回Integer对象实例。

IntegerCache.cache[i+(-IntegerCache.low)];

否则,使用new Integer(i)创建一个新的实例对象。

IntegerCache是什么?

从它的命名来看,不难猜出它应该和缓存有关系,简单猜测就是:如果i的值在某个区间范围内,则直接从缓存中获取对象。

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;
//high的值允许通过系统属性来调整
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
//如果配置了high的属性值,则取两者中最大的一个值作为IntegerCache的最高区间值。
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
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;
//遍历初始化每一个对象
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() {}
}

上述代码的实现逻辑非常简单:

  1. IntegerCache的取值区间为: IntegerCache.low=-128, IntegerCache.hign=127,其中hign是可以通过系统参数来调整。
  2. 创建一个Integer数组,循环初始化这个区间中的每一个值。

Integer为什么这么设计? 但凡涉及到Cache的,一定和性能有关,在Integer这个对象中,常用的数值区间是在-128到127之间,所以为了避免对这个区间范围内的数据频繁创建和销毁对象,所以构建了一个缓存。意味着后续只要不是通过new关键字创建的Integer实例,在这个区间内的数值都会从IntegerCache中获取。

问题解答

面试题:new Integer(112)和Integer.valueOf(112)的区别

理解了上面的原理后,再来解答这个问题就很容易了。

  • new Integer,是创建一个Integer对象实例。

  • Integer.valueOf(112),Integer默认提供了Cache机制,在-128到127区间范围内的数据,通过valueOf方法不需要创建新的对象实例,只需要从缓存中获取即可。

问题总结

Integer这个对象的变形面试题比较多,其中一个面试题比较典型。

有两个Integer变量a,b,通过swap方法之后,交换a,b的值,请写出swap的方法。

public class SwapExample {

    public static void main(String[] args){
Integer a=1;
Integer b=2;
System.out.println("交换前:a="+a+",b="+b);
swap(a,b);
System.out.println("交换后:a="+a+",b="+b);
} private static void swap(Integer a,Integer b){
//doSomething
}
}

基础不是很好的同学,可能会很直接的按照”正确的逻辑“来编写程序,可能的代码如下。

private static void swap(Integer a,Integer b){
Integer temp=a;
a=b;
b=temp;
}

程序逻辑,理论上是没问题,定义一个临时变量存储a的值,然后再对ab进行交换。而实际运行结果如下

交换前:a=1,b=2
交换后:a=1,b=2

Integer对象的重新赋值思考

Integer作为封装对象类型,通过函数传递该引用以后,理论上来说,main方法中定义的ab,以及传递到swap方法中的a、和b,指向同一个内存地址,那么按照上述代码的实现,理论上来说也是成立的。

Java中有两种参数传递类型。

  • 值传递,传递的是数据的副本,方法执行中形式参数值的改变不影响实际参数的值。
  • 引用传递,传递的是内存地址的引用,在方法执行中,由于引用对象的地址指向同一块内存,所以对于对象数据的修改,会影响到引用了该地址的变量。

这么设计的好处,是为了减少内存的占用,提升访问效率和性能。

那么Integer作为封装类型,为什么传递的是副本,而不是引用呢?

我们来看一下Integer中value值得定义,可以发现该属性是final修饰,意味着是不可更改。

    /**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;

结论:在Java中,只有一种参数传递方式,就是值传递。但是,当参数传的是基本类型时,传的是值的拷贝,对拷贝变量的修改不影响原变量;当传的是引用类型时,传的是引用地址的拷贝,但是拷贝的地址和真实地址指向的都是同一个真实数据,因此可以修改原变量中的值;当传的是Integer类型时,虽然拷贝的也是引用地址,指向的是同一个数据,但是Integer的值不能被修改,因此无法修改原变量中的值。

因此,上述代码之所以没有交换成功,是因为传递到swap方法中的ab,会创建一个变量副本,这个副本中的值虽然发生了交换,但不影响原始值。

了解了这块知识之后,我们的问题就变成了,如何对一个修饰了final关键字的属性进行数据修改。那就是通过反射来实现,实现代码如下.

public class SwapExample  {

    public static void main(String[] args){
Integer a=1;
Integer b=2;
System.out.println("交换前:a="+a+",b="+b);
swap(a,b);
System.out.println("交换后:a="+a+",b="+b);
} private static void swap(Integer a,Integer b){
try {
Field field=Integer.class.getDeclaredField("value");
Integer temp= a;
field.setAccessible(true); //针对private修饰的变量,需要通过该方法设置。
field.set(a,b);
field.set(b,temp);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}

那这段代码运行完是否能达到预期呢? 上述程序运行结果如下:

交换前:a=1,b=2
交换后:a=2,b=2

从结果来看,确实是发生了变化,但是变化并不完整,因为b=1这个预期值并没有出现。为什么呢?其实还是和今天分享得主题有关系,我们来逐步看一下。

  1. Integer temp=a这个地方,基于IntegerCache的原理,这里并不会产生一个新的temp实例,意味着temp变量和a变量指向的内存地址是同一个。
  2. 当通过field.set方法,把a内存地址的值通过反射修改成b以后,那么此时a的值应该是2。注意:由于内存地址的值变成了2,而temp这个变量又指向该内存地址,因此temp的值自然就变成了2.
  3. 接着使用filed.set(b,temp)修改b属性的值,此时temp的值时2,所以得到的结果b也变成了2.
private static void swap(Integer a,Integer b){
try {
Field field=Integer.class.getDeclaredField("value");
Integer temp= a;
field.setAccessible(true); //针对private修饰的变量,需要通过该方法设置。
field.set(a,b);
field.set(b,temp);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

理解了原理后,我们只需要修改Integer temp=a这段代码,改成下面这种写法。保证temp变量是一个独立的实例。

Integer temp=new Integer(a);

修改以后运行结果如下

交换前:a=1,b=2
交换后:a=2,b=1

Mic说: 只有基本功足够扎实,才能对任何问题的本质一眼看透,解决这些问题的时候也能得心应手。

关注[跟着Mic学架构]公众号,获取更多精品原创

因为一个小小的Integer问题导致阿里一面没过,遗憾!的更多相关文章

  1. Error-ASP.NET:由于未能找到 id 为“FileUpload1$gvFiles$ctl02$lnkBtnRemoveFile”的控件或在回发后将同一 ID 分配给另一个控件,导致发生错误。如果未分配 ID,请显式设置引发回发事件的控件的 ID 属性以避免此错误。

    ylbtech-Error-ASP.NET:由于未能找到 id 为“FileUpload1$gvFiles$ctl02$lnkBtnRemoveFile”的控件或在回发后将同一 ID 分配给另一个控件 ...

  2. hibernate中HQL练习时候一个小小的错误导致语法异常

    package cn.db.po.test; import java.util.List; import cn.db.po.User; import cn.db.po.biz.UserBiz; pub ...

  3. 一个因MySQL大小写敏感导致的问题

    做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 00 MYSQL对大小写敏感 见字如面,见标题知内容.你有遇到过因为MYSQL对大小写敏感而被坑的体验吗? 之前看过阿里巴 ...

  4. openssl客户端编程:一个不起眼的函数导致的SSL会话失败问题

    我们目前大部分使用的openssl库还是基于TLS1.2协议的1.0.2版本系列,如果要支持更高的TLS1.3协议,就必须使用openssl的1.1.1版本或3.0版本.升级openssl库有可能会导 ...

  5. [工作bug]一个weblogic跨应用导致session丢失的bug之旅

    近来,发布一个应用,开发和本地测试一切都好,一旦部署到测试环境之后,坑爹的问题随之而来,应用程序不定时的超时,导致用户正在操作过程中被踢了出来,纠结了几天,终于在今天将此问题搞定: 1.系统架构 系统 ...

  6. 一个关于Integer的秘密

    先让大家看一段简单的代码: public static voidmain(String[] args) { Integera = 1; Integerb = 1; Integerc = 222; In ...

  7. 无监控不运维——使用 Python 写一个小小的项目监控

    在公司里做的一个接口系统,主要是对接第三方的系统接口,所以,这个系统里会和很多其他公司的项目交互.随之而来一个很蛋疼的问题,这么多公司的接口,不同公司接口的稳定性差别很大,访问量大的时候,有的不怎么行 ...

  8. 初次玩耍lucene.net,一个小小的记录

    lucene.net虽说是强大,但是我还是一年前第一次玩耍,然后就没有然后了,最近准备养成记录博客的习惯了,所以又玩了玩,回来记录一下 首先新建一个类,便于调用 public class Lucene ...

  9. 记一个bootstrap定制container导致页面X轴出现横向滚动条的坑

     壹 ❀ 引 在bootstrap定制时,因为UI给的图纸的页面主体部分宽度为1200px,所以我将container容器宽度从默认的1170px改成了1200px,随后在页面缩小的调试过程中发现了页 ...

随机推荐

  1. 谈谈Linux系统启动流程

    @ 目录 大体流程分析 一.BIOS 1.1 BIOS简介 1.2 POST 二.BootLoader (GRUB) 2.1 What's MBR? 2.2 What's GRUB? 2.3 boot ...

  2. systemctl添加自定义系统服务

    [Service] Type=forking ExecStart=绝对路径 ExecStop=绝对路径 ExecReload=绝对路径 以上最精简版,文件/usr/lib/systemd/system ...

  3. Dockerfile 自动制作 Docker 镜像(一)—— 基本命令

    Dockerfile 自动制作 Docker 镜像(一)-- 基本命令 前言 a. 本文主要为 Docker的视频教程 笔记. b. 环境为 CentOS 7.0 云服务器 c. 上一篇:手动制作Do ...

  4. java.net.NoRouteToHostException: 无法指定被请求的地址

    今天压力测试时, 刚开始出现了很多异常, 都是 java.net.NoRouteToHostException: Cannot assign requested address.  经网上查资料, 是 ...

  5. HDU - 2544最短路 (dijkstra算法)

    HDU - 2544最短路 Description 在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt.但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以 ...

  6. 【OI】蛇形填数题的深入探究

    题目:在 n×n 方阵里填入 1,2,...n×n, 要求蛇形填数.例如,n=4 时方阵为: 10  11  12  1 9    16  13  2 8    15  14  3 7     6  ...

  7. PHP方法的返回值

    不仅是PHP,大部分编程语言的函数或者叫方法,都可以用return来定义方法的返回值.从函数这个叫法来看,本身它就是一个计算操作,因此,计算总会有个结果,如果你在方法体中处理了结果,比如进行了持久化保 ...

  8. Java基础系列(9)- 数据类型扩展及常见面试题

    整数拓展 // 整数拓展: 进制 二进制0b 十进制 八进制0 十六进制0x // 同一个数字在不同进制中,结果是不同的,进制换算 int i = 10; int i2 = 010; // 八进制 i ...

  9. Jmeter系列(9)- Linux环境安装之安装JDK

    step-1下载安装包 下载Linux环境下的jdk1.8,请去(官网)中下载jdk的安装文件:或者评论区留言 step-2解压到/usr/local目录 mkdir /usr/local/java ...

  10. k8s负载资源StatefulSet工作解析

    在k8s中工作负载资源StatefulSet用于管理有状态应用. 什么是无状态? 组成一个应用的pod是对等的,它们之前没有关联和依赖关系,不依赖外部存储. 即我们上篇小作文中deployment创建 ...