目录
[1.1 什么是抽象类](#1.1 什么是抽象类)
[1.2 抽象类的语法规则](#1.2 抽象类的语法规则)
[1.3 抽象类的核心特性](#1.3 抽象类的核心特性)
[1.4 抽象类的实战使用](#1.4 抽象类的实战使用)
[1.5 抽象类的设计意义](#1.5 抽象类的设计意义)
[2.1 什么是接口](#2.1 什么是接口)
[2.2 接口的语法规则](#2.2 接口的语法规则)
[2.3 接口的使用:implements 实现](#2.3 接口的使用:implements 实现)
[2.4 接口的核心特性](#2.4 接口的核心特性)
[2.5 类的多实现:突破单继承限制](#2.5 类的多实现:突破单继承限制)
[2.6 接口的多继承:规范的组合](#2.6 接口的多继承:规范的组合)
[2.7 接口的设计意义](#2.7 接口的设计意义)
[三、抽象类 VS 接口:核心区别与使用场景](#三、抽象类 VS 接口:核心区别与使用场景)
[四、Object 类:所有 Java 类的 "根"](#四、Object 类:所有 Java 类的 "根")
[4.1 toString ():获取对象的字符串表示](#4.1 toString ():获取对象的字符串表示)
[4.2 equals ():对象内容的比较](#4.2 equals ():对象内容的比较)
[4.3 hashCode ():对象的哈希码](#4.3 hashCode ():对象的哈希码)
[4.4 其他常用方法](#4.4 其他常用方法)
在 Java 面向对象编程中,抽象类、接口是实现多态和代码解耦的核心机制,而 Object 类作为所有类的根类,为 Java 对象提供了基础行为规范。这三个知识点不仅是日常开发的高频考点,也是面试中考察面向对象思想的重点。本文将从概念、语法、特性、使用场景出发,结合实战代码深入讲解,让你彻底掌握这些核心知识点。
一、抽象类:为子类搭建骨架的 "模板类"
1.1 什么是抽象类
在面向对象的世界里,并非所有类都能描绘具体的对象。如果一个类只包含了子类的通用特征,却无法独立创建实例(因为其部分方法没有具体实现),这样的类就是抽象类。
简单来说:抽象类是为子类提供统一模板的类,它包含了抽象方法(无实现体)和普通方法(有实现体),本身不能被实例化,只能被子类继承并实现其抽象方法。
生活类比:水果是一个抽象的概念,苹果、香蕉是具体的水果。水果有 "吃" 的行为,但无法定义具体怎么吃;而苹果可以 "洗了咬着吃",香蕉可以 "剥了皮吃"------ 这里的 "水果类" 就是抽象类,"吃" 就是抽象方法。
1.2 抽象类的语法规则
使用abstract关键字修饰类和方法,抽象方法没有方法体,以分号结尾。
java
// 抽象类:用abstract修饰
public abstract class Fruit {
// 普通属性
protected String name;
// 构造方法:抽象类可以有构造方法,供子类初始化父类成员
public Fruit(String name) {
this.name = name;
}
// 抽象方法:无实现体,子类必须重写
public abstract void eat();
// 普通方法:有实现体,子类可直接使用
public void showName() {
System.out.println("这是:" + this.name);
}
}
1.3 抽象类的核心特性
- 不能直接实例化 :
Fruit fruit = new Fruit("水果");编译直接报错,抽象类只是模板,无法创建具体对象。 - 抽象方法不能是 private :抽象方法需要被子类重写,private 修饰的方法子类无法访问,因此
abstract private void eat();非法。 - 抽象方法不能被 final/static 修饰:final 方法不能被重写,static 方法属于类而非实例,均与抽象方法的 "子类重写" 特性冲突。
- 子类必须重写所有抽象方法:如果子类不重写,那么子类也必须被定义为抽象类。
- 抽象类可以包含普通方法 / 属性 / 构造方法:构造方法用于子类创建对象时初始化父类的成员变量。
1.4 抽象类的实战使用
定义具体的子类继承抽象类,并重写抽象方法:
java
// 具体子类:苹果,继承水果抽象类
public class Apple extends Fruit {
public Apple(String name) {
super(name); // 调用父类构造方法
}
// 必须重写抽象方法eat()
@Override
public void eat() {
System.out.println(name + ":洗干净咬着吃,嘎嘣脆!");
}
}
// 具体子类:香蕉
public class Banana extends Fruit {
public Banana(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(name + ":剥了皮吃,软糯香甜!");
}
}
// 测试类
public class TestFruit {
public static void main(String[] args) {
Fruit apple = new Apple("红富士苹果");
apple.showName(); // 调用父类普通方法
apple.eat(); // 调用子类重写的抽象方法
Fruit banana = new Banana("小米蕉");
banana.showName();
banana.eat();
}
}
运行结果:
这是:红富士苹果
红富士苹果:洗干净咬着吃,嘎嘣脆!
这是:小米蕉
小米蕉:剥了皮吃,软糯香甜!
1.5 抽象类的设计意义
有人会问:普通类也能被继承,普通方法也能被重写,为什么非要用抽象类?
核心答案:编译器的强制校验。如果用普通类作为父类,不小心直接实例化父类时编译器不会报错;而抽象类会直接阻止实例化,让问题在编译阶段暴露,避免运行时错误。
抽象类的设计思想是 **"模板方法模式"**:将通用逻辑放在抽象类,具体实现放在子类,保证子类的一致性,同时提高代码复用性。
二、接口:行为的规范,实现多态的 "桥梁"
2.1 什么是接口
接口是多个类的公共行为规范 ,是一种引用数据类型,它只定义行为的标准,不定义行为的具体实现。接口的核心作用是实现解耦 和多实现,弥补 Java 中类 "单继承" 的限制。
生活类比:USB 接口是一种规范,U 盘、鼠标、键盘只要遵循这个规范,就能插在电脑上使用。电脑不需要关心设备具体是什么,只需要知道设备符合 USB 规范 ------ 这就是接口的 "面向规范编程"。
2.2 接口的语法规则
使用interface关键字定义接口,接口中的方法默认是public abstract,变量默认是public static final(全局常量)。
java
// 定义接口:命名建议以I开头,形容词词性
public interface USB {
// 全局常量:默认public static final
String VERSION = "USB3.0";
// 抽象方法:默认public abstract,可省略修饰符
void connect(); // 连接设备
void disconnect(); // 断开设备
}
2.3 接口的使用:implements 实现
接口不能直接实例化,需要通过实现类 使用implements关键字实现接口,并重写所有抽象方法。
java
// 实现类:U盘,实现USB接口
public class UDisk implements USB {
private String brand;
public UDisk(String brand) {
this.brand = brand;
}
// 重写接口的所有抽象方法,修饰符必须是public
@Override
public void connect() {
System.out.println(brand + "U盘,遵循" + VERSION + "规范,成功连接电脑!");
}
@Override
public void disconnect() {
System.out.println(brand + "U盘安全断开连接!");
}
// 实现类的独有方法
public void transferFile() {
System.out.println("U盘正在传输文件...");
}
}
// 实现类:鼠标,实现USB接口
public class UsbMouse implements USB {
private String type;
public UsbMouse(String type) {
this.type = type;
}
@Override
public void connect() {
System.out.println(type + "鼠标,遵循" + VERSION + "规范,成功连接电脑!");
}
@Override
public void disconnect() {
System.out.println(type + "鼠标断开连接!");
}
// 独有方法
public void click() {
System.out.println("鼠标左键单击!");
}
}
2.4 接口的核心特性
- 不能实例化 :
USB usb = new USB();编译报错。 - 方法默认是 public abstract:不能用 private、protected 修饰,重写时也必须是 public。
- 变量默认是 public static final:必须赋初始值,且不能被修改。
- 无构造方法和静态代码块:接口不是类,没有实例化的过程,因此不需要构造方法。
- 实现类必须重写所有抽象方法:否则实现类必须定义为抽象类。
- JDK8 + 新特性 :支持
default方法(有实现体,子类可重写)和static方法(类方法,通过接口名调用)。
JDK8+ default 方法示例:
java
public interface USB {
String VERSION = "USB3.0";
void connect();
void disconnect();
// default方法:有实现体,子类可直接使用或重写
default void showInfo() {
System.out.println("这是一个" + VERSION + "接口设备");
}
}
2.5 类的多实现:突破单继承限制
Java 中类与类之间是单继承 ,但一个类可以实现多个接口,用逗号分隔,这是接口最核心的价值之一。
示例:定义 "会跑"、"会飞"、"会游泳" 三个接口,让 "鸭子" 实现这三个接口:
java
// 定义三个行为接口
public interface IRunning {
void run();
}
public interface IFlying {
void fly();
}
public interface ISwimming {
void swim();
}
// 鸭子类:实现多个接口
public class Duck implements IRunning, IFlying, ISwimming {
private String name;
public Duck(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name + "用两条腿快速跑!");
}
@Override
public void fly() {
System.out.println(name + "扇动翅膀低空飞!");
}
@Override
public void swim() {
System.out.println(name + "浮在水面用脚蹼游!");
}
}
// 测试
public class TestDuck {
public static void main(String[] args) {
Duck duck = new Duck("唐老鸭");
duck.run();
duck.fly();
duck.swim();
}
}
运行结果:
唐老鸭用两条腿快速跑!
唐老鸭扇动翅膀低空飞!
唐老鸭浮在水面用脚蹼游!
2.6 接口的多继承:规范的组合
接口与接口之间可以通过extends实现多继承,将多个接口的规范组合成一个新接口,实现规范的复用。
java
// 组合接口:两栖动物 = 会跑 + 会游泳
public interface IAmphibious extends IRunning, ISwimming {
}
// 青蛙类:实现两栖动物接口,只需重写run和swim
public class Frog implements IAmphibious {
@Override
public void run() {
System.out.println("青蛙一蹦一跳地跑!");
}
@Override
public void swim() {
System.out.println("青蛙蹬着后腿在水里游!");
}
}
2.7 接口的设计意义
接口的核心设计思想是 **"面向接口编程"**,让程序只依赖于行为规范,而不依赖于具体实现,从而降低代码耦合度,提高扩展性。
比如开发一个支付系统,定义IPay接口,包含pay()方法,然后实现Alipay、WechatPay、UnionPay等实现类。当需要新增支付方式时,只需新增实现类,无需修改原有代码,完美符合开闭原则。
三、抽象类 VS 接口:核心区别与使用场景
抽象类和接口都是实现多态的重要方式,很多初学者容易混淆,我们从结构、权限、继承 / 实现、使用场景四个维度做核心对比:
| 对比维度 | 抽象类(abstract class) | 接口(interface) |
|---|---|---|
| 结构组成 | 普通类 + 抽象方法,可包含普通方法 / 属性 / 构造方法 | 抽象方法 + 全局常量,JDK8 + 支持 default/static 方法 |
| 访问权限 | 支持 public/protected/default/private | 方法默认 public,变量默认 public static final |
| 类的关联方式 | 子类用 extends单继承 | 实现类用 implements多实现 |
| 接口间关系 | 可实现多个接口 | 可 extends 多个接口(多继承) |
| 设计思想 | 体现is-a的继承关系,为子类搭骨架 | 体现has-a的特性关系,定义行为规范 |
核心使用场景
- 使用抽象类 :当多个类之间存在继承关系,且有通用的属性和方法时,用抽象类封装通用逻辑。比如:水果类→苹果类 / 香蕉类,动物类→猫类 / 狗类。
- 使用接口 :当多个类之间无继承关系,但有共同的行为时,用接口定义行为规范。比如:U 盘 / 鼠标 / 键盘都有 USB 行为,鸭子 / 飞机 / 鸟都有飞的行为。
一句话总结 :抽象类管 "是什么",接口管 "能做什么"。
四、Object 类:所有 Java 类的 "根"
Java 中除了 Object 类本身,所有类都直接或间接继承 Object 类,Object 类是 Java 类体系的根节点。所有对象都可以用 Object 类型接收,它提供了 Java 对象的基础行为,核心方法如下:
4.1 toString ():获取对象的字符串表示
Object 类中toString()的默认实现是:类名@十六进制哈希码,比如com.test.Apple@1b6d3586,这个结果对我们没有实际意义,因此子类通常需要重写 toString (),用于返回对象的具体属性信息。
示例:
java
public class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
// 重写toString()
@Override
public String toString() {
return "Student{name='" + name + "', score=" + score + "}";
}
}
// 测试
public class Test {
public static void main(String[] args) {
Student s = new Student("张三", 90);
System.out.println(s); // 等价于System.out.println(s.toString())
}
}
运行结果 :Student{name='张三', score=90}
4.2 equals ():对象内容的比较
在 Java 中,==的作用是:
- 比较基本类型 :比较的是值是否相等;
- 比较引用类型 :比较的是地址是否相等(是否是同一个对象)。
而 Object 类中的equals()方法默认实现就是==,即比较地址。如果我们需要比较对象的内容是否相等,就必须重写 equals () 方法。
示例:
java
public class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
// 重写equals(),比较name和score是否都相等
@Override
public boolean equals(Object obj) {
// 地址相同,直接返回true
if (this == obj) return true;
// obj为null或类型不同,返回false
if (obj == null || getClass() != obj.getClass()) return false;
// 向下转型,比较属性
Student student = (Student) obj;
return score == student.score && Objects.equals(name, student.name);
}
}
// 测试
public class Test {
public static void main(String[] args) {
Student s1 = new Student("张三", 90);
Student s2 = new Student("张三", 90);
System.out.println(s1 == s2); // false:地址不同
System.out.println(s1.equals(s2)); // true:内容相同
}
}
4.3 hashCode ():对象的哈希码
hashCode()方法返回一个 int 类型的哈希码,用于标识对象的存储位置(底层与哈希表相关)。Object 类中 hashCode () 是本地方法,由 C/C++ 实现,默认返回对象的内存地址相关值。
核心规则:
- 如果两个对象的
equals()返回 true,那么它们的hashCode()必须返回相同的值; - 如果两个对象的
hashCode()返回相同的值,equals()不一定返回 true(哈希冲突); - 重写
equals()时,必须重写 hashCode (),否则会违反上述规则。
示例 :使用Objects.hash()快速生成哈希码
java
public class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public boolean equals(Object obj) {
// 省略实现,同上文
}
// 重写hashCode()
@Override
public int hashCode() {
return Objects.hash(name, score); // 根据属性生成哈希码
}
}
// 测试
public class Test {
public static void main(String[] args) {
Student s1 = new Student("张三", 90);
Student s2 = new Student("张三", 90);
System.out.println(s1.hashCode()); // 2441392
System.out.println(s2.hashCode()); // 2441392
}
}
4.4 其他常用方法
clone():创建对象的拷贝,分为浅拷贝 和深拷贝 ,使用前需要实现Cloneable接口;getClass():返回对象的运行时类,用于反射;wait()/notify()/notifyAll():用于多线程的同步通信;finalize():对象被垃圾回收时调用,已被 JDK9 标记为过时,推荐使用显式的资源释放方式。
五、总结
本文围绕 Java 核心的抽象类、接口和 Object 类展开,核心知识点可以总结为以下几点:
- 抽象类 是带抽象方法的模板类,不能实例化,子类必须重写其抽象方法,用于封装子类的通用逻辑,体现
is-a关系; - 接口 是行为的规范,不能实例化,实现类必须重写其所有方法,支持多实现和多继承,弥补 Java 单继承的限制,体现
has-a关系; - 抽象类管 "是什么",接口管 "能做什么",这是两者最核心的设计区别;
- Object 类 是所有 Java 类的根类,提供了
toString()、equals()、hashCode()等基础方法,子类通常需要重写这些方法以满足实际业务需求; - 重写
equals()时必须重写hashCode(),保证两者的规则一致性。
这些知识点是 Java 面向对象编程的基石,无论是日常开发中的代码设计,还是面试中的逻辑考察,都占据着重要地位。希望通过本文的讲解,你能真正理解其设计思想,并能在实际开发中灵活运用。
编程之路,道阻且长,行则将至。掌握这些核心机制,让你的 Java 代码更具扩展性、可读性和优雅性!