Java基础(一):语言基础与面向对象
Java基础(一):语言基础与面向对象
导语:Java 基础是面试的第一道门槛,重点考察面向对象思想与语言特性细节。本篇覆盖面向对象三大特性、基础语法与数据类型、抽象类与接口、final/static、重载与重写,共 25 题。
一、面向对象三大特性
1. 面向对象的三大特性是什么?请分别说明。
答: 三大特性为封装、继承、多态(有的资料把"抽象"也列为第四大特性)。
- 封装:将对象的属性和实现细节隐藏,仅对外提供公共访问方式(getter/setter、公共方法)。目的是隔离变化、提高复用性和安全性,降低耦合。
- 继承:子类复用父类的属性和方法,并可进行扩展或重写。Java 中类只能单继承(一个类只有一个直接父类),但可实现多个接口。继承是多态的前提。
- 多态:同一引用变量指向不同子类/实现类实例时,调用同一方法表现出不同行为。分为编译时多态(方法重载)和运行时多态(方法重写 + 向上转型/动态绑定)。运行时多态是面向对象最精髓的部分。
实现运行时多态的必要条件:继承(或实现接口)、方法重写、父类引用指向子类对象(向上转型)。
2. 封装的本质是什么?它解决了什么问题?
答: 封装的本质是信息隐藏——把数据和对数据的操作绑定在一起,外部只能通过预定义的接口访问,而看不到内部实现。它解决了:
- 防止外部随意修改内部状态,保证数据合法性与安全性;
- 降低模块间耦合,便于独立演进与维护;
- 隐藏复杂实现,对外提供简洁接口,提高易用性。
3. 继承有哪些需要注意的点?Java 为什么只支持类的单继承?
答:
- 子类拥有父类非 private 的属性和方法;可以新增自己的属性和方法;可以用自己的方式重写父类方法。
- 构造器不能被继承,但子类构造器必须通过
super()调用父类构造器(隐式或显式)。 - 单继承原因:若允许多继承,两个父类有同名方法或属性时会产生"菱形继承"歧义(C++ 需用虚继承解决)。Java 选择单继承以保证简洁和安全,同时用接口多实现弥补表达能力的不足。
- 注意:接口支持多继承(
interface A extends B, C),类只能extends一个父类,但可implements多个接口。
4. 什么是多态?Java 是如何实现多态的?
答: 多态指"编译时声明的引用类型,在运行时才确定具体指向哪个对象,并调用该对象实际类型的方法"。
Java 通过动态绑定(后期绑定)实现运行时多态:方法调用在运行期根据对象的实际类型(而非引用类型)决定。必要条件为继承/实现、方法重写、向上转型。
Animal a = new Cat(); // 向上转型
a.sound(); // 运行时实际调用 Cat.sound(),而非 Animal.sound()重载属于编译时多态(静态分派,依据参数列表在编译期确定)。
二、基础语法与数据类型
5. Java 的基本数据类型有哪些?各自占用空间与默认值?
答: 8 种基本类型:
| 类型 | 位数/字节 | 默认值 |
|---|---|---|
| byte | 8 位 (1B) | 0 |
| short | 16 位 (2B) | 0 |
| int | 32 位 (4B) | 0 |
| long | 64 位 (8B) | 0L |
| float | 32 位 (4B) | 0.0f |
| double | 64 位 (8B) | 0.0d |
| char | 16 位 (2B) | '\u0000' |
| boolean | JVM 规范未定死(通常 1B 或 1 位) | false |
注意:基本类型不是对象,存放在栈或作为对象字段在堆中;
boolean只有 true/false,不能用 0/1 替代(与 C/C++ 不同)。
6. 成员变量与局部变量的区别?
答:
- 作用域:成员变量属于对象/类,整个类可见;局部变量只在方法或代码块内有效。
- 存储位置:成员变量随对象存于堆(static 变量在方法区);局部变量在栈帧中。
- 生命周期:成员变量随对象生灭;局部变量随方法调用生灭。
- 初始值:成员变量有默认初值;局部变量必须显式赋值后才能使用,否则编译报错。
7. 静态变量(类变量)与实例变量的区别?
答:
- 静态变量用
static修饰,属于类,在方法区(元空间)只有一份,所有实例共享;实例变量属于对象,每个实例各有一份。 - 静态变量可通过
类名.变量直接访问,实例变量需通过对象访问。 - 静态变量随类加载而初始化,实例变量随对象创建而初始化。
8. 静态方法为什么不能访问非静态成员?
答: 静态方法属于类,调用时不依赖任何实例,在类加载阶段即可执行;而非静态成员(变量/方法)依赖于具体实例,必须有对象存在。静态方法中没有 this 引用,因此无法直接访问实例成员。反之,实例方法可以访问静态成员。
9. 构造方法有哪些特性?构造器能否被重写?能否重载?
答:
- 特性:与类同名、无返回类型、在
new时由 JVM 自动调用、用于初始化对象。 - 不能被重写(override):构造器不能被继承,所以不存在"子类重写父类构造器"。但可以重载(overload),一个类可有多个参数不同的构造器。
- 若未显式定义构造器,编译器会生成无参空构造器;一旦定义了任意构造器,默认无参构造器不再自动生成。
10. 为什么要在类中定义一个无参且"什么都不做"的构造方法?
答: 当类被其他类继承时,子类构造器必须先调用父类构造器。若父类只提供了有参构造器,子类若未显式 super(参数) 则编译报错。提供一个无参构造器可以避免这种约束,提升类作为父类时的可用性(许多框架如反射、序列化、ORM 也依赖无参构造器创建实例)。
11. 在一个静态方法内调用一个非静态成员,为什么是非法的?
答: 见第 8 题。静态方法调用时可能还没有任何实例,而非静态成员依附于实例,因此无法在静态上下文中直接引用。解决方式是先创建实例,再通过实例访问。
12. char 型变量能否存储一个中文汉字?为什么?
答: 大部分可以。char 采用 UTF-16 编码,占用 2 字节(一个 code unit),能表示 Unicode 基本多文种平面(BMP,U+0000~U+FFFF)中的字符,绝大多数常用汉字都在 BMP 内。但增补平面(如部分生僻字、emoji 表情)需要两个 char(代理对 surrogate pair)才能表示一个完整字符,此时单个 char 存不下。
13. import java 和 import javax 有什么区别?
答: 历史上 java.* 是核心包,javax.* 是扩展包(extension)。如今二者在功能上已无本质区别——随着 Java 发展,许多 javax 内容已成为标准(如 javax.annotation、javax.swing)。仅是命名约定:核心 API 在 java,扩展/标准扩展 API 在 javax,编译运行都无需额外配置。
14. 什么是方法的返回值?它的作用是什么?
答: 返回值是指方法执行完毕后返回给调用者的结果,通过 return 语句带回。作用:将数据从被调方法传递回调用者,使方法具备"计算并产出"的能力,支持方法间的组合与数据流。无返回值时用 void。
三、抽象类与接口
15. 抽象类和普通类有什么区别?抽象类能被 final 修饰吗?
答:
- 普通类不能包含抽象方法;抽象类可以包含抽象方法(也可以有具体方法)。
- 抽象类不能直接实例化,普通类可以。
- 含抽象方法的类必须声明为抽象类;但抽象类不一定含抽象方法。
- 抽象类不能用
final修饰:final表示不可继承,而抽象类存在的目的就是被继承并实现其抽象方法,二者语义冲突,编译报错。
16. 接口和抽象类的区别是什么?
答:
| 维度 | 抽象类 | 接口(Java 8+) |
|---|---|---|
| 关键字 | abstract class | interface |
| 继承/实现 | 类 extends,只能单继承 | 类 implements,可多实现;接口可多继承 |
| 构造器 | 可以有 | 不能有 |
| 成员变量 | 任意(实例/静态/常量) | 默认 public static final 常量 |
| 方法 | 抽象方法 + 具体方法,访问修饰符任意 | 抽象方法(public)、default、static,Java 9+ 还支持 private |
| 设计定位 | "is-a" 关系,模板式代码复用 | "can-do" 行为契约,解耦 |
纠正语料误区:现代接口不再是"所有方法都是抽象方法"——Java 8 引入了
default和static方法,Java 9 引入了私有方法;接口可以有静态方法(语料旧文中"接口不能有静态方法"已过时)。
17. 什么时候用抽象类,什么时候用接口?
答: 经验原则:
- 需要代码复用(多个子类共享状态或方法实现)且是"同一类事物"的抽象时,用抽象类。
- 需要定义行为契约、解耦调用方与实现、或需要多实现时,优先用接口。
设计理念:行为模型应优先通过接口定义;"接口优于抽象类"。只有在既要约束子类行为、又要提供通用实现时,才选抽象类。
18. 接口有什么特点?(综合)
答: 接口是抽象的、完全解耦的类型:
- 成员变量默认
public static final(常量); - 普通方法默认
public abstract; - Java 8 起可含
default(实例默认实现)、static方法;Java 9+ 可含private方法供内部复用; - 没有构造器,不能直接实例化;
- 一个类可实现多个接口,接口之间可多继承。
四、final 与 static
19. final、finally、finalize 有什么区别?
答:
final:修饰符。修饰类→不可继承;修饰方法→不可重写;修饰变量→引用不可变(基本类型值不可变,对象引用不能指向别的对象,但对象内部状态可变)。finally:异常处理关键字,位于try-catch后,无论是否异常通常都会执行,用于释放资源。finalize:Object的方法,GC 回收对象前调用,用于资源清理。已废弃(Java 9 标记 deprecated),不应依赖它做资源释放(可用 try-with-resources 代替)。
20. final 修饰引用类型变量时,究竟什么不可变?
答: final 保证的是引用地址不可变,即不能再指向其他对象,但对象内部的内容可以被修改:
final List<String> list = new ArrayList<>();
list.add("a"); // 合法,对象内容可变
// list = new ArrayList<>(); // 编译错误,引用不能改指21. static 关键字有哪些用法?
答:
- 修饰变量:类变量,全类共享一份;
- 修饰方法:类方法,无
this,只能访问静态成员; - 修饰代码块:静态初始化块,类加载时执行一次;
- 修饰内部类:静态内部类,不持有外部类引用;
- 修饰导入:
import static静态导入,直接引用静态成员。
22. 静态代码块、构造代码块、构造方法的执行顺序是怎样的?
答: 顺序为:静态代码块(类加载时,仅一次)→ 构造代码块(每次创建对象)→ 构造方法。static{} 优先于一切实例初始化,且整个类生命周期只执行一次。
五、重载与重写
23. 重载(Overload)和重写(Override)的区别?重载能否根据返回类型区分?
答:
| 维度 | 重载 Overload | 重写 Override |
|---|---|---|
| 发生位置 | 同一个类(也可在父子类) | 子类与父类之间 |
| 方法名 | 必须相同 | 必须相同 |
| 参数列表 | 必须不同(类型/个数/顺序) | 必须完全相同 |
| 返回类型 | 无要求 | 协变:子类返回值类型 ≤ 父类(可为父类返回值的子类) |
| 访问修饰符 | 无要求 | 子类 ≥ 父类 |
| 异常 | 无要求 | 子类抛出的受检异常 ≤ 父类 |
| 多态性 | 编译时(静态分派) | 运行时(动态绑定) |
重载不能仅通过返回类型区分——仅靠返回值不同、参数列表相同,编译器无法在调用处决定调用哪个方法,编译报错。
24. 构造器(constructor)能否被重写?能否重载?
答: 见第 9 题:不能被重写(构造器不遗传,不存在"子类重写父类构造器"),但可以重载。
25. 重写方法时,返回值类型、访问权限、抛出异常有什么约束?
答:
- 返回值:协变返回——子类方法返回值类型可以是父类方法返回值类型的子类(Java 5+ 支持)。
- 访问权限:子类方法不能比父类更严格,即≥ 父类(如父类
protected,子类可为public,不能为private)。 - 异常:子类方法抛出的受检异常范围≤ 父类(不能抛出父类没有的新受检异常或更宽的受检异常);运行时异常不受限。
- 若父类方法为
private,则子类不是"重写"而是"新定义"了一个方法。 - 父类方法为
final或static时不能被重写:final方法禁止重写;static方法是类级方法、不参与多态,子类可"隐藏"式地声明同名static方法(并非重写,调用时按引用类型决定)。
