细读源码之Integer

我们知道Java是面向对象的语言,号称一切皆对象,但是有8种原始数据类型(boolean、byte 、short、char、int、float、double、long)需要除排除在外。

在面试过程中经常会遇到,考察原始数据类型和其包装类语言特性的问题。

本文就以原始数据类型int和其包装类Integer为例进行讲解,主要包含以下几个方面的内容:

1.Integer的不可变性

2.自动装箱和自动拆箱(boxing/unboxing)

3.Integer的缓存值

4.使用Integer注意的事项

一.Integer的不可变性

A.什么是不可变对象?

如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。

前面讲解的字符串String是不可变对象,另外8种包装类型也都是不可变对象,其中自然包括Integer。

B.Integer如何实现不可变

1.Integer类定义使用final修饰,不能被继承,这样Integer的所有方法就不能被重写;

2.存储数据的字段声明为private final int value,使用private final修饰,value的值构造函数初始化后不能被修改。

C.Integer不可变带来的好处

1.Integer只有设计成不可变的对象,才能为其建立缓存值(后面会相信讲解),以达到节约内存的目的;

2.Integer是不可变的,必然是线程安全的,这样同一个Integer对象就可以被多个线程安全地共享,而且不需要任何同步操作;

3.Integer是不可变的,可以保证信息的安全。比如我们使用Integer来保存服务器某个服务的端口,如果我们可以轻易地把Integer对象改变为其他数值,这会给产品的可靠性带来严重的问题。

D.举例说明:

 上面代码执行结果:

 number和numberToIncrease两个变量的内存示意图,如下图所示:

1. 执行number = 1的时候,number执行value = 1的Integer对象;

2. 调用increase方法,number作为实参传给numberToIncrease,此时 number和numberToIncrease都指向了value = 1的Integer对象;

3. 执行numberToIncrease = numberToIncrease + 1;numberToIncrease就指向了value = 2的Integer对象,但是number的指向不变,还是原来value = 1的Integer对象。

最终,有了上面的执行结果。

二.自动装箱和自动拆箱(boxing/unboxing)

Java中的自动装箱和拆箱操作是通过语法糖实现的。什么是语法糖?

语法糖,是指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。

通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

在讲解自动装箱和自动拆箱操作之前,先说明一下装箱和拆箱的概念:

装箱:把Java原始数据类型(如:int)转化为其对应的包装类型(如:Integer)的过程我们称为装箱操作;

拆箱:把Java包装类型(如:Integer)转化为其对应的原始数据类型(如:int)的过程我们称为拆箱操作。

下面举个例子,说明一下装箱和拆箱的操作,例1:

1. main方法的第一行,代码中的0是int类型,当赋值给Integer类型的sum变量时,调用了Integer.valueOf将int类型转换成Integer类型,这个过程就是装箱;

2. main方法的第二行,sum要进行加法操作时,Integer类型无法直接进行加法操作,先执行sum.intValue()变成int后,再进行加法操作,而转化为int的过程就是拆箱;

3. main方法的第二行,当需要把加1的结果,再赋值给sum的时,再次调用Integer.valueOf进行类型转换,又进行了一次装箱操作。

当然平时我们很少会写上面那种臃肿的代码,常用的写法如下,例2:

 但是我们查看这两个类main方法的字节码,结果完全一致,如下所示:

从上面的字节码可以看出:

虽然例2中的代码中没有显式地去调用Integer.valueOf和Integer.intValue方法。

但是编译后的class文件中,却有这两个方法的隐式调用,而这个隐式调用过程就是自动装箱和自动拆箱的操作。

自动装箱:当一个Integer类型的值,需要变成int的时候(比如要进行加法运算),Java编译器会加入Integer.intValue()的方法调用,将Integer类型自动转换成int;

自动拆箱:当一个int的值,需要变成Integer的时候(比如把int类型的值赋值给Integer),Java编译器会加入一段Integer.valueOf(int i)的方法调用,把int类型自动转换为Integer类型。

三.Integer的缓存值

关于Integer的值缓存,涉及到Java 5的一个改进。在Java 5之前,构建Integer对象的传统方式是,直接调用其构造函数创建出一个新的对象。

但是根据实践的结果,我们发现大部分int数值运算的结果都集中在有限的、较小的数值范围内。

因此,在Java 5在Integer类上新增了一个valueOf的静态工厂方法,在调用它的时候会利用一个缓存机制,最终带来了明显的性能改进。

我们先看看下面的代码,猜测一下代码执行的结果,代码如下:

代码执行结果如下:

当没有读过Integer的源码,看到上面的结果,是不是会很惊讶。

为什么a和b赋值为1,进行==判断返回true,而c和d赋值为128,进行==判断就返回false了呢?

前面了解了自动装箱操作后,我们知道,当把int型的值,赋值为Integer类型的时候,会调用Integer.valueOf进行自动装箱操作,奥秘应该就是在Integer.valueOf方法中,源码如下:

当传入的参数i大于等于IntegerCache.low并且小于等于IntegerCache.high时,则从IntegerCache.cache中取值;

而其他情况下则新创建一个Integer对象。

IntegerCache的low和high是多少,还有cache又是什么呢,接着读一下IntegerCache的源码:

通过上面的代码,我们得出下面结论:

1.low的值是固定为-128;

2.high的默认值是127,但是可以通过java.lang.Integer.IntegerCache.high这个property进行设置。

最终取127和设置值中的较大值,并且取Integer.MAX_VALUE - (-low) -1和设置值中的较小值,作为最终的high的值;

3.最后根据low和high的值,对cache进行初始化,其cache[0] = -128,cache[cache.length - 1] = high。

还是上面的代码,如果加上JVM参数-Djava.lang.Integer.IntegerCache.high=128后再次执行,结果如下:

通过JVM参数,IntegerCache缓存的最大值设置为128,128也进行了缓存,c == d就由原来的false变成了true。

四.使用Integer注意的地方

1.使用int类型替换Integer进行数值计算

Integer类型无法直接进行数值计算,在计算之前需要进行拆箱变成int后进行计算,在计算之后赋值给Integer类型的时候又要进行装箱操作。

大量的装箱和拆箱操作非常浪费CPU和内存,下面代码对比一下二者的效率,代码如下:

上面代码执行结果如下所示:

computeByInt方法中,直接对int变量进行操作;

而computeByInteger方法中,sum = sum + 1,有一次自动装箱和一次自动拆箱操作,极大地影响了性能。

从最终的结果来看,两者之前有成千上万倍的性能差异。

2.使用int[]数组替换Integer[]和ArrayList

我们知道Java的对象都是引用类型,如果是一个原始数据类型数组,它在内存里是一段连续的内存;

而对象数组则不一样,数据存储的是引用,对象往往是分散地存储在堆的不同位置。

这种设计虽然带来了极大灵活性,但是也导致了数据操作的低效,尤其是无法充分利用现代CPU缓存机制。

下面举个例子说明二者性能的差异:

上面代码运行结果如下:

从运行结果上看,sumInt方法和sumInteger两个方法的性能差异巨大,主要有两个原因:

1. sumInteger进行加法操作的时候,多了一次拆箱操作;

2. int[]数组内数据存储是连续的,可以充分利用现代CPU缓存机制,而Integer[]中每个元素的intValue值,就分散地存储在堆的不同位置,无法充分利用CPU缓存机制,导致性能损失。

所以,使用原始数据类型替换包装类,使用数组替换动态数组(如ArrayList),在性能极度敏感的场景往往具有比较大的优势,一些追求极致性能的产品或者类库,会极力避免创建过多对象。

当然,在大多数纯业务功能代码里,并没有必要这么做,还是以开发效率优先。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容

  • 1 前言 我们都知道,java提供了8种基本数据类型供我们使用,此外,java还提供了这些基本数据类型所对应的包装...
    l1fe1阅读 493评论 0 1
  • 1. 描述 API:int的包装类型,内部包含了一个int字段。 关键字段: 2. 构造函数 第一种 直接赋值:传...
    Oliver_Li阅读 230评论 0 0
  • Integer类将基础类型int包装到了对象中,形成一个引用类型。它提供的主要功能是与int相关的类型封装与转换。...
    LuckyBuzz阅读 672评论 1 2
  • 1.典型回答 int 是基本数据类型(Primitive Types),是 java 的8个基本数据类型之一,ja...
    憩在河岸上的鱼丶阅读 320评论 0 2
  • Integer 本文源码基于JDK8 Integer也是我们经常使用的工具类、包装类,此文主要用于记录学习笔记,主...
    luoyoub阅读 247评论 1 0