- 外文名
- integer
- 相对应概念
- 小数 / 浮点数
- 中文名
- 整数 / 整型数
- 范 畴
- 编程语言
-
2019-11-22 14:57:50
发现做项目的过程中,在数值类型的比较上容易犯错,特别是Integer和Integer的比较,Integer和int的比较。虽然这些都是些基础语法,但稍不留意就容易犯错,在实际开发过程中如果出现这类失误,很容易失之毫厘谬以千里。在这里,总结下这些基础知识点。
java虽然宣称一切都是对象,但原始数据类型是例外。int是整形数字,是java的9个原始数据类型(Primitive Types)(boolean、byte、short、char、int、float、double、long、void)之一。Integer是int对应的包装类,它有一个int类型的字段存储数据,并且提供了基本操作,比如数学运算、int和字符串之间转换等。在java 5中引入了自动装箱和自动拆箱功能(boxing/unboxing),java可以根据上下文,自动进行转换,极大地简化了相关编程。javac自动把装箱转换为Integer.valueOf(),把拆箱替换为Integer.intValue()。
自动装箱实际上算是一种语法糖。什么是语法糖?可以简单理解为java平台为我们自动进行了一些转换,保证不同的写法在运行时等价,他们发生在
编译阶段
,也就是生产的字节码是一致的。(此句摘自极客时间专栏)原始数据类型的变量,需要使用并发相关手段才能保证线程安全。如果有线程安全的计算需要,建议考虑使用类似AtomicInteger、AtomicLong这样的线程安全类。
原始数据类型和java泛型并不能配合使用
。因为java的泛型某种程度上可以算作伪泛型,它完全是一种编译期
的技巧,java编译期会自动将类型转换为对应的特定类型。这就决定了使用泛型,必须保证相应类型可以转换为Object。废话不多说,直接来demo,这样效果更直接。
public class Test { public static void main(String[] args) { Integer a1 = 6; Integer a2 = 6; int a11 = 6; System.out.println(a1 == a2); //true System.out.println(a1 == a11); //true System.out.println("----------------"); Integer a3 = 128; Integer a4 = 128; int a33 = 128; System.out.println(a3 == a4); //false //Integer会自动拆箱为int,所以为true System.out.println(a3 == a33); //true System.out.println(a3.equals(a4)); //true System.out.println("----------------"); Integer a5 = new Integer(6); Integer a6 = new Integer(6); System.out.println(a5 == a6); //false System.out.println(a5.equals(a6)); //true }
需要明确的一点是,包装型(Integer)和基本型(int)比较会自动拆箱(jdk1.5以上)。
在这里很多人比较容易迷惑的是如下情况:
Integer a1 = 6; Integer a2 = 6; System.out.println(a1 == a2); //true Integer a3 = 128; Integer a4 = 128; System.out.println(a3 == a4); //false
如果研究过jdk源码,你就会发现Integer a3 = 128;在java编译时会被翻译成 Integer a3 = Integer.valueOf(128); 我们再来看看valueOf()的源码就更清晰了。
public static Integer valueOf(int i) { assert IntegerCache.high >= 127; if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
由以上源码就会发现,
对于-128到127之间的数,会进行缓存
,Integer a1 = 6时,会将6进行缓存,下次再写Integer a2 = 6;时,就会直接从缓存中取,也就不用new一个对象了,所以a1和a2比较时就为true。但a3和a4是超过范围,会new一个对象,==是进行地址和值比较,是比较两个对象在JVM中的地址
,这时a3和a4虽然值相同但地址是不一样的,所以比较就为false了。通过上面的分析可知:
- 两个都不是new出来的Integer,且数值在-128~127之间,用==比较时,基本值相等时为true,否则为false;
- 两个都是new出来的Integer,为false
- int和Integer比较,数值相同,用==比较时为true。(因为Integer会自动拆箱为int去比较)
所有包装类对象之间值的比较,建议使用equals方法比较
。==
判断对象是否同一个。Integer var = ?在
缓存区间
的赋值,会复用已有对象,因此这个区间内的Integer使用==进行判断可通过,但是区间之外的所有数据,则会在堆
上新产生,不会通过。因此如果用== 来比较数值,很可能在小的测试数据中通过,而到了生产环境才出问题。
为了节省内容,对与下列包装对象的两个实例,当他们的基本值相同时,用==判断会为true:
Boolean Byte Character, \u0000 - \u007f(7f是十进制的127) Integer, -128 — 127
我们也可以看看其它包装型的缓存情况:
Boolean:(全部缓存) Byte:(全部缓存) Character(缓存范围'\u0000'到'\u007F') Short(-128 — 127缓存) Long(-128 — 127缓存) Float(没有缓存) Doulbe(没有缓存)
如果要比较两个Integer对象的值(均为new的对象),可以通过
.intValue()
进行转换后来比较,如下:Integer a3 = 128; Integer a4 = 128; System.out.println(a3.intValue() == a4.intValue());
也可以使用
equal()
来进行比较,如下:Integer a3 = 128; Integer a4 = 128; System.out.println(a3.equals(a4)); //true
更多相关内容 -
mybatis返回Integer
2013-08-01 12:56:20mybatis返回int会报错,改成Integer封装类型可以解决,具体参考我的博客 -
Integer缓存IntegerCache详解
2020-11-27 10:59:28IntegerCache缓存区间为[-128,127]。在调用Integer.valueOf(int i)方法进行自动装箱时假若i的值在[-128,127]区间则生成的Integer对象会被存入缓冲区。当再次对该值进行装箱时会先去缓冲区中获取;如果取到则返回,...
版权声明
- 本文原创作者:谷哥的小弟
- 作者博客地址:http://blog.csdn.net/lfdfhl
引子
今天,我们从一段非常简单的代码说起。
示例代码
package cn.com; /** * 本文作者:谷哥的小弟 * 博客地址:http://blog.csdn.net/lfdfhl * 示例描述:Integer缓存IntegerCache */ public class IntegerCacheTest { public static void main(String[] args) { Integer a=9527; Integer b=9527; System.out.println(a==b); Integer c=97; Integer d=97; System.out.println(c==d); } }
运行结果
请问,这段代码运行的结果是什么呢?两次输出的结果都是false,对不对?非也!!!
从上图可以清楚地看到:- 1、第一次输出的结果是false
- 2、第二次输出的结果是true
看此处,就有点丈二和尚摸不着头脑了。这是为什么呢?
源码剖析
在执行Integer a=9527;时会调用Integer类的静态方法public static Integer valueOf(int i)进行自动装箱,其源码如下:
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.low && i <= IntegerCache.high则调用IntegerCache.cache[i + (-IntegerCache.low)]
- 如果i的值不满足i >= IntegerCache.low && i <= IntegerCache.high则调用new Integer(i)
顺着这条主线,我们继续探究Integer缓存IntegerCache。IntegerCache是Integer类中的静态内部类,用于缓存数据便于节省内存、提高性能。其源码如下:
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; 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; } private IntegerCache() {} }
从这里我们可以看出 :
- IntegerCache.low = -128
- IntegerCache.high = 127
- 缓冲区cache是一个Integer类型的数组
也就是说:IntegerCache缓存区间为[-128,127]。所以,在调用Integer.valueOf(int i)方法进行自动装箱时假若i的值在[-128,127]区间则生成的Integer对象会被存入缓冲区。当再次对该值进行装箱时会先去缓冲区中获取;如果取到则返回,如果没有取到则创建包装类对象存入缓冲区并返回。
嗯哼,看到这里是不是可以理解之前的那小段代码了呢?
扩展与延伸
除了Integer之外,在其他包装类(例如:Byte,Short,Long等)中也存在类似的设计。
-
Integer.valueof()和Integer.parseInt()的区别
2020-05-26 11:43:01Integer. valueOf()可以将基本类型int转换为包装类型Integer,或者将String转换成Integer,String如果为Null或“”都会报错。 Integer. valueOf()是高效的 public static Integer valueOf(int i) { if (i >= ...Integer. valueOf()可以将基本类型int转换为包装类型Integer,或者将String转换成Integer,String如果为Null或“”都会报错。
Integer. valueOf()是高效的
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
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; 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; } private IntegerCache() {} }
从他的实现方法可以看出他int在[-128,127]之间的时候他会直接拿缓存,而不会new Integer(),Integer.valueOf()方法基于减少对象创建次数和节省内存的考虑,缓存了[-128,127]之间的数字。此数字范围内传参则直接返回缓存中的对象。在此之外,直接new出来。
下面我们来看一道选择题,就是关于Integer.valueOf()的知识,题目如下:
A.System.out.println(i01== i02);
B.System.out.println(i01== i03);
C.System.out.println(i03== i04);
D.System.out.println(i02== i04);
答案呢,我也做对了,但是也是靠排除法做对的,至于这道题考察的具体细节问题,我当时没考虑,不过等我查看了Integer的相关源码时,茅舍顿开。答案这里我这里先不公布,我们慢慢开始分析。分析
选项A
选项A中比较的是i01和i02,Integer i01=59这里涉及到自动装箱过程,59是整型常量,经包装使其产生一个引用并存在栈中指向这个整型常量所占的内存,这时i01就是Integer 的引用。
而int i02=59由于int是基本类型,所以不存在引用问题,直接由编译器将其存放在栈中,换一句话说,i02本身就是59。那么System.out.println(i01== i02)结果任何呢?这里涉及到了拆箱的过程,因为等号一边存在基本类型所以编译器后会把另一边的Integer对象拆箱成int型,这时等号两边比较的就是数值大小,所以是true。好了,到了这里,你有没有想到这样一个问题:如果是Integer i01=59;Integer i02=59;然后System.out.println(i01== i02)的结果是?可能你会说比较数值大小所以相等啊,也有可能说等号两边对象引用,所以比较的是引用,又因为开辟了不同的内存空间,所以引用不同所以返回false。可是正确答案是:true.
再来看这个问题::如果是Integer i01=300;Integer i02=300;然后System.out.println(i01== i02)的结果是? 你可能说上面你不是说了true嘛,怎么还问这样的问题,可是这次的答案是false。你是否会吃惊?大牛除外,我是小白,求不打脸!
解析:当靠想象无法解决问题的时候,这是就要看源代码了!!很重要!我们可以在Integer类中找到这样的嵌套内部类IntegerCache,这个类就是在Integer类装入内存中时,会执行其内部类中静态代码块进行其初始化工作,做的主要工作就是把一字节的整型数据(-128-127)装包成Integer类并把其对应的引用存入到cache数组中,这样在方法区中开辟空间存放这些静态Integer变量,同时静态cache数组也存放在这里,供线程享用,这也称静态缓存。
所以当用Integer 声明初始化变量时,会先判断所赋值的大小是否在-128到127之间,若在,则利用静态缓存中的空间并且返回对应cache数组中对应引用,存放到运行栈中,而不再重新开辟内存。
所以对于Integer i01=59;Integer i02=59;**i01 和 i02是引用并且相等都指向缓存中的数据,所以返回true。而对于**Integer i01=300;Integer i02=300;因为其数据大于127,所以虚拟机会在堆中重新new (开辟新空间)一个 Integer 对象存放300,创建2个对象就会产生2个这样的空间,空间的地址肯定不同导致返回到栈中的引用的只不同。所以System.out.println打印出false。补充:为什么1个字节的数据范围是-128到127呢,因为Java中数据的表示都是带符号数,所以最高位是用来表示数据的正负,0表示正数,1表示负数,所以正数最大的情况对应的二进制数为:01111111,负数最小对应的二进制数为:10000000.
B选项
从上面的分析,我们已经知道Integer i01=59返回的是指向缓存数据的引用。那么Integer.valueOf(59)返回的是什么或者操作是什么呢?
这个函数的功能就是把int 型转换成Integer,简单说就是装包,那他是新创建一个对象吗?还是像之前利用缓存的呢?有了之前的经验,肯定想到的是利用缓存,这样做既提高程序速度,又节约内存,何乐而不为?
来看一下源代码:public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
很明显跟之前的思想一致,若在-128到127范围,直接返回该对象的引用,否则在堆中重新new 一个。
到这,System.out.println(i01== i03)的结果毋庸置疑就是true.选项C
Integer.valueOf(59)返回的是已缓存的对象的引用,而Integer i04 = new Integer(59)是在堆中新开辟的空间,所以二者的引用的值必然不同,返回false,这道题呢就选C
选项D
System.out.println(i02== i04) i02是整型变量,i04是引用,这里又用到了解包,虚拟机会把i04指向的数据拆箱为整型变量再与之比较,所以比较的是数值,59==59,返回true.
出一道题:
System.out.println(Integer.valueOf("127")==Integer.valueOf("127"));
System.out.println(Integer.valueOf("128")==Integer.valueOf("128"));
System.out.println(Integer.parseInt("128")==Integer.valueOf("128"));输出是:
true
false
true
回答#1:
Integer.valueOf(String)确有一个不同寻常的行为。
valueOf会返回一个Integer(整型)对象,当被处理的字符串在-128和127(包含边界)之间时,返回的对象是预先缓存的。这就是为什么第一行的调用会返回true-127这个整型对象是被缓存的(所以两次valueOf返回的是同一个对象)——第二行的调用返回false是因为128没有被缓存,所以每次调用,都会生成一个新的整型对象,因此两个128整型对象是不同的对象。
重要的是你要知道在上面的比较中,你实际进行比较的是integer.valueOf返回的对象引用,所以当你比较缓存外的整型对象时,相等的判断不会返回true,就算你传个valueOf的值是相等的也没用。(就像第二行中Integer.valueOf(128)==Integer.valueOf(128))。想让这个判断返回true,你需要使用equals()方法。
parseInt()返回的不是整型对象,而是一个int型基础元素。这就是为什么最后一个判断会返回true,第三行的判断中,在判断相等时,实际比较的是128 == 128,所以它必然是相等的。
再来说说第三种比较中的一点区别,使得它的结果与第二种比较不一样了:
一个unboxing conversion(一种比较时的转换,把对对象的引用转换为其对应的原子类型)在第三行的比较中发生了。因为比较操作符使用了==同时等号的两边存在一个int型和一个Integer对象的引用。这样的话,等号右边返回的Integer对象被进一步转换成了int数值,才与左边进行相等判断。
所以在转换完成后,你实际比较的是两个原子整型数值。这种转换正是你在比较两个原子类型时所期待看到的那样,所以你最终比较了128等于128。
回答#2:
Integer类有一个静态缓存,存储了256个特殊的Integer对象——每个对象分别对应-128 和127之间的一个值。
有了这个概念,就可以知道上面三行代码之间的区别。
new Integer(123);
显示创建了一个新的Integer对象。
Integer.parseInt("123");
解析完字符串后返回一个int值。
Integer.valueOf("123");
这种情况比其他的要更复杂一些。首先进行了字符串解析,然后如果解析的值位于-128和127之间,就会从静态缓存中返回对象。如果超出了这个范围,就会调用Integer()方法并将解析的值作为参数传入,得到一个新的对象。
现在,让我们看一下问题中的3个表达式。
Integer.valueOf("127")==Integer.valueOf("127");
上面的表达式返回true,因为Integer的值从静态缓存中取了2次,表达式返回了对象与自己比较的结果。因为只有一个Integer对象,所以返回结果为true。
Integer.valueOf("128")==Integer.valueOf("128");
上面的表达式返回false,因为128没有存在静态缓冲区。所以每次在判断相等时等式两边都会创建新的Integer对象。由于两个Integer对象不同,所以==只有等式两边代表同一个对象时才会返回true。因此,上面的等式返回false。
Integer.parseInt("128")==Integer.valueOf("128");
上面的表达式比较的是左边的原始int值128与右边新创建的Integer对象。但是因为int和Integer之间比较是没有意义的,所以Java在进行比较前会将Integer自动拆箱,所以最后进行的是int和int值之间的比较。由于128和自己相等,所以返回true。
注意:此文只适应于jdk7或以上版本,因为jdk6与jdk7的Integer具体实现有差别,详情可查看下源代码.
Integer.parseInt()
Integer.valueof() 和 Integer.parseInt() 的底层都是用的Integer.parseInt(String s ,int radix)这个方法。在这里给这个方法做一下解释。
Integer.parseInt(String s ,int radix),radix用来表示传进来的值是什么进制的,并返回10进制的 int 类型的结果。
比如Integer.parseInt(“A”,16),则输出结果为10进制的10,其中16表示"A"是一个16进制的值。
根据:Character.MIN_RADIX=2和Character.MAX_RADIX=36 则,parseInt(String s, int radix)参数中radix的范围是在2~36之间,超出范围会抛异常。其中s的长度也不能超出7,否则也会抛异常。其中限制在36位之内是因为数字加字母刚好可以表示到36位,比如Integer.parseInt(“Z”,36),输出结果为35。
以下为parseInt(String s ,Int radix)的源代码实现。
/** * 字符串转换成整数 * @param s 待转换字符串 * @param radix 进制 * @return */ public static int parseInt(String s,int radix){ //边界值处理 if(s==null) throw new NumberFormatException("null"); if(radix<Character.MIN_RADIX){ throw new NumberFormatException("radix "+radix+" less than Character.MIN_RADIX"); } if(radix>Character.MAX_RADIX){ throw new NumberFormatException("radix "+radix+" greater than Character.MAX_RADIX"); } //最终返回的结果的负数形式 int result=0; //判断是否为负数 boolean negative=false; //字符串偏移指针 int i=0; int digit; int max=s.length(); //最大边界值 int limit; //最大边界值右移一位 int multmin; if(max>0){ //处理符号 if(s.charAt(0)=='-'){ negative=true; //边界值为0x80000000 limit=Integer.MIN_VALUE; i++; } else{ //边界值为-0x7fffffff limit=-Integer.MAX_VALUE; } //计算multmin 值 ,multmin = -214748364 负数跟整数的limit是不同的 multmin=limit/radix; if(i<max){ digit=Character.digit(s.charAt(i++), radix); if(digit<0){ throw NumberFormatException.forInputString(s); } else{ result=-digit; } } //开始循环追加数字,比如输入“123” 10进制数 while(i<max){ //获取字符转换成对应进制的整数,如上,这里第一次循环获取1 //第二次循环获取2 //第三次循环获取3 digit=Character.digit(s.charAt(i++), radix); if(digit < 0){ throw NumberFormatException.forInputString(s); } //判断,在追加后一个数字前,判断其是否能能够在继续追加数字,比如multmin = 123 //那么再继续追加就会变为123*10+下一个数字,就会溢出 if(result < multmin){ throw NumberFormatException.forInputString(s); } //第一次循环 result = 0; //第二次循环 result = -10; //第三次循环 result = -120; result*=radix; if(result<limit+digit){ //第一次循环 limit + digit = -2147483647+1; //第二次循环 limit + digit = -2147483647+2; //第三次循环 limit + digit = -2147483647+3; throw NumberFormatException.forInputString(s); } result-=digit; //第一次循环 result = -1; //第二次循环 result = -12; //第三次循环 result = -123; } } else{ throw NumberFormatException.forInputString(s); } if(negative){ if(i>1){ return result; } else{ throw NumberFormatException.forInputString(s); } } else{ //negative 值为false,所以 -result = -(-123) = 123 返回结果 return -result; } }
关键点:
- 正数的边界值为1至0x7fffffff;负数的边界值为-1至0x80000000;
- 代码中将所有数据当做负数(正数)来处理,最后处理符号问题;
- 方法中multmin这个变量是为了在循环中result*=radix不会发生越界;
Integer.parseInt("")
、Integer.valueOf("")
和new Integer("")
它们之间有什么区别呢?我们可以分别看一下它们的源码//Integer.parseInt("") public static int parseInt(String s) throws NumberFormatException { return parseInt(s,10); } //Integer.valueOf("") public static Integer valueOf(String s) throws NumberFormatException { return Integer.valueOf(parseInt(s, 10)); } //new Integer("") public Integer(String s) throws NumberFormatException { this.value = parseInt(s, 10); }
从源码中可以看出,
Integer.valueOf("")
和Integer.parseInt("")
内部实现是一样的,它们之间唯一的区别就是Integer.valueOf(“”)返回的是一个Integer对象,而Integer.parseInt(“”)返回的是一个基本类型的int。我们再看
Integer.valueOf("")
和new Integer("")
,它们同样返回的是一个Integer对象,但它们又有什么区别呢?我们再进入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); }
我们可以看到,
Integer.valueOf("")
会用到IntegerCache
对象,当IntegerCache
中存在时就从cache中取,不存在时才会调用new Integer(i)
构造函数返回一个Integer对象。所以Integer.valueOf("")
会用到cache,其效率可能会比用构造函数new Integer(i)
高。关于
IntegerCache
,在-127~128之间的值都会被cache,所以当我们要的值位于这个区间时返回的都是同一个实例,例如:System.out.println(Integer.valueOf(5) == Integer.valueOf(5)); System.out.println(Integer.valueOf(500) == Integer.valueOf(500));
输出结果:
true //会用到缓存 false //不会用到缓存
-
java Integer等号判断
2020-11-15 20:58:44在-128到127范围内的赋值,Integer对象在IntegerCache.cache产生,会复用已有对象,这个区间的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据都会在堆上产生,并不会复用已有对象,这是一个大坑,...《阿里Java开发手册》中有这样一项强制要求:
“所有整形包装类对象之间值的比较,全部使用equals方法比较。说明:对于Integer var= ?在-128到127范围内的赋值,Integer对象在IntegerCache.cache产生,会复用已有对象,这个区间的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。”
究其原因,为什么呢。我们先看一段代码:
public static void main(String[] args) { Integer i1 = 64; int i2 = 64; Integer i3 = Integer.valueOf(64); Integer i4 = new Integer(64); Integer i5 = 256; Integer i6 = Integer.valueOf(256); System.out.println("i1 == i2:" + (i1 == i2)); // true System.out.println("i1 == i3:" + (i1 == i3)); // true System.out.println("i1 == i4:" + (i1 == i4)); // false System.out.println("i2 == i4:" + (i2 == i4)); // true System.out.println("i3 == i4:" + (i3 == i4)); // false System.out.println("i3.equals(i4):" + (i3.equals(i4))); // true System.out.println("(i5 == i6):" + (i5 == i6)); // false }
如果有些结果和我们预料的不太一样,我们还是认真深究一下。
变量在JVM中的存储
在彻底弄清楚上问题之前,我们先来了解一下基础类型变量、引用类型变量在JVM中的存储。
通常变量分为局部变量和全局(成员)变量。局部变量是声明在方法内的变量,也包括方法参数;全局变量是声明在类中的成员变量。
基础类型的变量和值在分配的时候是在一起的,都在方法区或栈内存或堆内存。而引用类型的变量和值不一定在一起。
局部变量存储在方法栈中
当方法被调用时,Java虚拟机都同步创建一个栈帧,局部变量便存储在其中。当方法结束虚拟机会释放方法栈,其中声明的变量随着栈帧的销毁而结束。因此,局部变量只能在方法中有效。
此过程中,基础类型和引用类型的存储有所区别:
(1)基本类型:变量和对应的值存放在JAVA虚拟机的栈中;
(2)引用类型:变量存储在栈中,是一个内存地址,该地址值指向堆中的对象。
栈属于线程私有的空间,局部变量的生命周期和作用域一般都很短,为了提高gc效率,所以没必要放在堆里面。全局变量存储在堆中
全局变量存放在堆中,不会随着方法结束而销毁。同样在类中声明的变量也是分为基本类型和引用类型。
(1)基本类型:变量名和值存放在堆内存中。
(2)引用类型:变量是一个引用地址,该地址指向所引用的对象。此时,变量和对象都在堆中。
举个简单的例子,如下代码:
public class Person { int age = 10; String name = "Tom"; }
结合上面的理论,我们通过一段代码来分析一下各种类型所存储的位置。
public class DemoTest { int y; // 变量和值均在堆上 public static void main(String[] args) { int x = 1; // 变量和值分配在栈上 String name = new String("cat"); // 数据在堆上,name变量的指针在栈上 String address = "北京"; // 数据在常量池,属于堆空间,指针在栈上 Integer price = 4; // 包装类型为引用类型,编译时会自动装拆箱,数据在堆上,指针在栈 } }
基础类型的栈内存储
通过上面的实例,基本了解了不同类型的值的内存分配情况。下面我们重点讨论局部变量。
下面先来看看在同一栈帧中,针对int类型的处理模式。
int a = 3; int b = 3;
上述代码中a和b均为局部变量。假设编译器先处理int a=3,此时会在栈中创建a的引用变量,然后查找栈中是否存在3这个值,如果没有就将3存放进来,然后将a指向3。
接着处理int b=3,创建完b的引用变量后,同样进行查找。因为在栈中已经有3这个值,便将b直接指向3。
此时,a与b同时指向3这个值,自然是相等的。
关于基础类型与引用类型的底层比较,可稍微延伸一下:对于“==”操作符号,JVM会根据其两边相互比较的操作数的类型,在编译时生成不同的指令:
(1)对于boolean,byte、short、int、long这种整形操作数会生成if_icmpne指令。该指令用于比较整形数值是否相等。
(2)如果操作数是对象的话,编译器则会生成if_acmpne指令,与if_icmpne相比将i(int)改成了a(object reference)。
回归正题
学习了上面的底层理论知识,我们基本上可以得出如下结论:(1)两个int类型比较,直接使用双等号即可;(2)int的包装类Integer对象比较时,使用equals进行比较即可。
但是这并不能说明所有问题,还涉及到整形的装箱拆箱操作、Integer的缓存。我们下面逐一分析。
不同创建形式的比较
先看Integer的初始化,根据Integer的内部实现,创建Integer有三种,分别是:
Integer a = new Integer(1); //创建新的类 Integer b = Integer.valueOf(2); Integer c = 3; //自动包装,会调用valueOf方法
其中直接赋值底层会调用valueOf方法进行操作的,因此这两种操作效果是一样的。
因为通过new和valueOf创建的是完全两个对象,那么针对题目中的 i3 == i4 项,直接比较两个对象的引用肯定是不相等的,因此结果为false。但 i1 == i3项为什么为true呢?后面我们会讲到。
比较中的拆箱
在题目中,我们发现 i1 == i2 与 i2 == i4 都为true,而且它们的比较格式都是基础类型与包装类型的对比。
针对这种形式的对比,包装类型会进行自动拆箱,变成基础类型(int)。很显然,结果是相等的。当然,这种比较时候要注意NPE问题,又是另外一个话题了。
Integer的缓存
为什么i1和i3相等,但i5和i6却不相等呢?对应题目中的B和G项。这里就涉及到Integer的缓存机制。我们上面已经知道,Integer直接赋值和valueOf是等效的,那先看一下valueOf及相关的方法。
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } 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; String integerCacheHighPropValue = sum.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++); assert IntegerCache.high >= 127; } private IntegerCache() {} }
valueOf方法判断数字是否大于low(-128)并且小于high(127),如果满足条件,则直接从IntegerCache中返回对应数字。
IntegerCache用于存储一些常用的数,防止重复创建,在Integer类装入内存时通过静态代码进行初始化。
所以只要是用valueOf或者Integer直接赋值的方式创建的对象,其值小于127且大于-128的,无论对其进行==比较还是equals 比较,都是true。
上面的源码及原理也解释了阿里Java开发手册中所说明的原因。
为什么equals可以规避问题
对于不满足-128到127范围的数,无论通过什么方式创建,都会创建一个新的对象,只能通过equals进行比较。接下来我们再看看equals方法。
public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; }
equals实现比较简单,先比较类型是否一致,如果不一致,直接返回false;否则,再比较两者的值,相同则返回true。
小结
关于Integer的比较核心点有以下三点:引用对象的存储结构、Integer的缓存机制、自动装箱与拆箱。
Integer在==运算时,总结一下:
(1)如果==两端有一个是基础类型(int),则会发生自动拆箱操作,这时比较的是值。
(2)如果==两端都是包装类型(Integer),则不会自动拆箱,首先会面临缓存问题,即便在缓存范围内的数据还会再次面临创建方式的问题,因此强烈建议使用equals方法进行比较。
原文来自: https://cloud.tencent.com/developer/article/1688593
-
Integer
2016-10-11 23:35:27Interger:整数类型 1、属性。 static int MAX_VALUE:返回最大的整型数; static int MIN_VALUE:返回最小的整型数;...System.out.println(“Integer.MAX_VALUE: ” + Integer.MAX_VALUE ); 结果为:In -
Integer比较大小
2021-03-17 15:21:32Integer比较大小java的两种类型:● 基本类型基本数据类类型存的是数值本身●引用类型引用类型变量在内存放的是数据的引用基本类型通过==比较的是他们的值大小,而引用类型比较的是他们的内存地址正文在一些特殊的类... -
java Integer常用方法详解
2019-08-27 11:50:28先来Integer吧 package com.String; //封装类 /* * 比如int对应的类是Integer 这种类就叫做封装类 */ public class Damo { static void m1() { // Integer 的构造函数 Integer it1 = new Integer(5);// ... -
Java中的Integer
2021-02-26 10:07:58包装类———IntegerInteger 类在对象中包装了一个基本类型int的值。Integer类型的对象包含一个 int 类型的字段。此外,该类提供了多个方法,能在 int 类型和 String 类型之间互相转换,同时还提供了其他一些处理int... -
Integer 类型对象之间大小比较
2021-03-08 10:33:34一.Integer 类型对象之间大小比较1. 使用 new Integer() 创建的对象 :首先,我们知道,在java中,基本类型之间是可以做大小比较的,例如int i=1,int j=2,是可以直接使用关系运算符“”、“==”等符号来做比较的。但... -
Integer类
2018-04-18 17:59:17Integer类:基本数据类型和对应的包装类类型、Integer包装类的构造方法、int类型和String类型之间的转换、Integer包装类的其他成员方法、Integer包装类的进制转换、Integer包装类的自动拆装箱原理、Integer包装类的... -
Integer与Int
2019-06-15 19:55:00在进行描述之前先给出下面代码,观测结果: package ... /** * created by LMR on 2019/6/15 */ public class IntegetTest { ... public static void main(String[] args) { Integer i = 10; Inte... -
Integer与int的区别
2022-01-17 14:55:29Integer与int的区别 -
Integer详解
2021-11-24 21:09:12Integer 简介 Integer 类在对象中包装了一个基本类型 int 的值。Integer 类对象包含一个 int 类型的字段。此外,该类提供了多个方法,能在 int 类型和 String 类型之间互相转换,还提供了处理 int 类型时非常有用的... -
Java Integer类型比较问题
2021-05-07 10:22:09Java Integer类型比较问题 【强制】所有整型包装类对象之间值的比较,全部使用 equals 方法比较。 说明:对于 Integer var = ? 在-128至127范围内的赋值,Integer 对象是在 IntegerCache.cache产生,会复用已有对象... -
Integer类型比较
2021-10-26 10:29:21比较两个Integer类型的值是否相等 2、equals方法: 两个Integer类型的数值比较不能使用==比较 应该使用.equals进行比较 注意在使用equals方法前对equals前面的对象进行判空处理 3、Integer中重写了equals方法... -
Integer.valueOf和new Integer的区别
2022-02-16 13:16:46Integer currentVersion = new Integer(1); 此时sonar报错,并建议使用Integer.valueOf()方法: Constructors forString,BigInteger,BigDecimaland the objects used to wrap primitives should never be used. ... -
JDK源码阅读之Integer
2019-08-20 14:51:41我们在学习对象的自动装箱和自动拆箱时首次接触的就是Integer类。同时我们知道如果新建小数字对象其实是同一个对象,那么这之中的奥秘是什么呢?现在我们从源码的角度剖析一下吧! -
java - 将Integer转换为Long
2021-02-27 21:32:21java - 将Integer转换为Long我需要使用反射来获取字段的值。 碰巧我并不总是确定该字段的数据类型是什么。 为此,为了避免一些代码重复,我创建了以下方法:@SuppressWarnings("unchecked")private static T ... -
integer是关键字吗 integer在c语言中是什么意思
2021-04-21 20:34:58integer是不是关键字要看看是什么语言,每种语言不一样,例如 integer 是 delphi的保留字 却不是 c语言的保留字。Integer为什么不是Java关键字?Integer 是数据类型不是C的关键字,它就一个英文单词,本意为整数,可... -
int和Integer有什么区别
2021-07-02 14:18:56面试中会问道int和Integer的区别,这里我们详细说一下 Integer是int的包装类;int是基本数据类型。 Integer变量必须实例化后才能使用;int变量不需要。 Integer实际是对象的引用,当new一个Integer时,实际上是... -
从 Int 到 Integer 对象,细细品来还是有不少东西
2019-09-29 09:24:31int 是 Java 八大原始类型之一,是 Java 语言中为数不多不是对象的东西,Integer 是 int 的包装类,里面使用了一个 int 类型的变量来存储数据,提供了一些整数之间的常用操作,常规性的介绍就这么一点,程序员不喜欢... -
Java中的Integer类
2019-05-03 15:20:27常见对象(Integer类的概述和构造方法) 如果有两个需求: 需求: a: 将100转换成二进制, 八进制, 十六进制 b: 判断一个数是否在int的范围内 当然,可以使用最基本的方法累除来求一个十进制数的其他进制,也可以... -
Integer和int区别
2022-04-06 17:43:371、Integer是int的包装类,int则是java的一种基本数据类型 2、Integer变量必须实例化才能使用,int变量不需要实例化 3、Integer的默认值是null,而int的默认值是0 4、Integer实际是一个对象的引用,当new一个Integer... -
JAVA Integer类型自加
2021-02-13 02:13:09这些数据类型可以支持一些运算操作符,其中对于int类型的++/--操作符Integer类型是一个对象类型,居然也可以支持++运算,那么问题来了一个Integer对象执行++操作之后还是原来那个对象吗?测试代码public class ... -
在Java中将Double转换为Integer
2021-02-27 12:37:36您无法直接将 Integer 转换为 Double 对象 . 此外 Double 和 Integer 是不可变对象,因此您无法以任何方式修改它们 .每个数字 class 都有 primitive 替代( Double vs double , Integer vs int ,...) . 请注意,... -
Integer的最大值
2021-02-12 22:20:53for循环运行到 i = Integer.MAX_VALUE ,即 i = 2147483647,i再加1,就变成了-2147483648 (这个地方明白源码,反码,补码的同学已经懂了吧,不明白的等我详细的整理一个专题或者留言询问……请关注我) 这个... -
用Java将Double转换为Integer
2021-02-12 16:18:32您不能直接将投射Integer到Double对象。并且Double和Integer是不可变的对象,因此您不能以任何方式修改它们。每个数字类都有一个原始的替代项(Doublevs double,Integervs int,...)。请注意,这些原语以小写字母... -
integer表示的最大整数
2021-05-05 01:55:02Integer类型的变量可能存在的最大整数为? A.256 BInteger类的数据范围为,最小值为 -2^31,最大值为 2^31-1; 验证如下,创建java类TestInteger,做测试验证用,编写java代码,创建Integer对象,值为256,数据可以... -
源码看JAVA【八】Integer
2019-02-27 23:44:59int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val); int chars = Math.max(((mag + (shift - 1)) / shift), 1); char[] buf = new char[chars]; formatUnsignedInt(val, shift, buf, 0, chars); /...