JAVA基础篇

八股文

什么是反射

  1. 含义
    反射是 Java 提供的一种机制,可以在运行时动态地获取类的信息、创建对象、调用方法、访问字段。
  • 换句话说:
    • 平时你是写死调用类、方法
    • 反射让你在“运行时”动态操作类和对象
  1. 🧠 举个最简单的例子
    👇 正常方式:
    1
    2
    Person p = new Person();
    p.sayHello();
    👇 反射方式:
    1
    2
    3
    4
    Class<?> clazz = Class.forName("Person");
    Object obj = clazz.getDeclaredConstructor().newInstance(); // 创建对象
    Method method = clazz.getMethod("sayHello"); // 找到方法
    method.invoke(obj); // 调用方法
    🔍 说明:
  • Class.forName(“类名”):加载类
  • getDeclaredConstructor().newInstance():创建对象
  • getMethod(“方法名”):获取方法
  • invoke():执行方法
  1. 为什么说反射是在运行时
    通过学习JVM,我们已经知道,我们编写一个类,会执行为class文件,类加载器会将class文件加载到JVM中,其中方法区存的是类的属性和方法信息。
    👇当我们new了一个对象时,会在堆中开辟一块空间存放这个对象,这个过程是在编译过程中的,还没有开始运行,就已经知道了需要这个对象,所以开辟一块空间
    1
    2
    Person p = new Person();
    p.sayHello();
    当你执行:
    1
    2
    Class<?> clazz = Class.forName("com.example.MyClass");

    clazz.getMethod(“methodName”) 查的是:
    查的是方法区中该类的“方法表”,而不是去堆中查找。
    每个类在被加载的时候,JVM 会为它建立一个「类元信息结构」:
  • 方法名
  • 返回类型
  • 参数类型
  • 修饰符(public/private/static)
  • 方法在字节码里的位置
  • 字节码数组(code)

反射就是读取这些信息,并用它来动态调用

final,finally,finalize的区别是什么

关键词类型用途是否是关键字
final关键字声明常量/不可变对象/类/方法✅ 是
finally关键字用于 try-catch-finally 中,一定会执行的代码块✅ 是
finalize()Object 方法垃圾回收前由 GC 调用的钩子函数,已过时❌ 是方法

面试题

  • 面试官:JAVA的基础八大类型
  • 我:
    类型类别示例
    byte整数byte a = 10;
    short整数short b = 20;
    int整数int c = 100;
    long整数long d = 1000L;
    float浮点float e = 3.14f;
    double浮点double f = 3.1415;
    char字符char g = ‘A’;
    boolean布尔boolean h = true;
  • 面试官: 怎么把string类型的时间转换成时间类型
  • 我:

    • 使用 DateTimeFormatter
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      import java.time.LocalDateTime;
      import java.time.format.DateTimeFormatter;

      public class Main {
      public static void main(String[] args) {
      String timeStr = "2025-07-24 15:30:00";
      DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
      LocalDateTime localDateTime = LocalDateTime.parse(timeStr, formatter);

      System.out.println("转换后的 LocalDateTime:" + localDateTime);
      }
      }
  • 面试官: 判断一个字段是一个字符串的第几位,用哪个方法

  • 我:

    • String.indexOf()
      底层是通过字符数组遍历+匹配来实现的。对于单字符查找,它会从字符串左边开始,逐个比较字符值是否相等;对于子字符串查找(indexOf(String)),JDK 使用的是一种改进版的暴力匹配算法

    面试官问我的时候,我想不起来了,我说用charAt循环遍历一下,太好笑了,估计给面试官都整无语了,想得起来这个,想不起来 String.indexOf()

  • 面试官:Spring Boot 启动核心注解

  • 我:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @SpringBootApplication
    @EnableCreateCacheAnnotation
    @EnableMethodCache(basePackages = "com.shortplay.server")
    @EnableScheduling
    @Slf4j
    public class StartApp {
    public static void main(String[] args) {
    ConfigurableApplicationContext context = SpringApplication.run(StartApp.class, args);
    Environment env = context.getEnvironment();
    String active = env.getProperty("spring.profiles.active");
    log.info("服务启动成功, active = {}", active);
    }

    }

    ✅ @SpringBootApplication(最核心)等价于以下三个注解的组合:

    • @Configuration // 表示该类是配置类(等同于配置文件)
    • @EnableAutoConfiguration // 开启自动配置功能(根据 classpath 中的 jar 自动配置)
    • @ComponentScan // 开启包扫描(默认扫描当前类所在包及其子包)
    注解来源作用
    @EnableSchedulingSpring启用定时任务
    @EnableMethodCacheJetCache启用方法级缓存,识别 @Cached 等注解
    @EnableCreateCacheAnnotationJetCache启用 @CreateCache,用于声明缓存对象

    集合篇

  • 面试官:介绍一下hashmap

  • 我:

    HashMap 是 Java 中最常用的集合类之一,它基于哈希表(Hash Table)实现,用于以键值对(Key-Value)的形式存储数据,属于 java.util 包中的一部分。
    HashMap的特点:

    • 线程不安全
    • JDK8之前:数组 + 链表;JDK8之后:数组 + 链表/红黑树(链表长度超过 8 且数组长度大于 64 时转为红黑树)
    • ✅ 允许一个 null 键和多个 null 值
    • 初始容量 默认是 16
    • 负载因子 默认是 0.75(即 75% 满时扩容)

    HashMap存储数据的原理:

    1
    map.put("apple", 1);

    • ✅ 第一步:计算哈希值(扰动函数)
      1
      int hash = hash(key);
    • ✅ 第二步:定位数组下标(index)
      1
      int index = (table.length - 1) & hash;
      1
      int index = (n - 1) & hash;
    • ✅ 第三步:判断该位置是否已有元素

      • 如果该位置是空的:说明没有哈希冲突,直接插入新节点。
    • ✅ 第四步:如果该位置不为空(发生哈希冲突)

      • 此时需要将该位置的数据取出,判断key是否相等
        • 相等的话则直接覆盖 value即可 (例如:map.put(“apple”, 1),key指的是apple)
        • 不相等则遍历链表或者红黑树在末尾插入数据
          1
          2
          3
          4
          5
          6
          7
          Node<K,V> p = table[index];
          if (p.hash == hash && (p.key == key || (key != null && key.equals(p.key)))) {
          // key 相等,直接覆盖旧值
          p.value = value;
          } else {
          // 遍历链表或红黑树
          }
    • ✅ 第五步:是否需要扩容
      • 每次插入后判断是否需要扩容

    put(key, value)

    计算 hash → 计算 index = (n-1) & hash

    判断 table[index]

    为空 → 直接插入新节点

    不为空 → 判断是否 key 相等

    key 相等 → 覆盖旧值

    key 不相等 → 链表尾部插入或转红黑树

    插入后判断是否需要扩容

  • 面试官: hashmap为什么会选择红黑树而不是其他的树
  • 我:

    1. 红黑树是一种自平衡二叉搜索树,可以保证基本操作(查找、插入、删除)时间复杂度为 O(log n)。
    2. 兼顾查找速度和维护成本
      • AVL 树查找速度稍优于红黑树,但插入/删除时旋转较多,开销大。(AVL树的性质:左子树高度 - 右子树高度,且值只能是 -1、0、1。)
      • 红黑树牺牲一点查找效率,换来更少的重平衡操作,整体性能更均衡。
  • 面试官: 什么是哈希表

  • 我:
    1. 哈希表的结构:
      哈希表(Hash Table)的底层结构主要由 数组 组成,结合 链表 或 红黑树 来处理哈希冲突。
    • 数组(Bucket 数组)
      • 核心存储结构是一个数组,每个数组位置称为“桶”(bucket)。
      • 哈希函数计算出的哈希值会映射到数组的某个索引位置。
      • 数组的每个元素是一个链表或红黑树的头节点,用来处理哈希冲突。
    • 链表(或红黑树)
      • 因为不同的键可能映射到相同的桶,产生冲突(哈希冲突)。
      • 传统解决冲突的方法是用链表把冲突的元素串起来,存放在同一个桶中。
      • JDK8 及以后,当链表长度过长时,会将链表转换成红黑树,提高查找效率。
    • 结构示意图
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      table(数组):
      +----------+----------+----------+----------+------------------+
      | bucket0 | bucket1 | bucket2 | bucket3 | bucket4 |
      | null | ↓ | null | ↓ | ↓ |
      | | Node1 | | Node3 | TreeNodeA(B) | ← 红黑树根节点(黑)
      | | ↓ | | ↓ | / \ |
      | | Node2 | | Node4 | R(B) T(R) |
      | | | | | / \ \ |
      | | | | | ... ... X(B) |
      +----------+----------+----------+----------+------------------+

      说明:
      - bucket0:为空(null)
      - bucket1:有两个冲突元素,用链表 Node1 → Node2 存储
      - bucket3:链表 Node3 → Node4
      - bucket4:冲突太多,链表已转为红黑树(TreeNodeA 是根)
  • 面试官:如果一个对象 没有重写 equals() 方法,将它作为 HashMap 的 key,会产生什么问题?
  • 我:

    HashMap 判断两个 key 是否相同,要经过两个步骤:

    • 比较 hash 值是否相等:hash(key1) == hash(key2)
    • 调用 equals() 方法判断是否真的“相等”:key1.equals(key2)
      如果没有重写hashCode(),则两个对象的hash值可能会不同
      如果没有重写equals的话,equals等价于 ==,只有引用相同时才会返回true
      例如:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      class Person {
      String name;

      Person(String name) {
      this.name = name;
      }
      }

      HashMap<Person, String> map = new HashMap<>();
      //new了两个对象,所以在JVM的堆内存中 会有两个对象属性 p1和p2
      Person p1 = new Person("Alice");
      Person p2 = new Person("Alice");
      //将p1加到hashmap中
      map.put(p1, "Developer");
      //(前提:已经重写hashCode(),没有重写equles)因为没有重写equals,所以map.get的方法中的比较key时会是false,会发现没有一样的key,所以会返回null
      //(前提:已经重写equles,没有重写hashCode())因为没有重写hashCode(),所以map.get的方法中通过计算hash来确定数据在哪一个位置,但是p2的hash值和p1的hash值不同,所以p1的位置在map中的第一个位置,但是却去第二个位置查找了,所以会一致查询不到
      System.out.println(map.get(p2)); // 输出NULL?
    • 重写equles和hashcode示例
      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
      import java.util.Objects;

      public class Person {
      private String name;
      private int age;

      // 构造函数
      public Person(String name, int age) {
      this.name = name;
      this.age = age;
      }

      // 重写 equals() 方法
      @Override
      public boolean equals(Object obj) {
      if (this == obj) return true; // 1. 引用相同
      if (obj == null || getClass() != obj.getClass()) // 2. 类型不同
      return false;
      Person person = (Person) obj; // 3. 类型一样,强制转换
      return age == person.age && // 4. 按字段比较
      Objects.equals(name, person.name);
      }

      // 重写 hashCode() 方法
      @Override
      public int hashCode() {
      return Objects.hash(name, age); // 推荐写法
      }

      // 可选:用于打印
      @Override
      public String toString() {
      return "Person{name='" + name + "', age=" + age + '}';
      }
      }
  • 面试官:如何实现把hashmap的数据按key排序,可以利用其他数据结构也可以在hashmap上

  • 我:
    1. ✅ 方法一:使用 TreeMap (TreeMap会自动排序)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      import java.util.*;

      public class Main {
      public static void main(String[] args) {
      Map<String, Integer> map = new HashMap<>();
      map.put("banana", 3);
      map.put("apple", 5);
      map.put("pear", 2);

      // 将 HashMap 转成 TreeMap(自动按 key 升序排序)
      Map<String, Integer> sortedMap = new TreeMap<>(map);

      for (Map.Entry<String, Integer> entry : sortedMap.entrySet()) {
      System.out.println(entry.getKey() + " = " + entry.getValue());
      }
      }
      }
    2. ✅ 方法二:使用 HashMap.entrySet() + List 排序
      HashMap.entrySet():是 Java Map 接口中的一个非常重要的方法,它的作用是返回 该 Map 中所有键值对(Entry)的集合。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      import java.util.*;

      public class Main {
      public static void main(String[] args) {
      Map<String, Integer> map = new HashMap<>();
      map.put("banana", 3);
      map.put("apple", 5);
      map.put("pear", 2);

      // 转换为 List<Map.Entry>
      List<Map.Entry<String, Integer>> entryList = new ArrayList<>(map.entrySet());

      // 按 key 升序排序
      entryList.sort(Map.Entry.comparingByKey());

      // 排序后插入 HashMap 保持顺序

      }
      }