背景:
很多面试题都有Java拆装箱问题,其中Integer与int区别最多,下面结合自己看的一些视频及文章做下总结。
先上测试代码:
public static void main(String[] args) {
int i = 150;
Integer i2 = 150; ❸
Integer i3 = new Integer(150); ❷
System.out.println(i == i2); // ★ Integer会自动拆箱为int,所以为true ❶
System.out.println(i == i3); //true,理由同上 ❶
System.out.println(i2 == i3); //false
System.out.println(i2.equals(i3)); //true
Integer i4 = 127; ❸
Integer i5 = 127; ❸
System.out.println(i4 == i5);//true ❹
Integer i6 = 128;
Integer i7 = 128;
System.out.println(i6 == i7);//false ❺
Integer i8 = new Integer(127);
System.out.println(i5 == i8); //false ❻
}
❶属于自动拆箱,这里之所以选150测试,是避免Integer在-128到127之间进行缓存的误解。
注:自动装箱和拆箱是由编译器来完成的,编译器会在编译期根据语法决定是否进行装箱和拆箱动作。
❷属于直接创建Integer对象,new过程会直接从堆中新开辟一个内存保存Integer数据,将地址存入栈中对象。==比较的就是栈中对象的地址信息。
equals则不同,看源码
/**
* 将此对象与指定对象进行比较。
* 当且仅当参数不是null并且是一个包含与该对象相同的int值的整数对象时,结果是真的。
*/
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue(); // ⚘
}
return false;
}
public int intValue() { // ♆
return value;
}
public Integer(int value) {
this.value = value;
}
private final int value;
可以看到,equals比较的是初始化时传入的int值。一般比较Integer值相等最好用equals()方法。
❸部分在编译时被翻译成:Integer i4 = 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);
}
该部分代码进行了一个判断:在 IntegerCache.low 和 IntegerCache.high之间的值,则从IntegerCache.cache[i + (-IntegerCache.low)]数组对应的位置去数据,否则在堆内存中重新创建新的Integer对象。
为什么是 IntegerCache.cache[i + (-IntegerCache.low)],IntegerCache.low 和 IntegerCache.high又分别是多少?
最关键的部分来了,IntegerCache这个类,Ctrl + 左键 进行跟踪:
private static class IntegerCache {
static final int low = -128; // 缓存下界,值不可变
static final int high; // 缓存上界
static final Integer cache[]; // cache缓存是一个存放Integer类型的数组
static {
// high值可以由属性进行配置
int h = 127;
// getSavedProperty() 获取虚拟机启动时配置参数java.lang.Integer.IntegerCache.high的值,该值为自行设置的缓存上界,看下面代码
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) { // 判断调用者是否设置改参数
try {
// 将配置的integerCacheHighPropValue值传给i,parseInt()将字符串参数作为有符号的十进制整数进行解析。
int i = parseInt(integerCacheHighPropValue); // ⚘
// 取配置的integerCacheHighPropValue值与127中最大的赋值给i,保证了上线至少是127
i = Math.max(i, 127);
// h取 i 与 Integer.MAX_VALUE - (-low) -1的最小值,其中 Integer.MAX_VALUE = 2^31 -1 = 2147483647,16进制标识为0x7fffffff
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// 如果属性不能解析为int,则忽略它,为parseInt()抛出的NumberFormatException异常 ,try -catch了,因此程序依然往下走.
}
}
high = h; // 没有直接使用high值进行操作,而是先初始h值再赋值给high,没想通设计者的这样写的具体目的
// 创建一个长度为(high - low) + 1的Integer数组,此处为什么写成(high - low) + 1?为什么不是high - low,下面的循环写成k <= cache.length?
cache = new Integer[(high - low) + 1];
int j = low; // 此处则控制了,cache缓存数组的下界是不可变的,new Integer(j++)。
// 原来上面的写法,不仅直观,而且数组下标从0开始,而数组长度从1开始,如果写成high - low,k <= cache.length则会出现下标越界。
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是Integer的静态内部类,因此在Integer初始化时同时初始化IntegerCache及其所有属性。具体详情,看上面代码注释,便于查看。
关于 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high")
参数的设置
规范就是规范,设置java.lang.Integer.IntegerCache.high
的值用于什么地方,对于该写法一目了然。而该值怎么设置?两种办法
-XX:AutoBoxCacheMax=128 或者 -Djava.lang.Integer.IntegerCache.high=128 进行设置
添加此设置后:第❺部分则为true
Eclipse可以通过 右击执行类 ——> Run AS ——> Run configurations ——> Arguments选项卡 ——> VM arguments中添加。
包装类 | 是否缓存 |
---|---|
Boolean | 全部缓存 |
Byte | 全部缓存 |
Character | <= 127 缓存 |
Short | -128 — 127 缓存 |
Long | -128 — 127 缓存 |
Float | 没有缓存 |
Doulbe | 没有缓存 |
再看 parseInt(String s)源码部分
/**
* 将字符串参数作为有符号的十进制整数进行解析。除了第一个字符可以是用来表示负值的 ASCII 减号 '-' ( '\u002D')
* 外,字符串中的字符都必须是十进制数字。返回得到的整数值,就好像将该参数和基数 10 作为参数赋予
* parseInt(java.lang.String, int) 方法一样。
*/
public static int parseInt(String s) throws NumberFormatException { // ♆
return parseInt(s,10); // ⚘
}
内部使用 parseInt(String s,int radix)方法
/**
* 使用第二个参数指定的基数,将字符串参数解析为有符号的整数。除了第一个字符可以是用来
* 表示负值的 ASCII 减号 '-' ( '\u002D’)外,字符串中的字符必须都是指定基数的数字
*(通过 Character.digit(char, int) 是否返回一个负值确定)。返回得到的整数值。
*
* 如果发生以下任意一种情况,则抛出一个 NumberFormatException 类型的异常:
*
* ● 第一个参数为 null 或一个长度为零的字符串。
* ● 基数小于 Character.MIN_RADIX 或者大于 Character.MAX_RADIX。
* ● 假如字符串的长度超过 1,那么除了第一个字符可以是减号 '-' ('u002D’) 外,
* \字符串中存在任意不是由指定基数的数字表示的字符。
* ● 字符串表示的值不是 int 类型的值。
*
* 示例:parseInt("11", 10) 返回 11
* parseInt("11", 22) 返回 23
*
* @param s - 包含要解析的整数表示形式的 String
* @param radix - 解析 s 时使用的基数,即以几进制输出
* @return 使用指定基数的字符串参数表示的整数
*
*/
public static int parseInt(String s, int radix) throws NumberFormatException { // ♆
/*
* 警告:因为valueOf方法使用了parseInt方法和IntegerCache对象,因此valueOf在parseInt初始化之前使用会出现异常
*/
// 下面三个if用来判断参数是否合法。radix大小在2~36之间。
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, len = s.length(); //索引变量和字符串长度
int limit = -Integer.MAX_VALUE; // 最大值限制,加'-'号是为了后面的result < 做统一处理
int multmin; //基数下的最小值
int digit; //记录每一位的数字
if (len > 0) {
char firstChar = s.charAt(0);
if (firstChar < '0') { // 所有的特殊符号和字母字符均小于'0',判断是否有'-'或'+'
if (firstChar == '-') { // 判断是否为'-'或'+'
negative = true; // 标识所传为负数
limit = Integer.MIN_VALUE;
} else if (firstChar != '+') // 格式非法,既不为'-',也不为'+'则抛出异常
throw NumberFormatException.forInputString(s);
if (len == 1) // 排除只为'-'或者'+'的字符串情况
throw NumberFormatException.forInputString(s);
i++;
}
multmin = limit / radix;
while (i < len) {
// 利用了Character类中的digit方法,作用是解析一个字符返回int值,基本就是'0'-'9'则返回 0-9
digit = Character.digit(s.charAt(i++),radix); // ⚘
if (digit < 0) {
throw NumberFormatException.forInputString(s);
}
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}
// result进行基数转换,放在result -= digit前面,如转换"123",则为-(((1*22)+2)*22+3)即 1*22^2 +2*22^1+3*22^0
result *= radix;
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
result -= digit; // 将字符处理后返回的digit赋值给result
}
} else {
throw NumberFormatException.forInputString(s);
}
return negative ? result : -result; // 这里进行上面加'-'统一处理的问题
}
再看 Character.digit(char ch, int radix) 源码
public static int digit(char ch, int radix) { // ♆
return digit((int)ch, radix); // ⚘
}
内部调用Character.digit(int codePoint, int radix)源码
/**
* 返回使用指定基数的字符 ch 的数值
*
* 如果基数不在 MIN_RADIX <= radix <= MAX_RADIX 范围之内,
* 或者 ch 的值是一个使用指定基数的无效数字,则返回 -1。
* 如果以下条件中至少有一个为真,则字符是一个有效数字:
*
* ● 方法 isDigit 为 true,且字符(或分解的单字符)的 Unicode 十进制数值小于指定的基数。在这种情况下,返回十进制数值
* ● 字符为 'A' 到 'Z' 范围内的大写拉丁字母之一,且它的代码小于 radix + 'A' - 10。在这种情况下,返回 ch - 'A' + 10
* ● 字符为 'a' 到 'z' 范围内的小写拉丁字母之一,且它的代码小于 radix + 'a' - 10。在这种情况下,返回 ch - 'a' + 10
* 注:此方法无法处理增补字符。若要支持所有 Unicode 字符,包括增补字符,请使用 digit(int, int) 方法
*
* @param 要转换的字符(Unicode 代码点),一般由char强转的int,char '0'-'9' 对应 int 48-57
* @param 基数
* @return 使用指定基数的字符所表示的数值
* @开始版本 1.5
*/
public static int digit(int codePoint, int radix) { // ♆
return CharacterData.of(codePoint).digit(codePoint, radix); // ⚘
}
再看CharacterData.of(int ch) 源码,根据位运算选取CharacterData的实现类
char的值,如果在255以内,就调用CharacterDataLatin1来处理,再多一位二进制数字
用CharacterData00,然后CharacterData01,CharacterData02
static final CharacterData of(int ch) { // ♆
if (ch >>> 8 == 0) { // fast-path
return CharacterDataLatin1.instance;
} else {
switch(ch >>> 16) { //plane 00-16
case(0):
return CharacterData00.instance;
case(1):
return CharacterData01.instance;
case(2):
return CharacterData02.instance;
case(14):
return CharacterData0E.instance;
case(15): // Private Use
case(16): // Private Use
return CharacterDataPrivateUse.instance;
default:
return CharacterDataUndefined.instance;
}
}
}
以CharacterDataLatin1.digit(int ch, int radix) 为例,大概就是传的是 '0' - '9' 之间的字符,就返回相应的数字。
这块有些复杂,不是科班出身,需要脑补计算机基础,有兴趣的可以自行百度!
int digit(int ch, int radix) { // ♆
int value = -1;
if (radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX) {
int val = getProperties(ch); // ⚘
int kind = val & 0x1F;
if (kind == Character.DECIMAL_DIGIT_NUMBER) {
value = ch + ((val & 0x3E0) >> 5) & 0x1F;
}
else if ((val & 0xC00) == 0x00000C00) {
// Java supradecimal digit
value = (ch + ((val & 0x3E0) >> 5) & 0x1F) + 10;
}
}
return (value < radix) ? value : -1;
}
int getProperties(int ch) { // ♆
char offset = (char)ch;
int props = A[offset];
return props;
}
static final int A[] = new int[256]; // 2^8=256
static final String A_DATA =
"\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800"+
// 省略....
"\u061D\u7002";
static {
{ 这个代码是GenerateCharacter自动创建;这块静态代码块里写构造代码块的具体目的?
char[] data = A_DATA.toCharArray();
assert (data.length == (256 * 2));
int i = 0, j = 0;
while (i < (256 * 2)) {
int entry = data[i++] << 16;
A[j++] = entry | data[i++]; // 这块 i 和 j 的关系 : i = j * 2
}
}
}
static final String A_DATA =
"\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800"+
// 省略....
"\u061D\u7002";
可以将上面代码单独拿出来自行测试,看看运算过程:
public static void main(String[] args) {
System.out.println( (char)57);
System.out.println(Character.digit(57,10));
// a();
}
public static void a() {
System.out.println(87 >>> 8);
String A_DATA = "\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800"+
// 省略。。。
"\u061D\u7002";
int A[] = new int[256];
char[] data = A_DATA.toCharArray();
assert (data.length == (256 * 2));
int i = 0, j = 0;
while (i < (256 * 2)) {
int entry = data[i++] << 16;
char a = data[i++] ;
System.out.println(j+"-"+i+": "+(entry | a));
A[j++] = entry | a;
}
System.out.println((int)'1');
System.out.println( A['1'] & 0x1F);
System.out.println((int)'1' + ((A['1'] & 0x3E0) >> 5) & 0x1F);
}
网友评论