Java:高频面试知识分享1

一、Java 语言核心特性(面向对象编程)

核心知识点梳理:
  1. 面向对象三大特性

    • 封装:隐藏对象内部实现,通过 public 方法暴露接口(例:类的 private 字段 + get/set 方法)。
    • 继承:子类继承父类非 private 属性和方法,减少代码冗余(注意:Java 单继承,通过接口实现多继承效果)。
    • 多态:同一行为的不同表现形式(编译时多态:方法重载;运行时多态:子类重写父类方法,父类引用指向子类对象)。
  2. 接口与抽象类的区别

    • 接口:全抽象(JDK8 后可有 default 方法),字段默认 public static final,类可实现多个接口。
    • 抽象类:可含抽象方法和具体方法,字段可非静态,类只能继承一个抽象类。
    • 应用场景:接口用于定义规范(如 DAO 层接口),抽象类用于抽取共性实现(如 BaseService)。
  3. 其他高频点

    • 重载(Overload)vs 重写(Override):重载是同一类中方法名相同、参数不同;重写是子类覆盖父类方法,参数和返回值需一致(协变返回类型除外)。
    • 关键字:static(静态变量 / 方法属于类,非实例)、final(修饰类不可继承、方法不可重写、变量不可修改)、this(当前实例)、super(父类引用)。
面试常见问题及回答思路:
  • Q:多态的实现原理是什么?

    A:核心是 "动态绑定":编译时编译器根据父类引用类型检查方法是否存在,运行时 JVM 根据实际对象类型调用对应子类的重写方法(依赖方法表:每个类的 Class 对象中维护方法表,记录方法的实际入口地址)。

    举例:List list = new ArrayList(); list.add(1); 编译时用 List 接口检查 add 方法,运行时实际调用 ArrayList 的 add 实现。

  • Q:为什么 Java 不支持多继承?

    A:避免 "菱形继承" 问题(多个父类有相同方法时,子类无法确定继承哪一个),通过接口多实现替代,接口仅定义规范,无具体实现冲突。

二、集合框架

核心知识点梳理:
  1. 整体结构

    • 接口:Collection(List/Set)、Map
    • 核心实现类:
      • List:ArrayList(数组,查快改慢)、LinkedList(双向链表,增删快查慢);
      • Set:HashSet(底层 HashMap,无序去重)、TreeSet(红黑树,有序去重,需实现 Comparable);
      • Map:HashMap(数组 + 链表 / 红黑树,JDK1.8 后链表长度 > 8 转红黑树)、ConcurrentHashMap(线程安全,JDK1.7 分段锁,JDK1.8 CAS+synchronized)。
  2. 高频细节

    • HashMap:初始容量 16,负载因子 0.75,扩容为 2 倍;key 为 null 存放在索引 0;线程不安全(多线程 put 可能导致死循环,JDK1.8 后修复但仍有数据覆盖问题)。
    • HashSet:本质是HashMap的 key(value 为常量),所以去重依赖 key 的hashCode()equals()(先比 hashCode,再比 equals)。
    • 线程安全集合:Vector(所有方法 synchronized,效率低)、Collections.synchronizedList()(包装类,加锁粒度同 Vector)、CopyOnWriteArrayList(写时复制,读不加锁,适合读多写少)。
面试常见问题及回答思路:
  • Q:ArrayList 和 LinkedList 的区别?如何选型?

    A:从底层结构、操作效率、内存占用三方面说:

    • 底层:ArrayList 是动态数组,LinkedList 是双向链表;
    • 效率:ArrayList 随机访问(get (index))快(O (1)),增删(尤其是中间位置)慢(需移动元素,O (n));LinkedList 增删快(O (1),找到位置后修改指针),随机访问慢(O (n));
    • 选型:查多改少用 ArrayList,增删多(尤其是中间)用 LinkedList;小数据量时差异不大。
  • Q:HashMap 的 put 流程?JDK1.7 和 1.8 有什么区别?

    A:put 流程:

    1. 计算 key 的 hash 值(hashCode () 高 16 位异或低 16 位,减少哈希冲突);
    2. 计算索引(hash & (容量 - 1));
    3. 若桶位为空,直接放新节点;若不为空,判断 key 是否存在(hash+equals),存在则替换 value;不存在则插入链表 / 红黑树;
    4. 插入后若链表长度 > 8 且容量 >=64,转红黑树;
    5. 若元素数 > 容量 * 负载因子,触发扩容(2 倍)。
      区别:1.7 是头插法(扩容时可能死循环),1.8 是尾插法;1.8 新增红黑树优化长链表查询;1.7 的 hash 计算更简单(1.8 增加高 16 位异或)。

三、多线程编程基础

核心知识点梳理:
  1. 线程创建方式

    • 继承Thread类(重写 run ());
    • 实现Runnable接口(重写 run (),可多线程共享资源);
    • 实现Callable接口(重写 call (),有返回值,结合Future获取结果)。
  2. 线程状态

    新建(New)→ 就绪(Runnable)→ 运行(Running)→ 阻塞(Blocked/Waiting/Timed Waiting)→ 终止(Terminated)。

    • 阻塞原因:synchronized 未拿到锁(Blocked)、wait ()(Waiting)、sleep (long)/join (long)(Timed Waiting)。
  3. 同步机制

    • synchronized:可修饰方法(锁当前对象 / 类)、代码块(锁括号内对象);JDK1.6 后优化为偏向锁→轻量级锁→重量级锁(锁升级),减少性能消耗。
    • Lock接口:ReentrantLock(可重入、可中断、可超时、公平锁 / 非公平锁),需手动lock()unlock()(建议放 finally)。
    • 区别:synchronized 是 JVM 层面隐式锁,自动释放;Lock 是 API 层面显式锁,灵活但需手动释放。
  4. 线程池

    • 核心参数:corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(非核心线程空闲存活时间)、workQueue(任务队列)。
    • 常用线程池:Executors.newFixedThreadPool()(固定线程数)、newCachedThreadPool()(缓存线程池,可扩容)、newSingleThreadExecutor()(单线程);实际开发建议用ThreadPoolExecutor手动创建,避免资源耗尽(如 FixedThreadPool 的队列无界可能 OOM)。
面试常见问题及回答思路:
  • Q:synchronized 和 volatile 的区别?

    A:两者都用于线程安全,但作用不同:

    • volatile:修饰变量,保证可见性(一个线程修改后,其他线程立即看到)和禁止指令重排序(如单例模式双重检查锁中修饰 instance),但不保证原子性(例:i++ 仍会有线程安全问题);
    • synchronized:保证原子性(临界区代码互斥执行)、可见性(解锁前刷新内存)、有序性(临界区代码单线程执行),但开销比 volatile 大。
      总结:volatile 适合修饰状态标志(如 boolean isRunning),synchronized 适合复杂逻辑的同步。
  • Q:什么是死锁?如何避免?

    A:死锁是两个或多个线程互相持有对方需要的锁,导致无限等待。

    例:线程 1 持有锁 A,等待锁 B;线程 2 持有锁 B,等待锁 A。

    避免:1. 按固定顺序获取锁(如都先获取 A 再获取 B);2. 定时释放锁(tryLock (time));3. 减少锁的持有时间。

四、JVM 内存结构和垃圾回收机制

核心知识点梳理:
  1. 内存结构(JDK8+)

    • 线程私有:
      • 程序计数器:记录当前线程执行的字节码行号,唯一不会 OOM 的区域;
      • 虚拟机栈:存储栈帧(局部变量表、操作数栈、返回地址等),栈深过深会 StackOverflowError(如递归无出口),内存不足会 OOM;
      • 本地方法栈:类似虚拟机栈,为 Native 方法服务。
    • 线程共享:
      • 堆:存储对象实例,GC 主要区域,分新生代(Eden+From Survivor+To Survivor)和老年代;内存不足会 OOM;
      • 方法区(元空间,MetaSpace):存储类信息、常量、静态变量等,JDK8 前是永久代(PermGen),元空间使用本地内存,默认无上限(可通过 - XX:MaxMetaspaceSize 限制),内存不足会 OOM。
  2. 垃圾回收(GC)

    • 回收区域:堆和方法区(常量、无用类)。
    • 对象存活判断:可达性分析(以 GC Roots 为起点,不可达的对象标记为可回收);GC Roots 包括:虚拟机栈中引用的对象、本地方法栈中引用的对象、静态变量、常量等。
    • 引用类型:强引用(new 的对象,不会被回收)、软引用(OOM 前回收,适合缓存)、弱引用(GC 时必回收,如 WeakHashMap)、虚引用(回收时通知,跟踪 GC)。
    • 垃圾收集算法:
      • 标记 - 清除:标记可回收对象,直接清除,会产生碎片;
      • 标记 - 复制:将存活对象复制到另一块区域,无碎片,适合新生代(Eden:From:To=8:1:1);
      • 标记 - 整理:标记后将存活对象移到一端,清除另一端,适合老年代。
    • 垃圾收集器:
      • 新生代:SerialGC(单线程,STW 长)、Parallel Scavenge(多线程,注重吞吐量)、ParNew(多线程,配合 CMS);
      • 老年代:Serial Old(单线程,配合 SerialGC)、Parallel Old(多线程,配合 Parallel Scavenge)、CMS(并发标记清除,低延迟,三步 STW:初始标记、重新标记、并发清除,有碎片)、G1(Region 分区,兼顾吞吐量和延迟,Mixed GC 回收部分老年代)。
面试常见问题及回答思路:
  • Q:JVM 堆内存分代?为什么这么划分?

    A:堆分新生代(Young)和老年代(Old),新生代又分 Eden、From Survivor、To Survivor。

    原因:基于 "大部分对象朝生夕死" 的经验法则(新生代对象存活时间短,回收频繁;老年代对象存活时间长,回收少),分代后可采用不同 GC 算法优化效率:新生代用标记 - 复制(效率高,适合短生命周期对象),老年代用标记 - 整理(避免碎片,适合长生命周期对象)。

  • Q:G1 收集器的工作原理?

    A:G1 将堆划分为多个大小相等的 Region(默认 1-32MB),每个 Region 可动态标记为 Eden、Survivor、Old。

    流程:1. 初始标记(STW,标记 GC Roots 直接关联的对象);2. 并发标记(遍历对象引用链,无 STW);3. 最终标记(STW,处理并发标记遗漏的对象);4. 筛选回收(STW,根据 Region 的回收价值排序,优先回收价值高的,混合回收部分老年代 Region)。

    优势:可设置最大 STW 时间,兼顾吞吐量和延迟,适合大堆(如 8GB 以上)。

五、复习建议

  1. 画思维导图:把每个模块的知识点串联起来(如集合框架→线程安全集合→多线程同步),形成知识网。
  2. 看源码 :重点看ArrayListadd()HashMapput()Threadstart()等核心方法,理解底层实现。
  3. 刷面试题:针对每个知识点,找 5-10 道高频题练习(如牛客网、力扣 Java 面试专题),确保能清晰表达。
  4. 模拟面试:找同学互相提问,训练临场反应,避免紧张忘词。

核心是 "理解原理 + 能举例子",面试官更看重你是否真的懂,而不是背概念。比如讲 HashMap 时,能说出自己项目中遇到的哈希冲突问题及解决方法,会比单纯背流程更加分。

List list = new ArrayList(); 这个是小类型转换成大类型是自动转换是吗,如果反过来呢

在 Java 中,List list = new ArrayList(); 涉及的是多态中的向上转型,而非基本数据类型的自动转换(小转大),但逻辑上有相似之处:

  • ArrayListList 接口的实现类,List 是父类型(接口可视为 "抽象父类"),ArrayList 是子类型。
  • 这种 "父类引用指向子类对象" 的写法是自动向上转型,无需显式声明,编译器允许这种转换(因为子类一定符合父类的接口规范)。

如果反过来写 ArrayList arrayList = new List();,则完全不允许,原因是:

  1. List 是接口,不能直接实例化(new List() 本身语法错误);
  2. 即使是类之间的转换(如 ArrayListLinkedList,同属 List 子类型),父类型也不能直接转换为子类型,因为父类型引用的对象可能根本不是目标子类型的实例。

若要将父类型引用转回子类型,必须满足两个条件:

  1. 父类型引用的实际对象是目标子类型的实例
  2. 必须通过显式强制转型声明。

例如:

复制代码
List list = new ArrayList();  // 向上转型(自动)
ArrayList arrayList = (ArrayList) list;  // 向下转型(显式强制,合法,因为list实际指向ArrayList)

但如果实际对象类型不匹配,强制转型会抛出 ClassCastException

复制代码
List list = new LinkedList();  
ArrayList arrayList = (ArrayList) list;  // 运行时异常:LinkedList不能转为ArrayList
相关推荐
用户20187928316730 分钟前
Java序列化之幽灵船“Serial号”与永生契约
android·java
用户20187928316732 分钟前
“对象永生”的奇幻故事
android·java
周某某~35 分钟前
Rabbit MQ的消息模式-Java原生代码
java·分布式·rabbitmq
天天摸鱼的java工程师41 分钟前
如何实现一个分布式锁?——来自 Java 老兵的实战总结 🚀🔐
java·后端·面试
LZQqqqqo43 分钟前
C# XML 文件
xml·java·c#
big_eleven2 小时前
JVM入门基础
java·后端·面试
微笑听雨2 小时前
全面解读 ConcurrentHashMap:Java 中的高效并发数据结构
java·后端
微笑听雨2 小时前
深入解析 Java HashMap 的高性能扩容机制与树化优化
java·后端
未既2 小时前
java设计模式 -【责任链模式】
java·设计模式·责任链模式
云和数据.ChenGuang2 小时前
k8s-master03加入集群失败解决方法之一
java·容器·kubernetes