Java基础面试:数据类型

文摘   2024-11-23 09:00   山东  

Java 中的数据类型

Java 有八种基本数据类型:byte,short,int,long,float,double,char,boolean

八种基本数据类型

类型
位数
字节
默认值
取值范围
包装类型
byte
8
1
0
-128 到 127
Byte
short
16
2
0
-32,768 到 32,767
Short
int
32
4
0
-2,147,483,648 到 2,147,483,647
Integer
long
64
8
0L
-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
Long
float
32
4
0.0f
±1.4E-45 到 ±3.4E+38
Float
double
64
8
0.0d
±4.9E-324 到 ±1.8E+308
Double
char
16
2
\u0000\u0000
\uffff (0~65535)
Character
boolean
1
1
falsetrue
false
Boolean

基本类型与包装类型的区别

Java 是面向对象的语言,在Java 中,一切皆对象,而八种基本数据类型却不是对象,比如我们想操作一个数字的集合,但是在Java 中集合只能存放对象而不能存储基本数据类型,存储基本数据类型只能用数组,但是数组却没有集合更丰富的API,怎么办呢?这时使用基本数据类型对应的包装类就好了,完美地解决了这个问题。那么基本类型与包装类型究竟有哪些区别呢?

默认值:

  • 基本类型:在声明但未初始化时,它们会有默认值,默认值如上述表中所示;在做局部变量时,必须初始化才能使用;

  • 包装类型:任何未初始化的包装类型变量的默认值为null

声明方式:

  • 基本类型:基本类型可以直接声明并赋值,比如:int a = 10

  • 包装类型:包装类型可以通过new 关键字创建对象,如Integer a = new Integer(10),也可以通过自动装箱创建,如Integer a = 10;

存储方式:

  • 基本类型:它们直接在栈内存中分配空间,存储的是具体的值;

  • 包装类型:包装类作为类对象在堆内存中分配空间,引用变量存储在栈中,但引用指向堆中的对象;

使用场景:

  • 基本类型:适用于简单的数值计算和逻辑判断;在性能敏感的场景中,基本类型通常更高效,因为它们直接存储值,没有额外的对象开销;

  • 包装类型:提供丰富的API,如方法调用、类型转换和空值处理,更面相对象,适用于需要对象功能的场景,如使用集合框架等。

包装类型的缓存机制

Java 中,包装类实际上是对基本数据类型的封装,每个包装类都包含了对应的基本数据类型的值,以及其他的一些方法(如类型转换、比较等)。为了提高性能,部分包装类型(如IntegerByteShortCharacterLongBoolean)使用了缓存机制。这种缓存机制可以减少对象的创建次数,提高程序的运行效率。而浮点数类型的包装类(Float 和 Double)并没有实现缓存机制,主要是因为浮点数的表示范围非常大,且使用场景多样,缓存效果并不明显。

实现原理

本文以Integer 为例进行分析,Integer 类提供了一个静态内部类IntegerCache,用于缓存一定范围内的Integer 对象。默认情况下,这个范围是从 -128 到 127。这个范围可以通过系统属性java.lang.Integer.IntegerCache.high 来调整。Integer.valueOf(int i) 方法是推荐的装箱方法,因为它会利用缓存机制,当调用IntegervalueO(int i) 方法时,会先检查传入的数值是否在缓存范围内,如果是,则直接从缓存中返回对应的对象;否则,会创建一个新的对象并返回。Integer 的缓存核心源码如下:

public static Integer valueOf(int i) {
   // 判断传入的数值是否在 IntegerCache 的缓存范围内
   if (i >= IntegerCache.low && i <= IntegerCache.high)
       // 命中缓存直接取缓存值返回
       return IntegerCache.cache[i + (-IntegerCache.low)];
   // 否则创建一个新的对象返回
   return new Integer(i);
}

// 静态内部类 IntegerCache
private static class IntegerCache {
   static final int low = -128// 低值默认-128
   static final int high;
   static final Integer cache[]; // 缓存池

   static {
       // 高值可按属性配置,默认127
       int h = 127;
       String integerCacheHighPropValue =
           sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
       if (integerCacheHighPropValue != null) {
           try {
               int i = parseInt(integerCacheHighPropValue);
               i = Math.max(i, 127);
               // 缓存池最大数组大小为 Integer.MAX_VALUE
               h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
           } catch( NumberFormatException nfe) {
               // 处理异常情况,如果属性设置的值无法解析为 int,忽略。
           }
       }
       high = h;

       cache = new Integer[(high - low) + 1];
       int j = low;
       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() {}
}

最佳实践

缓存范围限制:缓存范围默认为-128 到 127,超出这个范围的值不会被缓存。因此,在比较两个Integer 对象时,要特别小心,应该使用equals() 方法而不是== 运算符,因为它们可能指向不同的对象。

系统属性调整:虽然可以通过系统属性java.lang.Integer.IntegerCache.high 来调整缓存的上限,但是并不推荐这样做,因为过大的缓存范围会占用更多的内存资源,并且会影响性能。

使用valueOf 方法:推荐使用Integer.valueOf(int i) 方法来进行装箱操作,而不是使用new Integer(int i),因为valueOf() 方法会利用缓存机制,从而提高性能和减少内存消耗。

阿里开发手册中要求:

【强制】所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
说明:对于 Integer var = ? 在 -128 至 127 之间的赋值,Integer 对象是在 IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用 == 进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。

什么是自动拆装箱

自动和拆装箱是Java 5.0 引入了一个特性,它允许在基本数据类型和它们的包装类之间进行自动转换,无需显式地进行转换。但是,这种自动转换可能会带来性能开销,特别是在涉及大量数据或循环时。

什么是装箱(Boxing)

装箱是指将基本数据类型转换为对应的包装类对象的过程。例如,将一个int 值转换成一个Integer 对象。在早期版本的Java 中,这个过程需要显式地创建一个新的包装类对象来完成,如下所示:

int num = 10;
Integer numObj = new Integer(num); // 显式装箱

Java 5.0 开始,引入了自动装箱的概念,上述代码可以简化为:

Integer num = 10// 自动装箱

什么是拆箱(Unboxing)

拆箱则是指将包装类对象转换回基本数据类型的过程。例如,将一个Integer 对象转换为int 值。同样地,在Java 5.0 之前,这需要显式地调用方法来实现:

Integer numObj = new Integer(10);
int num = numObj.intValue(); // 显式拆箱

在支持自动拆箱的 Java 版本中,可以简化为:

int num = new Integer(10); // 自动拆箱

应用场景示例

自动拆装箱在很多场景下都非常有用,尤其是在集合框架中,因为集合只能存储对象引用而不能直接存储基本数据类型。例如,使用ArrayList<Integer> 时,添加元素时会自动装箱,获取元素时则会自动拆箱:

List<Integer> list = new ArrayList<>();
list.add(10); // 自动装箱
int value = list.get(0); // 自动拆箱

最佳实践

避免不必要的装箱:虽然自动拆装箱简化了代码,但它并不是没有代价的。每次装箱操作都会创建一个新的对象,这可能导致内存消耗增加以及垃圾回收(GC)的压力加大;

注意NULL 值:在处理可能为null 的包装类对象时,确保先检查null 以避免(NPE)空指针异常;

性能敏感慎用:在循环内或者性能要求较高的模块中,应尽量避免使用自动拆装箱,以减少性能开销。

阿里面试题

public class IntegerCacheDemo {
   public static void main(String[] args) {
       Integer a = 100;
       Integer b = 100;
       Integer c = 200;
       Integer d = 200;

       System.out.println(a == b); // true,因为100在缓存范围内
       System.out.println(c == d); // false,因为200不在缓存范围内

       // 使用new关键字创建对象,总是会创建新的对象
       Integer e = new Integer(100);
       Integer f = new Integer(100);
       System.out.println(e == f); // false
   }
}

为什么a == b 的结果为true ?为什么c == d 的结果为false?为什么e == f的结果为false

想必此刻已经了然于胸了吧!



【Java驿站】持续给大家更新


扫描下方二维码


关注【Java驿站】公众号


👇👇👇


Java驿站
这里是【Java驿站】,一个Java编程学习与交流平台。
 最新文章