首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >从一道面试题探究 Integer 的实现

从一道面试题探究 Integer 的实现

作者头像
周三不加班
发布于 2019-06-04 10:06:39
发布于 2019-06-04 10:06:39
44800
代码可运行
举报
文章被收录于专栏:程序猿杂货铺程序猿杂货铺
运行总次数:0
代码可运行

记得有次面试,面试官问我:

如何写一个方法交换两个 Integer 类型的值?

当时心里一惊,这是把我当小白了呀!交换两个数的值还不容易么,最简单的直接搞一个中间变量,然后就可以交换了… …

面试官随即拿出一张雪白雪白的 A4 纸

工具用多了,有没有体验过白纸写代码?来吧,开始你的表演,小伙子。

此时稍微有点心虚,但还是要装腔作势,把自己想象成大佬才行。

有的人可能会问,你不是说很简单么,还心虚个啥?写过代码的都知道,工具写代码是有自动补全提示的,这白板写代码纯粹就是考察你对代码的熟练度,其实相当考验代码功底。

于是乎,提起笔,奋笔疾书,唰唰唰不到两分钟,我就写完了。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
    Integer a = 1, b = 2;
    System.out.println("交换前:a = " + a + ", b = " + b);
    swap(a,b);
    System.out.println("交换后:a = " + a + ", b = " + b);
}

public static void swap(Integer i, Integer j) {
    int temp = i;
    i = j;
    j = temp;
}

当我胸有成竹的把纸递过去的时候,我仿佛看见面试官嘴角哪不经意间的微笑。

这一笑不要紧,要紧的是一个大男人对着我笑干嘛?

难道我的代码感动到他了?

明人不说暗话,这明显不可能。

难道是他要对我… …

想到此处,我不禁赶紧回忆了下来时的路,怎么样可以快速冲出去… …

喂,醒醒,想啥呢,在面试呢,少年

面试官瞄了一眼代码之后开始发问呢。

你确定你这段代码真的可以交换两个 Integer 的值吗?(竟然在 Integer 上加了重音)

我的天呐,难道有问题,多年面试经验告诉我,面试重音提问要不就是在故意混淆,要不就是在善意提醒你,看能不能挖掘出点其他技术深度出来。

所以根据面试官的意思肯定是使用这段代码不能交换呢,哪么不能交换的原因在哪里?

首先,想了下,要交换两个变量的值,利用中间变量这个思路是不会错的。既然思路没错,哪就要往具体实现上想,问题出在哪里。

第一个知识点:值传递和引用传递

我们都知道,Java 中有两种参数传递

  • 值传递 方法调用时,实际参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参数的值。
  • 引用传递 也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。

简单总结一下就是:

也就是说 对象类型(地址空间)的变量存在于堆中,其引用存在于栈中。

至于为什么这么设计:主要还是为了考虑访问效率和提升代码性能上考虑的。

难道问题出在这个地方?

可是 Integer 不就是 引用类型

为什么不能改变呢?

难道 Integer 的实现有什么特殊之处?

你别说,还真是 Integer 有他自己的独特之处。

第二个知识点:Integer 在源码实现上存在这么一个属性
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
     * The value of the {@code Integer}.
     *
     * @serial
     */
private final int value;

这个属性也是表示这个 Integer 实际的值,但是他是 private final 的,Integer 的 API 也没有提供给外部任何可以修改它的值接口,也就是说这个值改变不了。

简单理解就是上面的 swap 方法其实真实交换的是 两个形参 i 和 j 的值,而没有去改变 a 和 b 的值

画个图简单理解一下:

哪如何去改变这个 value 值呢 ?

第三个知识点来了:反射

赶紧给面试官陪着笑脸说刚才激动了,代码我能不能再改改?

面试官:可以,代码本来就是一个不断优化的过程,你改吧!

然后又是一顿奋笔疾书,再次唰唰唰写了如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void swap(Integer i, Integer j) throws NoSuchFieldException, IllegalAccessException {
    /*int temp = i;
        i = j;
        j = temp;*/
    Field value = Integer.class.getDeclaredField("value");
    int temp = i.intValue();
    value.set(i,j.intValue());
    value.set(j,temp);
}

这次长脑子呢,我又回过头检查了一遍代码,没办法,很蛋疼,这要是有电脑先跑一遍再说。

白纸只能靠你自己脑子想,脑子编译,脑子运行(当然运行不好可能就烧坏了)

果然,查出问题来了(还好够机智)

前边不是说了这个 value 是私有属性么,既然是 private 的 ,final 的,在 Java 中是不允许的,再访问的时候会报

java.lang.IllegalAccessException异常,

在反射的时候还需要加value.setAccessible(true),设置代码执行时绕过对私有属性的检查,哪么代码就变成了如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void swap(Integer i, Integer j) throws NoSuchFieldException, IllegalAccessException {
    /*int temp = i;
        i = j;
        j = temp;*/
    Field value = Integer.class.getDeclaredField("value");
    value.setAccessible(true);
    int temp = i.intValue();
    value.set(i,j.intValue());
    value.set(j,temp);
}

另外多提几句:设置了 setAccessible(true)就能访问到私有属性是因为他的源码是这样的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public void setAccessible(boolean flag) throws SecurityException {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
    setAccessible0(this, flag);
}

可以看到,他调用了 setAccessible0()这个方法,继续看下这个:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static void setAccessible0(AccessibleObject obj, boolean flag)
        throws SecurityException
    {
        if (obj instanceof Constructor && flag == true) {
            Constructor<?> c = (Constructor<?>)obj;
            if (c.getDeclaringClass() == Class.class) {
                throw new SecurityException("Cannot make a java.lang.Class" +
                                            " constructor accessible");
            }
        }
        obj.override = flag;
    }

这段代码我们需要关注有两点:

  • 参数是 boolean flag 而这个 flag 实际的值恰好是我们设置进去的setAccessible()中的参数
  • 这个参数真正的作用是把一个 AccessibleObject 对象的 override属性进行了赋值

哪么这个 override属性的作用又是什么呢?

我们一起来看下value.set()这个方法的源码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@CallerSensitive
    public void set(Object obj, Object value)
        throws IllegalArgumentException, IllegalAccessException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        getFieldAccessor(obj).set(obj, value);
    }

看着这段代码是不瞬间就明白了,原来这个 overried 属性就好比一个开关,负责控制在 set值得时候是否需要检查访问权限(很多时候,一直说要阅读源码阅读源码,因为源码就好比火眼金睛,在源码面前,很多妖魔鬼怪都是无所遁形的)

看着这段代码乐开了花,心里想着这下应该总能交换了吧,我又非常自信的把代码递给了面试官

面试官:为什么要这么改?为什么要使用反射?为什么要加这行 setAccessible(true) ?

哇 此时正和我意啊,写了这半天,就等你问我这几个点,于是我很利索的把上边描述的给面试官讲了一遍,他听完之后继续微微一笑,这个笑很迷,也很渗人。

难道这还不对?

他又开始发问:

面试官:这段代码还是会有问题,最终输出结果会是 a = 2, b = 2。可以提示你一下,你知道拆箱装箱吗?

呃,这还涉及到拆箱装箱了… …

第四个知识点:拆箱装箱

我们在上面的代码中

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Integer a = 1, b = 2;

a 和 b 是 Integer 类型,但是 1 和 2 是 int 类型,为什么把 int 赋值给 Integer 不报错?

因为 Java 中有自动装箱(如果感兴趣的话可以使用 javap 命令去查看一下这行代码执行的字节码)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
实际上 Integer a = 1 就相当于执行了 Integer a = Integer.valueOf(1);

哪么,valueOf()方法的实现又是什么样的呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public static Integer valueOf(int i) {
     if (i >= IntegerCache.low && i <= IntegerCache.high)
         return IntegerCache.cache[i + (-IntegerCache.low)];
     return new Integer(i);
 }

这个方法的代码说明,如果你的值是在某个范围之内,会从 IntegerCache这个缓存中获取值,而不是去 new 一个新的 Integer对象。继续研究 IntegerCache这个类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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
                    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;
        }

根据以上代码可以得到:在范围在 -128 - 127 之间的数字,会被直接初始化好之后直接加入得到缓存中,之后处于这个范围中的所有Integer 会直接从缓存获取值,这样提高了访问效率

为了验证这一点,你可以直接试一试,写一段代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Integer a = 100,b = 100;
System.out.println(a == b);

根据我们在 Java 领域的理解,对于引用类型,使用 == 比较的是他们在内存中的地址,哪么,对于Integer这个引用类型,直接使用 == 结果应该是false

可是,你如果实际调试试一试的话,会发现这是 true, 是不是有点不可思议?

哪么为什么是ture ,就回到了上边说的缓存问题,因为 100 处于 -128-127 这份范围

如果你定义的变量是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Integer a = 200,b = 200;
System.out.println(a == b);

这个结果输出肯定是 false,因为根据前边Integer。valueOf()实现的源码可以得到:超过-128-127的值需要重新 new Integer(i),但凡是 new 出来的,使用 == 比肯定是 false

继续深究下去你会发现面试官说的 a = 2, b = 2 是对的,具体原因是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void swap(Integer i, Integer j) throws NoSuchFieldException, IllegalAccessException {
        Field value = Integer.class.getDeclaredField("value");
        value.setAccessible(true);
        int temp = i.intValue();
        // 此处 我们使用 j.intValue 返回结果是个 int 类型数据 
        // 而 value.set()方法需要的是一个 Object 对象 此处就涉及到了装箱 
        // 所以 i 值的实际变化过程为:i = Integer.valueOf(j.intValue()).intValue()
        value.set(i,j.intValue());
        // 同理 j 值得实际变化过程为:j = Integer.valueOf(temp).intValue()
        // 因为 valueOf() 要从缓存获取值 也就是此时需要根据 temp 的下标来获取值
        // 可是在上一步中 i 的值已经被自动装箱之后变成了 2 
        // 所以此处会把 j 的值设置成 2
        value.set(j,temp);
    }

综上:我们就搞清楚了为什么面试官会说结果是 a = 2, b = 2 .

既然分析出了为什么会变成 a = 2 b = 2,哪就好办呢。

发现问题,解决问题,永远是程序员最优秀的品质,对了,还要脸皮厚(小声哔哔)

我又厚着脸把代码要过来了(面试官还是一如既往的微笑,一如既往很迷的笑)

第五个知识点:如何避免拆箱和装箱操作
  • 把 set 改为 setInt 避免装箱操作 public static void swap(Integer i, Integer j) throws NoSuchFieldException, IllegalAccessException { Field value = Integer.class.getDeclaredField("value"); value.setAccessible(true); int temp = i.intValue(); value.setInt(i,j.intValue()); value.setInt(j,temp); } }
  • 把 temp 重新创建一个对象进行赋值,这样就不会和 i 的值产生相互影响 public static void swap(Integer i, Integer j) throws NoSuchFieldException, IllegalAccessException { Field value = Integer.class.getDeclaredField("value"); value.setAccessible(true); int temp = new Integer(i.intValue()); value.setInt(i,j.intValue()); value.setInt(j,temp); }

靠着脸厚,我第三次把代码交给了面试官,没办法,厚度不是你所能想象的…

这一次,他终于不再笑了,不再很迷的笑了

看来这场面试要迎来终结了 … …

面试官:嗯,你总算答对了,现在来总结一下这道题涉及到的知识点(这是要考察表达能力啊)

总结:
  • 值传递和引用传递
  • Integer 实现缓存细节
  • 使用反射修改私有属性的值
  • 拆箱和装箱

有没有不总结不知道,一总结吓一跳的感觉,这么一道看似简单的题,竟然考察到了这么多东西

面试官:好了,技术问题我们今天就先面到这里,接下来能否说一说你有什么长处? 我:我是一个思想积极乐观向上的人。 面试官:能否举个例子。 我:什么时候开始上班?

你有什么有趣的面试经历吗?欢迎留言来一起分享哦!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-02-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员啊粥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【77期】这一道面试题就考验了你对Java的理解程度
想要搞清楚具体的原因,在这里你需要搞清楚以下几个概念,如果这个概念搞清楚了,你也不会把上面的实现方法写错
良月柒
2020/11/06
4840
【77期】这一道面试题就考验了你对Java的理解程度
与IntegerCache有关的一个比较坑的面试题
对于这个题目,可能一开始想到的就是最简单的办法,直接在swap方法内实现互换即可。不妨尝试一下:
冬天里的懒猫
2020/08/11
3940
与IntegerCache有关的一个比较坑的面试题
你所不知道的Java之Integer
以下内容为作者辛苦原创,版权归作者所有,如转载演绎请在“光变”微信公众号留言申请,转载文章请在开始处显著标明出处。
白凡
2018/06/04
5780
搞清楚一道关于Integer的面试题
面试题3中的a=99相当于a=new Integer(99);重新给把一个新的对象引用地址给了a,所以a变了,最后输出是99。
田维常
2019/09/25
4130
一道面试题考验了你对java的理解程度
想要搞清楚具体的原因,在这里你需要搞清楚以下几个概念,如果这个概念搞清楚了,你也不会把上面的实现方法写错
java思维导图
2019/11/04
5480
两个Integer的引用对象传递给一个swap方法的内部进行交换,返回后,两个引用的值是否会发生变化
数组元素作为函数的实参时,用法跟普通变量作参数相同,将数组元素的值传递给形参时进行函数体调用,函数调用完返回后,数组元素的值不变。这种传递方式是”值传递“方式,即只能从实参传递给形参,而不能从形参传递给实参
须臾之余
2019/08/20
3.2K1
两个Integer的引用对象传递给一个swap方法的内部进行交换,返回后,两个引用的值是否会发生变化
面试官六连问拆箱装箱Integer那些事,给我整懵圈了!
小萌:由于基本数据类型不是对象,所以java并不是纯面向对象的语言,好处是效率较高(全部包装为对象效率较低)。Java是一个面向对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
乔戈里
2020/02/21
5710
面试官六连问拆箱装箱Integer那些事,给我整懵圈了!
由自动装箱,引发的关于Integer的有趣现象
看完这篇文章,相信你一定可以很好的理解包装器类以及有些包装器类的自带缓存机制,也可以反手就给面试官说出为什么,下面代码的输出值为true:
东边的大西瓜
2022/05/05
3140
由自动装箱,引发的关于Integer的有趣现象
Java大型互联网公司经典面试题,论JDK源码的重要性的无限思考
论JDK源码的重要性:一道面试题引发的无限思考!大家在看到这个标题时想的是什么?小编我为什么要讲这个问题呢?
Java后端技术
2018/08/09
1.1K0
Java大型互联网公司经典面试题,论JDK源码的重要性的无限思考
JAVA面试50讲之4:int和Integer的区别
对于第4条的原因: java在编译Integer i = 100 ;时,会翻译成为Integer i = Integer.valueOf(100);,而java API中对Integer类型的valueOf的定义如下:
用户1205080
2019/01/02
1.1K0
金蝶一面:基本数据类型有哪些?包装类型的常量池技术了解么?
对于 boolean,官方文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素。
Guide哥
2022/04/11
5220
金蝶一面:基本数据类型有哪些?包装类型的常量池技术了解么?
java面试|精选基础题(2)
阅读本文大概需要6分钟 继续挖掘一些有趣的基础面试题,有错望指出来哈,请赐教~ 1.包装类的装箱与拆箱 简单一点说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转为基本数据
KEN DO EVERTHING
2019/01/17
9911
面试官:兄弟,说说基本类型和包装类型的区别吧
六年前,我从苏州回到洛阳,抱着一幅“海归”的心态,投了不少简历,也“约谈”了不少面试官,但仅有两三个令我感到满意。其中有一位叫老马,至今还活在我的手机通讯录里。他当时扔了一个面试题把我砸懵了:说说基本类型和包装类型的区别吧。
沉默王二
2019/09/29
6010
面试官:兄弟,说说基本类型和包装类型的区别吧
阿里巴巴面试题- - -Java体系最新面试题(2)
装箱就是自动将基本数据类型转换为包装器类型(int-->Integer);调用方法:Integer的
叶秋学长
2022/08/23
2400
阿里巴巴面试题- - -Java体系最新面试题(2)
Java 中为什么设计了包装类
在 Java 中,万物皆对象,所有的操作都要求用对象的形式进行描述。但是 Java 中除了对象(引用类型)还有八大基本类型,它们不是对象。那么,为了把基本类型转换成对象,最简单的做法就是「将基本类型作为一个类的属性保存起来」,也就是把基本数据类型包装一下,这也就是包装类的由来。
飞天小牛肉
2021/03/18
1.4K0
Java 中为什么设计了包装类
你真的了解JAVA的形参和实参吗?
看到这个题后 瞬间觉得有坑。也觉得为什么要书写一个 swap方法呢?如下实现不是更简单:
Bug开发工程师
2019/07/15
1.9K0
你真的了解JAVA的形参和实参吗?
【Java面试题系列】:Java基础知识面试题,看这一篇就够了
参加过社招的同学都了解,进入一家公司面试开发岗位时,填写完个人信息后,一般都会让先做一份笔试题,然后公司会根据笔试题的回答结果,确定要不要继续此次面试,如果答的不好,有些公司可能会直接说“技术经理或者总监在忙,你先回去等通知吧”,有些公司可能会继续面试,了解下你的项目经验等情况。
用户5546570
2019/06/06
5120
必考:从字节码层面看自动拆装箱的原理
上一篇文章中,我们分享包装类的缓存机制时,提到了自动装箱的问题。今天我们就重点分享自动装箱的原理,及其相关的知识点(笔试必考)!
程序视点
2023/11/16
1850
必考:从字节码层面看自动拆装箱的原理
面试官:兄弟,说说基本类型和包装类型的区别吧
POJO 的英文全称是 Plain Ordinary Java Object,翻译一下就是,简单无规则的 Java 对象,只有属性字段以及 setter 和 getter 方法,示例如下。
Remember_Ray
2020/01/21
3.8K1
聊聊面试-int和Integer的区别
最近面试了很多候选人,发现很多人都不太重视基础,甚至连工作十几年,项目经验十几页的老程序员,框架学了一大堆,但是很多 Java 相关的基础知识却很多都答不上来。还有很多人会回答,只知道要用,但是从来不会去看看它具体是怎么实现的。
phoenix.xiao
2020/08/05
5000
推荐阅读
相关推荐
【77期】这一道面试题就考验了你对Java的理解程度
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档