当前位置: 首页 > 图灵资讯 > 技术篇> 当涉及到基本数据类型和包装类时,一些你需要了解、可能容易被忽略的细节。(附面试题)

当涉及到基本数据类型和包装类时,一些你需要了解、可能容易被忽略的细节。(附面试题)

来源:图灵教育
时间:2023-05-28 09:35:11

基本数据类型

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

字符型

\u00000

'\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 定义泛型,代码:

List<Integer> list = 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,trueB:true,falseC:false,trueD: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:2147483648B:-2147483648C:程序报错D:以上都不是

答:B

主题分析:这是因为整数在内存中以补码的形式表示,最高位是符号位 0 表示正数,1 当执行时,表示负数 +1 时间,最高位变成了 1.结果成了 -2147483648。

5. 以下程序执行的结果是什么?

Set<Short> set = new HashSet<>();for (short i = 0; i < 5; i++) {    set.add(i);    set.remove(i - 1);}System.out.println(set.size());

A:1B:0C:5D:以上都不是

答: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 类型,所以会报错。如下图所示:

当涉及到基本数据类型和包装类时,一些你需要了解、可能容易被忽略的细节。(附面试题)_Java_02

8. 为什么需要包装?

答:包装有两个原因。

① Java 设计理念是万物都是对象,包装类体现了面向对象的设计理念;② 包装类包含许多属性和方法,比基本数据类型功能更多,如获取哈希值(hashCode)或获取类(getClass)的方法等。

9. 基本类 int 和包装类 Integer,在 -128~127 现有的缓存对象会在两者之间重用吗?这是正确的吗?

答:不正确,只有包装高频区域数据才有缓存。

10. 包装类 Double 和 Integer 都有高频区域数据缓存,这是正确的吗?

答:不正确,基本数据类型的包装只有 Double 和 Float 高频区域没有缓存。

11. 用什么方法比较包装值?

答:由于高频区域数据缓存,建议使用包装类 equals() 该方法比较了值。

12. 包装有哪些功能?

答:包装类提供以下功能。

  • 功能丰富:包装包含 hashCode、getClass 、max、min 等方法;
  • 一般类型参数可以定义:例如 List<Integer> list = new ArrayList<>(); ;
  • 序列化:包装类实现了 Serializable 因此,包装类自然支持序列化和反序列化;
  • 类型转换:包装类型提供了方便的类型转换方法,如 Integer 的 parseInt() 方法;
  • 现有的缓存对象可用于高频区域数据缓存。

详见正文“包装类型”部分。

13. 泛型可以是基本类型吗?为什么?

答:基本数据类型不能用于泛型。泛型在 JVM(Java虚拟机)编译时会删除类型,如代码 List<Integer> list 在 JVM 编译时会转换成 List list ,因为泛型是在 JDK 5 时间提供,而 JVM 类型擦除是一种与之前代码兼容的折中方案,类型擦除后变成 Object,而 Object 基本数据类型不能存储,但基本数据类型对应的包装类型可以使用,因此类似 List<int> list 这样的代码是不允许的,编译阶段会检查报错,但是 List<Integer> 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,falseB:false,false,trueC:false,true,trueD:true,false,false

答:B

题目解析:new Integer(10) 每次都会创造新的对象,Integer.valueOf(10) 缓存池中的物体将被使用。

17. 3*0.1==0.3 返回值是多少?

答:返回值如下:false。

主题分析:由于有些浮点数不能完全准确地表示,如下代码:

System.out.println(3 * 0.1);

返回的结果是:0.3万万万万万万万万万四。