JAVA基础面试题——最常问面试题(1)

发布于 2025-09-02 16:16:40 浏览 29 次

Java 基础面试题

1. JDK动态代理和CGLIB动态代理有什么区别?

· 原理不同:JDK 动态代理基于接口实现,通过生成接口的匿名实现类代理目标方法;CGLIB 基于继承实现,通过生成目标类的子类并重写方法实现代理。

· 适用场景:JDK 动态代理要求目标类实现接口,否则无法代理;CGLIB 可代理未实现接口的类,但不能代理 final 类和 final 方法。

· 性能差异:JDK 动态代理在 JDK8 + 后性能优化较好,生成代理类更快;CGLIB 生成代理类时字节码操作更复杂,但运行时调用效率略高。

· 依赖不同:JDK 动态代理是 JDK 自带功能,无需额外依赖;CGLIB 需要引入第三方库。

2. 什么是 Java 的多态特性?

Java 的多态特性,核心是 “同一行为在不同对象上表现出不同实现”,即同一方法调用,因对象类型不同,执行逻辑会动态适配,本质是 “行为的动态绑定”,主要通过以下 3 个条件实现:

· 前提基础:需存在继承(子类继承父类)或实现(类实现接口)关系,这是多态的结构基础;

· 核心动作:子类需重写父类(或接口)的方法(方法名、参数列表、返回值兼容需一致),定义差异化实现;

· 关键操作:通过向上转型(父类 / 接口引用指向子类对象),调用方法时,JVM 会动态判断实际对象类型,执行子类的重写方法,而非父类的原方法。

举个简单例子:

· 定义父类Animal,含方法void makeSound();

· 子类Cat重写该方法为 “喵喵叫”,子类Dog重写为 “汪汪叫”;

· 代码中用Animal animal = new Cat()(向上转型),调用animal.makeSound()时,会执行Cat的实现;若animal = new Dog(),则执行Dog的实现 —— 这就是多态的直观体现。

3. Java 中的参数传递是按值还是按引用?

Java 中参数传递的规则很明确:始终是按值传递(pass-by-value),不存在按引用传递。

具体表现为:

· 对于基本类型(如 int、char):传递的是实际值的副本,方法内修改形参不会影响外部实参。

· 对于引用类型(如对象、数组):传递的是对象内存地址的副本,形参和实参指向同一个对象。因此,通过形参修改对象的属性会影响原对象,但修改形参的指向(如重新赋值 new 对象)不会改变实参的指向。

本质区别:按引用传递允许直接修改实参的指向,而 Java 中无法做到这一点,所以本质仍是按值传递,只是引用类型传递的 "值" 是地址而已。

4. 为什么 Java 不支持多重继承?

Java 不支持类的多重继承(即一个类不能同时继承多个父类),核心原因是为了避免多重继承带来的 “菱形问题”(Diamond Problem),并简化语言设计、降低开发复杂度,具体可从以下 3 点理解:

·解决 “菱形问题” 的冲突
若类 A 有一个方法 func(),类 B 和类 C 都继承自 A 并重写了 func(),此时若类 D 同时继承 B 和 C,调用 D.func() 时,JVM 无法判断应执行 B 还是 C 的 func()—— 这就是多重继承的核心矛盾(菱形问题)。
Java 为避免这种二义性,直接禁止类的多重继承,从根源上消除冲突。

·简化语言设计与开发逻辑
多重继承会让类的继承关系变得复杂(如 “继承链混乱”“方法优先级难以定义”),开发者需额外处理大量冲突场景(如显式指定调用哪个父类的方法),增加学习和维护成本。
Java 选择 “单继承 + 接口” 的方案:类仅支持单继承(保证继承链清晰),同时通过接口(可多实现)满足 “多行为扩展” 的需求,既规避冲突,又兼顾灵活性。

·接口替代方案更优
接口仅定义方法签名(无具体实现),一个类可实现多个接口(如 class D implements B, C)。即使多个接口有同名方法,Java 8+ 也通过 “默认方法优先级规则”(如 “类中的方法优于接口默认方法”“实现的接口中显式指定则优先”)解决冲突,且接口不涉及属性继承,不会引发类似类多重继承的资源冲突问题,是更安全的 “多能力扩展” 方式。

5. Java 面向对象编程与面向过程编程的区别是什么?

Java 面向对象编程(OOP)与面向过程编程(POP)的核心区别体现在设计思想和组织方式上:

核心思想:

·面向过程:以 “步骤 / 过程” 为中心,将问题拆解为一系列函数(操作),按顺序执行。
·面向对象:以 “对象” 为中心,将数据(属性)和操作数据的方法封装在对象中,通过对象交互解决问题。

组织方式:

·面向过程:用函数串联流程,数据与函数分离(如 C 语言的函数操作外部变量)。
·面向对象:通过类定义对象模板,利用封装、继承、多态组织代码,强调数据与行为的绑定。

灵活性与复用性:

·面向过程:适合简单流程,修改需调整步骤,复用性较差(函数复用有限)。
·面向对象:适合复杂系统,通过继承、多态实现代码复用,易扩展和维护(如修改对象内部逻辑不影响外部调用)。

6. Java 方法重载和方法重写之间的区别是什么?

定义场景:

·重载:同一类中,多个方法同名但参数列表(类型、个数、顺序)不同;
·重写:子类中,定义与父类同名、同参数列表、同返回值(或子类兼容类型)的方法,覆盖父类实现。

作用与目的:

·重载:允许同一功能有不同参数形式(如 add(int a) 和 add(int a, int b)),方便调用;
·重写:子类根据自身需求修改父类方法的实现,体现多态。

修饰符与限制:

·重载:与返回值、访问修饰符无关,只需参数列表不同;
·重写:子类方法访问权限不能严于父类,不能抛出更宽泛异常,且父类方法不能是 final(无法重写)。

7. 什么是Java内部类?它有什么作用?

Java 内部类是定义在另一个类内部的类,可分为成员内部类、局部内部类、匿名内部类、静态内部类。

作用主要有:

增强封装:内部类可直接访问外部类私有成员,同时外部类也能访问内部类私有成员,且内部类对其他类隐藏。

便于实现多继承:一个内部类可继承一个类,外部类通过内部类间接获得该类功能,弥补 Java 单继承局限。

简化代码结构:适合定义仅在当前类使用的辅助类,如监听器、迭代器等,使代码更紧凑。

8. JDK8有哪些新特性?

JDK8(Java 8)引入了多项里程碑式特性,极大改变了 Java 编程方式,核心包括:

·Lambda 表达式:允许以简洁代码块表示匿名函数,简化函数式编程(如(a,b) -> a+b),配合函数式接口使用。

·Stream API:提供高效的集合数据处理方式(如过滤、映射、聚合),支持链式操作和并行处理,简化复杂集合运算(如list.stream().filter(xxx).collect(...))。
函数式接口:带@FunctionalInterface注解的单方法接口(如Runnable、Consumer),为
Lambda 提供目标类型。

·接口默认方法与静态方法:接口可定义带实现的default方法(避免实现类强制修改)和static方法,增强接口扩展性。

·新日期时间 API:java.time包(如LocalDateTime、Instant),线程安全且易用,替代传统Date和Calendar。

·其他优化:如Optional类(减少空指针异常)、方法引用(::语法复用已有方法)、 Nashorn JavaScript 引擎等。

这些特性显著提升了代码简洁性、可读性和开发效率,尤其强化了 Java 对函数式编程的支持。

9. Java中String、StringBuffer和StringBuilder的区别是什么?

·可变性

String:不可变(底层是 final 字符数组),每次修改都会创建新对象,原对象不变;
StringBuffer 和 StringBuilder:可变(字符数组无 final 修饰),修改时直接操作内部数组,不创建新对象。

·线程安全性

String:天然线程安全(不可变特性);
StringBuffer:线程安全(方法加了 synchronized 锁);
StringBuilder:非线程安全(无锁,性能更高)。

·性能

频繁修改字符串时,String 性能最差(大量创建新对象);
StringBuilder 性能优于 StringBuffer(无锁开销);
仅当单线程或无需线程安全时,优先用 StringBuilder。

10.Java的StringBuilder 是怎么实现的?

StringBuilder 基于可变字符数组实现,核心机制如下:

·底层用非 final 的 char[] 存储字符,通过 count 记录实际字符数,支持直接修改数组内容(区别于 String 的不可变)。

·初始容量默认 16,可通过构造器指定;容量不足时自动扩容(默认当前容量 ×2+2,不足则用实际需求值),通过数组拷贝实现。

·所有修改操作(append/insert/delete 等)直接操作字符数组,不创建新对象,效率高。
无同步锁(区别于 StringBuffer),非线程安全,单线程下性能更优。

·调用 toString() 时,基于当前字符数组生成新 String 对象(避免共享底层数组)。

11.Java中包装类型和基本类型的区别是什么?

本质不同:

基本类型是数据值本身(如 int 存 4 字节整数),直接存储在栈内存;
包装类型是对象,存储在堆内存,变量持有的是对象引用。

默认值不同:

基本类型有默认值(如 int 为 0,boolean 为 false);
包装类型默认值为 null。

使用场景不同:

基本类型适合简单数值计算,效率更高;
包装类型用于需要对象的场景(如集合框架、泛型、反射等),支持 null 值表示 “无数据”。

功能差异:

包装类型提供了类型转换(如 Integer.parseInt ())、比较等方法;
基本类型仅能直接进行算术运算。

自动装箱 / 拆箱:

基本类型与对应包装类型可自动转换(如 int ↔ Integer),本质是编译器自动插入 valueOf ()/intValue () 等方法调用。

12.接口和抽象类有什么区别?

接口和抽象类的核心区别体现在设计目的、实现机制和使用场景上:

定义与实现:

·抽象类:用abstract修饰,可包含抽象方法(无实现)和具体方法(有实现),允许定义成员变量。
·接口:用interface修饰,Java 8+ 可包含抽象方法、默认方法(default)和静态方法,成员变量默认是public static final(常量)。

继承 / 实现规则:

·抽象类:一个类只能继承一个抽象类(单继承),子类需重写所有抽象方法(除非自身也是抽象类)。
·接口:一个类可实现多个接口(多实现),需重写所有抽象方法(默认方法可选择性重写)。

设计意图:

·抽象类:体现 “is-a” 关系,用于抽取类的共性(包括属性和行为),强调继承体系的统一性。
·接口:体现 “has-a” 能力,用于定义类的行为规范(不关注属性),强调功能扩展的灵活性。

构造器:

·抽象类有构造器(供子类调用);
·接口无构造器(不能实例化)。

13.JDK和JRE有什么区别?

JDK(Java Development Kit)和 JRE(Java Runtime Environment)的核心区别在于功能定位和包含内容:

功能定位:

·JDK 是开发工具包,面向开发者,提供 Java 程序的开发、编译、调试等工具;
·JRE 是运行时环境,面向用户,提供 Java 程序运行所需的环境支持。

包含内容:

·JDK 包含 JRE,还额外提供编译器(javac)、调试工具(jdb)、文档工具(javadoc)等开发工具,以及 Java 开发的核心类库源码;
·JRE 仅包含运行 Java 程序的必要组件:JVM(Java 虚拟机)、核心类库(如 rt.jar)、其他支持文件。

简单说:开发 Java 程序必须装 JDK,运行 Java 程序只需装 JRE(JDK 已包含 JRE,装了 JDK 也能运行程序)。

14.Java中hashCode和equals方法是什么?它们与 == 操作符有什么区别?

在Java中,hashCode和equals是对象比较的两种不同机制,而==操作符用于比较基本类型或引用类型变量的值或引用。

区别与作用:

· ==操作符‌

‌基本类型‌:比较变量的值是否相等(如int、float等)。
‌引用类型‌:比较两个变量是否指向同一个对象(即内存地址是否相同)。 ‌
· equals方法‌

默认情况下,比较对象的内存地址(与==相同)。
可被重写以比较对象内容是否相等(如String、Integer等)。 ‌
· hashCode方法‌

返回对象的哈希码(整数),用于哈希表(如HashMap)快速定位对象。
相等对象必须拥有相同的哈希码,但哈希码相同不一定表示对象相等(存在哈希冲突)。 ‌

**注意事项
·覆盖equals时必须同步覆盖hashCode,否则可能导致哈希表异常。 ‌
·使用==比较非基本类型变量可能导致错误结果,应优先使用equals。**

15.Java中的hashCode和equals方法之间有什么关系?

· 核心契约:若 a.equals (b) 为 true,则 a.hashCode () 必须等于 b.hashCode ();反之,hashCode 相等时 equals 不一定为 true(哈希碰撞)。

· 重写要求:重写 equals 必须同时重写 hashCode,否则哈希表(如 HashMap)会出现逻辑错误(如无法去重)。

equals() 决定 “对象是否逻辑相等”,hashCode() 决定 “对象在哈希表中的存储位置”;两者必须配套重写,且严格遵守 “相等对象必同哈希,同哈希对象未必相等” 的规则,否则会导致哈希相关集合(HashMap、HashSet 等)功能异常。

16.什么是Java中的动态代理?

动态代理类是Java语言中位于java.lang.reflect包下的编程机制,通过InvocationHandler接口和Proxy类实现。其核心功能是在运行时动态生成代理类,替代实际对象执行接口方法,并允许在调用前后插入自定义操作。

动态代理的实现依赖于两个关键类:InvocationHandler接口定义了invoke方法,用于处理代理实例的方法调用;Proxy类提供newProxyInstance方法,通过指定类加载器、接口数组及调用处理器生成代理对象。开发者需实现InvocationHandler并在其中调用被代理对象的实际方法(如method.invoke(sub, args)),同时可添加预处理或后处理逻辑。客户端通过反射机制获取代理类构造函数并创建实例,使被代理对象与接口能在运行时灵活替换,实现动态控制关系。

0 条评论

发布
问题