2025年高频java基础面试题及答案_第1页
2025年高频java基础面试题及答案_第2页
2025年高频java基础面试题及答案_第3页
2025年高频java基础面试题及答案_第4页
2025年高频java基础面试题及答案_第5页
已阅读5页,还剩17页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

2025年高频java基础面试题及答案1.面向对象的三大特性是什么?请结合实际场景说明多态的实现机制面向对象的三大核心特性是封装、继承和多态。封装通过访问控制(private、protected、public)隐藏对象内部状态,仅暴露必要接口;继承允许子类复用父类代码并扩展新功能;多态则指同一行为在不同对象上表现不同形态。多态的实现依赖于方法重写(Override)和动态绑定(DynamicBinding)。例如定义抽象类Animal,包含抽象方法cry(),子类Cat和Dog分别重写该方法。当通过Animal类型引用指向Cat对象时(Animala=newCat()),调用a.cry()会在运行时根据实际对象类型调用Cat的cry()方法,这一过程由JVM通过对象头的类型指针查找方法表(vtable)实现动态分派。2.抽象类和接口的区别是什么?Java8之后接口有哪些新特性?抽象类可包含抽象方法和具体方法,有构造方法,子类通过extends单继承;接口只能定义抽象方法(Java8前)或默认方法、静态方法(Java8后),无构造方法,类通过implements多实现。Java8为接口引入default方法(提供方法默认实现,解决接口升级时子类必须重写的问题)和static方法(可直接通过接口名调用);Java9增加private方法(用于封装接口内多个default方法的公共逻辑);Java16引入密封接口(sealedinterface),限制可实现该接口的类。例如:```javapublicinterfaceMyInterface{defaultvoiddefaultMethod(){commonLogic();//调用private方法}privatevoidcommonLogic(){System.out.println("公共逻辑");}}```3.HashMap的底层数据结构是什么?JDK7和JDK8有哪些主要差异?HashMap底层采用“数组+链表+红黑树”的复合结构。数组作为哈希表的主干(table数组),每个元素是链表或红黑树的头节点。当插入元素时,通过hash(key)&(n-1)计算桶索引(n为数组长度),若发生哈希冲突则以链表形式挂载;当链表长度≥8且数组长度≥64时,链表转换为红黑树(O(n)查询优化为O(logn));当红黑树节点数≤6时退化为链表。JDK7与JDK8的核心差异:数据结构:JDK7是“数组+链表”,JDK8引入红黑树;插入方式:JDK7采用头插法(可能导致扩容时链表反转引发死循环),JDK8采用尾插法(避免多线程扩容的环链问题);hash计算:JDK7的hash()方法通过4次异或+位移扰动,JDK8简化为(h=key.hashCode())^(h>>>16)(保留高16位参与计算,减少低位碰撞);扩容条件:JDK7先判断是否需要扩容再插入,JDK8先插入再判断是否需要扩容。4.说说ArrayList和LinkedList的区别,以及如何选择?ArrayList基于动态数组实现,支持O(1)时间的随机访问(通过索引定位),但插入/删除元素(非尾部)需要移动后续元素,时间复杂度O(n);LinkedList基于双向链表实现,节点包含prev和next指针,插入/删除元素只需修改相邻节点指针(O(1)时间,若已知节点位置),但随机访问需遍历链表(O(n)时间)。选择建议:频繁随机访问(如遍历、按索引查询)选ArrayList;频繁插入/删除(尤其是中间位置)选LinkedList;ArrayList默认初始容量10,扩容时新容量为原1.5倍(oldCapacity+(oldCapacity>>1)),大量数据时需注意扩容开销;LinkedList无容量限制,但节点额外占用内存(每个节点存储两个指针)。5.解释synchronized的底层实现和锁升级过程synchronized是Java的内置锁,基于对象头的Monitor(监视器)实现。Java对象头包含MarkWord(存储哈希码、GC分代年龄、锁状态等)和类型指针(指向类元数据)。锁升级过程为:偏向锁:线程首次访问同步块时,MarkWord存储该线程ID(偏向状态),后续无竞争时直接进入同步块(无CAS操作);轻量级锁:其他线程尝试获取锁时,偏向锁升级为轻量级锁(当前线程栈帧中创建LockRecord,MarkWord指向该记录),通过CAS竞争锁;若CAS失败,线程自旋尝试获取(避免立即挂起);重量级锁:自旋超过一定次数(或JVM认为竞争激烈),升级为重量级锁,依赖操作系统Mutex锁,未获取锁的线程进入阻塞状态(切换到内核态,开销大)。锁状态仅能升级不能降级,目的是减少无竞争场景下的性能损耗。6.谈谈volatile的作用和底层实现volatile是轻量级同步机制,保证变量的可见性和禁止指令重排序,但不保证原子性。可见性指当一个线程修改了volatile变量,其他线程能立即看到最新值(通过内存屏障实现);禁止重排序指编译器和CPU不能对volatile变量的读写操作进行指令重排(通过插入内存屏障限制指令顺序)。底层实现依赖JVM的内存屏障(MemoryBarrier):写volatile变量时,JVM会在写操作后插入StoreStore屏障(保证前面的写操作先于volatile写完成)和StoreLoad屏障(防止volatile写与后续读操作重排);读volatile变量时,在读取前插入LoadLoad屏障(防止volatile读与前面的读操作重排)和LoadStore屏障(防止volatile读与后续写操作重排)。典型应用场景:状态标志(如线程终止标志位)、单例模式的双重检查锁定(DCL)中防止指令重排导致的空指针问题:```javapublicclassSingleton{privatestaticvolatileSingletoninstance;//volatile防止指令重排privateSingleton(){}publicstaticSingletongetInstance(){if(instance==null){synchronized(Singleton.class){if(instance==null){instance=newSingleton();//禁止"分配内存-初始化对象-引用赋值"重排为"分配内存-引用赋值-初始化对象"}}}returninstance;}}```7.线程池的核心参数有哪些?拒绝策略有哪几种?线程池通过ThreadPoolExecutor创建,核心参数包括:corePoolSize:核心线程数(即使空闲也不会销毁,除非设置allowCoreThreadTimeOut为true);maximumPoolSize:最大线程数(核心线程+临时线程的最大数量);keepAliveTime:临时线程的空闲存活时间(超过此时间会被销毁);unit:keepAliveTime的时间单位;workQueue:任务队列(存储待执行的任务,类型有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue等);threadFactory:线程工厂(用于创建线程,可自定义线程命名、优先级等);handler:拒绝策略(当任务队列满且线程数达到maximumPoolSize时,对新任务的处理方式)。拒绝策略默认有4种:AbortPolicy(默认):抛出RejectedExecutionException异常;CallerRunsPolicy:由调用线程(提交任务的线程)直接执行该任务;DiscardPolicy:静默丢弃新任务,无任何提示;DiscardOldestPolicy:丢弃任务队列中最旧的任务(队首),然后尝试重新提交新任务。例如,使用LinkedBlockingQueue(无界队列)时,若corePoolSize线程处理速度慢于任务提交速度,队列会无限增长,可能导致OOM;而SynchronousQueue(同步队列)不存储任务,每个任务必须立即被线程处理,因此maximumPoolSize需设置较大以避免拒绝。8.JVM的内存区域如何划分?各区域的作用和常见异常是什么?JVM内存分为线程私有和线程共享区域:线程私有区域:程序计数器(ProgramCounterRegister):记录当前线程执行的字节码行号(分支、循环、跳转等依赖此实现),唯一无OOM的区域;虚拟机栈(JavaVirtualMachineStack):存储栈帧(局部变量表、操作数栈、动态链接、方法出口等),每个方法调用对应一个栈帧入栈。若线程请求的栈深度超过限制(如递归未终止),抛StackOverflowError;若栈扩展失败(动态扩展场景),抛OutOfMemoryError(OOM);本地方法栈(NativeMethodStack):为本地方法(如用C实现的方法)服务,HotSpot虚拟机将其与虚拟机栈合并,同样可能抛StackOverflowError或OOM。线程共享区域:堆(Heap):存储对象实例和数组,是GC的主要区域。若对象分配内存超过堆最大容量,抛OOM;方法区(MethodArea):存储类元数据(类名、字段、方法、常量池等)、静态变量、JIT编译后的代码等。JDK7前实现为永久代(PermGen),依赖堆内存;JDK8后改为元空间(MetaSpace),使用本地内存(NativeMemory),存储位置变更减少了永久代的OOM问题(如字符串常量池移至堆)。若元空间内存不足(如加载大量类),抛OOM。此外,运行时常量池(RuntimeConstantPool)是方法区的一部分,存储编译期提供的字面量和符号引用(JDK7后字符串常量池移至堆)。9.垃圾回收的常见算法有哪些?G1收集器和ZGC的核心特点是什么?常见垃圾回收算法:标记-清除(Mark-Sweep):标记存活对象,清除未标记对象。缺点是产生内存碎片;标记-复制(Mark-Copy):将内存分为两块,标记存活对象复制到另一块,清空原块。缺点是可用内存减半(适用于存活对象少的场景,如新生代);标记-整理(Mark-Compact):标记存活对象后,将其向内存一端移动并整理,避免碎片(适用于存活对象多的场景,如老年代)。G1(Garbage-First)收集器(JDK7u4引入)特点:堆划分:将堆分为多个大小相等的Region(默认2048个),部分Region标记为Eden、Survivor、Old,还有HumongousRegion(存储大对象,超过Region50%大小);并发标记:通过RememberedSet(记录跨Region引用)避免全堆扫描,减少STW(StopTheWorld)时间;混合收集:优先回收垃圾最多的Region(Garbage-First策略),可预测停顿时间(通过-XX:MaxGCPauseMillis设置目标)。ZGC(JDK11引入)特点:基于Region的堆划分(支持动态调整大小);颜色指针(ColoredPointers):在64位指针中存储4位标志位(标记对象是否被访问、是否移动等),无需在对象头存储GC信息;读屏障(LoadBarrier):在访问对象引用时动态更新指针(解决并发转移时的指针不一致问题);极低停顿时间(≤10ms),支持TB级堆内存(适用于大内存场景)。10.String、StringBuilder、StringBuffer的区别是什么?String的intern()方法在JDK6和JDK7后的行为有何不同?String是不可变类(内部通过finalchar[]value存储字符),每次修改都会提供新对象;StringBuilder(JDK1.5)和StringBuffer(JDK1.0)是可变字符序列,内部通过char[]数组动态扩容(初始容量16,扩容为原容量×2+2)。区别:线程安全:StringBuffer的方法用synchronized修饰(线程安全),StringBuilder无同步(线程不安全,性能更高);适用场景:单线程字符串拼接用StringBuilder,多线程用StringBuffer,少量修改用String。Sern()方法作用是将字符串实例放入字符串常量池,并返回池中的引用。JDK6与JDK7+的差异:JDK6:字符串常量池在永久代,intern()会复制字符串实例到永久代,返回永久代中的新对象;JDK7+:字符串常量池移至堆,intern()若发现池已存在该字符串(内容相同),则返回堆中已有实例的引用(无需复制);若不存在,则将当前实例的引用存入常量池并返回。示例:```javaStrings1=newString("abc");//堆中创建对象,"abc"字面量在常量池Strings2=ern();//JDK6中s2指向永久代的"abc",s1!=s2;//JDK7+中s2指向常量池中的"abc"(即字面量的引用),s1!=s2(s1是堆中新建的对象),但若执行Strings3=newString("a")+newString("bc"),则ern()可能返回s3本身(若常量池无"abc"时,将s3的引用存入常量池)。```11.反射的作用是什么?如何通过反射访问私有方法?反射允许程序在运行时获取类的信息(如字段、方法、构造器)并操作其对象,是框架(如Spring、MyBatis)实现依赖注入、动态代理的核心。获取Class对象的方式有:Class.forName("全类名")(静态方法,需处理ClassNotFoundException);对象.getClass()(如"abc".getClass());类名.class(如String.class)。访问私有方法步骤:1.获取Class对象;2.通过getDeclaredMethod(Stringname,Class<?>...parameterTypes)获取方法(包括私有方法);3.调用method.setAccessible(true)关闭访问检查;4.通过method.invoke(Objectobj,Object...args)执行方法(obj为方法所属实例,静态方法obj为null)。示例:```javapublicclassReflectDemo{privatevoidprivateMethod(Stringmsg){System.out.println(msg);}publicstaticvoidmain(String[]args)throwsException{Class<?>clazz=ReflectDemo.class;ReflectDemoobj=newReflectDemo();Methodmethod=clazz.getDeclaredMethod("privateMethod",String.class);method.setAccessible(true);method.invoke(obj,"通过反射调用私有方法");//输出:通过反射调用私有方法}}```12.枚举(Enum)的底层实现是什么?为什么枚举单例是线程安全的?枚举的底层是final类,继承自java.lang.Enum,每个枚举常量是该类的静态final实例。编译器会为枚举提供values()和valueOf()方法(values()返回所有枚举常量的数组,valueOf()根据名称获取常量)。例如:```javapublicenumColor{RED,GREEN,BLUE}//编译后等价于:publicfinalclassColorextendsEnum<Color>{publicstaticfinalColorRED=newColor("RED",0);publicstaticfinalColorGREEN=newColor("GREEN",1);publicstaticfinalColorBLUE=newColor("BLUE",2);privatestaticfinalColor[]VALUES={RED,GREEN,BLUE};publicstaticColor[]values(){returnVALUES.clone();}publicstaticColorvalueOf(Stringname){...}privateColor(Stringname,intordinal){super(name,ordinal);}}```枚举单例线程安全的原因:枚举类的实例在类加载时由JVM创建(类加载过程线程安全,由类加载器的锁机制保证),且构造方法私有(防止外部实例化)。此外,反射无法通过newInstance()创建枚举实例(会抛IllegalArgumentException),反序列化时也不会创建新实例(反序列化机制保证返回已存在的枚举常量),因此枚举是实现单例的最安全方式。13.解释Lambda表达式和函数式接口的关系,并举出4种常用的函数式接口Lambda表达式是Java8引入的函数式编程特性,用于简化函数式接口的匿名内部类写法。函数式接口是仅含一个抽象方法的接口(可用@FunctionalInterface注解标记,编译器会检查),Lambda表达式通过类型推断匹配该抽象方法的参数和返回值。常用函数式接口:Predicate<T>:断言型接口,抽象方法booleantest(Tt)(判断条件,如过滤集合元素);Consumer<T>:消费型接口,抽象方法voidaccept(Tt)(处理数据,如遍历集合时执行操作);Function<T,R>:函数型接口,抽象方法Rapply(Tt)(转换数据,如将String转为Integer);Supplier<T>:供给型接口,抽象方法Tget()(提供数据,如创建对象)。示例(使用Predicate过滤列表中的偶数):```javaList<Integer>numbers=Arrays.asList(1,2,3,4,5);Predicate<Integer>isEven=n->n%2==0;List<Integer>evens=numbers.stream().filter(isEven).collect(Collectors.toList());```14.泛型的类型擦除是什么?为什么不能创建泛型类型的数组?泛型是编译期特性,用于保证类型安全(如集合只能存储指定类型元素),但在运行时泛型信息会被擦除(TypeErasure),替换为原始类型(RawType)。例如List<String>和List<Integer>在运行时都是List类型,JVM无法区分。不能创建泛型类型数组的原因:数组在运行时会检查元素类型(动态类型检查),而泛型类型擦除后无法确定具体类型,可能导致类型不一致。例如:```javaList<String>[]listArray=newList<String>[10];//编译错误//若允许,可能存入List<Integer>,运行时数组类型检查失败(但泛型擦除后List<String>和List<Integer>都是List,无法在运行时检测)```替代方案是使用List<List<String>>或通过反射创建。15.动态代理的实现方式有哪些?JDK动态代理和CGLIB的区别是什么?动态代理是运行时提供代理类,用于扩展目标对象的功能(如AOP中的日志、事务)。实现方式主要有JDK动态代理(基于接口)和CGLIB(基于继承)。JDK动态代理:依赖java.lang.reflect.Proxy和InvocationHandler接口;只能为实现了接口的类提供代理(代理类实现相同接口,调用方法时通过InvocationHandler的invoke()方法拦截);示例:```javapublicinterfaceUserService{voidaddUser();

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论