前言
分类汇总 50+ 常见的 Java基础 经典后端面试题,并对题目进行了精炼总结,旨在帮助大家高效记忆,在面试中游刃有余,不至于陷入词穷的窘境。
Java 基础
JDK 和 JRE 和 JVM 分别是什么,有什么区别?
-
JDK:Java开发工具包,包含开发Java应用所需的所有工具和库,包括JRE和编译器等。
-
JRE:Java运行时环境,包含运行Java应用所需的库和JVM。
-
JVM:Java虚拟机,负责执行Java字节码,是Java跨平台的核心。
区别:JDK用于开发,包含JRE和开发工具;JRE用于运行Java程序,包含JVM和运行库;JVM是执行Java字节码的虚拟机。
什么是字节码?采用字节码的最大好处是什么?
字节码是 Java 程序编译后的中间代码,是⼆进制代码,可以跨平台运⾏,需JVM解释执行。
最大好处
-
可移植性:中间代码,确保Java程序在任何JVM上运行。
-
安全性:通过JVM验证,防止恶意代码执行。
-
可读性:可以进⾏反汇编和调试。
什么是OOP?
面向对象,是一种软件开发思想和方法。通过类和对象组织代码,实现数据与方法的封装、继承和多态。
面向对象的特点和优势?
特点:
-
封装性:隐藏对象内部细节,只暴露必要接口。
-
继承性:子类继承父类特性,实现代码复用和层次化管理。
-
多态性:同一接口,不同实现。
-
抽象性:提取共同特征,忽略细节。
-
模块化:将系统分解为独立模块。
优势: 灵活性和可扩展性,让软件的开发更加符合问题的本质,降低代码的复杂度和维护成本。
讲下你对 Java 构造方法的理解
Java 构造函数是用于初始化新创建对象的特殊方法。
-
名称与类名相同。
-
没有返回类型,也不能定义为void。
-
可以重载,即一个类可以有多个构造函数。
-
如果类没有显式定义构造函数,编译器会自动生成一个无参构造函数。
-
在创建对象时自动调用。
说出5个你常用的 Java 包
-
java.lang:提供基本类,如
String
、Object
、System
等,自动导入。 -
java.util:提供集合框架、日期时间工具等,如
List
、Map
、Date
等。 -
java.io:提供输入输出流相关类,如
File
、InputStream
、OutputStream
等。 -
javax.servlet:提供 Servlet 编程相关类,如
HttpServlet
、HttpServletRequest
、HttpServletResponse
等。 -
java.sql:提供数据库操作相关类,如
Connection
。
什么是重载和重写?
-
重载(Overloading):在同一个类中定义多个方法,它们具有相同的方法名但参数列表不同(参数类型、数量或顺序不同)。重载方法的返回类型可以相同也可以不同。
-
重写(Overriding):在子类中重新定义父类中已有的方法,方法名、参数列表和返回类型必须相同。重写方法可以提供不同的实现,但访问权限不能缩小。
final 关键字的作用
final关键字可以用于类、方法和变量。使变量的值不可变。方法不可被重写。类不可被继承。
final、finally 和 finalize 各有什么区别?
-
final:修饰符,用于声明不可变(常量)、不可继承(类)或不可重写(方法)。
-
finally:异常处理块,确保某些代码无论是否发生异常都会执行。
-
finalize:对象的终结方法,在对象被垃圾回收前由 GC 调用,不推荐使用。
静态变量与实例变量的区别?
-
静态变量:属于类,所有实例共享(全局变量);使用static关键字声明;通过类名访问。
-
实例变量:属于对象,每个实例独立;无需static关键字;通过对象实例访问。
==和equals最大的区别是?
-
==:是运算符,比较对象的内存地址,判断是否为同一对象。
-
equals:是Object的方法,比较对象的内容,由类定义其行为,通常用于判断内容是否相同。
String s = “” 与 new String() 的区别
-
String s = “”:创建一个空字符串,存储在常量池中,可以节省内存。
-
new String():创建一个新的String对象,存储在堆内存中,即使内容为空。
-
如果需要频繁地创建相同的字符串,使用字符串字面量可能更合适;如果需要动态地创建具有不同值的字符串,使用 new String() 可能更灵活。
String str=“abc”和String str=new String(“abc”)会产生几个对象?
-
String str=“abc”:1个对象,存储在常量池。
-
String str=new String(“abc”):2个对象,1个在常量池,1个在堆内存。
Switch能否用string做参数?
java 7之前不行,java 7及以上版本可以。
Java访问修饰符public、private、protected,以及无修饰符的区别?
-
public:可以被任何类或对象访问。
-
private:只能被定义该成员的类访问,其他类无法访问。
-
protected:可以被当前类、子类和同一个包中的类访问。
-
默认(无修饰符):可以被同一个包中的类访问。
什么叫序列化,如何序列化?
序列化: 将对象转化为字节流的过程,便于存储和传输。
如何序列化:
-
实现Serializable接口。
-
使用ObjectOutputStream将对象写入流。
-
为每个可序列化的类定义一个serialVersionUID字段。
什么叫反序列化?如何反序列化?
反序列化: 把字节序列恢复为对象的过程称为对象的反序列化。
如何反序列化:
-
实现Serializable接口。
-
使用ObjectInputStream读取序列化后的对象。
接口和抽象类有什么区别?
-
接口: 仅定义方法签名,无实现;类可实现多个接口;不能有实例变量。
-
抽象类: 可包含抽象和非抽象方法;类只能继承一个抽象类;可以有实例变量。
什么是内部类?
内部类: 定义在另一个类内部的类,可以访问外部类的成员。类型分为:
-
成员内部类:定义在类内部,与成员变量同级。
-
静态内部类:使用static修饰,只能访问外部类的静态成员。
-
局部内部类:定义在方法或代码块内部。
-
匿名内部类:没有名字,通常用于实现接口或抽象类。
Java 中的参数传递是按值还是按引用?
-
基本数据类型:按值传递,传递的是变量的副本。
-
对象:按引用传递,传递的是对象引用的副本,实际对象内容共享。
Exception 和 Error 有什么区别?
-
Exception: 运行时异常,程序运行时可预料的异常情况,可捕获和处理。如
NullPropagation
。 -
Error: 受检异常,通常由JVM产生,不可捕获和处理,如
OutOfMemoryError
。
throw和throws的区别?
-
throw:用于方法内部,手动抛出异常对象。
-
throws:用于方法声明,声明该方法可能抛出的异常类型。
String、StringBuffer 和 StringBuilder 的区别是什么?
-
String:不可变,每次修改创建新对象,线程安全。
-
StringBuffer:可变,线程安全,性能较低。
-
StringBuilder:可变,非线程安全,性能较高。
StringBuilder 的实现
-
可变性:内部使用字符数组(char[])存储数据,长度可动态调整。
-
性能优化:通过数组扩容机制减少内存分配次数,提高性能。
-
非线程安全:不涉及同步操作,适用于单线程环境。
引用类型(包装类型)和基本类型的区别?
-
基本类型:如int、char,存储在栈中,性能高,无方法。
-
包装类型:如Integer、Double,存储在堆中,性能较低,有方法和属性。
什么是装箱和拆箱?
-
装箱:将基本数据类型的值转换为对应的包装类类型。
-
拆箱:将包装类类型的值转换回基本数据类型。
java的8种基本数据类型?
-
byte:字节类型,占用1个字节,范围为-128 到 127。
-
short:短整型,占用2个字节,范围为-2^15 到 2^15 -1。
-
int:整型,占用4个字节,范围为-2^31 到 2^31 -1。
-
long:长整型,占用8个字节,范围为-2^63 到 2^63-1。
-
float:单精度浮点型,占用4个字节,范围为-2^31 到 2^31 -1,尾数精度为7位有效数字。
-
double:双精度浮点型,占用8个字节,范围为-2^63 到 2^63 -1,尾数精度为15位有效数字。
-
char:字符型,占用2个字节,表示Unicode编码字符,范围为0到2^15 -1。
-
boolean:布尔型,占用1位,取值为true或false。
什么是隐式转换和显示转换?
隐式转换:小范围到大范围,自动转换,无数据丢失。例如:int i = 10; double d = i
。
显式转换:大范围到小范围,需强制转换,可能数据丢失。例如:double d = 10.5; int i = (int) d
。
用double,1.0-0.9的结果不是0.1,为什么?
使用double类型表示小数时,由于浮点数的存储方式和精度限制,可能会出现精度损失问题。因此,1.0-0.9的结果并不严格等于0.1。
BigDecimal与Double的差别?
-
精确性:BigDecimal 提供精确的数值计算,避免了浮点数运算的精度问题。
-
可控性:可以精确控制小数点后的位数和舍入方式。
-
适合金融计算:在需要高精度计算的场景(如金融领域),BigDecimal 是更好的选择。
使用Bigdecimal有哪些注意事项?
-
构造方式:避免使用浮点数构造 BigDecimal,应使用字符串构造以确保精度。
-
性能:BigDecimal 的性能比基本类型差,尽量避免在大数据量和高频计算中使用。
-
舍入模式:选择合适的舍入模式(如 ROUND_HALF_UP)以满足业务需求。
-
不可变性:BigDecimal 是不可变对象,每次运算都会返回新对象,注意内存和性能开销。
标识符命名规则
-
字母、数字、下划线和美元符号:标识符可以包含字母(A-Z, a-z)、数字(0-9)、下划线(_)和美元符号($)。
-
不能以数字开头:标识符不能以数字开头。
-
区分大小写:标识符是区分大小写的。
-
不能是关键字:标识符不能是 Java 关键字(如 int, class, public 等)。
-
长度无限制:标识符长度没有限制,但应保持合理长度以便阅读和维护。
你使用过哪些 JDK 提供的工具?
-
javac:编译器,将Java源代码编译成字节码。
-
java:运行Java应用程序。
-
jdb:Java调试器,用于调试Java程序。
-
jconsole:图形化工具,监控和管理Java应用程序的性能。
-
jvisualvm:图形化工具,监控、故障排除和分析Java应用程序。
-
jmap:生成堆转储快照,分析内存使用情况。
-
jstack:生成线程转储,分析线程状态。
-
jstat:监控JVM统计信息,如垃圾回收、类加载等。
-
jar:创建和管理Java归档文件(JAR文件)。
说一下Java对象创建过程?
-
类加载:检查类是否已加载,未加载则进行类加载。
-
分配内存:在堆中为新对象分配内存。
-
初始化零值:将分配的内存空间初始化为零值。
-
设置对象头:设置对象头信息,如类元数据、哈希码等。
-
执行init方法:调用构造方法进行对象初始化。
说一下Java的类加载过程?
-
加载:从文件或网络加载字节码,生成 Class 对象。
-
链接:包括验证、准备和解析。
- 验证:确保字节码正确和安全。
- 准备:分配静态变量内存并初始化默认值。
- 解析:将符号引用替换为直接引用。
-
初始化:执行类构造器 方法,初始化静态变量和静态代码块。
介绍下双亲委派模型的工作过程?
当需要一个类的时候,子类加载器依次去请求父类加载器加载,一直往上请求到最高类加载器:启动类加载器。当启动类加载器加载不了的时候,依次往下让子类加载器进行加载,当达到最底下的时候,如果还是加载不到该类,就会出现ClassNotFoundException
的情况。
equals和HashCode的区别?
-
equals:用于比较两个对象是否相等,默认实现是比较对象引用。
-
hashCode:返回对象的哈希码,用于在哈希表中快速查找对象。
-
如果两个对象通过equals方法比较相等,它们的hashCode值必须相同。两个对象的hashCode值相同,不一定通过equals方法比较相等(哈希冲突)。
什么是哈希冲突?
哈希冲突是指在哈希表(如 HashMap、HashSet)中,不同的键(key)经过哈希函数计算后得到相同的哈希值,导致它们被映射到哈希表的同一个位置。解决哈希冲突的方法包括链地址法(如 Java 的 HashMap 使用链表或红黑树)和开放地址法(如线性探测、二次探测)。
什么是泛型?
允许在定义类、接口和方法时使用类型参数,使得代码可以处理多种数据类型,同时保持类型安全。
什么是泛型擦除?
Java 泛型在编译时会进行类型擦除,将泛型类型转换为实际类型,生成的字节码中不包含泛型信息。实现了泛型的类型安全性和代码复用性。
什么是泛型的上下界限定符?
泛型的上下界限定符用于限制泛型类型参数的范围:
-
上界限定符(<? extends T>):表示类型参数必须是 T 或 T 的子类。
-
下界限定符(<? super T>):表示类型参数必须是 T 或 T 的父类。
Java 8 的新特性有哪些?
-
Lambda表达式:允许以更简洁的方式编写匿名函数。
-
函数式接口:只有一个抽象方法的接口,提供了
@FunctionalInterface
注解; -
方法引用:可以将存在的方法作为变量来传递使用。
-
接口默认方法:允许在接口中定义带有实现的方法。
-
Optional容器:用于更好地处理可能为 null 的值,减少
NullPointerException
。 -
Stream API:提供了一种高效且易于并行处理集合数据的方式。。
什么是反射?
允许程序在运行时检查和操作对象的类型、属性和方法。通过反射,可以获取类的信息、检查和修改对象的属性、调用对象的方法以及创建新的对象实例。
反射机制的意义
允许程序在运行时动态检查和操作对象的类型、属性和方法,提供了极大的灵活性和动态性。
反射的主要用途
-
框架和库的开发:例如 Spring 框架使用反射来实现依赖注入。
-
调试和测试工具:帮助开发者检查和操作对象的内部状态。
-
动态代理:在运行时创建代理对象,实现 AOP(面向切面编程)。
获取Class的三种方式
-
通过类名的静态属性 class,如
Class<MyClass> clazz = MyClass.class
。 -
通过对象的实例方法 getClass,如
new MyClass().getClass()
。 -
通过类名的字符串 forName,如
Class.forName("com.example.MyClass")
。
Java中注解的原理是什么?
Java 注解原理基于反射机制,通过在编译时或运行时读取注解信息,实现对代码的元数据处理。注解本身不直接影响代码执行,但可以通过工具或框架在运行时解析和利用这些元数据,实现如配置管理、代码生成、日志记录等功能。
在你的项目中,是如何应用反射的?
-
框架开发:应用的Spring框架使用了反射实现依赖注入。
-
动态代理:实现AOP实现功能的增强。
-
调试和测试工具:用来帮助检查和操作对象的内部状态。
什么是映射关系?
映射关系是指两个集合之间元素的对应关系,通常表示为一个集合中的每个元素都唯一对应另一个集合中的一个元素。在编程中,映射关系常用 Map 接口实现,如 HashMap
、TreeMap
等。
什么是代理?
代理是一种常用的设计模式,它允许通过一个代理对象来控制对另一个对象的访问。代理对象可以在调用真实对象的方法前后执行额外操作,如权限检查、日志记录等。常见的代理实现方式包括静态代理和动态代理(如 JDK 动态代理和 CGLIB 代理)。
什么是 Java 中的静态代理?
静态代理是指在编译时就已经确定的代理类。静态代理类需要手动编写,它实现了与目标对象相同的接口,并在代理类中持有一个目标对象的引用。
什么是 Java 中的动态代理?
动态代理是指在运行时动态生成的代理类。动态代理不需要手动编写代理类,而是通过Java的反射机制在运行时创建代理对象。
JDK 动态代理和 CGLIB 动态代理有什么区别?
-
JDK 动态代理:基于接口,要求目标对象实现至少一个接口。
-
CGLIB 动态代理:基于类,通过生成目标类的子类来实现代理,不要求目标对象实现接口。
什么是 Java 的深拷贝和浅拷贝?
-
浅拷贝:复制对象时,只复制对象的基本数据类型字段,引用类型字段仍指向原对象的引用。
-
深拷贝:复制对象时,不仅复制基本数据类型字段,还递归复制引用类型字段,生成全新的对象图。
Java 的 I/O 流 有哪些?
-
字节流:以字节为单位进行读写,如
InputStream
和OutputStream
。 -
字符流:以字符为单位进行读写,如
Reader
和Writer
。 -
缓冲流:提供缓冲区以提高读写效率,如
BufferedInputStream
和BufferedOutputStream
。 -
数据流:提供读写基本数据类型和字符串的方法,如
DataInputStream
和DataOutputStream
。 -
对象流:用于序列化和反序列化对象,如
ObjectInputStream
和ObjectOutputStream
。
你会如何判断一个常量是废弃常量和一个类是无用的类?
运行时常量池主要回收的是废弃的常量。
-
代码中未使用:通过静态代码分析工具(如 SonarQube、FindBugs)检查常量是否在代码中被引用。
-
文档和注释:检查常量是否有相关文档或注释说明其已废弃。
-
版本控制历史:查看版本控制历史,确认常量是否在某个版本后不再使用。
方法区主要回收的是无用的类。
-
未被引用:通过类加载器检查类是否在运行时被加载,或通过静态代码分析工具检查类是否被其他类引用。
-
无实例化:检查类是否有实例化对象,或是否有静态方法被调用。
-
无子类:检查类是否有子类继承。
-
无接口实现:检查类是否实现任何接口。
你能解释一下Java中多态的实现原理吗?
Java 中的多态是通过方法重写(Overriding)和动态绑定(Dynamic Binding)来实现的。
-
方法重写:子类重写父类的方法,提供不同的实现。
-
动态绑定:在运行时根据对象的实际类型确定调用的方法,而不是根据引用变量的类型。