跳到主要内容

面向对象

问题

Java 面向对象的三大特性是什么?抽象类和接口有什么区别?方法重载和重写的区别?

答案

三大特性

1. 封装(Encapsulation)

将数据和操作数据的方法绑定在一起,对外隐藏内部实现细节,只暴露必要的接口:

Account.java
public class Account {
// 字段私有化,外部无法直接访问
private double balance;

public Account(double initialBalance) {
if (initialBalance < 0) {
throw new IllegalArgumentException("初始余额不能为负");
}
this.balance = initialBalance;
}

// 通过公开方法控制访问,可以加入校验逻辑
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("存款金额必须为正");
this.balance += amount;
}

public double getBalance() {
return balance;
}
}

四种访问修饰符

修饰符同类同包子类不同包
private
默认(无修饰符)
protected
public

2. 继承(Inheritance)

子类继承父类的属性和方法,实现代码复用:

InheritanceDemo.java
public class Animal {
protected String name;

public void eat() {
System.out.println(name + " is eating");
}
}

public class Dog extends Animal {
public Dog(String name) {
this.name = name;
}

// 子类特有方法
public void bark() {
System.out.println(name + " is barking");
}
}
Java 继承的特点
  • 单继承:一个类只能继承一个父类(extends 单个类)
  • 多层继承:A → B → C 是允许的
  • 多实现:一个类可以实现多个接口(implements 多个接口)
  • 所有类的根类是 Object

3. 多态(Polymorphism)

同一个方法调用,根据对象的实际类型执行不同的实现:

PolymorphismDemo.java
public class Shape {
public double area() { return 0; }
}

public class Circle extends Shape {
private double radius;
public Circle(double radius) { this.radius = radius; }

@Override
public double area() { return Math.PI * radius * radius; }
}

public class Rectangle extends Shape {
private double width, height;
public Rectangle(double w, double h) { this.width = w; this.height = h; }

@Override
public double area() { return width * height; }
}

// 使用多态:统一接口,不同实现
Shape shape1 = new Circle(5); // 父类引用指向子类对象
Shape shape2 = new Rectangle(3, 4);
System.out.println(shape1.area()); // 78.54(运行时调用 Circle.area())
System.out.println(shape2.area()); // 12.0(运行时调用 Rectangle.area())

多态的实现条件

  1. 继承或实现接口
  2. 方法重写(Override)
  3. 父类引用指向子类对象

多态的绑定时机

  • 静态绑定(编译期):方法重载、private/static/final 方法
  • 动态绑定(运行期):方法重写,通过虚方法表(vtable)实现

方法重载 vs 方法重写

维度重载(Overload)重写(Override)
发生位置同一个类中子类与父类之间
方法名相同相同
参数列表必须不同必须相同
返回类型可以不同相同或协变(子类类型)
访问修饰符无要求不能比父类更严格
异常无要求不能抛出更宽泛的受检异常
绑定时机编译期运行期
OverloadVsOverride.java
// 重载:同一个类中,方法名相同,参数不同
public class Calculator {
public int add(int a, int b) { return a + b; }
public double add(double a, double b) { return a + b; }
public int add(int a, int b, int c) { return a + b + c; }
}

// 重写:子类覆盖父类方法
public class Animal {
public void speak() { System.out.println("..."); }
}
public class Cat extends Animal {
@Override // 推荐加此注解,编译器会校验
public void speak() { System.out.println("Meow!"); }
}

抽象类 vs 接口

维度抽象类(abstract class)接口(interface)
实例化不能不能
构造方法没有
成员变量任意变量只能 public static final 常量
方法可以有抽象和具体方法JDK 8 前只有抽象方法
默认方法JDK 8+ 支持 default 方法
静态方法支持JDK 8+ 支持
私有方法支持JDK 9+ 支持
继承/实现单继承多实现
设计理念is-a(是什么)has-a / can-do(能做什么)
AbstractVsInterface.java
// 抽象类:模板方法模式,定义骨架
public abstract class AbstractLogger {
// 具体方法:通用逻辑
public void log(String message) {
String formatted = formatTime() + " " + message;
doLog(formatted);
}

// 抽象方法:子类必须实现
protected abstract void doLog(String message);

private String formatTime() {
return LocalDateTime.now().toString();
}
}

// 接口:定义能力约定
public interface Serializable { }
public interface Comparable<T> {
int compareTo(T o);
}

// JDK 8 接口默认方法
public interface Collection<E> {
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
}
选择抽象类还是接口?
  • 抽象类:需要共享代码(模板方法)、需要非 public 成员、需要构造器
  • 接口:定义行为契约、需要多实现、定义混入能力(Mixin)
  • JDK 8+ 趋势:接口有了 default 方法后,很多以前需要抽象类的场景可以用接口代替

Object 类的常用方法

所有 Java 类都继承自 Object,其核心方法:

方法说明是否建议重写
equals(Object)判断对象是否相等
hashCode()返回哈希值是(与 equals 一起)
toString()返回字符串表示
getClass()获取运行时类型否(final)
clone()浅拷贝按需
finalize()GC 前调用否(已废弃)
wait()/notify()/notifyAll()线程通信
EqualsAndHashCode.java
public class Person {
private String name;
private int age;

// 重写 equals:比较字段内容
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}

// 重写 hashCode:保证 equals 相等的对象 hashCode 也相等
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
equals 和 hashCode 的契约
  • equals 相等的两个对象,hashCode 必须相等
  • hashCode 相等的两个对象,equals 不一定相等(哈希碰撞)
  • 重写 equals必须同时重写 hashCode,否则在 HashMap 等集合中会出问题

常见面试问题

Q1: 面向对象和面向过程的区别?

答案

维度面向过程面向对象
核心过程/函数对象
思维方式按步骤分解任务按对象/职责分解
数据与行为分离封装在一起
代码复用函数调用继承、组合、多态
扩展性修改函数新增子类
典型语言CJava、C++、Python

面向对象更适合复杂的业务系统,面向过程在性能敏感的底层代码中有优势。

Q2: Java 是值传递还是引用传递?

答案

Java 只有值传递。但需要区分:

  • 基本类型:传递值的拷贝,方法内修改不影响原变量
  • 引用类型:传递引用(指针)的拷贝,指向同一个对象。方法内可以修改对象的字段,但不能让原引用指向新对象
public void swap(Integer a, Integer b) {
Integer temp = a;
a = b;
b = temp;
// 这里只是交换了局部变量的引用,原始变量不变
}

Q3: 深拷贝和浅拷贝的区别?

答案

  • 浅拷贝clone() 默认行为,基本类型复制值,引用类型复制引用(指向同一个对象)
  • 深拷贝:递归复制所有引用对象,完全独立的副本

实现深拷贝的方式:

  1. 重写 clone() 方法,对引用类型字段逐个 clone
  2. 通过序列化/反序列化(ObjectOutputStreamObjectInputStream
  3. JSON 序列化/反序列化

Q4: finalfinallyfinalize 的区别?

答案

关键字用途
final修饰类(不可继承)、方法(不可重写)、变量(不可重新赋值)
finallytry-catch-finally 中的代码块,无论是否异常都会执行
finalizeObject 的方法,GC 前调用,已废弃(JDK 9+标记为 @Deprecated

Q5: 接口中可以有方法实现吗?

答案

JDK 8 之后可以:

  • default 方法:提供默认实现,实现类可选择重写
  • static 方法:通过接口名直接调用

JDK 9 还支持 private 方法,用于 default 方法之间共享代码。

public interface Logger {
void log(String msg); // 抽象方法

default void info(String msg) { // JDK 8 默认方法
log("[INFO] " + msg);
}

static Logger console() { // JDK 8 静态方法
return msg -> System.out.println(msg);
}

private String format(String msg) { // JDK 9 私有方法
return LocalDateTime.now() + " " + msg;
}
}

Q6: 为什么 Java 不支持多继承?

答案

多继承会引发菱形问题(Diamond Problem):如果 B 和 C 都继承 A,D 同时继承 B 和 C,当调用 D 中从 A 继承的方法时,到底用 B 还是 C 的版本?

Java 通过单继承 + 多接口实现来避免此问题。JDK 8 接口的 default 方法引入了类似的冲突可能,但有明确的解决规则:

  1. 类优先:类中的方法优先于接口的默认方法
  2. 子接口优先:更具体的接口默认方法优先
  3. 显式选择:如果有歧义,必须在实现类中显式重写

Q7: 成员变量和局部变量的区别?

答案

维度成员变量局部变量
位置类中,方法外方法/代码块中
存储堆(随对象)栈(随方法)
默认值有(0/null/false)无,必须初始化
作用域整个类方法/代码块内
修饰符可用访问修饰符、static 等只能用 final
生命周期随对象创建/销毁随方法调用/结束

Q8: thissuper 关键字的作用?

答案

  • this:引用当前对象

    • this.field:访问当前对象的字段(区别于同名局部变量)
    • this.method():调用当前对象的方法
    • this(args):在构造方法中调用本类的其他构造方法(必须在第一行)
  • super:引用父类

    • super.field:访问父类字段
    • super.method():调用父类方法(常用于重写中保留父类逻辑)
    • super(args):在构造方法中调用父类构造方法(必须在第一行)

this()super() 不能同时出现在同一个构造方法中。

相关链接