1. 包装类是什么
- Java 有 8 种基本数据类型,每种基本类型都有一个对应的包装类
- 包装类是一个类,内部有一个实例变量,保存着对应的基本类型的值,这个类一般还有一些静态方法、静态变量和实例方法,以方便对数据进行操作
- 除了
Integer
和Character
,其他包装类的类名称与基本类型基本一样,只是首字母大写
2. 包装类有什么用
- Java 中很多代码(比如容器类)只能操作对象,为了能操作基本数据类型,需要使用其对应的包装类
- 包装类提供了很多有用的方法,可以方便对数据操作
3. 包装类与基本类型怎样互相转换
- 每种包装类都有一个静态方法
valueOf()
,接收基本类型,返回引用类型 - 也都有一个实例方法
xxxValue()
,返回对应的基本类型
4. 怎样理解自动装箱和自动拆箱
- 装箱:将基本类型转换为包装类的过程
- 拆箱:将包装类型转换为基本类型的过程
- 装箱和拆箱写起来比较繁琐,Java 5 以后引入了自动装箱和自动拆箱技术,可以直接将基本类型赋值给引用类型,反之亦可
- 自动装箱/自动拆箱是 Java 编译器提供的能力
- 每种包装类也都有构造方法,可以通过
new
创建(不推荐)
5. 创建包装类对象,应该用静态的 valueOf()
方法还是使用 new
呢
- 一般建议使用静态
valueOf()
方法 new
每次都会创建一个新对象,而除了Float
和Double
外的其他包装类,都会缓存包装类对象,减少需要创建对象的次数,节省空间、提升性能- 实际上,从 Java 9 开始,这些构造方法已经被标记为过时了,推荐使用静态的
valueOf()
方法
6. 所有包装类都重写了 Object
类的哪些方法
boolean equals(Object obj)
、int hashCode()
、String toString()
7. 怎样理解 equals()
方法
equals()
用于判断当前对象和参数传入的对象是否相同Object
类的默认实现是比较地址,对于两个变量,只有这两个变量指向同一个对象时,equals()
才返回true
,它和比较运算符==
的结果是一样的equals()
应该反映的是对象间的逻辑相等关系,所以Object
类的默认实现一般是不合适的,子类经常需要重写该实现以实现自己期望的相等判断逻辑- 所有包装类都重写了该实现,实际比较用的是其包装类的基本类型值(看源码)
8. 怎样理解 hashCode()
方法
hashCode()
返回一个对象的哈希值,在Object
类中用native
修饰是一个本地方法、没有给出具体的实现- 哈希值是一个
int
类型的数,由对象中一般不变的属性映射得来,用于快速对对象进行区分、分组等 - 一个对象的哈希值不能改变,相同对象的哈希值必须一样,反之不一定
9. hashCode()
方法和 equals()
方法的关系是
- 二者联系密切,对两个对象,如果
equals()
方法返回true
,则hashCode
也必须一样,反之不要求。equals()
方法返回false
时,hashCode
可以一样,也可以不一样,但应该尽量不一样 hashCode()
的默认实现一般是将对象的内存地址转换为整数,子类如果重写了equals()
方法,也必须重写hashCode()
方法(否则和依赖与hash
的集合类,比如HashMap
、HashSet
和HashTable
等,一起使用时就会出错)- 之所以有这个规定,是因为 Java API 中很多类依赖于这个行为,尤其是容器中的一些类
- 参考:为什么要重写 hashcode 和 equals 方法
10. 对于包装类 Boolean
,其 hashCode()
代码为:public int hashCode() { return value ? 1231 : 1237; }
根据基础类型值返回了两个不同的数,为什么选这两个值
- 因为这两个数是质数(只能被 1 和自身整除),质数用于哈希时比较好,不容易冲突
11. Comparable
接口代码是怎样的,包装类是怎样应用的
Comparable
接口代码1
2
3public interface Comparable<T> {
public int compareTo(T o);
}每个包装类都实现了
Comparable
接口,接口只有一个方法:compareTo(T o)
,表示当前对象与参数对象进行比较,在小于、等于、大于参数时,应分别返回-1
、0
、1
各个包装类的实现基本都是根据基本类型值进行比较
对于
Boolean
,false
小于true
对于
Float
和Double
,存在和equals()
方法一样的问题,0.01 和 0.1 * 0.1 相比的结果并不为 0(因为计算机本身就不能精确计算很多数,这是二进制的局限,但肯定比十进制优越得多)
12. 包装类中常用常量有哪些
- 包装类中除了定义静态方法和实例方法外,还定义了一些静态变量,对
Boolean
来说,有public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
- 所有数值类型都定义了
MAX_VALUE
和MIN_VALUE
,表示能表示的最大值和最小值。比如,对Integer
,有public static final int MAX_VALUE = 0x7fffffff;
public static final int MIN_VALUE = 0x80000000;
Float
和Double
还定义了一些特殊数值,比如正无穷、负无穷、非数值。比如,对于Double
,有public static final double POSITIVE_INFINITY = 1.0 / 0.0; //正无穷
public static final double NEGATIVE_INFINITY = -1.0 / 0.0; //负无穷
public static final double NaN = 0.0 d / 0.0; //非数值(0 除以 0,或者 0 乘以无穷大)
13. 怎样理解 Number
类
Number
类是一个抽象类Number
类是 6 种数值类型包装类的父类
14. 怎样理解包装类的不可变性
- 包装类都是不可变类
- 所谓不可变:指实例对象一旦创建,就没有办法修改了
15. 包装类的不可变性是怎样实现的
- 是通过下面三种方式强制实现的
- 所有包装类都声明为了
final
,不能被继承 - 内部基本类型值是私有的,且声明为了
final
- 没有定义
setter
方法
- 所有包装类都声明为了
16. 为什么要把包装类定义成不可变类
- 不可变使得程序更为简单安全,因为不用操心数据被意外改写的可能
- 可以安全地共享数据,尤其是在多线程的环境下
17. 怎样理解 Integer
类的位翻转二进制操作
- 位翻转:将
int
当做二进制,左边的位与右边的位进行互换 Integer
有两个静态方法public static int reverse(int i); //按位进行互换
public static int reverseBytes(int i); //按字节进行互换
reverseBytes(int i)
方法内部实现:通过左移、右移、与操作、或操作,达到字节翻转的目的
18. 怎样理解 reverse(int i)
方法源码
- 代码第一行是一个注释:// HD,Figure 7-1
- HD 表示的是一本书,书名为《Hacker’s Delight》,中文版为《算法心得:高效算法的奥秘》(这翻译的中文名真不geek)
- HD 是这本书的缩写,Figure 7-1 是书中的图 7-1,
reverse(int i)
的代码就是复制了这本书中图 7-1 的代码。
- 高效实现位翻转的基本思路:首先交换相邻的单一位,然后以两位为一组,再交换相邻的位,接着是 4 位一组、然后是 8 位、16位,16位之后就完成了。这个思路不仅适用于二进制,而且适用于十进制
19. reverse(int i)
代码为什么要写得这么晦涩,或者说不能用更容易理解的方式写吗。比如,实现翻转,一种常见的思路是:第一个和最后一个交换,第二个和倒数第二个交换,直到中间两个交换完成
- 如果数据不是二进制,这个思路是好的,但对于二进制,这个思路实现的效率比较低
- CPU 指令并不能高效地操作单个位,它操作的最小数据单位一般是 32 位(32位机器)。另外,CPU 可以高效地实现移位和逻辑运算,但实现加、减、乘、除运算则比较慢
reverse(int i)
是在充分利用 CPU 的这些特性,并行高效地进行相邻位的交换。也可以通过其他更容易理解的方式实现相同功能,但很难比这个代码更高效
20. 怎样理解 valueOf()
方法的实现
- 方法内部使用了
IntegerCache
,这是一个私有静态内部类 IntegerCache
表示Integer
缓存,其中的cache
变量是一个静态的Integer
数组,在静态初始化代码块中被初始化。默认情况下,保存了 -128 ~ 127 共 256 个整数对应的Integer
对象- 在
valueOf()
代码中,如果数值位于被缓存的范围,即默认 -128 ~ 127,则直接从IntegerCache
中获取已预先创建的Integer
对象。因为这个范围的数字使用频率较高,只有不在缓存范围时,才通过new
创建对象 - 通过共享常用对象,可以节省内存空间,由于
Integer
是不可变的,所以缓存的对象可以安全地被共享。Boolean
、Byte
、Short
、Long
、Character
都有类似的实现 - 这种共享常用对象的思路,是一种常见的设计模式,叫享元模式。英文叫
Flyweight
,即共享的轻量级元素
21. 怎样理解 Unicode
- Unicode 给世界上每个字符都分配了一个编号,编号范围为:0x000000 ~ 0x10FFFF
- 常用字符集(BMP: Basic Multilingual Plane):编号范围在 0x0000 ~ 0xFFFF 的字符
- 增补字符(supplementary character):编号范围在 0x10000 ~ 0x10FFFF 的字符
- Unicode 主要规定了编号,但没有规定如何把编号映射为二进制
- UTF-16 是一种编码方式,或者叫映射方式,它将编号映射为 2 个或 4 个字节。对 BMP 字符,它直接用两个字节表示,对于增补字符,使用 4 个字符表示
- 前两个字节叫高代理项(high surrogate),范围为 0xD800 ~ 0xDBFF;后两个字节叫低代理项(low surrogate),范围为 0xDC00 ~ 0xDFFF
- UTF-16 定义了一个公式,可以将编号与 4 字节表示进行相互转换
- Java 内部采用 UTF-16 编码,
char
表示一个字符,但只能表示 BMP 中的字符,对于增补字符,需要使用两个char
表示,一个表示高代理项,一个表示低代理项- 使用
int
可以表示任意一个 Unicode 字符,低 21 位表示 Unicode 编号,高 11 位设为 0 - 整数编号在 Unicode 中一般称为代码点(code point),表示一个 Unicode 字符;与之相对,还有一个词代码单元(code unit)表示一个
char
- 使用
- Unicode 在给每个字符分配一个编号之外,还分配了一些属性。
Character
类封装了对 Unicode 字符属性的检查和操作。Unicode 给每个字符分配了一个类型- 需要注意的是,不光字符
'0'
、'1'
、……
、'9'
是数字,中文全角字符的 0 ~ 9 也是数字 - 更常用的检查空格的方法:
pubilc static boolean isWhitespace(int codepoint)
,不仅能匹配空格字符本身,还能匹配实际产生空格效果的字符,如 Tab 控制键'\t'
、'\n'
、全角空格' '
和 半角空格' '
的返回值都为true
- 需要注意的是,不光字符
22. 怎样理解 Java 标识符
- Java 标识符:是 Java 中的变量名、方法名、类名等
- 字母(Alphabetic)、美元符号($)、下划线(_)可以作为 Java 标识符的第一个字符,但数字字符不可以
- Java 标识符的中间字符可以包含数字
23. Character
类的作用
Character
有很多静态方法,封装了 Unicode 字符级别(而非char
级别)的各种操作,是 Java 文本处理的基础- 通过将字符处理的细节交给
Character
类,其他类就可以在更高的层次上处理文本了