众所周知,java 是一门面向对象的高级语言。但是 java 中的基本类型不能作为对象使用,为了解决对象的调用问题,为每个基本类型创造了对应的包装类型。
先来看一道包装类的题目吧
int a = 10;
Integer b = 10;
System.out.println(a == b); ①
System.out.println(b.equals(a)); ②
Integer c = 100;
Integer d = 100;
System.out.println(c == d); ③
System.out.println(c.equals(d)); ④
Integer e = new Integer(100);
Integer f = new Integer(100);
System.out.println(e == f); ⑤
System.out.println(e.equals(f)); ⑥
Integer g = 1000;
Integer h = 1000;
System.out.println(g == h); ⑦
System.out.println(g.equals(h)); ⑧
在看知识点前先看一下习题,看看是否存在不了解的点,以便对症下药
这些题考查的都是 java 中很基础的一类概念
包括包装类与基本类型的转换,包括自动拆箱装箱
以及 == 和 equals 的区别
给上答案
①true ②true ③true ④true ⑤false ⑥true ⑦false ⑧true
以下示范都以Integer为例,其他包装类型与之无明显差异,大家看会了 Integer 其他包装类也就都能理解了
为什么需要包装类
- 基本类的值不属于对象,因此基本类型只存在一个值,没有各种属性,也没有各种方法可以调用。
- 很多场景需要使用对象,比如 java 中的集合,所以为了能使基本类型的值也能存入集合中,或者用于其他类中,则需要把它们包装成对象。
- 每个非基本类都继承自基类 Object ,所以天生神力,自带一身方法。但是基本类型没有方法可以调用,用包装类可以继承 Object 方法,也可以添加自身方法。
- 非基本类型变量不赋值默认为 null,但基本类型没有 null 值,比如 int 默认为 0,Boolean 默认为 false。
- 在某些场合中,我们可能会希望使用到 null 值,表示某些意义,那基本类型则就做不到了。
包装类与基本类型的转换 (包括自动装拆箱)
JDK自从1.5之后就引入了自动装拆箱的功能,这大大简化了开发人员的代码复杂度,使得对基本类型的代码书写效率大大提高。
但是这也带来了一个问题,很多小白搞不明白基本类型和包装类型,使很多开发人员对此形成误解。
首先,最基本的,用 new 来构造一个 Integer 对象
再用 Integer 的 intValue() 方法返回它的int值
Integer a = new Integer(1);
int b = a.intValue();
但在 java 中一般不提倡 new 一个 Integer 包装类对象
用 Integer.valueOf() 静态方法来获取一个 Integer 对象
a = Integer.valueOf(5);
Integer a = 5; // 也可以写成
而这个方法即是 int --> Integer 的自动装箱方法 (即上面两行代码是等同的)
同理自动拆箱
Integer a = 5;
// int b = a.intValue();
int b = a; // 这行代码与上面一行代码等同
比如之前题目中的①和② 就有许多装拆箱的知识点
int a = 10;
Integer b = 10; // 自动装箱
// 将b自动拆箱与a比较
System.out.println(a == b);
// 将a自动装箱成为对象才能作为equals中的参数
System.out.println(b.equals(a));
但是在自动装箱需要注意一个细节,也就是之前题目中的③④⑦⑧。
在 -128 ~ 127 的数中,也就是 byte 的取值范围,自动装箱时,会从常量池中去取Integer对象,因而包装出的 Integer 对象是同一个对象,不在这个范围内的数字,包装出的 Integer 对象则都是 new 出来的新对象。
Integer c = 100; // 从常量池中取出Integer对象
Integer h = 1000; // 相当于用new创建出一个新对象
因此在之前题目中值为 100 的 Integer 对象 == 为 true,而值为 1000 的Integer对象则为 false。
基本类型和包装类型与String的转换
由于存在自动装拆箱的功能,所以对于 String 与数字转换时,可以将其转换为基本类型 int,也可以转换为包装类 Integer,通过自动装拆箱即可以正确赋值给变量
String 和 Integer 之间的转换
// String --> Integer
Integer a = new Integer("12345");
// Integer --> String
String s = a.toString();
String 和 int 之间的转换
// String --> int
int a = Integer.parseInt("12345");
// String --> Integer --> int
a = new Integer("12345");
// int --> Integer --> String
String s = a + ""; //自动装箱调用toString()
s = (Integer.valueOf(a)).toString();
包装类基础
基本类 | 包装类 | 继承 | 基本类类信息 | 包装类对应类信息 |
---|---|---|---|---|
byte | Byte | Number | byte.class | Byte.TYPE |
short | Short | Number | short.class | Short.TYPE |
int | Integer | Number | int.class | Integer.TYPE |
long | Long | Number | long.class | Long.TYPE |
float | Float | Number | float.class | Float.TYPE |
double | Double | Number | double.class | Double.TYPE |
char | Character | Object | char.class | Character.TYPE |
boolean | Boolean | Object | boolean.class | Boolean.TYPE |
一些关键源码(解释写在注释之中)
首先是 Integer 的范围,与 int 相同,都为 -2的31次方~2的31次方-1。
其中0x表示16进制,但是仔细一看,会发现最小值比最大值大一,这是因为在二进制中负数是用补码表示的,最高位是符号位,0是正,1位负,所以正数最大值没有问题,而负数最小值则根据首位1推出为负数。
对于补码不细讲,只需记住
正数的补码 = 原码
负数的补码 = 原码符号位不变 + 数值位按位取反后+1
或者 = 原码符号位不变 + 数值位从右边数第一个1及其右边的0保持不变,左边安位取反
@Native public static final int MIN_VALUE = 0x80000000;
@Native public static final int MAX_VALUE = 0x7fffffff;
构造方法很简单
public Integer(int value) {
this.value = value;
} // 给指定值创建对象
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
} // 通过字符串解析出值创建对象
装箱方法
public static Integer valueOf(int i) {
// 如果值在low和high之间 low为-128 high一般默认为127
if (i >= IntegerCache.low && i <= IntegerCache.high)
// 则直接返回常量池中的对象
return IntegerCache.cache[i + (-IntegerCache.low)];
// 否则用new新建一个Integer对象返回
return new Integer(i);
}
在源码中我们发现了一个静态嵌套类IntegerCache,它拥有两个 final 值 low 和 high,代表缓存的 Integer 的对象的范围,这样在装箱时可以直接获取,而不用再创建对象。
private static class IntegerCache {
static final int low = -128; // 缓存最小值
static final int high; // 缓存最大值
// low到high的Integer对象都被保存在这里 装箱时取
static final Integer cache[];
// 根据类加载机制 以下方法块在类初始化时执行
// 会率先将low到high的Integer保存在cache数组中
static {
int h = 127;
/* 此处省略一些无关代码 */
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
// 私有构造方法 保证类无法创建对象
// 因为该类仅作为事先缓存Integer对象
private IntegerCache() {}
}
再看toString()方法
public static String toString(int i) {
if (i == Integer.MIN_VALUE) //为最小值则直接return
return "-2147483648";
// 计算数字的长度
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
// 根据长度创建字符数组
char[] buf = new char[size];
// 不断获取字符放入数组中
getChars(i, size, buf);
// 根据字符数组返回新字符串
return new String(buf, true);
}
里面的 stringSize 方法很有趣,很多情况一般会用循环除10来计算长度,不众所周知,JDK源码是非常讲究效率的,它直接保存了在 MAX 范围内的所有每个长度最大值,直接依次比较,十分高效。
final static int [] sizeTable = { 9, 99, 999, 9999,
99999, 999999, 9999999,
99999999, 999999999, Integer.MAX_VALUE };
static int stringSize(int x) {
for (int i=0; ; i++)
if (x <= sizeTable[i])
return i+1;
}
总之对于我们来说,java 中的每一个基础类我们都应把它学习透彻,理解它的执行原理,尽量对源码中的关键方法都能掌握,这样在我们的编程生涯中,才能做到游刃有余,不犯一些低级错误。
同时,jdk 的源码都是十分优秀,高效的,在 jdk 版本不断迭代的过程中,很多关键类的代码都会进行优化和调整,确保程序的高效执行。
文章到这里就结束啦,我还是大学生哦,喜欢学习的小伙伴可以评论交流,或者加关注,一起学习更轻松。
素质三连。。。