Skip to content

Java面向对象高级特性:多态、代码块与内部类完全指南

目录


一、核心知识点

1. 多态

1.1 多态概述

定义: 一种事物有不同的形态

面向对象三大特征: 封装、继承、多态

1.2 多态的前提条件

  1. 必须有子父类继承关系或者接口实现关系
  2. 必须有方法的重写(没有方法的重写,多态没有意义)
  3. 父类引用指向子类对象: 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. 什么是多态?多态的前提是什么?

答案:

  • 多态是指一种事物有不同的形态
  • 前提条件:
    1. 必须有子父类继承关系或接口实现关系
    2. 必须有方法的重写
    3. 父类引用指向子类对象

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工程师;维护部根据所需维护的内容不同,又分为网络维护工程师、硬件维护工程师。

要求:

  1. 定义员工体系中的所有类
  2. 指定类之间的继承关系
  3. 创建工程师对象并调用工作方法

参考实现:

员工体系结构

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规范。

要求:

  1. USB接口,包含开启功能、关闭功能
  2. 笔记本类,包含运行功能、关机功能、使用USB设备功能
  3. 鼠标类,要符合USB接口
  4. 键盘类,要符合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: 多态应用

题目: 使用多态实现一个图形计算器,可以计算不同图形的面积和周长。

要求:

  1. 定义图形抽象类
  2. 实现圆形、矩形、三角形类
  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学习资料整理,涵盖多态、代码块、内部类三大核心知识点,适合复习和面试准备使用。