主题
Java面向对象高级特性:多态、代码块与内部类完全指南
目录
一、核心知识点
1. 多态
1.1 多态概述
定义: 一种事物有不同的形态
面向对象三大特征: 封装、继承、多态
1.2 多态的前提条件
- 必须有子父类继承关系或者接口实现关系
- 必须有方法的重写(没有方法的重写,多态没有意义)
- 父类引用指向子类对象:
Fu fu = new Zi()
1.3 多态的成员访问特点
成员变量: 看等号左边是谁,先调用谁中的成员变量
java
public class Fu {
int num = 100;
}
public class Zi extends Fu {
int num = 10;
}
Fu fu = new Zi();
System.out.println(fu.num); // 输出: 100 (父类的num)成员方法: 看new的是谁,就先调用谁中的方法
java
public class Fu {
public void show() {
System.out.println("Fu");
}
}
public class Zi extends Fu {
@Override
public void show() {
System.out.println("Zi");
}
}
Fu fu = new Zi();
fu.show(); // 输出: Zi (子类重写的方法)1.4 多态的好处与弊端
好处:
- 扩展性强
- 形参使用父类类型,可以动态接收任意子类对象
弊端:
- 不能直接调用子类特有功能
对比原始方式:
- 原始方式: 既能调用重写的,还能调用继承的,还能调用子类特有的,但扩展性差
- 多态方式: 扩展性强,但不能直接调用子类特有功能
1.5 多态中的转型
向上转型(自动类型转换):
java
父类引用指向子类对象 -> Fu fu = new Zi()向下转型(强转):
java
将父类类型转成子类类型
Fu fu = new Zi(); // 好比 double b = 10
Zi zi = (Zi)fu; // 好比 int a = (int)b作用: 可以调用子类特有功能
1.6 类型判断
ClassCastException: 类型转换异常
出现原因: 转型的时候,等号左右两边类型不一致
解决方案: 强转之前判断类型
关键字: instanceof
用法:
java
对象名 instanceof 类型 // 判断对象是否属于该类型
// 传统方式
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.lookHome();
}
// 新特性(Java 16+): 隐含强转
if (animal instanceof Dog dog) {
dog.lookHome();
}2. 代码块
2.1 构造代码块
格式:
java
{
代码
}执行特点:
- 优先于构造方法执行
- 每new一次,构造代码块就执行一次
示例:
java
public class Person {
public Person() {
System.out.println("无参构造方法");
}
{
System.out.println("构造代码块");
}
}
Person p1 = new Person(); // 输出: 构造代码块 -> 无参构造方法
Person p2 = new Person(); // 输出: 构造代码块 -> 无参构造方法2.2 静态代码块
格式:
java
static {
代码
}执行特点:
- 优先于构造代码块和构造方法执行
- 只执行一次
示例:
java
public class Person {
public Person() {
System.out.println("无参构造方法");
}
{
System.out.println("构造代码块");
}
static {
System.out.println("静态代码块");
}
}
Person p1 = new Person(); // 输出: 静态代码块 -> 构造代码块 -> 无参构造方法
Person p2 = new Person(); // 输出: 构造代码块 -> 无参构造方法执行顺序: 静态代码块 > 构造代码块 > 构造方法
2.3 静态代码块使用场景
应用场景: 如果有一些数据只需要初始化一次,而且需要先初始化,这些数据就可以放到静态代码块中
典型应用:
- 数据库连接池初始化
- 配置文件加载
- 静态资源初始化

3. 内部类
3.1 内部类概述
使用场景: 当一个事物的内部,还有一个部分需要完整的结构进行描述,而这个内部的完整结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类
示例: 人类都有心脏,人类本身需要用属性、行为去描述,心脏也需要特殊的属性和行为来描述,此时心脏就可以定义成内部类
分类:
- 成员内部类(静态、非静态)
- 局部内部类
- 匿名内部类(重点)
3.2 静态成员内部类
格式:
java
public class A {
static class B {
}
}特点:
- 内部类中可以定义属性、方法、构造等
- 可以被final或abstract修饰
- 不能调用外部的非静态成员
- 可以被四种权限修饰符修饰
调用方式:
java
外部类.内部类 对象名 = new 外部类.内部类()示例:
java
public class Person {
static class Heart {
public void jump() {
System.out.println("心脏在咣咣咣跳");
}
}
}
Person.Heart heart = new Person.Heart();
heart.jump();3.3 非静态成员内部类
格式:
java
public class 类名 {
class 类名 {
}
}特点:
- 内部类中可以定义属性、方法、构造等
- 可以被final或abstract修饰
- 可以调用外部的非静态成员
- 可以被四种权限修饰符修饰
调用方式:
java
外部类.内部类 对象名 = new 外部类().new 内部类()示例:
java
public class Person {
class Heart {
public void jump() {
System.out.println("心脏在咣咣咣跳");
}
}
}
Person.Heart heart = new Person().new Heart();
heart.jump();3.4 变量重名问题
外部类、内部类、局部变量重名时的区分:
java
public class Student {
String name = "张三"; // 外部类成员变量
class Inner {
String name = "李四"; // 内部类成员变量
public void display() {
String name = "王五"; // 局部变量
System.out.println(name); // 王五
System.out.println(this.name); // 李四
System.out.println(Student.this.name); // 张三
}
}
}3.5 局部内部类
定义位置: 可以定义在方法中、代码块中、构造方法中
示例:
java
public class Person {
public void eat() {
class Heart {
public void jump() {
System.out.println("跳");
}
}
new Heart().jump();
}
}3.6 接口和抽象类作为方法参数和返回值
接口作为方法参数: 需要传递实现类对象
java
public static void method(USB usb) {
usb.open();
}
USB usb = new Mouse();
method(usb);接口作为方法返回值: 返回的是实现类对象
java
public static USB method01() {
Mouse mouse = new Mouse();
return mouse;
}抽象类作为方法参数: 传递的是子类对象
java
public static void method01(Animal animal) {
animal.eat();
}抽象类作为方法返回值: 返回的是子类对象
java
public static Animal method02() {
Dog dog = new Dog();
return dog;
}二、重点难点解析
1. 多态的核心要点
理解关键: 多态是运行时行为,编译看左边,运行看右边
记忆口诀:
- 成员变量: 编译看左边,运行看左边
- 成员方法: 编译看左边,运行看右边
2. 向下转型的风险
问题: 直接向下转型可能导致ClassCastException
解决方案: 使用instanceof判断类型
最佳实践:
java
public static void method(Animal animal) {
animal.eat();
// 使用新特性,避免类型转换异常
if (animal instanceof Dog dog) {
dog.lookHome();
} else if (animal instanceof Cat cat) {
cat.catchMouse();
}
}3. 静态代码块的应用
典型场景:
java
public class JDBCUtils {
private static DataSource dataSource;
static {
try {
Properties props = new Properties();
props.load(JDBCUtils.class.getResourceAsStream("db.properties"));
dataSource = DruidDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
}
}4. 内部类的选择
静态内部类: 当内部类不需要访问外部类的非静态成员时使用
非静态内部类: 当内部类需要访问外部类的非静态成员时使用
局部内部类: 当内部类只在某个方法内部使用时使用
三、常见面试题
⭐ 基础题
1. 什么是多态?多态的前提是什么?
答案:
- 多态是指一种事物有不同的形态
- 前提条件:
- 必须有子父类继承关系或接口实现关系
- 必须有方法的重写
- 父类引用指向子类对象
2. 多态中成员变量和成员方法的访问特点是什么?
答案:
- 成员变量: 编译看左边,运行看左边(看等号左边是谁)
- 成员方法: 编译看左边,运行看右边(看new的是谁)
3. 静态代码块、构造代码块、构造方法的执行顺序是什么?
答案:
- 执行顺序: 静态代码块 > 构造代码块 > 构造方法
- 静态代码块只执行一次
- 构造代码块和构造方法每new一次执行一次
4. 内部类有哪些分类?
答案:
- 成员内部类: 静态成员内部类、非静态成员内部类
- 局部内部类
- 匿名内部类
⭐⭐ 进阶题
5. 多态的好处和弊端是什么?如何解决弊端?
答案:
- 好处: 扩展性强,形参使用父类类型可以接收任意子类对象
- 弊端: 不能直接调用子类特有功能
- 解决方案: 向下转型,配合instanceof判断类型
6. 向下转型可能会出现什么问题?如何避免?
答案:
- 问题: ClassCastException(类型转换异常)
- 原因: 转型时等号左右两边类型不一致
- 避免: 使用instanceof判断类型后再转型
代码示例:
java
Animal animal = new Cat();
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 会抛出ClassCastException
dog.lookHome();
}7. 静态内部类和非静态内部类有什么区别?
答案:
| 区别点 | 静态内部类 | 非静态内部类 |
|---|---|---|
| 关键字 | static修饰 | 无static修饰 |
| 创建对象 | new 外部类.内部类() | new 外部类().new 内部类() |
| 访问外部成员 | 只能访问静态成员 | 可以访问所有成员 |
| 内存占用 | 不依赖外部类对象 | 依赖外部类对象 |
8. 接口和抽象类作为方法参数和返回值时,实际传递和返回的是什么?
答案:
- 接口作为参数: 传递的是实现类对象
- 接口作为返回值: 返回的是实现类对象
- 抽象类作为参数: 传递的是子类对象
- 抽象类作为返回值: 返回的是子类对象
⭐⭐⭐ 高级题
9. 请解释多态的实现原理
答案:
- 多态是基于动态绑定机制实现的
- 编译时: 检查父类是否有该方法(编译看左边)
- 运行时: JVM根据对象的实际类型调用对应的方法(运行看右边)
- 方法表: JVM为每个类维护一个方法表,运行时根据实际对象类型查找方法表
10. 在多态环境下,如何正确调用子类特有方法?
答案:
java
public static void method(Animal animal) {
animal.eat(); // 调用重写的方法
// 方式1: 传统方式
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.lookHome();
}
// 方式2: Java 16+新特性
if (animal instanceof Dog dog) {
dog.lookHome();
}
}11. 静态代码块在实际开发中有哪些应用场景?
答案:
- 数据库连接池初始化
- 配置文件加载
- 静态资源初始化
- 日志框架初始化
- 单例模式的实现
示例:
java
public class ConfigManager {
private static Properties config;
static {
config = new Properties();
try {
config.load(ConfigManager.class.getResourceAsStream("config.properties"));
} catch (IOException e) {
throw new RuntimeException("配置文件加载失败", e);
}
}
public static String getProperty(String key) {
return config.getProperty(key);
}
}12. 什么时候应该使用内部类?
答案:
- 当一个类的内部还需要完整的结构描述时
- 当内部类只为外部类服务,不对外暴露时
- 当需要实现多重继承时
- 当需要隐藏实现细节时
典型应用:
- Map接口中的Entry内部接口
- 迭代器模式的实现
- 事件监听器的实现
- 回调函数的实现
四、实用代码示例
1. 多态综合示例
java
public abstract class Employee {
private int id;
private String name;
public Employee() {}
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public abstract void work();
public int getId() { return id; }
public String getName() { return name; }
}
public class JavaEE extends Employee {
public JavaEE(int id, String name) {
super(id, name);
}
@Override
public void work() {
System.out.println("员工编号为:" + getId() + "的" + getName() + "正在开发电商网站");
}
}
public class Test {
public static void main(String[] args) {
Employee emp = new JavaEE(1, "张三");
emp.work(); // 多态调用
}
}2. USB设备多态示例
java
public interface USB {
void open();
void close();
}
public class Mouse implements USB {
@Override
public void open() {
System.out.println("打开鼠标");
}
@Override
public void close() {
System.out.println("关闭鼠标");
}
}
public class KeyBoard implements USB {
@Override
public void open() {
System.out.println("键盘打开");
}
@Override
public void close() {
System.out.println("键盘关闭");
}
}
public class Computer {
public void useUSB(USB usb) {
usb.open();
System.out.println("使用USB设备");
usb.close();
}
}
public class Test {
public static void main(String[] args) {
Computer computer = new Computer();
computer.useUSB(new Mouse());
computer.useUSB(new KeyBoard());
}
}3. 类型判断与向下转型
java
public class Animal {
public void eat() {
System.out.println("吃吃吃");
}
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗啃骨头");
}
public void lookHome() {
System.out.println("狗看家");
}
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
public class Test {
public static void main(String[] args) {
Animal animal1 = new Dog();
Animal animal2 = new Cat();
method(animal1);
method(animal2);
}
public static void method(Animal animal) {
animal.eat();
if (animal instanceof Dog dog) {
dog.lookHome();
} else if (animal instanceof Cat cat) {
cat.catchMouse();
}
}
}4. 静态代码块应用
java
public class DatabaseUtil {
private static Connection connection;
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test",
"root",
"password"
);
System.out.println("数据库连接初始化成功");
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
return connection;
}
}5. 内部类完整示例
java
public class Person {
private String name = "张三";
public void eat() {
System.out.println("吃饭");
}
static class StaticHeart {
public void jump() {
System.out.println("静态心脏跳");
}
}
class Heart {
private String name = "李四";
public void jump() {
System.out.println("心脏在咣咣咣跳");
System.out.println("外部类name: " + Person.this.name);
System.out.println("内部类name: " + this.name);
}
}
public void testLocalClass() {
class LocalHeart {
public void jump() {
System.out.println("局部心脏跳");
}
}
new LocalHeart().jump();
}
}
public class Test {
public static void main(String[] args) {
Person.StaticHeart staticHeart = new Person.StaticHeart();
staticHeart.jump();
Person.Heart heart = new Person().new Heart();
heart.jump();
Person person = new Person();
person.testLocalClass();
}
}五、经典练习题
练习1: 员工体系
题目: 某IT公司有多名员工,按照员工负责的工作不同,进行了部门的划分(研发部、维护部)。研发部根据所需研发的内容不同,又分为JavaEE工程师、Android工程师;维护部根据所需维护的内容不同,又分为网络维护工程师、硬件维护工程师。
要求:
- 定义员工体系中的所有类
- 指定类之间的继承关系
- 创建工程师对象并调用工作方法
参考实现:

java
public abstract class Employee {
private int id;
private String name;
public Employee() {}
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public abstract void work();
public int getId() { return id; }
public String getName() { return name; }
}
public abstract class Developer extends Employee {
public Developer(int id, String name) {
super(id, name);
}
}
public class JavaEE extends Developer {
public JavaEE(int id, String name) {
super(id, name);
}
@Override
public void work() {
System.out.println("员工编号为:" + getId() + "的" + getName() + "正在开发电商网站");
}
}
public class Android extends Developer {
public Android(int id, String name) {
super(id, name);
}
@Override
public void work() {
System.out.println("员工编号为:" + getId() + "的" + getName() + "正在开发电商的手机客户端软件");
}
}练习2: USB设备
题目: 定义笔记本类,具备开机、关机和使用USB设备的功能。鼠标和键盘要想能在电脑上使用,必须遵守USB规范。
要求:
- USB接口,包含开启功能、关闭功能
- 笔记本类,包含运行功能、关机功能、使用USB设备功能
- 鼠标类,要符合USB接口
- 键盘类,要符合USB接口

参考实现:
java
public interface USB {
void open();
void close();
}
public class Mouse implements USB {
@Override
public void open() {
System.out.println("打开鼠标");
}
@Override
public void close() {
System.out.println("关闭鼠标");
}
public void click() {
System.out.println("鼠标点击");
}
}
public class KeyBoard implements USB {
@Override
public void open() {
System.out.println("键盘打开");
}
@Override
public void close() {
System.out.println("键盘关闭");
}
public void type() {
System.out.println("键盘打字");
}
}
public class Laptop {
public void powerOn() {
System.out.println("笔记本开机");
}
public void powerOff() {
System.out.println("笔记本关机");
}
public void useUSB(USB usb) {
usb.open();
if (usb instanceof Mouse mouse) {
mouse.click();
} else if (usb instanceof KeyBoard keyboard) {
keyboard.type();
}
usb.close();
}
}练习3: 多态应用
题目: 使用多态实现一个图形计算器,可以计算不同图形的面积和周长。
要求:
- 定义图形抽象类
- 实现圆形、矩形、三角形类
- 使用多态方式调用计算方法
参考实现:
java
public abstract class Shape {
public abstract double getArea();
public abstract double getPerimeter();
}
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
@Override
public double getPerimeter() {
return 2 * Math.PI * radius;
}
}
public class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
@Override
public double getPerimeter() {
return 2 * (width + height);
}
}
public class Calculator {
public static void calculate(Shape shape) {
System.out.println("面积: " + shape.getArea());
System.out.println("周长: " + shape.getPerimeter());
}
}六、学习建议
1. 多态学习要点
理解核心概念:
- 多态是运行时行为,不是编译时行为
- 掌握"编译看左边,运行看右边"的规律
- 理解动态绑定机制
实践建议:
- 多写代码验证成员访问特点
- 练习向下转型的场景
- 掌握instanceof的使用
2. 代码块学习要点
记忆执行顺序:
- 静态代码块 > 构造代码块 > 构造方法
- 静态代码块只执行一次
应用场景:
- 理解静态代码块的初始化作用
- 掌握实际开发中的应用场景
3. 内部类学习要点
分类记忆:
- 静态内部类: static修饰,不依赖外部类对象
- 非静态内部类: 依赖外部类对象
- 局部内部类: 在方法内部定义
实践重点:
- 掌握不同内部类的创建方式
- 理解变量重名时的访问方式
- 掌握内部类的实际应用场景
4. 面试准备建议
重点掌握:
- 多态的实现原理和成员访问特点
- 向下转型的风险和解决方案
- 静态代码块的应用场景
- 内部类的分类和使用场景
常见考点:
- 多态代码的执行结果判断
- 类型转换异常的处理
- 静态代码块的执行顺序
- 内部类的创建和使用
5. 代码实践建议
多写多练:
- 每个知识点都要写代码验证
- 尝试修改代码观察不同结果
- 结合实际场景思考应用
深入理解:
- 不仅知道"是什么",还要知道"为什么"
- 理解底层实现原理
- 思考设计思想和应用场景
附录: 知识点速查表
多态速查
| 概念 | 说明 |
|---|---|
| 前提条件 | 继承/实现 + 重写 + 父类引用指向子类对象 |
| 成员变量 | 编译看左边,运行看左边 |
| 成员方法 | 编译看左边,运行看右边 |
| 向上转型 | Fu fu = new Zi() |
| 向下转型 | Zi zi = (Zi)fu |
| 类型判断 | instanceof关键字 |
代码块速查
| 类型 | 格式 | 执行时机 | 执行次数 |
|---|---|---|---|
| 静态代码块 | static {} | 类加载时 | 1次 |
| 构造代码块 | {} | 创建对象时 | 每次创建对象 |
| 构造方法 | 类名() | 创建对象时 | 每次创建对象 |
内部类速查
| 类型 | 创建方式 | 访问外部成员 |
|---|---|---|
| 静态内部类 | new 外部类.内部类() | 只能访问静态成员 |
| 非静态内部类 | new 外部类().new 内部类() | 可以访问所有成员 |
| 局部内部类 | 在方法内部创建 | 可以访问所有成员 |
文档说明: 本文档基于Day10学习资料整理,涵盖多态、代码块、内部类三大核心知识点,适合复习和面试准备使用。