Java面试题第一弹

基础问题

1. Java 基础概念

1. JVM、JRE、JDK 都是什么含义?

解答

解答:

  • JVM(Java Virtual Machine): 是 java 虚拟机。 它只认识.class 为后缀的文件,它能将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。JVM 是 java 能够跨平台的核心机制。通俗的说就是跨平台用的,就是把我们写的代码,转换成 class 文件用的。
  • JRE(Java Runtime Environment): 是 Java 运行环境 JRE 包括 Java 虚拟机 (JVM Java Virtual Machine)和 Java 程序所需的核心类库等,如果想要运行一个开发好的 Java 程序,计算机中只需要安装 JRE 即可。通俗的说就是运行用的。
  • JDK(Java Development Kit): 是 Java 的开发工具包 JDK 是提供给 Java 开发人员使用的,其中包含了 Java 的开发工具和 JRE。其中的开发工具包括:编译工具(javac.exe)打包工具(jar.exe)等。通俗的说就是开发用的。

2. Java 中所有类的父类是什么,有哪些常用方法?

解答

解答:

  • 所有类的父类是 java.lang.Object
  • 常用方法包括:
    • toString(): 返回对象的字符串表示。
    • equals(Object obj): 比较两个对象是否相等。
    • hashCode(): 返回对象的哈希码值。
    • getClass(): 返回对象的运行时类。
    • clone(): 创建并返回对象的副本(需要实现 Cloneable 接口)。

3. 解释一下继承的概念。

解答

解答:

  • 继承是面向对象编程中的一种机制,允许一个类(子类)从另一个类(父类)获取属性和方法。
  • 子类可以直接使用父类的公共和受保护成员,也可以通过重写(override)来修改父类的方法。
  • 继承的优点:
    • 提高代码的复用性。
    • 提供了多态的基础。
  • Java 中通过 extends 关键字实现继承,例如:
  • 注意:
    • Java 不支持多继承(一个类只能有一个直接父类),但可以通过接口实现类似功能。

4. 思考重写(override)与重载(overload)的区别。

解答

解答:

  • 重写(Override):

    • 发生在子类与父类之间。
    • 子类对父类的非静态方法进行重新实现,方法签名(方法名和参数列表)必须相同。
    • 重写的方法不能比父类方法有更严格的访问权限。
    • 主要用于实现多态。
  • 重载(Overload):

    • 发生在同一个类中。
    • 方法名相同,但参数列表(参数类型、数量或顺序)必须不同。
    • 与返回值类型无关。
    • 主要用于提高方法的灵活性。

5. 解释 super 与 this 关键字的区别。

解答

解答:

  • this 关键字:

    • 用于引用当前对象。
    • 常见用途:
      1. 访问当前类的成员变量或方法。
      2. 调用当前类的构造方法(通过 this())。
  • super 关键字:

    • 用于引用父类对象。
    • 常见用途:
      1. 访问父类的成员变量或方法。
      2. 调用父类的构造方法(通过 super())。
  • 主要区别:

    • this 引用当前类的对象,而 super 引用父类的对象。
    • this() 调用当前类的构造方法,而 super() 调用父类的构造方法。

2. 数据类型与修饰符

1. 常见的基本类型有哪些?是否可以相互转换?

解答

解答:

  • 常见的基本类型:

    • 整数类型:byte(1 字节)、short(2 字节)、int(4 字节)、long(8 字节)
    • 浮点类型:float(4 字节)、double(8 字节)
    • 字符类型:char(2 字节,存储单个字符)
    • 布尔类型:boolean(1 位,表示 truefalse
  • 是否可以相互转换:

    • 基本类型之间可以通过 自动类型转换强制类型转换 相互转换。
    • 自动类型转换(隐式转换): 小范围类型可以自动转换为大范围类型,例如:
      1
      2
      int a = 10;
      long b = a; // int 自动转换为 long
    • 强制类型转换(显式转换): 大范围类型需要强制转换为小范围类型,可能会丢失精度,例如:
      1
      2
      long a = 100L;
      int b = (int) a; // long 强制转换为 int
    • 特殊情况:boolean 类型不能与其他基本类型相互转换。

2. 基本数据类型占用字节的大小?

解答

解答:

  • 整数类型:

    • byte: 1 字节(8 位)
    • short: 2 字节(16 位)
    • int: 4 字节(32 位)
    • long: 8 字节(64 位)
  • 浮点类型:

    • float: 4 字节(32 位)
    • double: 8 字节(64 位)
  • 字符类型:

    • char: 2 字节(16 位),采用 Unicode 编码。
  • 布尔类型:

    • boolean: 虽然理论上占用 1 位,但实际大小依赖于 JVM 实现,通常以 1 字节为单位存储。

3. 说下常见的权限修饰符以及其大小关系。

解答

解答:

  • 常见的权限修饰符:

    • public: 对所有类可见。
    • protected: 对同一个包中的类和所有子类可见。
    • 默认(无修饰符): 仅对同一个包中的类可见。
    • private: 仅对当前类可见。

4. final 有什么作用?

解答

解答:

  • final 的作用:

    1. 修饰变量:
      • 表示变量的值一旦初始化后就不能再更改。
      • 对于引用类型,final 修饰的变量不能指向新的对象,但对象的内容可以被修改。
    2. 修饰方法:
      • 表示方法不能被子类重写(override)。
    3. 修饰类:
      • 表示类不能被继承。

中级问题

1. 字符串与引用

1. 为什么不建议在循环中直接用加号拼接字符串?

解答

解答:

  • 为什么不建议在循环中直接用加号拼接字符串:

    • 在 Java 中,字符串是不可变的,每次使用 + 拼接字符串时,都会创建一个新的字符串对象。
    • 如果在循环中频繁使用 + 拼接字符串,会导致大量的临时对象创建,增加内存开销和垃圾回收的压力。
    • 建议使用 StringBuilderStringBuffer 来拼接字符串,它们是可变的,性能更高。

2. String、StringBuffer、StringBuilder 的区别?

解答

解答:

  • String:

    • 不可变对象,每次修改都会创建新的对象。
    • 适用于少量字符串操作。
    • 线程安全。
  • StringBuffer:

    • 可变对象,支持字符串的修改。
    • 线程安全,所有方法都被 synchronized 修饰。
    • 适用于多线程环境下的字符串操作。
  • StringBuilder:

    • 可变对象,支持字符串的修改。
    • 非线程安全,性能比 StringBuffer 高。
    • 适用于单线程环境下的字符串操作。

3. == 和 equals 的区别。

解答

解答:

  • ==equals 的区别:

    • ==:
      • 比较的是两个对象的内存地址是否相同。
      • 对于基本数据类型,比较的是值是否相等。
    • equals:
      • 比较的是两个对象的内容是否相等。
      • 默认情况下,Object 类的 equals 方法与 == 等效,但许多类(如 String)重写了 equals 方法,用于比较内容。

2. 泛型与异常

1. 什么是泛型?

解答

解答:

  • 泛型是 Java 中的一种机制,用于在类、接口和方法中定义类型参数,从而使代码具有更好的类型安全性和可读性。

  • 泛型的主要作用:

    1. 类型安全: 在编译时检查类型,避免运行时的 ClassCastException
    2. 代码复用: 提高代码的通用性,减少重复代码。
    3. 可读性: 通过明确的类型定义,增强代码的可读性。
  • 泛型的使用:

    • 定义泛型类:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      public class Box<T> {
      private T item;
      public void setItem(T item) {
      this.item = item;
      }
      public T getItem() {
      return item;
      }
      }
    • 使用泛型类:
      1
      2
      3
      Box<String> stringBox = new Box<>();
      stringBox.setItem("Hello");
      String item = stringBox.getItem();
  • 注意事项:

    1. 泛型在编译时会进行类型擦除,运行时不会保留类型信息。
    2. 不能直接创建泛型类型的实例,例如 new T() 是非法的。
    3. 泛型不支持基本数据类型(如 int),需要使用包装类(如 Integer)。

2. 举例说明常见的异常?

解答

解答:

  • 常见的异常分类:

    1. 运行时异常(RuntimeException):

      • 这些异常在编译时不需要显式捕获或声明。
      • 示例:
        • NullPointerException: 当尝试访问空引用的成员时抛出。
        • ArrayIndexOutOfBoundsException: 当访问数组的索引超出范围时抛出。
        • ArithmeticException: 当发生算术错误(如除以零)时抛出。
        • ClassCastException: 当尝试将对象强制转换为不兼容的类型时抛出。
    2. 检查异常(CheckedException):

      • 这些异常在编译时必须显式捕获或声明。
      • 示例:
        • IOException: 当发生 I/O 操作失败时抛出。
        • SQLException: 当数据库操作失败时抛出。
        • ClassNotFoundException: 当尝试加载的类不存在时抛出。
    3. 错误(Error):

      • 这些通常是由 JVM 抛出的严重问题,程序无法处理。
      • 示例:
        • OutOfMemoryError: 当 JVM 无法分配足够的内存时抛出。
        • StackOverflowError: 当方法调用栈溢出时抛出。
  • 异常处理示例:

    1
    2
    3
    4
    5
    6
    7
    8
    try {
    int[] arr = new int[5];
    System.out.println(arr[10]); // 可能抛出 ArrayIndexOutOfBoundsException
    } catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("数组索引越界: " + e.getMessage());
    } finally {
    System.out.println("执行完成");
    }

3. 其他

1. 什么是构造器引用和方法引用,使用条件是什么?

解答

解答:

  • 构造器引用:

    • 构造器引用是指通过 类名::new 的形式引用类的构造方法。

    • 使用条件:

      1. 构造器的参数列表必须与目标函数式接口的抽象方法参数列表一致。
      2. 适用于需要创建对象的场景。
    • 示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      interface Factory<T> {
      T create();
      }

      class Example {
      public Example() {
      System.out.println("构造器被调用");
      }
      }

      public class Main {
      public static void main(String[] args) {
      Factory<Example> factory = Example::new;
      Example example = factory.create(); // 调用构造器
      }
      }
  • 方法引用:

    • 方法引用是指通过 类名::方法名对象名::方法名 的形式引用已有的方法。

    • 使用条件:

      1. 方法的参数列表和返回值类型必须与目标函数式接口的抽象方法一致。
      2. 适用于已有方法可以直接复用的场景。
    • 常见类型:

      1. 静态方法引用: 类名::静态方法名
      2. 实例方法引用: 对象名::实例方法名
      3. 特定类型实例方法引用: 类名::实例方法名
      4. 构造器引用: 类名::new
    • 示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import java.util.function.Function;

      public class Main {
      public static void main(String[] args) {
      // 静态方法引用
      Function<String, Integer> parseInt = Integer::parseInt;
      System.out.println(parseInt.apply("123")); // 输出 123

      // 实例方法引用
      String str = "Hello";
      Function<Integer, Character> charAt = str::charAt;
      System.out.println(charAt.apply(1)); // 输出 'e'
      }
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65

      {% endfolding %}

      #### 2. 什么是序列化?它有什么作用?

      {% folding 解答 %}
      解答:

      - **什么是序列化**:

      - 序列化是指将对象的状态转换为字节流的过程,以便将对象保存到文件、数据库或通过网络传输。
      - 在 Java 中,序列化通过实现 `java.io.Serializable` 接口来实现。

      - **作用**:

      1. **持久化**: 将对象的状态保存到存储介质中(如文件或数据库),以便稍后恢复。
      2. **传输**: 在分布式系统中,通过网络传输对象的字节流。
      3. **缓存**: 将对象序列化后存储在缓存中,以便快速恢复。

      - **反序列化**:

      - 反序列化是指将字节流恢复为对象的过程。

      - **示例**:

      ```java
      import java.io.*;

      class Person implements Serializable {
      private static final long serialVersionUID = 1L;
      private String name;
      private int age;

      public Person(String name, int age) {
      this.name = name;
      this.age = age;
      }

      @Override
      public String toString() {
      return "Person{name='" + name + "', age=" + age + "}";
      }
      }

      public class SerializationExample {
      public static void main(String[] args) {
      Person person = new Person("Alice", 25);

      // 序列化
      try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
      oos.writeObject(person);
      System.out.println("对象已序列化");
      } catch (IOException e) {
      e.printStackTrace();
      }

      // 反序列化
      try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
      Person deserializedPerson = (Person) ois.readObject();
      System.out.println("反序列化的对象: " + deserializedPerson);
      } catch (IOException | ClassNotFoundException e) {
      e.printStackTrace();
      }
      }
      }
  • 注意事项:

    1. 序列化的类必须实现 Serializable 接口。
    2. serialVersionUID 用于版本控制,避免类结构变化导致反序列化失败。
    3. transient 关键字可以标记不需要序列化的字段。

高级问题

1. 集合框架

1. 介绍 Java 中的集合框架?

解答

解答:

2. ArrayList 和 LinkedList 的实现原理和区别?

解答

解答:

3. ArrayList 和 LinkedList 的时间复杂度?

解答

解答:

4. HashMap 的 key 是否可以存储 null?底层实现是怎样的?

解答

解答:

  • HashMap 的 key 是可以存储 null 的。
  • 当 key 为 null 时,HashMap 会将其存储在表的第一个位置(即索引为 0 的桶中)。
  • 这是通过 hash(key) 方法中特殊处理 null 值实现的,null 的 hash 值被固定为 0。
  • 需要注意的是,HashMap 中最多只能有一个 key 为 null 的键值对。

5. 为什么 LinkedHashSet 是有序的?

解答

解答:

6. HashSet 的底层原理是什么?

解答

解答:

2. 拷贝

1. 什么是深拷贝?什么是浅拷贝?Java 中如何实现?

解答

解答:

算法与设计模式

1. 算法

1. 请说出冒泡排序、选择排序以及二分查找的思路。

解答

解答:

2. 设计模式

1. 请说出单例设计模式的三种写法。

解答

解答: