Bootstrap

每日 Java 面试题分享【第 15 天】

欢迎来到每日 Java 面试题分享栏目!
订阅专栏,不错过每一天的练习

今日分享 3 道面试题目!

评论区复述一遍印象更深刻噢~

目录

  • 问题一:Java 中的基本数据类型有哪些?
  • 问题二:什么是 Java 中的自动装箱和拆箱?
  • 问题三:什么是 Java 中的迭代器(Iterator)?

问题一:Java 中的基本数据类型有哪些?

Java 中的基本数据类型(Primitive Data Types)是 Java 提供的最基本的数据存储单元,它们直接存储数据值,而不是对象引用。Java 的基本数据类型分为 四类八种


四类基本数据类型

  1. 整型(Integer Types)

    • byte:一个字节,取值范围为 -128 ~ 127。
    • short:两个字节,取值范围为 -32,768 ~ 32,767。
    • int:四个字节,取值范围为 -2,147,483,648 ~ 2,147,483,647。
    • long:八个字节,取值范围为 -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807。
      • 使用 L 标记 long 类型字面值,例如:long l = 123456789L;
  2. 浮点型(Floating-Point Types)

    • float:四个字节,单精度,精度约为 6~7 位有效小数。
      • 使用 F 标记 float 类型字面值,例如:float f = 3.14F;
    • double:八个字节,双精度,精度约为 15~16 位有效小数。
      • 默认小数类型为 double,例如:double d = 3.141592653589793;
  3. 字符型(Character Type)

    • char:两个字节,存储单个 Unicode 字符(取值范围为 0 ~ 65,535)。
      • 字符使用单引号表示,例如:char c = 'A';
      • 支持转义字符,例如:char newline = '\n';
  4. 布尔型(Boolean Type)

    • boolean:只能存储两个值,truefalse
      • 占用空间依赖于 JVM 实现,通常用 1 位表示。

基本数据类型及其对应的字节大小与默认值

数据类型字节大小默认值取值范围
byte1 字节0-128 ~ 127
short2 字节0-32,768 ~ 32,767
int4 字节0-2,147,483,648 ~ 2,147,483,647
long8 字节0L-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
float4 字节0.0f~ ±3.40282347E+38F (~ 6-7 位小数)
double8 字节0.0d~ ±1.79769313486231570E+308 (~ 15-16 位小数)
char2 字节‘\u0000’0 ~ 65,535(Unicode 范围)
booleanJVM 定义falsetrue 或 false

基本数据类型的特点

  1. 值存储在栈中

    • 基本数据类型直接存储值,性能更高。
  2. 不属于对象

    • 基本数据类型不是对象,没有内置的方法。
    • 可以通过对应的包装类(如 IntegerDouble)将其包装为对象。
  3. 不可为空

    • 基本数据类型不能存储 null,但其包装类可以。

自动装箱与拆箱

在 Java 中,基本数据类型与其对应的包装类之间可以自动转换:

  • 装箱(Boxing):将基本数据类型转换为对应的包装类对象。
  • 拆箱(Unboxing):将包装类对象转换为基本数据类型。

示例:

int a = 10;
Integer boxed = a;  // 自动装箱
int unboxed = boxed; // 自动拆箱

扩展:常见问题与注意事项

  1. 为什么使用基本数据类型?

    • 性能高,内存占用少,适合处理大量数据的场景。
  2. 基本数据类型与包装类的区别

    • 基本数据类型:存储值,效率高。
    • 包装类:存储对象引用,适合与集合类(如 List)搭配使用。
  3. 浮点数计算不精确问题

    • 浮点数(floatdouble)是基于二进制存储的,可能会导致精度丢失。
    • 解决方案:使用 BigDecimal 处理精确计算。
  4. 字符类型与编码

    • Java 的 char 是 Unicode 字符,支持国际化,但与 ASCII 编码可能不一致。

总结

  • Java 的基本数据类型为四类八种:整型(byte、short、int、long)、浮点型(float、double)、字符型(char)、布尔型(boolean)。
  • 基本数据类型提供了高效的内存使用和数据存储方式,是 Java 核心语言特性的重要组成部分。
  • 对于需要对象的场景,可以使用对应的包装类,结合自动装箱与拆箱提升开发效率。

问题二:什么是 Java 中的自动装箱和拆箱?

定义

  • 自动装箱(Auto-Boxing):将 基本数据类型 自动转换为其对应的 包装类对象 的过程。
  • 自动拆箱(Auto-Unboxing):将 包装类对象 自动转换为其对应的 基本数据类型 的过程。

这是 Java 1.5(JDK 5)引入的一个特性,用于简化基本数据类型和包装类之间的转换。


为什么需要自动装箱和拆箱?

  1. 简化代码:在 JDK 1.5 之前,基本数据类型与包装类的转换需要手动完成。自动装箱和拆箱消除了显式转换代码,使代码更简洁。

  2. 支持泛型和集合框架:Java 集合框架(如 ListMap)只能存储对象,自动装箱使得基本数据类型也可以轻松存储到集合中。


代码示例

public class AutoBoxingUnboxingExample {
    public static void main(String[] args) {
        // 自动装箱
        Integer boxed = 10;  // 基本数据类型 int 自动装箱为包装类 Integer
        System.out.println("装箱后的对象: " + boxed);

        // 自动拆箱
        int unboxed = boxed;  // 包装类 Integer 自动拆箱为基本数据类型 int
        System.out.println("拆箱后的值: " + unboxed);

        // 在集合中使用
        java.util.List<Integer> list = new java.util.ArrayList<>();
        list.add(100);  // 自动装箱:将 int 转为 Integer
        int value = list.get(0);  // 自动拆箱:将 Integer 转为 int
        System.out.println("集合中的值: " + value);
    }
}

手动装箱与拆箱

在没有自动装箱/拆箱之前,必须使用显式的方法进行转换:

  • 装箱:Integer.valueOf(intValue)
  • 拆箱:intValue.intValue()

示例:

Integer boxed = Integer.valueOf(10); // 手动装箱
int unboxed = boxed.intValue();      // 手动拆箱

包装类与基本数据类型的对应关系

基本数据类型对应的包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

注意事项和常见问题

  1. 性能开销

    • 自动装箱会创建新的包装类对象,因此可能会增加内存分配的开销,尤其是在频繁进行装箱的场景中。

    • 拆箱时可能触发空指针异常(NullPointerException),例如:

      Integer num = null;
      int value = num;  // 自动拆箱,抛出 NullPointerException
      
  2. Integer 缓存池

    • Integer 在 -128 到 127 的范围内会使用缓存对象,因此在该范围内的装箱操作不会创建新对象。

    • 示例:

      Integer a = 100;
      Integer b = 100;
      System.out.println(a == b);  // true,指向同一缓存对象
      
      Integer c = 200;
      Integer d = 200;
      System.out.println(c == d);  // false,不在缓存范围内
      
  3. 对象比较

    • 自动装箱后的对象比较要注意使用 equals 方法,而不是 ==,因为 == 比较的是引用。

    • 示例:

      Integer a = 100;  // 自动装箱
      Integer b = 100;
      System.out.println(a.equals(b)); // true
      System.out.println(a == b);      // true(在缓存范围内)
      
      Integer c = 200;
      Integer d = 200;
      System.out.println(c.equals(d)); // true
      System.out.println(c == d);      // false(不在缓存范围内)
      

扩展:实际应用场景

  1. 集合框架 自动装箱使得基本数据类型可以方便地存储到集合中:

    List<Integer> numbers = new ArrayList<>();
    numbers.add(10);  // 自动装箱
    int first = numbers.get(0);  // 自动拆箱
    
  2. 简化数学计算 自动拆箱与装箱可以让包装类直接参与数学运算:

    Integer a = 5;
    Integer b = 10;
    Integer sum = a + b;  // 自动拆箱计算后自动装箱
    System.out.println("Sum: " + sum);
    

总结

  • 自动装箱和拆箱 是 Java 提供的语法糖,简化了基本数据类型与包装类之间的转换。
  • 优点:减少手动转换的代码复杂度,提升了与集合和泛型的兼容性。
  • 注意事项:小心空指针异常、缓存池的作用范围,以及对象比较时的潜在问题。
  • 在实际开发中,应合理使用装箱和拆箱,避免性能和内存问题。

问题三:什么是 Java 中的迭代器(Iterator)?

定义

Iterator 是 Java 集合框架中的一种设计模式,用于遍历集合中的元素。通过 Iterator,我们可以在不暴露集合内部结构的情况下,逐一访问集合中的每个元素。

核心接口

Iterator 接口位于 java.util 包中,主要方法如下:

public interface Iterator<E> {
    boolean hasNext(); // 检查是否还有元素可迭代
    E next();          // 返回下一个元素
    void remove();     // 移除当前元素(可选操作,部分实现可能不支持)
}

使用示例

import java.util.ArrayList;
import java.util.Iterator;

public class IteratorExample {
    public static void main(String[] args) {
        // 创建一个集合
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");

        // 获取迭代器
        Iterator<String> iterator = list.iterator();

        // 使用迭代器遍历集合
        while (iterator.hasNext()) {
            String element = iterator.next();
            System.out.println(element);
        }
    }
}

输出:

Apple
Banana
Cherry

常用方法说明

  1. hasNext()

    • 返回 true 表示还有未迭代的元素。
    • 如果返回 false,表示迭代已经结束。
  2. next()

    • 返回集合中的下一个元素。
    • 如果迭代到末尾,再调用 next() 会抛出 NoSuchElementException
  3. remove()

    • 移除迭代器当前指向的元素。
    • 在调用 remove() 前必须先调用 next(),否则会抛出 IllegalStateException
    • 并不是所有集合都支持 remove(),如 Collections.unmodifiableList 会抛出 UnsupportedOperationException

增强 for 循环与迭代器的关系

  • 增强 for 循环(for-each)其实是基于 Iterator 实现的,它是 Iterator 的简化形式。

  • 示例:

    for (String fruit : list) {
        System.out.println(fruit);
    }
    
  • 这与手动使用迭代器的效果一致,但代码更简洁。


Iterator 的优点

  1. 屏蔽底层实现

    • 使用 Iterator 可以隐藏集合的底层结构,无需关心集合的存储方式(如数组、链表等)。
  2. 统一遍历接口

    • 无论是 ArrayListLinkedList 还是其他集合,都可以使用相同的 Iterator 接口进行遍历。
  3. 安全性

    • 迭代器支持 快速失败(fail-fast) 机制,当集合在迭代过程中被修改(如增加、删除元素),迭代器会抛出 ConcurrentModificationException,从而避免潜在的并发问题。

扩展:fail-fast 机制

  • 原理:在迭代时,Iterator 会维护一个 modCount 变量,用于记录集合的修改次数。如果在迭代过程中发现集合的 modCount 与预期不符(比如直接调用集合的 addremove 方法修改集合),会抛出 ConcurrentModificationException

  • 示例

    ArrayList<String> list = new ArrayList<>();
    list.add("A");
    list.add("B");
    list.add("C");
    
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        String element = iterator.next();
        if ("B".equals(element)) {
            list.remove(element);  // 修改集合会导致 ConcurrentModificationException
        }
    }
    

ListIterator

  • ListIteratorIterator 的增强版本,专门用于遍历 List 集合。它提供了额外的功能:
    1. 双向遍历(前后移动)。
    2. 修改元素。
    3. 获取当前索引位置。

主要方法:

public interface ListIterator<E> extends Iterator<E> {
    boolean hasPrevious(); // 是否有前一个元素
    E previous();          // 获取前一个元素
    void set(E e);         // 修改当前元素
    void add(E e);         // 在当前元素位置添加新元素
    int nextIndex();       // 获取下一个元素的索引
    int previousIndex();   // 获取前一个元素的索引
}

示例:

import java.util.ArrayList;
import java.util.ListIterator;

public class ListIteratorExample {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        ListIterator<String> iterator = list.listIterator();
        while (iterator.hasNext()) {
            System.out.println("Next: " + iterator.next());
        }

        while (iterator.hasPrevious()) {
            System.out.println("Previous: " + iterator.previous());
        }
    }
}

总结

  1. Iterator 是 Java 集合框架中用于遍历集合元素的重要工具,提供了统一的接口。
  2. 主要方法
    • hasNext() 检查是否有更多元素。
    • next() 获取下一个元素。
    • remove() 移除当前元素。
  3. ListIterator 是增强版,可以双向遍历,并支持元素修改和索引获取。
  4. fail-fast 机制 是迭代器的一大安全特性,用于避免在遍历集合时修改集合带来的问题。
  5. 增强 for 循环是对 Iterator 的简化,但无法显式调用 remove()

总结

今天的 3 道 Java 面试题,您是否掌握了呢?持续关注我们的每日分享,深入学习 Java 面试的各个细节,快速提升技术能力!如果有任何疑问,欢迎在评论区留言,我们会第一时间解答!

明天见!🎉

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;