你不知道的java基本数据类型和包装类(java面试题)-mile米乐体育
基本数据类型
java 基本数据按类型可以分为四大类:布尔型、整数型、浮点型、字符型,这四大类包含 8 种基本数据类型。
布尔型:boolean
整数型:byte、short、int、long
浮点型:float、double
字符型:char
8 种基本类型取值如下:
数据类型 | 代表含义 | 默认值 | 取值 | 包装类 |
---|---|---|---|---|
boolean | 布尔型 | false | 0(false) 到 1(true) | boolean |
byte | 字节型 | (byte)0 | ﹣128 到 127 | byte |
char | 字符型 | '\u0000'(空) | '\u0000' 到 '\uffff' | character |
short | 短整数型 | (short)0 | -215215 到 215215-1 | short |
int | 整数型 | 0 | ﹣231231 到 231231-1 | integer |
long | 长整数型 | 0l | ﹣263263 到 263263-1 | long |
float | 单浮点型 | 0.0f | 1.4e-45 到 3.4e 38 | float |
double | 双浮点型 | 0.0d | 4.9e-324 到 1.798e 308 | double |
除 char 的包装类 character 和 int 的包装类 integer 之外,其他基本数据类型的包装类只需要首字母大写即可。包装类的作用和特点,本文下半部分详细讲解。
我们可以在代码中,查看某种类型的取值范围,代码如下:
public static void main(string[] args) { // byte 取值:-128 ~ 127 system.out.println(string.format("byte 取值:%d ~ %d", byte.min_value, byte.max_value)); // int 取值:-2147483648 ~ 2147483647 system.out.println(string.format("int 取值:%d ~ %d", integer.min_value, integer.max_value)); }
包装类型
我们知道 8 种基本数据类型都有其对应的包装类,因为 java 的设计思想是万物既对象,有很多时候我们需要以对象的形式操作某项功能,比如说获取哈希值(hashcode)或获取类(getclass)等。
那包装类特性有哪些?
1. 功能丰富
包装类本质上是一个对象,对象就包含有属性和方法,比如 hashcode、getclass 、max、min 等。
2. 可定义泛型类型参数
包装类可以定义泛型,而基本类型不行。
比如使用 integer 定义泛型,代码:
listlist = new arraylist<>();
如果使用 int 定义就会报错,代码:
list list = new arraylist<>(); // 编译器代码报错
3. 序列化
因为包装类都实现了 serializable 接口,所以包装类天然支持序列化和反序列化。比如 integer 的类图如下:
4. 类型转换
包装类提供了类型转换的方法,可以很方便的实现类型之间的转换,比如 integer 类型转换代码:
string age = "18";int ageint = integer.parseint(age) 2;// 输出结果:20system.out.println(ageint);
5. 高频区间的数据缓存
此特性为包装类很重要的用途之一,用于高频区间的数据缓存,以 integer 为例来说,在数值区间为 -128~127 时,会直接复用已有对象,在这区间之外的数字才会在堆上产生。
我们使用 == 对 integer 进行验证,代码如下:
public static void main(string[] args) { // integer 高频区缓存范围 -128~127 integer num1 = 127; integer num2 = 127; // integer 取值 127 == 结果为 true(值127 num1==num2 => true) system.out.println("值127 num1==num2 => " (num1 == num2)); integer num3 = 128; integer num4 = 128; // integer 取值 128 == 结果为 false(值128 num3==num4 => false) system.out.println("值128 num3==num4 => " (num3 == num4)); }
从上面的代码很明显可以看出,integer 为 127 时复用了已有对象,当值为 128 时,重新在堆上生成了新对象。
为什么会产生高频区域数据缓存?我们查看源码就能发现“线索”,源码版本 jdk8,源码如下:
public static integer valueof(int i) { if (i >= integercache.low && i <= integercache.high) return integercache.cache[i (-integercache.low)]; return new integer(i); }
由此可见,高频区域的数值会直接使用已有对象,非高频区域的数值会重新 new 一个新的对象。
各包装类高频区域的取值范围:
boolean:使用静态 final 定义,就会返回静态值
byte:缓存区 -128~127
short:缓存区 -128~127
character:缓存区 0~127
long:缓存区 -128~127
integer:缓存区 -128~127
包装类的注意事项
int 的默认值是 0,而 integer 的默认值是 null。
推荐所有包装类对象之间的值比较使用
equals()
方法,因为包装类的非高频区数据会在堆上产生,而高频区又会复用已有对象,这样会导致同样的代码,因为取值的不同,而产生两种截然不同的结果。代码示例:
public static void main(string[] args) { // integer 高频区缓存范围 -128~127 integer num1 = 127; integer num2 = 127; // integer 取值 127 == 结果为 true(值127 num1==num2 => true) system.out.println("值127 num1==num2 => " (num1 == num2)); integer num3 = 128; integer num4 = 128; // integer 取值 128 == 结果为 false(值128 num3==num4 => false) system.out.println("值128 num3==num4 => " (num3 == num4)); // integer 取值 128 equals 结果为 true(值128 num3.equals(num4) => true) system.out.println("值128 num3.equals(num4) => " num3.equals(num4)); }
float 和 double 不会有缓存,其他包装类都有缓存。
integer 是唯一一个可以修改缓存范围的包装类,在 vm optons 加入参数:
-xx:autoboxcachemax=666 即修改缓存最大值为
666
。
示例代码如下:
public static void main(string[] args) { integer num1 = -128; integer num2 = -128; system.out.println("值为-128 => " (num1 == num2)); integer num3 = 666; integer num4 = 666; system.out.println("值为666 => " (num3 == num4)); integer num5 = 667; integer num6 = 667; system.out.println("值为667 => " (num5 == num6)); }
执行结果如下:
值为-128 => true值为666 => true值为667 => false
由此可见将 integer 最大缓存修改为 666 之后,667 不会被缓存,而 -128~666 之间的数都被缓存了。
相关面试题
1. 以下 integer 代码输出的结果是?
integer age = 10; integer age2 = 10; integer age3 = 133; integer age4 = 133; system.out.println((age == age2) "," (age3 == age4));
答:true,false
2. 以下 double 代码输出的结果是?
double num = 10d; double num2 = 10d; double num3 = 133d; double num4 = 133d; system.out.println((num == num2) "," (num3 == num4));
答:false,false
3. 以下程序输出结果是?
int i = 100; integer j = new integer(100); system.out.println(i == j); system.out.println(j.equals(i));
a:true,true
b:true,false
c:false,true
d:false,false
答:a
题目分析:有人认为这和 integer 高速缓存有关系,但你发现把值改为 10000 结果也是 true,true
,这是因为 integer 和 int 比较时,会自动拆箱为 int 相当于两个 int 比较,值一定是 true,true
。
4. 以下程序执行的结果是?
final int imax = integer.max_value; system.out.println(imax 1);
a:2147483648
b:-2147483648
c:程序报错
d:以上都不是
答:b
题目解析:这是因为整数在内存中使用的是补码的形式表示,最高位是符号位 0 表示正数,1 表示负数,当执行 1 时,最高位就变成了 1,结果就成了 -2147483648。
5. 以下程序执行的结果是?
setset = new hashset<>();for (short i = 0; i < 5; i ) { set.add(i); set.remove(i - 1); } system.out.println(set.size());
a:1
b:0
c:5
d:以上都不是
答:5
题目解析:short 类型 -1 之后转换成了 int 类型,remove() 的时候在集合中找不到 int 类型的数据,所以就没有删除任何元素,执行的结果就是 5。
6. short s=2;s=s 1;
会报错吗?short s=2;s =1;
会报错吗?
答:s=s 1 会报错,s =1 不会报错,因为 s=s 1 会导致 short 类型升级为 int 类型,所以会报错,而 s =1 还是原来的 short 类型,所以不会报错。
7. float f=3.4;
会报错吗?为什么?
答:会报错,因为值 3.4 是 double 类型,float 类型级别小于 double 类型,所以会报错。如下图所示:
8. 为什么需要包装类?
答:需要包装类的原因有两个。
① java 的设计思想是万物既对象,包装类体现了面向对象的设计理念;
② 包装类包含了很多属性和方法,比基本数据类型功能多,比如提供的获取哈希值(hashcode)或获取类(getclass)的方法等。
9. 基本类 int 和包装类 integer,在 -128~127 之间都会复用已有的缓存对象,这种说法正确吗?
答:不正确,只有包装类高频区域数据才有缓存。
10. 包装类 double 和 integer 一样都有高频区域数据缓存,这种说法正确吗?
答:不正确,基本数据类型的包装类只有 double 和 float 没有高频区域的缓存。
11. 包装类的值比较要使用什么方法?
答:包装类因为有高频区域数据缓存,所以推荐使用 equals() 方法进行值比较。
12. 包装类有哪些功能?
答:包装类提供的功能有以下几个。
功能丰富:包装类包含了有 hashcode、getclass 、max、min 等方法;
可定义泛型类型参数:例如
list
;list = new arraylist<>(); 序列化:包装类实现了 serializable 接口,所以包装类天然支持序列化和反序列化;
类型转换:包装类提供了方便的类型转换方法,比如 integer 的 parseint() 方法;
高频区域数据缓存:高频区域可使用已有的缓存对象。
详见正文“包装类型”部分内容。
13. 泛型可以为基本类型吗?为什么?
答:泛型不能使用基本数据类型。泛型在 jvm(java虚拟机)编译的时候会类型檫除,比如代码 list
在 jvm 编译的时候会转换为 list list
,因为泛型是在 jdk 5 时提供的,而 jvm 的类型檫除是为了兼容以前代码的一个折中方案,类型檫除之后就变成了 object,而 object 不能存储基本数据类型,但可以使用基本数据类型对应的包装类,所以像 list
这样的代码是不被允许的,编译器阶段会检查报错,而 list
是被允许的。
14. 选择包装类还是基本类的原则有哪些?
答:我们知道正确的使用包装类,可以提供程序的执行效率,可以使用已有的缓存,一般情况下选择基本数据类型还是包装类原则有以下几个。
① 所有 pojo 类属性必须使用包装类;
② rpc 方法返回值和参数必须使用包装类;
③ 所有局部变量推荐使用基本数据类型。
15. 基本数据类型在 jvm 中一定存储在栈中吗?为什么?
答:基本数据类型不一定存储在栈中,因为基本类型的存储位置取决于声明的作用域,来看具体的解释。
当基本数据类型为局部变量的时候,比如在方法中声明的变量,则存放在方法栈中的,当方法结束系统会释放方法栈,在该方法中的变量也会随着栈的销毁而结束,这也是局部变量只能在方法中使用的原因;
当基本数据类型为全局变量的时候,比如类中的声明的变量,则存储在堆上,因为全局变量不会随着某个方法的执行结束而销毁。
16. 以下程序执行的结果是?
integer i1 = new integer(10); integer i2 = new integer(10); integer i3 = integer.valueof(10); integer i4 = integer.valueof(10); system.out.println(i1 == i2); system.out.println(i2 == i3); system.out.println(i3 == i4);
a:false,false,false
b:false,false,true
c:false,true,true
d:true,false,false
答:b
题目解析:new integer(10) 每次都会创建一个新对象,integer.valueof(10) 则会使用缓存池中的对象。
17. 3*0.1==0.3 返回值是多少?
答:返回值为:false。
题目解析:因为有些浮点数不能完全精确的表示出来,如下代码:
system.out.println(3 * 0.1);
返回的结果是:0.30000000000000004。