2026最全Java高频面试题汇总

张开发
2026/4/10 0:19:54 15 分钟阅读

分享文章

2026最全Java高频面试题汇总
Java的接口和抽象类区别1接口接口是Java语言中的一个抽象类型用于定义对象的公共行为。使用关键字interface实现在接口的实现中可以定义方法和常量其普通方法是不能有具体代码实现的。在JDK8之后接口中可以创建static和default方法了并且这两种方法可以有默认的方法实现。JDK8接口中可以定义static和default方法并且这两种方法都可以包含具体的代码实现实现接口要使用implements关键字接口不能直接实例化接口中定义的变量默认是 public static final 类型子类不可以重写接口中的static和default方法不重写的情况下默认调用的是接口的实现方法2抽象类抽象类使用关键字abstract实现。一个方法无法给出具体明确的该方法就可以声明为抽象类。抽象类使用abstract关键字声明抽象类中可以包含普通方法和抽象方法抽象方法不能有具体的代码实现抽象类需要使用extends关键字来继承抽象类不能直接实例化抽象类中属性控制符无限制可以定义private类型的属性3区别不同点​ 0**定义的关键字不同。**接口使用interface定义而抽象类使用abstract定义。并且子类对于接口使用的关键字是实现implement对于抽象类使用的是继承extends。​ 1**概念不同。**接口主要用于对类的行为进行约束实现了某个接口就具有了对应的行为。抽象类主要用于代码复用强调的是所属的关系。​ 2**类型扩展不同。**抽象类只能单继承而接口可以多实现。​ 3**方法访问控制符不同。**接口中的成员变量只能是public、static、final类型的不能被修改且必须要有初始值而抽象类的成员变量默认default可在子类中被重新定义也可被重新赋值。​ 4**内部方法的实现不同。**接口中普通方法没有实现但JDK8中static和default方法必须有实现而抽象类中普通方法必须有实现而抽象方法不能有实现。共同点​ 1都不能被实例化。2都可以包含抽象方法。3都可以有默认实现的方法Java8中可以使用default关键字在接口中定义默认方法。String 不可变String为什么不可变什么是不可变对象不可变对象遵守以下几条规则类内部所有的字段都是final修饰的类内部的所有字段都是私有的即被private所修饰类不能被集成和拓展类不能对外提供能够修改内部状态的方法类内部的字段如果是引用也就是说指向可变对象那么这个引用不可被获取String为什么不可变1String保存数据的char数组被final修饰且是私有的并且String类没有提供/暴露修改这个字符串的方法String的方法对字符串每次操作时都会返回一个新的字符串对象原有的字符串不会改变。2String类被final修饰导致其不能被继承进而避免了子类破坏String的不可变。String不可变的好处1字符串常量池的要求字符串常量池是方法区中的一个特殊的存储区。当一个字符串被创建并且该字符串已经存在于池中时将返回现有字符串的引用而不是创建一个新的对象。如果一个字符串是可变的用一个引用改变字符串的值会导致其他引用的错误。2缓存哈希值字符串的哈希码在Java中经常被使用例如在HashMap和HashSet中经常被使用不可变保证哈希码始终相同因此无需担心即可实现。每次使用哈希码时都无需计算。3安全性考虑String被广泛用作Java类的参数例如网络连接、打开文件等。如果String不是不可变的则连接或者文件将被更改这可能会导致严重的安全威胁。4线程安全问题因为不可变对象不能改变所以可以在多个线程之间自由共享这消除了对于同步的要求。String、StringBuilder、StringBuffer的区别String是不可变字符串而StringBuilder和StringBuffer是可变字符串。String字符串对象是不可变对象每次操作都会返回新的字符串原有字符串不会被改变StringBuilder和StringBuffer是可变字符串他们的字符串对象可以被更改对字符串的操作不会生成新的对象即对同一个字符串进行修改。StringBuilder 是线程不安全的适用于单线程环境而StringBuffer是线程安全的适用于多线程环境。它们两个都适用于大量字符串拼接的场景因为它们对于字符串的拼接操作来说不仅效率高而且不占用额外的空间。什么方法能改变String// 可以通过反射来改变String的值 String name xiaoao; Field field String.class.getDeclaredField(value); field.setAccessible(true); char[] values (char[]) field.get(name); System.out.println(name name.hashCode()); values[0] z; System.out.println(name name.hashCode()); // 输出 // xiaoao -759499955 // ziaoao -759499955运行项目并下载源码java运行虽然String的字符数组声明为final并且被private修饰但是这个final仅仅只是让value的引用不改变而不能让字符数组的字符不能改变。可以看到上面的代码即时字符串的值发生了改变但是它的hashCode仍然是一样的。private int hash; // Default to 0 public int hashCode() { int h hash; if (h 0 value.length 0) { char val[] value; for (int i 0; i value.length; i) { h 31 * h val[i]; } hash h; } return h; }运行项目并下载源码java运行为什么hashCode不会改变呢因为在第一次调用过hashCode之后字符串对象内通过hash这个属性缓存了hashCode的计算值只要缓存过之后就不会再计算了所以hashCode的值不会发生改变。StringBuilder和StringBuffer的线程安全问题场景在方法的内部一个String类型的List将这些元素用逗号分割拼接成一个完整的字符串然后作为方法的返回值返回这个方法可能在多线程的环境下然后用StringBuilder或者StringBuffer会产生线程安全问题吗1使用StringBuilder在多线程情况下拼接字符串会产生线程安全问题而StringBuffer则不会。2为什么StringBuilder会产生线程安全问题因为StringBuilder底层的append方法调用的其实是父类的append方法。而AbstractStringBuilder的append方法中有一个字符串长度的相加的操作count len这个操作不是一个原子操作首先从内存中加载count到寄存器在寄存器执行相加操作将结果写入内存在多线程环境下各个线程的操作的结果可能被其他线程给覆盖掉所以得到的结果不会是正确结果。而且还有可能会抛出下标越界异常这是因为StringBuilder会进行扩容操作如果在进行扩容操作的时候数组的长度被其他线程修改了那么可能扩容后的数组放不下新的字符串就会出现数组下标越界异常。篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、场景题、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】​​https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1hoString有长度限制吗String类型的对象是有长度限制的String对象并不能“存储”无限制长度的字符串。关于String的长度限制要从编译时限制和运行时限制两方面考虑。1编译时限制。对于String s abc这样的字符串声明字符串常量abc肯定会被放入方法区的常量池中String长度之所以会受到限制是因为JVM的规范对常量池有所限制。常量池中的每一种数据项都有自己的类型。Java中UTF-8编码的Unicode字符串在常量池中以CONSTANT_Utf8类型表示。CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }运行项目并下载源码java运行bytes数组就是真正存储常量数据的地方。而length就是数组可以存储的最大字节数。length的类型是u2u2是无符号的16位整数因此理论上允许的最大长度是216-1 65535。所以上面byte数组的最大长度是65535。//65535个d编译报错 java: 常量字符串过长 String s dd..dd; //65534个d编译通过 String s1 dd..d;运行项目并下载源码java运行起始Javac编译器的额外限制在Javac源代码中可以看到private void checkStringConstant(DiagnosticPosition var1, Object var2) { if (this.nerrs 0 var2 ! null var2 instanceof String ((String)var2).length() 65535) { this.log.error(var1, limit.string, new Object[0]); this.nerrs; } }运行项目并下载源码java运行代码中可以看出当参数类型为String并且长度大于等于65535的时候就会导致编译失败。注意String的限制并不是对字符串长度的限制而是对字符串底层存储的限制。Java中的字符串都是使用UTF8编码的UTF8编码使用1~4个字节来表示具体的Unicode字符。所以有的字符占1个字节而平常使用的中文需要3个字节来存储。//65534个a编译通过 String s1 aa..a; //21845个中文”自“,编译通过 String s2 好...好; // 一个英文字母a加上21845个中文”自“编译失败 java: 对于常量池来说, 字符串 a好好好好好好好好好好好好好好好好好好好... 的 UTF8 表示过长 String s3 a好...好;运行项目并下载源码java运行对于s1一个字母a的UTF8编码占用一个字节65534字母占用65534个字节长度是65534也没超过Javac的限制所以可以编译通过。对于s2一个中文占用3个字节21845个正好占用65535个字节而且字符串长度是21845并没有超过javac对长度的限制所以可以编译通过。对于s3一个英文字母a加上21845个中文”自“占用65535个字节超过了最常限制编译失败。2运行时限制String运行时的限制主要体现在String的构造函数上。// 分配一个新的Stringoffset参数是子数组的第一个字符的索引count参数指定子数组的长度 public String(char value[], int offset, int count) { ... }运行项目并下载源码java运行上面的count值也就是字符串的最大长度。在Java中int的最大长度是231-1。所以在运行时String的最大长度就是231-1。但是这个也是理论上的长度实际长度还是要看JVM的内存。一个最大的字符串会占用(2^31-1)*2*16/8/1024/1024/1024 4GB所以在最坏的情况下一个最大的字符串需要4GB的内存如果虚拟机不能分配这么多内存则直接报错。(2^31-1)*2*16/8/1024/1024/1024 4GB 在 Unicode 编码下每个字符通常占用 2 个字节16 位即使用 2 个字节表示一个字符。char占2个字节 由于 1 GB 1024 MB 1024 KB 1024 Bytes所以可将该字节数转换为单位 GB运行项目并下载源码3总结String的长度是有限制的编译期限制字符串的UTF8编码值的字节数不能超过65535字符串的长度不能超过65534。运行时限制字符串的长度不能超过231 - 1占用的内存数不能超过虚拟机能提供的最大值。泛型什么是泛型Java泛型是JDK5中引入的一个新特性。使用泛型参数可以增强代码的可读性以及稳定性。泛型的本质是为了参数化类型在不创建新的类型的情况下通过泛型指定的不同类型来控制形参具体限制的类型。**编译器可以对泛型参数进行检测并且通过泛型参数可以指定传入的对象类型。**比如ListPerson persons new ArrayList();这行代码就指明了ArrayList对象只能传入Person对象如果传入其他类型的对象就会报错。使用泛型的意义1适用于多种数据类型执行相同的代码即代码复用。2泛型中的类型在使用时指定不需要进行强制类型转换即类型安全编译器会检查类型。如何使用泛型泛型有三种使用方式分别为泛型类、泛型接口、泛型方法。? 无限制通配符 ? extends E extends 关键字声明了类型的上界表示参数化的类型可能是所指定的类型或者是此类型的子类 ? super E super 关键字声明了类型的下界表示参数化的类型可能是指定的类型或者是此类型的父类 // 使用原则《Effictive Java》 // 为了获得最大限度的灵活性要在表示 生产者或者消费者 的输入参数上使用通配符使用的规则就是生产者有上限、消费者有下限 1. 如果参数化类型表示一个 T 的生产者使用 ? extends T; 2. 如果它表示一个 T 的消费者就使用 ? super T 3. 如果既是生产又是消费那使用通配符就没什么意义了因为你需要的是精确的参数类型。运行项目并下载源码java运行篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、场景题、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】​​https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho如何理解Java中的泛型是伪泛型Java泛型这个特性是从JDK1.5才开始加入的因此为了兼容之前的版本Java泛型的实现采取了“伪泛型”的策略即Java在语法上支持泛型但是在编译阶段会进行所谓的“类型擦除”Type Erasure将所有的泛型表示尖括号中的内容都替换为具体的类型其对应的原生态类型就像完全没有泛型一样。泛型的类型擦除的原则是消除类型参数声明即删除及其包围的部分。根据类型参数的上下界推断并替换所有的类型参数为原生态类型如果类型参数是无限制通配符或没有上下界限定则替换为Object如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型即父类。为了保证类型安全必要时插入强制类型转换代码。自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”。List如果不用泛型会出现什么问题如果List不使用泛型那么默认的数据类型是Object。1需要强制类型转换。在想要获取对应的元素的类型时必须要进行强制转换。2可读性较差很可能会出现ClassCastException。在使用的时候如果不考虑兼容那么会出现类型转换异常。泛型的好处**1类型的安全。**泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制编译器可以在很高的程度上验证类型假设。**2消除强制类型转换。**泛型的一个附带好处是消除源代码中的许多强制类型转换。这使得代码更加可读并且减少了出错机会。**3潜在的性能收益。**泛型为较大的优化带来可能。在泛型的初始实现中编译器将强制类型转换插入生成的字节码中。但是更多类型信息可用于编译器这一事实为未来版本的 JVM 的优化带来可能。由于泛型的实现方式支持泛型几乎不需要 JVM 或类文件更改。所有工作都在编译器中完成编译器生成类似于没有泛型和强制类型转换时所写的代码只是更能确保类型安全而已。反射什么是反射反射是在运行状态中​ 对于任意一个类都能知道这个类的所有属性和方法​ 对于任意一个对象都能够调用它的任意一个方法和属性这种动态获取信息以及动态调用对象的方法功能称为Java语言的反射机制。反射机制的应用场景1JDBC中利用反射动态加载了数据库驱动程序。2Web服务器中利用反射调用了Servlet的服务方法。3很多框架都用到反射机制注入属性调用方法比如spring使用Component注解就可以声明一个类为SpringBean这就是基于反射获取类然后再获取类上的注解进行一定的逻辑梳理。除此之外除了使用new创建对象之外还可以使用Java反射创建对象。反射机制的优缺点1优点能够在运行时动态获取类的实例提高灵活性可以和动态编译相结合2缺点对性能有影响需要解析字节码将内存中的对象进行解析这类操作总是慢于直接执行Java代码3解决方法通过setAccessible(true)关闭JDK的安全检查来提升反射速度多次创建一个类的实例时有缓存会快很多可以使用ReflectASM工具类通过字节码生成的方式加快反射速度注解什么是注解Annotation(注解)是JDK5开始引入的新特性可以看作是一种特殊的注释主要用于修饰类、方法或者变量提供某些信息供程序在编译或者运行时使用。注解本质上就是继承了Antation接口。注解解析的方法注解只有被解析后才会生效常见的解析方法有两种编译期直接扫描编译器在编译Java代码的时候扫描对应的注解并处理比如某个方法使用Override注解编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。运行期通过反射处理像框架中自带的注解(比如 Spring 框架的Value、Component)都是通过反射来进行处理的。序列化什么是序列化和反序列化篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、场景题、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】​​https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中或者在网络传输 Java 对象这些场景都需要用到序列化。序列化 将数据结构或对象转换成二进制字节流的过程反序列化将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程要实现Java对象的序列化只需要实现接口Serializable。下面是序列化和反序列化常见应用场景对象在进行网络传输比如远程方法调用 RPC 的时候之前需要先被序列化接收到序列化的对象之后需要再进行反序列化将对象存储到文件之前需要进行序列化将对象从文件中读取出来需要进行反序列化将对象存储到数据库如 Redis之前需要用到序列化将对象从缓存数据库中读取出来需要反序列化序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。常见的序列化方式序列类型是否跨语言优缺点hession支持跨语言序列化后体积小速度较快protostuff支持跨语言序列化后体积小速度快但是需要Schema可以动态生成jackson支持跨语言序列化后体积小速度较快且具有不确定性fastjson支持跨语言支持较困难序列化后体积小速度较快只支持java c#kryo支持跨语言支持较困难序列化后体积小速度较快fst不支持跨语言支持较困难序列化后体积小速度较快兼容jdkjdk不支持序列化后体积很大速度快​不想序列化的字段**1transient。**对于不想进行序列化的变量使用transient关键字修饰。transient关键字的作用是阻止实例中那些用此关键字修饰的的变量序列化当对象被反序列化时被transient修饰的变量值不会被持久化和恢复。关于transient还有几点注意transient只能修饰变量不能修饰类和方法。transient修饰的变量在反序列化后变量值将会被置成类型的默认值。例如如果是修饰int类型那么反序列后结果就是0。2static。static变量因为不属于任何对象(Object)所以无论有没有transient关键字修饰均不会被序列化。**3默认方法。**也可以通过在目标类中定义默认的writeObject()和readObject()方法来实现指定的序列化字段没有被指定的字段不会被序列化。为什么不推荐使用JDK的序列化1不支持语言调用如果开发的时候是其他语言开发的服务就不支持JDK的序列化2性能差相比于其他序列化框架的性能更低主要原因是序列化之后的字节数组体积大导致传输成本加大。3存在安全问题序列化和反序列化本身并不存在问题但是当输入的反序列化的数据可以被用户控制那么攻击者可以通过恶意构造让反序列化产生非预期的对象在此过程种任意构造代码。

更多文章