一、类与对象
1.1 什么是“类”?
类(Class) 是一种模板或蓝图,描述了一组对象具有的属性(变量)和行为(方法)。
可以把类想象成“制造对象的图纸”或“对象的设计说明书”。
类的组成
一个类主要由以下几个部分组成:
-
属性(字段):用来描述对象的状态(例如名字、年龄)。
-
方法(函数):用来描述对象的行为(例如说话、行走)。
示例:定义一个类
// 定义一个类
class Person {
// 属性
String name;
int age;
// 方法
void sayHello() {
System.out.println("Hi, I'm " + name + ", and I'm " + age + " years old.");
}
}
此类定义了一个 Person
,它有两个属性 name
和 age
,以及一个方法 sayHello()
。
1.2 什么是“对象”?
对象(Object) 是类的一个具体实例,是实际在内存中创建的实体。
类就像模具(模板),而对象就像用模具生产出来的“具体物品”。
创建对象(实例化)
public class Main {
public static void main(String[] args) {
// 创建对象
Person p1 = new Person();
p1.name = "Alice";
p1.age = 25;
p1.sayHello(); // 输出:Hi, I'm Alice, and I'm 25 years old.
}
}
解释:
-
new Person()
:在内存中开辟空间创建一个Person
对象。 -
p1
:是对象的引用变量,指向这个新建的Person
。 -
p1.name
:访问对象的属性。 -
p1.sayHello()
:调用对象的方法。
1.3 类和对象的内存结构(高级理解)
Person p1 = new Person();
这行代码背后发生了什么?
-
JVM 在堆内存中分配一块空间,用来存储
Person
对象。 -
类的属性被初始化(
name = null
,age = 0
)。 -
返回对象地址(引用)赋值给变量
p1
(在栈内存中)。
1.4 多个对象是相互独立的
Person p1 = new Person();
Person p2 = new Person();
p1.name = "Alice";
p2.name = "Bob";
System.out.println(p1.name); // Alice
System.out.println(p2.name); // Bob
每个对象都有自己独立的一份属性,互不影响。
1.5 类 vs 对象 的总结对比
比较项 | 类(Class) | 对象(Object) |
---|---|---|
定义 | 模板 / 设计图 | 模板创建出的具体实例 |
是否占内存 | 不直接占用堆内存 | 占用内存(堆) |
数量 | 一个类可创建多个对象 | 每个对象都独立存在 |
操作方式 | 用来创建对象 | 用来存储和操作数据 |
1.6 类中可以定义什么?
一个类中可以包含以下成员:
成员类型 | 描述 |
---|---|
属性(字段) | 用于存储数据 |
方法 | 描述类对象的行为 |
构造函数 | 用于初始化对象 |
内部类 | 定义在类内部的类 |
静态块 | 类加载时执行的初始化代码块 |
代码块 | 每次创建对象时执行的匿名代码块 |
1.7 示例项目
项目结构
CarDemo/
├── Car.java // 自定义类
└── Main.java // 程序主入口
Car.java
// 文件:Car.java
public class Car {
// 属性(封装)
private String brand;
private int speed;
// 构造函数
public Car(String brand) {
this.brand = brand;
this.speed = 0;
}
// 加速方法
public void accelerate() {
speed += 10;
System.out.println(brand + " 加速,当前速度:" + speed + " km/h");
}
// 减速方法
public void decelerate() {
if (speed >= 10) {
speed -= 10;
} else {
speed = 0;
}
System.out.println(brand + " 减速,当前速度:" + speed + " km/h");
}
// 显示信息方法
public void displayStatus() {
System.out.println("品牌:" + brand + ",当前速度:" + speed + " km/h");
}
}
Main.java
// 文件:Main.java
public class Main {
public static void main(String[] args) {
// 创建两个 Car 对象
Car car1 = new Car("特斯拉");
Car car2 = new Car("保时捷");
// 调用方法
car1.accelerate();
car1.accelerate();
car1.decelerate();
car1.displayStatus();
System.out.println("---------------");
car2.accelerate();
car2.accelerate();
car2.accelerate();
car2.displayStatus();
}
}
输出示例
特斯拉 加速,当前速度:10 km/h
特斯拉 加速,当前速度:20 km/h
特斯拉 减速,当前速度:10 km/h
品牌:特斯拉,当前速度:10 km/h
---------------
保时捷 加速,当前速度:10 km/h
保时捷 加速,当前速度:20 km/h
保时捷 加速,当前速度:30 km/h
品牌:保时捷,当前速度:30 km/h
项目重点回顾
-
Car
是类,car1
、car2
是对象(实例)。 -
每个对象有自己的
brand
和speed
,互不干扰。 -
使用了封装(
private
属性 +public
方法)保护数据。 -
使用构造函数初始化品牌名称。
二、构造函数
构造函数(Constructor) 是一种特殊的方法,用于在创建对象时初始化对象的状态(属性)。
它在执行 new 类名()
时自动调用,用于给新对象赋初始值。
2.1 构造函数的语法格式
class 类名 {
类名(参数列表) {
// 初始化代码
}
}
特点:
-
方法名 必须和类名相同
-
没有返回值类型(不能写 void/int/...)
-
只能通过
new
创建对象时调用 -
可以有多个构造函数(重载)
2.2 构造函数的分类
1)默认构造函数(无参)
没有显式写构造函数时,Java 自动提供一个无参构造函数。
class Person {
String name;
int age;
}
// 使用时
Person p = new Person(); // 系统自动调用默认构造函数
2)自定义构造函数(有参)
也可以手动定义构造函数,用来传递参数并初始化属性。
class Person {
String name;
int age;
// 自定义构造函数
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
// 使用:
Person p = new Person("Alice", 25);
2.3 构造函数中的 this
构造函数中经常用 this
来区分成员变量与参数名重名的问题。
Person(String name) {
this.name = name; // this.name 是成员变量,name 是参数
}
2.4 构造函数重载
可以定义多个构造函数,只要参数不同(数量或类型不同)即可构成重载。
class Person {
String name;
int age;
// 无参构造
Person() {
this.name = "未命名";
this.age = 0;
}
// 单参构造
Person(String name) {
this.name = name;
this.age = 0;
}
// 双参构造
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
调用:
new Person(); // 使用无参构造
new Person("Bob"); // 使用单参构造
new Person("Alice", 25); // 使用双参构造
2.5 构造函数中的 this()
调用其它构造
可以在一个构造函数中调用另一个构造函数,必须放在第一行。
class Person {
String name;
int age;
Person() {
this("未命名", 0); // 调用双参构造
}
Person(String name) {
this(name, 0); // 调用双参构造
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
2.6 构造函数和对象创建过程
Person p = new Person("Tom", 20);
这行代码执行了三件事:
-
分配内存,创建对象(在堆中)
-
调用构造函数初始化属性
-
返回对象引用到变量
p
(在栈中)
2.7 构造函数 vs 普通方法
区别点 | 构造函数 | 普通方法 |
---|---|---|
名称 | 必须和类名一致 | 任意合法名字 |
返回值类型 | 没有返回值,不能写 void | 必须声明返回值类型(可为 void) |
调用方式 | 创建对象时自动调用(new ) | 手动调用(对象.方法()) |
功能 | 初始化对象 | 执行具体操作 |
2.8 示例项目
项目结构
BookConstructorDemo/
├── Book.java // 自定义类,包含多个构造函数
└── Main.java // 主类,用来测试
Book.java
// 文件:Book.java
public class Book {
private String title;
private String author;
private double price;
// 无参构造函数
public Book() {
this("未命名", "未知作者", 0.0); // 调用带参构造函数
System.out.println("调用了无参构造函数");
}
// 单参数构造函数
public Book(String title) {
this(title, "未知作者", 0.0);
System.out.println("调用了单参数构造函数");
}
// 双参数构造函数
public Book(String title, String author) {
this(title, author, 0.0);
System.out.println("调用了双参数构造函数");
}
// 三参数构造函数
public Book(String title, String author, double price) {
this.title = title;
this.author = author;
this.price = price;
System.out.println("调用了三参数构造函数");
}
// 展示书籍信息
public void displayInfo() {
System.out.println("书名: " + title);
System.out.println("作者: " + author);
System.out.println("价格: ¥" + price);
System.out.println("---------------------");
}
}
Main.java
// 文件:Main.java
public class Main {
public static void main(String[] args) {
// 使用无参构造
Book book1 = new Book();
book1.displayInfo();
// 使用单参数构造
Book book2 = new Book("Java从入门到精通");
book2.displayInfo();
// 使用双参数构造
Book book3 = new Book("Effective Java", "Joshua Bloch");
book3.displayInfo();
// 使用三参数构造
Book book4 = new Book("算法导论", "Thomas H. Cormen", 128.00);
book4.displayInfo();
}
}
输出结果
调用了三参数构造函数
调用了无参构造函数
书名: 未命名
作者: 未知作者
价格: ¥0.0
---------------------
调用了三参数构造函数
调用了单参数构造函数
书名: Java从入门到精通
作者: 未知作者
价格: ¥0.0
---------------------
调用了三参数构造函数
调用了双参数构造函数
书名: Effective Java
作者: Joshua Bloch
价格: ¥0.0
---------------------
调用了三参数构造函数
书名: 算法导论
作者: Thomas H. Cormen
价格: ¥128.0
---------------------
项目要点回顾
技术点 | 应用方式 |
---|---|
构造函数重载 | 定义多个构造函数,参数不同 |
this() 构造函数调用 | 在构造函数中复用逻辑,避免重复代码 |
属性初始化 | 构造函数中直接赋值 |
对象创建与测试 | 在 Main.java 中通过多种方式 new 出对象测试 |
三、封装
封装(Encapsulation)就是将类的属性(数据)和方法(操作)包装在一起,隐藏内部的实现细节,只对外暴露必要的接口。
通俗理解:封装就像电饭煲,用户只需按按钮,不需要知道里面如何加热——内部复杂逻辑对外部屏蔽。
3.1 封装的三个关键点
关键点 | 说明 |
---|---|
私有化属性 | 使用 private 修饰成员变量,不允许外部直接访问 |
公开访问方法 | 使用 public 提供 getter 和 setter 方法间接访问属性 |
控制访问权限 | 可以在方法中加入校验逻辑,控制属性的有效性、安全性和可操作性 |
3.2 封装的 Java 示例
public class Person {
// 封装:将属性设为 private
private String name;
private int age;
// 提供 public 方法访问 private 属性
public String getName() {
return name;
}
public void setName(String name) {
// 可以加入校验逻辑
if (name == null || name.isEmpty()) {
System.out.println("名字不能为空!");
return;
}
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 校验年龄合法性
if (age < 0 || age > 150) {
System.out.println("年龄不合法!");
return;
}
this.age = age;
}
}
3.3 封装的好处
好处 | 说明 |
---|---|
增强安全性 | 外部不能随意修改对象的状态(如年龄为负) |
提高代码可维护性 | 修改内部实现不影响外部调用 |
增强可控性和规范性 | 可在 set 方法中加入数据校验或转换逻辑 |
符合面向对象设计原则 | 隐藏实现细节,提高模块独立性 |
3.4 使用封装的完整示例
public class Main {
public static void main(String[] args) {
Person p = new Person();
p.setName("张三");
p.setAge(25);
System.out.println("姓名:" + p.getName());
System.out.println("年龄:" + p.getAge());
p.setAge(-10); // 年龄不合法!
}
}
输出:
姓名:张三
年龄:25
年龄不合法!
3.5 封装 vs 非封装代码对比
非封装(属性暴露)
public class User {
public String username;
public String password;
}
别人可以随便改密码,没有控制逻辑:
user.password = "123"; // 不安全
封装后更安全:
public class User {
private String password;
public void setPassword(String pwd) {
if (pwd.length() < 6) {
System.out.println("密码太短!");
} else {
this.password = pwd;
}
}
}
3.6 常见模式:JavaBean 规范
JavaBean 是一种封装模式:类具有私有属性、公共的构造函数、getter/setter 方法。
public class Student {
private String name;
private int score;
public Student() {} // 无参构造
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getScore() { return score; }
public void setScore(int score) { this.score = score; }
}
3.7 小结
封装是通过“隐藏数据 + 公开方法”的方式来增强对象的安全性与规范性,是 Java 面向对象最基础的设计理念之一。
四、继承
继承(Inheritance)是指一个类(子类)自动拥有另一个类(父类)所有的非私有成员(属性和方法),是代码复用与建模抽象的基础。
通俗理解:
子类像儿子,继承父亲的基因(属性和行为),也可以拥有自己特有的能力。
4.1 Java 中继承的语法
class 父类 {
// 成员
}
class 子类 extends 父类 {
// 子类扩展的成员
}
4.2 简单例子:动物继承
// 父类:Animal
public class Animal {
public void eat() {
System.out.println("动物在吃东西");
}
}
// 子类:Dog
public class Dog extends Animal {
public void bark() {
System.out.println("狗在叫");
}
}
// 测试类
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // 父类方法
dog.bark(); // 子类方法
}
}
输出:
动物在吃东西
狗在叫
4.3 继承的特性
特性 | 描述 |
---|---|
单继承 | Java 只支持单继承,一个子类只能继承一个父类 |
多级继承 | 子类也可以作为父类,形成继承链 |
方法重写(Override) | 子类可以重写父类的方法,提供自己的实现 |
构造器不能继承 | 子类不能继承父类构造器,但可通过 super() 调用 |
4.4 super 关键字的使用
-
super()
:调用父类构造器 -
super.属性
或super.方法()
:访问父类的成员
public class Animal {
String name = "动物";
public void info() {
System.out.println("我是一个动物");
}
}
public class Dog extends Animal {
String name = "狗";
public void show() {
System.out.println(super.name); // 动物
super.info(); // 我是一个动物
}
}
4.5 构造函数的继承流程
子类构造器中默认会先调用父类的无参构造函数:
class Animal {
Animal() {
System.out.println("Animal 构造函数");
}
}
class Dog extends Animal {
Dog() {
System.out.println("Dog 构造函数");
}
}
输出:
Animal 构造函数
Dog 构造函数
如果父类没有无参构造,子类必须用 super(...)
显式调用有参构造。
4.6 继承的好处
优点 | 说明 |
---|---|
提高代码复用性 | 子类可以复用父类已有的属性和方法 |
便于维护和扩展 | 修改父类可影响所有子类,修改成本小 |
支持多态 | 子类对象可以被当作父类使用,增强系统灵活性 |
4.7 继承的注意点
-
不能继承 private 成员(但可以通过 public 方法访问)
-
不能继承构造器
-
Java 不支持多继承(类层面),但可以通过接口解决
-
子类方法名与父类相同即为“重写”,需注意
@Override
注解规范
4.8 继承完整案例
// 父类
public class Person {
protected String name;
public Person(String name) {
this.name = name;
}
public void introduce() {
System.out.println("我是:" + name);
}
}
// 子类
public class Student extends Person {
private String school;
public Student(String name, String school) {
super(name); // 调用父类构造器
this.school = school;
}
// 方法重写
@Override
public void introduce() {
System.out.println("我是学生:" + name + ",就读于:" + school);
}
}
// 测试类
public class Main {
public static void main(String[] args) {
Student s = new Student("小明", "xx大学");
s.introduce(); // 调用子类重写的方法
}
}
输出:
我是学生:小明,就读于:xx大学
4.9 继承 vs 组合(扩展理解)
技术方式 | 描述 | 推荐使用场景 |
---|---|---|
继承 | is-a 关系,继承父类结构 | 子类完全是一种父类,逻辑紧密 |
组合 | has-a 关系,通过引用其他类 | 灵活组合功能,适合低耦合场景 |
4.10 小结
继承是代码复用和面向对象建模的重要手段,Java 中通过 extends
实现单继承,支持方法重写与父类访问控制,并是多态实现的基础。
五、多态
多态(Polymorphism),意思是“同一个行为在不同对象中表现不同的形式”。
在 Java 中,主要指:父类引用指向子类对象,通过父类引用调用的方法,在运行时决定执行哪个版本。
通俗类比:“动物都会叫”,但猫是“喵”、狗是“汪”——同一个行为(叫),具体对象不同,表现不同。
5.1 实现多态的三个条件
条件 | 说明 |
---|---|
1. 继承关系 | 子类继承父类 |
2. 方法重写(Override) | 子类重写父类中的方法(方法名、参数、返回值都一样) |
3. 父类引用指向子类对象 | 使用 父类 类型 = new 子类(); |
5.2 Java 示例:展示多态
class Animal {
public void sound() {
System.out.println("动物在叫");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("狗叫:汪汪!");
}
}
class Cat extends Animal {
@Override
public void sound() {
System.out.println("猫叫:喵喵!");
}
}
public class Main {
public static void main(String[] args) {
Animal a1 = new Dog(); // 父类引用指向子类对象
Animal a2 = new Cat();
a1.sound(); // 输出:狗叫:汪汪!
a2.sound(); // 输出:猫叫:喵喵!
}
}
虽然变量是 Animal
类型,但调用的是 Dog
和 Cat
各自的 sound()
方法。这就是多态。
5.3 多态的原理:动态绑定(Dynamic Binding)
编译时 vs 运行时:
阶段 | 绑定行为 |
---|---|
编译阶段 | 根据变量的引用类型(父类)决定是否允许调用某方法 |
运行阶段 | 根据变量的实际对象类型决定执行哪一个方法(子类) |
Java 的多态方法调用是 动态绑定 的,而非 static
方法,它们是 静态绑定。
5.4 多态的好处
优势 | 举例 |
---|---|
提高程序扩展性 | 新增一个 Bird 类继承 Animal ,原有代码无需改动 |
提高代码可维护性 | 写一个方法操作父类对象,可以支持所有子类 |
实现统一编程模型 | 如 List 接口支持 ArrayList 、LinkedList ,都是多态体现 |
5.5 多态的注意事项
1)多态只适用于方法重写,不适用于属性
class A { public int x = 1; }
class B extends A { public int x = 2; }
A obj = new B();
System.out.println(obj.x); // 输出 1,不是 2
2)子类新增方法,父类引用不能调用
A obj = new B();
obj.bOnlyMethod(); // 编译报错
3)如果要访问子类独有成员,需要强制类型转换(类型检查推荐用 instanceof
)
5.6 实际应用:数组或列表中使用多态
Animal[] animals = { new Dog(), new Cat(), new Dog() };
for (Animal animal : animals) {
animal.sound(); // 调用的是各自子类的 sound 方法
}
5.7 演示统一接口调用的例子(非常常见)
public class AnimalOperator {
public void makeSound(Animal a) {
a.sound();
}
}
public class Main {
public static void main(String[] args) {
AnimalOperator op = new AnimalOperator();
op.makeSound(new Dog()); // 输出:狗叫:汪汪!
op.makeSound(new Cat()); // 输出:猫叫:喵喵!
}
}
这就是**“编程面向父类或接口,运行靠子类”**的核心思想!
5.8 小结
多态是一种接口统一、实现多样的机制,是面向对象三大特性(封装、继承、多态)中最核心的表现,依赖方法重写 + 父类引用指向子类对象 + 动态绑定来实现。
六、抽象类
抽象类(Abstract Class)是包含一个或多个抽象方法的类,它不能被实例化,只能被继承。
抽象类是“不完全的类”,它用来作为其他类的模板或父类。
通俗理解:抽象类就像“规则说明书”或“蓝图”,子类要按照它来实现自己的具体功能。
6.1 语法格式
// 声明一个抽象类
abstract class Animal {
abstract void sound(); // 抽象方法:没有方法体
}
// 子类必须重写抽象方法,否则也必须声明为 abstract
class Dog extends Animal {
@Override
void sound() {
System.out.println("狗叫:汪汪");
}
}
6.2 抽象类的特性总结
特性 | 说明 |
---|---|
使用 abstract 关键字声明类 | abstract class 类名 {} |
抽象类中可以有 抽象方法和普通方法 | 既可以定义方法体,也可以定义规范 |
抽象类不能实例化 | 只能通过子类实例化 |
抽象方法没有方法体(结尾无 {} ) | 子类必须实现所有未实现的抽象方法,否则子类也必须是抽象类 |
构造器是允许的 | 抽象类可以有构造函数供子类使用(不能直接 new ) |
可以有成员变量 | 也可以定义 static 方法、常量等 |
6.3 例子:抽象动物类
abstract class Animal {
String name;
public Animal(String name) {
this.name = name;
}
// 抽象方法(不写方法体)
public abstract void makeSound();
// 普通方法
public void sleep() {
System.out.println(name + " 睡觉了");
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + ":喵喵~");
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + ":汪汪!");
}
}
public class Main {
public static void main(String[] args) {
Animal a1 = new Cat("小猫");
Animal a2 = new Dog("小狗");
a1.makeSound(); // 多态调用
a2.makeSound();
a1.sleep();
a2.sleep();
}
}
输出:
小猫:喵喵~
小狗:汪汪!
小猫 睡觉了
小狗 睡觉了
6.4 抽象类 vs 普通类
项目 | 抽象类 | 普通类 |
---|---|---|
是否能实例化 | 不能 | 可以 |
是否包含抽象方法 | 可以 | 不可以 |
目的 | 提供“规范+通用功能”的父类模板 | 具体实现类 |
使用场景 | 设计层级模型、作为父类定义行为规范 | 实际功能开发类 |
6.5 抽象类 vs 接口
项目 | 抽象类 | 接口 (interface ) |
---|---|---|
是否能有成员变量 | 有,也可有构造函数 | 变量必须是 public static final 常量 |
是否能有构造器 | 可以 | 不可以 |
是否能包含方法体 | 可以(普通方法) | Java 8 起支持 default、static 方法 |
是否支持多继承 | 不支持类的多继承 | 接口支持多继承 |
使用 extends / implements | 子类使用 extends 继承抽象类 | 类使用 implements 实现接口 |
6.6 实际应用场景
-
当想为一组类定义通用的行为模板(有些实现留给子类),使用抽象类;
-
比如:
-
Shape
(图形)是抽象类,具体有Circle
、Rectangle
-
Animal
是抽象类,具体有Cat
、Dog
-
HttpHandler
是抽象类,具体有JsonHandler
、HtmlHandler
-
6.7 小结
抽象类是定义通用“行为模板”的不完整类,只能被继承,不能被实例化。子类必须实现抽象方法,从而实现“规范与实现分离”。
七、接口
接口(Interface)是一种特殊的“抽象类型”,用来定义类应当具有哪些方法(规范),而不关心具体实现。
接口 = 纯规范、无状态、无行为逻辑。
7.1 接口的语法结构
public interface 接口名 {
// 默认都是 public abstract
void 方法名();
// Java 8+ 可以有 default 方法和 static 方法
default void 默认方法() {
System.out.println("默认实现");
}
static void 静态方法() {
System.out.println("静态方法");
}
}
类实现接口:
class 类名 implements 接口名 {
@Override
public void 方法名() {
// 必须实现接口中的所有方法
}
}
7.2 接口和类的区别
项目 | 接口(interface) | 抽象类(abstract class) |
---|---|---|
是否可实例化 | 不能 | 不能 |
是否能有普通方法 | Java 8 以后可以有 default 和 static | 可以 |
是否有构造函数 | 不可以 | 可以 |
是否支持多继承 | 可以实现多个接口 | 只能继承一个抽象类 |
成员变量 | 默认是 public static final 常量 | 可定义任意修饰符的成员变量 |
7.3 接口示例:USB 接口
// 定义一个 USB 接口
interface USB {
void connect(); // 抽象方法
void disconnect();
}
// 实现类:U盘
class FlashDrive implements USB {
public void connect() {
System.out.println("U盘已连接");
}
public void disconnect() {
System.out.println("U盘已断开");
}
}
// 实现类:键盘
class Keyboard implements USB {
public void connect() {
System.out.println("键盘已连接");
}
public void disconnect() {
System.out.println("键盘已断开");
}
}
public class Main {
public static void main(String[] args) {
USB usb1 = new FlashDrive();
USB usb2 = new Keyboard();
usb1.connect(); // 输出:U盘已连接
usb2.connect(); // 输出:键盘已连接
usb1.disconnect(); // 输出:U盘已断开
usb2.disconnect(); // 输出:键盘已断开
}
}
接口统一了调用方式,增强了代码扩展性和灵活性。
7.4 接口的高级特性(Java 8+)
1)默认方法 default
interface A {
default void print() {
System.out.println("A 的默认方法");
}
}
用途:给接口添加新方法而不破坏原有实现类。
2)静态方法
interface A {
static void printInfo() {
System.out.println("接口的静态方法");
}
}
用途:工具方法,例如 Comparator.comparing()
。
7.5 接口的实际作用
功能 | 描述 |
---|---|
统一规范 | 类似“协议”、“合同”,定义类必须实现的方法 |
解耦合 | 编程面向接口而非具体类 |
多态 | 父接口引用指向子类对象(即接口的多态) |
支持多实现 | 一个类可以实现多个接口,弥补 Java 单继承的不足 |
插件式开发 / 策略模式 | 实现类可以自由替换,主程序逻辑无需修改 |
7.6 一个综合案例:动物行为接口
interface Speakable {
void speak();
}
interface Runnable {
void run();
}
class Dog implements Speakable, Runnable {
public void speak() {
System.out.println("狗叫:汪汪");
}
public void run() {
System.out.println("狗跑得飞快");
}
}
public class Main {
public static void main(String[] args) {
Speakable s = new Dog();
s.speak(); // 多态调用
Runnable r = new Dog();
r.run(); // 多态调用
}
}
7.7 小结
接口是“纯抽象”的规范,用于定义一组行为规范,不关心具体实现。接口是实现多态、解耦、模块化的关键武器。
八、this
在 Java 中,this
是一个 当前对象的引用,它在 类的内部方法或构造方法中使用,用于指代当前对象本身。
换句话说,this
指的是 哪个对象调用了这个方法,this
就代表谁。
8.1 this
的常见用途
1)区分成员变量和局部变量同名冲突
class Person {
String name;
Person(String name) {
this.name = name; // this.name 是成员变量,name 是参数
}
}
如果不用 this.name = name;
,编译器会认为 name = name;
是给自己赋值,没有效果。
2)在类的方法中引用当前对象
public void printName() {
System.out.println("我的名字是:" + this.name);
}
虽然 name
不加 this
也可以访问,但加 this
更清晰地表示它是“当前对象的属性”。
3)在构造方法中调用另一个构造方法(必须放在第一行)
class Student {
String name;
int age;
Student(String name) {
this(name, 0); // 调用另一个构造方法
}
Student(String name, int age) {
this.name = name;
this.age = age;
}
}
用途:构造器重载时避免重复代码。
4)作为参数传递当前对象
class Person {
String name;
Person(String name) {
this.name = name;
}
void introduce() {
Helper.printPerson(this); // 传递当前对象
}
}
class Helper {
static void printPerson(Person p) {
System.out.println("我是:" + p.name);
}
}
用途:传递当前对象给其他方法/类使用。
5)返回当前对象(实现链式调用)
class Counter {
int count = 0;
public Counter add() {
this.count++;
return this; // 返回当前对象
}
}
public class Main {
public static void main(String[] args) {
Counter c = new Counter();
c.add().add().add(); // 链式调用
System.out.println(c.count); // 输出:3
}
}
用途:链式调用模式(Builder模式等)。
8.2 this
总结:
用途 | 记忆口诀 |
---|---|
区分成员变量和参数 | this.属性 = 参数 |
在方法中引用当前对象 | 谁调用我,this 就是谁 |
构造器中调用构造器 | this(...) 仅限第一行 |
传参或返回当前对象 | this 可用于链式编程和对象传参 |
8.3 实际例子
class Book {
String title;
Book(String title) {
this.title = title;
}
void printTitle() {
System.out.println("书名:" + this.title);
}
Book rename(String newTitle) {
this.title = newTitle;
return this; // 实现链式调用
}
}
public class Main {
public static void main(String[] args) {
Book b = new Book("Java入门");
b.printTitle(); // 输出:Java入门
b.rename("Java进阶").printTitle(); // 输出:Java进阶
}
}
8.4 this 与静态(static)对比
关键字 | 指代 | 是否属于对象 |
---|---|---|
this | 当前对象的引用 | 属于对象 |
static | 类本身相关(与对象无关) | 属于类 |
静态方法中不能使用 this
,因为没有“当前对象”。
8.5 关于 this
的常见问题
-
this
可以用于静态方法中吗?
不可以,因为静态方法不属于任何对象。 -
构造函数中
this(...)
的位置有限制吗?
有限制,必须是构造函数的第一行。 -
this
是指当前类吗?
是当前类的对象实例,不是类本身。
8.6 小结
this
是当前对象的引用,用于访问自己的属性、调用构造器或方法,并支持链式调用与对象传递,是面向对象编程中理解“对象自我”的核心。
九、super
super
是父类对象的引用,表示“当前对象的父类部分”。
通俗理解:如果 this
是“我自己”,那 super
就是“我的父类”。
9.1 super
的三大主要用途
1)调用父类的构造函数
class Parent {
Parent(String msg) {
System.out.println("父类构造函数:" + msg);
}
}
class Child extends Parent {
Child() {
super("来自子类的调用"); // 调用父类的构造函数
System.out.println("子类构造函数");
}
}
注意:super(...)
必须是子类构造器中的第一行!
2)访问父类的成员变量(当子类成员变量与父类同名时)
class Parent {
int x = 10;
}
class Child extends Parent {
int x = 20;
void print() {
System.out.println("子类 x: " + x);
System.out.println("父类 x: " + super.x); // 访问父类的 x
}
}
3)调用父类被重写的方法
class Animal {
void speak() {
System.out.println("动物叫");
}
}
class Dog extends Animal {
void speak() {
System.out.println("狗叫");
}
void show() {
this.speak(); // 调用子类方法
super.speak(); // 调用父类被重写的方法
}
}
9.2 super vs this 区别对比
特性 | this | super |
---|---|---|
指代对象 | 当前类的对象 | 当前对象的父类部分 |
调构造函数 | 调用本类的其他构造器(this(...) ) | 调用父类的构造器(super(...) ) |
调方法/变量 | 当前类的方法或变量 | 父类的方法或变量 |
使用限制 | 可在非静态方法中使用 | 可在子类中使用,super(...) 必须在首行 |
9.3 完整示例:演示 super 的三种用途
class Animal {
String name = "动物";
Animal() {
System.out.println("Animal 构造函数");
}
void speak() {
System.out.println("Animal 叫");
}
}
class Dog extends Animal {
String name = "狗";
Dog() {
super(); // 调用父类构造函数
System.out.println("Dog 构造函数");
}
void printNames() {
System.out.println("子类 name: " + name);
System.out.println("父类 name: " + super.name); // 访问父类变量
}
void speak() {
System.out.println("Dog 叫");
}
void showSpeak() {
this.speak(); // 子类 speak
super.speak(); // 父类 speak
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.printNames();
d.showSpeak();
}
}
输出结果:
Animal 构造函数
Dog 构造函数
子类 name: 狗
父类 name: 动物
Dog 叫
Animal 叫
9.4 super 使用注意事项
注意点 | 说明 |
---|---|
super(...) 必须在构造器第一行 | 不能和 this(...) 同时出现 |
父类无无参构造器,必须显式调用 | 如果父类没有无参构造器,子类必须使用 super(...) |
static 方法中不能用 super | 因为 static 属于类而不属于对象 |
9.5 小结
super
是子类对“父类部分”的访问桥梁,常用于调用父类构造函数、属性或被重写的方法,体现了继承的本质与多态的延伸。
十、final
final
表示“最终的、不可更改的”。
在 Java 中,final
可以用来修饰:
使用位置 | 含义 |
---|---|
变量 | 这个值只能被赋值一次,之后不能修改 |
方法 | 这个方法不能被子类重写 |
类 | 这个类不能被继承 |
10.1 final
修饰变量
1)修饰基本类型变量
final int a = 10;
a = 20; // 编译错误:final 变量不能修改
一旦赋值,就不能再次赋值。
2)修饰引用类型变量
final Person p = new Person("Tom");
p.name = "Jerry"; // 可以修改对象的属性
p = new Person("Alice"); // 不可以修改引用本身
结论:final 引用不能换地址,但对象内容可以改。
3)用于常量定义(常与 static 一起使用)
public static final double PI = 3.14159;
推荐命名:常量一般用 全大写 + 下划线 命名。
10.2 final
修饰方法
class Animal {
final void breathe() {
System.out.println("动物呼吸");
}
}
class Dog extends Animal {
// void breathe() { ... } // 编译错误:不能重写 final 方法
}
用于阻止方法在子类中被重写(override),保护核心逻辑不被篡改。
10.3 final
修饰类
final class Constants {
public static final double PI = 3.14;
}
// class MyConstants extends Constants {} // 编译错误,final 类不能继承
用于创建“工具类”、“常量类”、“不可被扩展的类”。
10.4 为什么使用 final
?
用途 | 作用/意义 |
---|---|
修饰变量 | 定义常量,防止被误修改 |
修饰方法 | 防止被重写,确保逻辑安全 |
修饰类 | 防止被继承,增强封装与安全 |
性能优化 | 编译器能做更多优化(例如:方法内联) |
线程安全 | 多线程中确保对象引用不可变,提高安全性 |
10.5 关于 final
的几个注意点
场景 | 是否允许 |
---|---|
final 局部变量必须初始化 | 是(否则编译错误) |
final 引用对象内容能变? | 可以变(引用不可变) |
final 方法能重载吗? | 可以(不能重写) |
构造方法能用 final 修饰? | 不可以 |
abstract 和 final 同时用? | 冲突:抽象是为了重写,final 是禁止重写 |
10.6 final 方法 vs 抽象方法(abstract)
特性 | final 方法 | abstract 方法 |
---|---|---|
是否必须实现 | 已实现 | 未实现,必须重写 |
是否能被重写 | 不允许 | 子类必须重写 |
用途 | 保护实现不被子类更改 | 让子类自定义具体实现 |
10.7 小结
final
是 Java 中用于声明“不可变”的关键字,修饰变量可变为常量,修饰方法可防止重写,修饰类可防止继承,是编写安全、健壮、可维护代码的重要工具。
十一、static
static
表示“静态”,它修饰的成员属于类本身,而不是某个对象。
通俗理解:static
的成员是“所有对象共享的”,只会存在一份,不依赖实例。
11.1 static
的用途总结
用法位置 | 作用 |
---|---|
修饰变量 | 类变量(共享变量) |
修饰方法 | 类方法(无需创建对象调用) |
修饰代码块 | 静态代码块(类加载时执行) |
修饰内部类 | 静态内部类(不依赖外部类对象) |
11.2 static
修饰变量:静态变量
class Student {
static String school = "xx大学"; // 所有学生共用
String name;
Student(String name) {
this.name = name;
}
void print() {
System.out.println(name + " 来自 " + school);
}
}
特点:
-
所有对象共享,类加载时创建。
-
一处修改,处处生效。
-
推荐使用类名访问:
Student.school
。
11.3 static
修饰方法:静态方法
class MathUtil {
static int add(int a, int b) {
return a + b;
}
}
// 使用
int result = MathUtil.add(3, 5); // 不需要 new 对象
特点:
-
不依赖对象,可以直接通过类名调用。
-
不能使用 this 和 super。
-
只能访问其他 static 成员。
11.4 static
代码块:静态初始化块
class Demo {
static {
System.out.println("类加载时执行:初始化静态资源");
}
}
特点:
-
类第一次被加载时执行一次。
-
用于初始化静态变量或复杂资源(如数据库连接池等)。
11.5 static
修饰内部类:静态内部类
class Outer {
static class Inner {
void hello() {
System.out.println("静态内部类方法");
}
}
}
// 使用
Outer.Inner inner = new Outer.Inner();
inner.hello();
特点:
-
不依赖外部类对象,可以直接通过外部类名访问。
-
与外部类的实例无关。
11.6 static 的限制与注意事项
限制 | 原因/说明 |
---|---|
static 方法不能用 this / super | 因为 static 不属于对象 |
static 方法只能访问 static 成员 | 因为它可能在对象创建前被调用 |
static 不能修饰局部变量 | 局部变量是栈上动态创建的 |
构造方法不能是 static 的 | 因为构造函数是用于构造对象的 |
11.7 常见使用场景总结
场景 | 用法示例 |
---|---|
工具类方法 | Math.random() 、Arrays.sort() |
常量定义 | public static final double PI |
单例模式 | 静态变量保存单例实例 |
计数器/ID生成器 | 静态变量自增 |
静态内部类 | Map.Entry 、Thread.State 等 |
11.8 对比:实例变量 vs 静态变量
对比点 | 实例变量(无 static) | 静态变量(有 static) |
---|---|---|
所属对象 | 每个对象一份 | 所有对象共享一份 |
生命周期 | 对象创建时生成 | 类加载时生成 |
访问方式 | 对象.变量 | 类名.变量 |
常见使用场景 | 描述对象特征 | 描述公共信息 |
11.9 综合示例代码
class Counter {
static int count = 0; // 静态变量:所有对象共享
Counter() {
count++; // 每创建一个对象,count +1
System.out.println("当前对象数:" + count);
}
static void showCount() {
System.out.println("总共创建了 " + count + " 个对象");
}
}
public class Main {
public static void main(String[] args) {
new Counter();
new Counter();
Counter.showCount(); // 调用静态方法
}
}
11.10 小结
static
关键字使变量或方法变成“属于类而非对象”,用于实现共享数据、工具方法、静态初始化等,是面向对象中“类层面编程”的核心工具。
十二、inner class(内部类)
内部类(Inner Class)是定义在另一个类内部的类。
它相对于外部类来说是“内部作用域”的类,具有更强的封装性和更紧密的逻辑关联。
12.1 内部类的分类(4种)
类型 | 定义位置 | 是否 static | 依赖外部类对象 |
---|---|---|---|
成员内部类 | 类中,方法外 | 否 | 是 |
静态内部类 | 类中,方法外,加 static 修饰 | 是 | 否 |
局部内部类 | 方法内部 | 否 | 是 |
匿名内部类 | 方法中,用于快速实现接口/父类 | 否 | 是 |
12.2 成员内部类(最基本形式)
class Outer {
private String msg = "Hello";
class Inner {
void show() {
System.out.println("Inner: " + msg); // 访问外部类私有成员
}
}
}
public class Test {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // 注意创建方式
inner.show();
}
}
特点:
-
可以访问外部类的所有成员(包括
private
) -
依赖外部类实例来创建(必须先创建
Outer
对象)
12.3 静态内部类(加 static)
class Outer {
static class Inner {
void show() {
System.out.println("Static inner class");
}
}
}
public class Test {
public static void main(String[] args) {
Outer.Inner inner = new Outer.Inner(); // 不需要外部类对象
inner.show();
}
}
特点:
-
可以独立创建,不依赖外部类实例
-
只能访问外部类的静态成员
-
常用于工具类、常量类、封装模块
12.4 局部内部类(定义在方法中)
class Outer {
void outerMethod() {
final String name = "局部变量";
class Inner {
void print() {
System.out.println("Inner uses: " + name);
}
}
Inner inner = new Inner();
inner.print();
}
}
特点:
-
类定义在方法中,作用域仅限该方法
-
只能访问方法中的
final
或 “等同 final” 的局部变量 -
常用于:线程任务、事件监听、数据封装等
12.5 匿名内部类(最常见)
abstract class Animal {
abstract void sound();
}
public class Test {
public static void main(String[] args) {
Animal cat = new Animal() {
void sound() {
System.out.println("Meow~");
}
};
cat.sound();
}
}
特点:
-
没有名字的类,定义和使用合并为一行
-
通常用于:快速实现接口或抽象类
-
常见于:线程、事件监听、回调函数
new Thread(new Runnable() {
public void run() {
System.out.println("线程启动");
}
}).start();
12.6 使用场景总结
内部类类型 | 常见用途 |
---|---|
成员内部类 | 封装外部类内部逻辑,表示强耦合关系 |
静态内部类 | 工具类、Builder 模式、数据结构嵌套类 |
局部内部类 | 临时封装类、回调或任务 |
匿名内部类 | 一次性接口/抽象类实现、事件监听器 |
12.7 注意事项与限制
-
内部类可以访问外部类的所有成员(包括私有)
-
外部类访问内部类成员,需先创建内部类对象
-
匿名类不能定义构造器,只能用代码块初始化
-
Java 8 后局部内部类可访问“等同 final”的变量
12.8 小结
内部类是类中类的实现形式,用于封装紧密相关的逻辑,可以增强代码结构的清晰性与安全性,在 GUI、回调、工具封装等场景非常常见。
十三、访问控制权限(private/protected/public/default)
访问控制权限(Access Modifiers)是 Java 中 封装性(Encapsulation)的核心体现,用于控制类的成员(变量、方法、构造器等)在不同作用范围内的访问权限。掌握它是写出安全、清晰、模块化代码的基础。
13.1 四种访问权限概览
访问修饰符 | 同类(自己) | 同包(包内其他类) | 子类(包内外) | 外部类(不同包) |
---|---|---|---|---|
public | ✔ | ✔ | ✔ | ✔ |
protected | ✔ | ✔ | ✔ | ✘(非子类不可) |
默认(default) | ✔ | ✔ | ✘ | ✘ |
private | ✔ | ✘ | ✘ | ✘ |
注意:**默认访问权限(default)**是“无修饰符”时的权限,也叫包访问权限。
13.2 各个访问修饰符
1)public
—— 公开的,任何地方都能访问
public class Person {
public String name;
public void sayHi() {
System.out.println("Hi, I'm " + name);
}
}
-
用于公共 API(如库函数、工具类)
-
类、变量、方法、构造器都可以是
public
-
类只能是
public
或 default,不能是private/protected
2)private
—— 私有的,只能在类内部访问
public class Account {
private double balance; // 外部无法访问
public void deposit(double amount) {
if (amount > 0) balance += amount;
}
}
-
常用于封装属性,实现安全访问
-
强制外部通过 getter/setter 间接访问
-
不能修饰类(外部类)
3)protected
—— 受保护的:包内 + 子类可访问
class Animal {
protected void eat() {
System.out.println("吃东西");
}
}
class Dog extends Animal {
void bark() {
eat(); // 子类可访问
}
}
-
用于允许子类访问但隐藏给其他类
-
在 继承结构中非常重要
-
包外的非子类无法访问
4)default(无修饰符)—— 包内可访问
class Teacher { // 没有修饰符,即 default
void teach() {
System.out.println("授课中...");
}
}
-
只能在同一个包中的类之间访问
-
适合将相关类组织在一个包内进行模块化
13.3 应用建议
目标 | 建议修饰符 |
---|---|
封装属性,控制访问 | private + getter/setter |
公开使用的类或方法 | public |
仅子类需要访问的方法 | protected |
只在本包内使用的工具类方法 | default |
13.4 访问控制常见例子
封装属性:
public class Student {
private int age;
public int getAge() { return age; }
public void setAge(int age) {
if (age >= 0) this.age = age;
}
}
模块化包设计:
// 包内可见
class DaoHelper {
// default 权限,仅限同包使用
}
13.5 编译器错误示例
package a;
class A {
private void test() {}
}
package b;
class B {
void call() {
new A().test(); // 编译错误:test() 是 private
}
}
13.6 小结
访问修饰符决定了类的“成员”能被谁看到、谁使用,是封装与模块化的基础。在设计类时,应合理控制暴露范围,隐藏实现细节,开放必要接口。
权限从高到低:
public > protected > default > private
十四、方法重写 vs 重载
方法重载(Overloading) vs 方法重写(Overriding)
比较项 | 方法重载(Overloading) | 方法重写(Overriding) |
---|---|---|
所属关系 | 同一个类内部 | 父类与子类之间 |
方法名 | 必须相同 | 必须相同 |
参数列表 | 必须不同(个数、类型、顺序) | 必须相同 |
返回类型 | 可以不同 | 必须相同(或协变返回类型) |
修饰符 | 无特别限制 | 子类方法权限必须 ≥ 父类(不能更低) |
static/final | 可用于 static/final 方法 | 不能重写 final 或 static 方法 |
编译时 or 运行时 | 编译时决定(静态绑定) | 运行时决定(动态绑定,多态) |
14.1 方法重载(Overloading)—— 同一个类中参数不同
特点:
-
方法名相同,参数不同
-
和返回值无关
-
编译器在编译期根据参数决定调用哪个方法(静态绑定)
示例:
public class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
}
应用场景:
设计统一操作(如打印、加法等)时,支持不同参数类型
14.2 方法重写(Overriding)—— 父类子类之间
特点:
-
方法签名(方法名+参数)必须与父类一致
-
返回值必须相同(或是父类返回类型的子类,即协变返回类型)
-
子类不能缩小访问权限(如父类是
public
,子类也必须是public
) -
运行时决定调用哪个方法(多态/动态绑定)
示例:
class Animal {
public void sound() {
System.out.println("Some sound");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("Bark");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Dog(); // 多态
a.sound(); // 输出:Bark
}
}
应用场景:
-
父类定义规范(如抽象类或接口),子类根据需要实现
-
用于实现多态(同一个接口,多种行为)
14.3 @Override 注解的作用
@Override
public void sound() { ... }
编译器检查是否真的在“重写”
-
若方法名拼错或参数不对,会报错
-
强烈建议加上
14.4 不能被重写的方法
-
final
方法:不能被重写 -
static
方法:静态方法不参与多态,属于类本身 -
private
方法:不是继承的,子类不能访问或重写
class A {
private void hello() {}
static void greet() {}
final void bye() {}
}
14.5 重载 vs 重写对比
-
重载是 一个类 方法名一样、参数不一样
-
重写是 继承关系中 方法名、参数都一样,改写行为
-
重载是 编译期绑定(静态)
-
重写是 运行期绑定(动态)
class A {
public void print(int a) {
System.out.println("A:int");
}
public void print(String s) {
System.out.println("A:String");
}
}
class B extends A {
@Override
public void print(String s) {
System.out.println("B:String");
}
}
public class Test {
public static void main(String[] args) {
A obj = new B();
obj.print(10); // 输出?
obj.print("hello"); // 输出?
}
}
输出:
A:int // 调用 A 中的重载方法
B:String // 调用 B 重写后的方法(动态绑定)
14.6 小结
重载是方法名相同参数不同、编译期决定;重写是继承中行为替换、运行时决定,是实现多态的关键。
十五、多态的原理:方法调用的“动态绑定”
Java 中的 多态(Polymorphism) 是面向对象的三大特性之一,它允许「父类引用指向子类对象」,并在运行时调用真正子类中实现的方法。这一机制背后的原理就是 —— 方法调用的动态绑定(Dynamic Binding)。
动态绑定(Dynamic Binding)指的是:在运行时(而非编译时)决定调用哪个方法的机制。
也叫 后期绑定(Late Binding)。
举例说明:
class Animal {
void speak() {
System.out.println("Animal speaks");
}
}
class Dog extends Animal {
void speak() {
System.out.println("Dog barks");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Dog(); // 父类引用指向子类对象
a.speak(); // 输出: Dog barks(运行时决定)
}
}
-
编译时只知道
a
是Animal
类型 -
运行时发现
a
实际是Dog
类型 → 调用的是Dog.speak()
方法
这就是 多态的体现,调用哪个方法,不是编译器决定的,而是 JVM 在运行时通过动态绑定来决定的。
15.1 为什么需要动态绑定?
因为:
Java 支持多态 —— 一个方法可以根据对象的真实类型来表现不同行为。
也就是说:
-
编译器只检查方法是否存在于父类或接口
-
真正要调用谁,由对象的实际类型在运行时决定
15.2 工作原理底层解析
当我们写下:
Animal a = new Dog();
a.speak();
背后发生了什么?
-
编译期:检查
Animal
类中是否有speak()
方法 —— 有,编译通过。 -
运行期:
-
JVM 发现
a
实际上是Dog
对象。 -
JVM 查找
Dog
类是否覆盖了speak()
方法。 -
如果有,则绑定并调用
Dog.speak()
。
-
实现机制:
JVM 会为每个类维护一个叫作「虚方法表」(vtable)的数据结构,里面记录了类中所有可被重写的方法。
-
当通过引用调用方法时,JVM 会查 vtable,找到真正该调用的方法地址。
15.3 哪些方法支持动态绑定?
方法类型 | 动态绑定? |
---|---|
非 static 方法 | 支持 |
private 方法 | 不支持,编译期静态绑定(不参与继承) |
final 方法 | 不支持,编译期静态绑定(不能重写) |
static 方法 | 不支持,编译期静态绑定(属于类,不属于对象) |
15.4 静态绑定(Static Binding)对比
class Demo {
static void hello() {
System.out.println("Static Hello");
}
}
public class Test {
public static void main(String[] args) {
Demo d = null;
d.hello(); // 静态方法,编译期已决定,输出: Static Hello
}
}
即使 d
是 null,仍然能调用,因为编译器已经绑定好了。
15.5 实际例子:多态 + 动态绑定
abstract class Shape {
abstract void draw();
}
class Circle extends Shape {
void draw() {
System.out.println("画圆");
}
}
class Square extends Shape {
void draw() {
System.out.println("画正方形");
}
}
public class Test {
public static void main(String[] args) {
Shape s1 = new Circle();
Shape s2 = new Square();
s1.draw(); // 运行时决定,输出:画圆
s2.draw(); // 输出:画正方形
}
}
15.6 小结
多态的核心就是“动态绑定”:方法调用在运行时根据对象真实类型来决定执行哪一个方法,是实现“同一接口,不同行为”的关键