面试连环炮之Integer和int

最近在招聘面试的过程中,考察一些候选人的基础掌握能力中发现,还是有大多数干了有1~3年的开发者在基础这块儿掌握的不够牢靠,没有去思考过为什么这样做,以及这样做的原因是什么?那么今天我们就来聊聊Java中的Integer和int,以及他们在面试中一般会如何考候选人呢?

想查看更多的文章请关注公众号:IT巡游屋
在这里插入图片描述

首先我们来看如下的一些面试连环炮:

  1. 开发中你在定义常量的时候,一般是用的Integer还是int,他们之间有什么区别?
  2. 什么叫包装类,它是如何包装基本类型的?
  3. Integer的自动装箱和自动拆箱的原理是什么?以及所发生在哪个阶段?带来的好处和坏处是什么?什么情况下会发生自动装箱/自动拆箱?
  4. 那你能说下Integer的值缓存是什么吗?
  5. 你觉得 Integer的源码有哪些设计要点?

或者还有诸如以下的一些笔试题:

    public static void main(String[] args) {
        Integer a = 100;
        Integer b = 100;
        System.out.println(a == b);
        Integer c = 200;
        Integer d = 200;
        System.out.println(c == d);
    }

请问:ab的值以及cd的值分别是多少?

以上问题大家可以先思考下如果面试中遇到你会如何回答?

首先来条华丽的分割线


一. Java包装类、装箱和拆箱

1.1 包装类

在 Java的设计中提倡一种思想,即一切皆对象。但是从数据类型的划分中,我们知道 Java 中的数据类型分为基本数据类型和引用数据类型,但是基本数据类型怎么能够称为对象呢?于是 Java 为每种基本数据类型分别设计了对应的类,称之为包装类(Wrapper Classes),也有地方称为外覆类或数据类型类。

我们来看下基本类型对应的包装类:

基本数据类型包装类
byteByte
shorthort
intInteger
longLong
booleanBoolean
charCharacter
folatFloat
doubleDouble

两个维度分析:

  • 内存占用角度:包装类是对象,除了基本数据以外,还需要有对象头。所以包装类占的空间比基本类型大。
  • 处理速度角度:基本类型在内存中存的是值,找到基本类型的内存位置就可以获得值;而包装类型存的是对象的引用,找到指定的包装类型后,还要根据引用找到具体对象的内存位置。会产生更多的IO,计算性能比基本类型差。

因此在开发中,如果需要做计算,尽量优先使用基本类型。

那么大家思考一个问题:Java为什么还要设计出包装类来使用?目的是什么?

1.2 为什么使用包装类

在Java开发中,特别是在企业中开发的时候处理业务逻辑的过程中,我们通常都要使用Object类型来进行数据的包装和存储,Object更具备通用的能力,更抽象,解决业务问题编程效率高。而基本类型的包装类,相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。特别是:Java的泛型,我们的基本数据类型并不能和泛型配合使用,因此要想配合泛型使用也得保证类型可以转换为Object。

同样的我们今天要说的Integer就是int的包装类,除了提供int类型的字段进行存储外,还给我们提供了基本操作,比如数学运算、int和字符串之间的转换等,我们可以来看看:


在这里插入图片描述

可以看到我们Integer中的方法前面都有M,代表大部分方法是静态方法,可以直接使用,并且里面还有很多的变量都是使用了final进行修饰:

变量里面的value即是用来存储我们的int值的,也就是被Integer包装的值,被private final修饰后,是无法被访问的且经过构造函数赋值后无法被改变:(其余的成员变量都是被static所修饰)

 /**
     * 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;
    }

引用网上一位博主的解释:

JAVA设计者的初衷估计是这样的:如果开发者要做计算,就应该使用primitive value如果开发者要处理业务问题,就应该使用object,采用Generic机制;反正JAVA有auto-boxing/unboxing机制,对开发者来讲也不需要注意什么。然后为了弥补object计算能力的不足,还设计了static valueOf()方法提供缓存机制,算是一个弥补。

这里提到了auto-boxing/unboxing机制,没错这就是接下来我们要讲的 自动装箱/自动拆箱 机制

1.3 自动装箱

平时我们写代码的过程中,比如定义一个变量i为10,我们一般会写:int i = 10;

那么我们也可以使用包装类:Integer i = 10;Integer i = new Integer(10)可以达到相同的效果;

这里Integer i = 10;即使用了「自动装箱」的机制,在Integer的内部实际是使用:Integer i = Integer.valueOf(10);所以我们要来看看 valueOf这个方法到底是如何实现的:

 /**
     * Returns an {@code Integer} instance representing the specified
     * {@code int} value.  If a new {@code Integer} instance is not
     * required, this method should generally be used in preference to
     * the constructor {@link #Integer(int)}, as this method is likely
     * to yield significantly better space and time performance by
     * caching frequently requested values.
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     *
     * @param  i an {@code int} value.
     * @return an {@code Integer} instance representing {@code i}.
     * @since  1.5
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

可以从文档注释中也能发现,该方法是JDK1.5引入的一个方法,并且返回的是一个Integer的实例,也就是通过该方法也能直接得到一个Integer对象,并且注释文档也说明了:如果不需要新的实例,通常应该优先使用此方法,而不是直接走构造函数,因此此方法可能通过频繁缓存产生明显更好的空间和时间性能,和要求的价值。This method will always cache values in the range -128 to 127, inclusive, and may cache other values outside of this range. 此方法将始终缓存-128到127(包括端点)范围内的值,并可以缓存此范围之外的其他值

1.4 缓存设计

valueOf 方法中 IntegerCache.low = -128; IntegerCache.high = 127 ; 也就是当我们传入的int 值在-128~127之间这个范围时, 会返回:return IntegerCache.cache[i + (-IntegerCache.low)];

接下来我们一起看看 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;//附默认值为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;//配置文件中有值,并且满足上方条件对比及可更改high值

            cache = new Integer[(high - low) + 1];//初始化cache数组,并且长度为127+128+1=256
            int j = low;//j=-128
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);//创建-128~127对应的Integer对象,缓存在数组中
								//0的位置对应 -128   ---->  128的位置对应的值就是0
          			//1的位置对应 -127
          			//......
          			//10的位置对应 -118  ----->  138的位置对应的值就是10
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

上方for循环里大家看注释应该就能推演出公式:我们传入的i值如果符合-128~127范围,要想取出对应的缓存值那么应该在cache数组的 i + (-low)即:i+128,从索引为138的位置取出对应的值,就是10

为什么设计这个缓存?

实践发现,大部分数据操作都是集中在有限的、较小的数值范围。所以在Java 5 中增加了静态工厂方法 valueOf(),在调用它的时候利用缓存机制,这样不用反复new 值相同的Integer对象,减少了内存占用。

1.5 自动拆箱

什么情况下Integer会自动拆箱呢,比如下面的代码:

Integer i = new Integer(10);
int j = 20;
System.out.print(i == j);

由于i是integer类型,j是基本类型,当两者需要进行比较的时候,i 就需要进行自动拆箱为int,然后进行比较;其中自动拆箱使用的是 intValue() 方法 。

看看源码,直接返回value值即可:

  /**
     * Returns the value of this {@code Integer} as an
     * {@code int}.
     */
    public int intValue() {
        return value;
    }

从注释中可以发现其实该方法是来自Number类,而我们的Integer是继承至Number类,同时还实现了Comparable接口

public final class Integer extends Number implements Comparable<Integer> {

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PXwIEiDP-1600659470988)(/Users/zhongpeihuan/传智播客/教研部管理/精品文章/公众号文章/Java基础-Integer和int/Number.png)]

Number 是一个抽象类,主要表示基本类型之间的转换。

好了,到这儿就基本上把Integer相关的核心源码看完了,同时也能清晰的知道包装类,以及自动装箱和自动拆箱的使用以及原理。

二. 常见面试题

2.1 Integer和int的区别:

1、Integer是int的包装类,int则是java的一种基本数据类型
2、Integer变量必须实例化后才能使用,而int变量不需要
3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
4、Integer的默认值是null,int的默认值是0

2.2 常见问答:
问1:
 public static void main(String[] args) {
        Integer a = 100;
        Integer b = 100;
        System.out.println(a == b);
        Integer c = 200;
        Integer d = 200;
        System.out.println(c == d);
    }

现在我们再来看文章开头的时候这个问题,大家能说出答案了吗?

答案: ab 为true; cd为false

原因分析:

Integer a = 100; 实际上会被翻译为: Integer a = ValueOf(100); 从缓存中取出值为100的Integer对象;同时第二行代码:Integer b = 100; 也是从常量池中取出相同的缓存对象,因此a跟b是相等的

而c 和 d 因为赋的值为200,已经超过 IntegerCache.high 会直接创建新的Integer对象,因此两个对象相比较肯定不相等,两者在内存中的地址不同。

问2:
Integer a = 100;
int b = 100;
System.out.println(a == b);
Integer a = new Integer(100);
int b = 100;
System.out.println(a == b);

答案为:ture,因为a会进行自动拆箱取出对应的int值进行比较,因此相等

问3:
Integer a = new Integer(100);
Integer b = new Integer(100);
System.out.println(a == b);

答案为:false,因为两个对象相比较,比较的是内存地址,因此肯定不相等

问4:

最后再来一道发散性问题,大家可以思考下输出的结果为多少:

 public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Class cache = Integer.class.getDeclaredClasses()[0];
        Field myCache = cache.getDeclaredField("cache");
        myCache.setAccessible(true);

        Integer[] newCache = (Integer[]) myCache.get(cache);
        newCache[132] = newCache[133];

        int a = 2;
        int b = a + a;
        System.out.printf("%d + %d = %d", a, a, b); 
    }

答案为: 2 + 2 = 5. 大家可以下来自己好好思考下为什么会得到这个答案🤔

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值