编程语言Java——核心技术篇(一)封装、继承和多态

同专栏基础知识篇写在这里,有兴趣的可以去看看:

编程语言Java入门------基础知识篇(一)-CSDN博客

编程语言Java入门------基础知识篇(二)-CSDN博客

编程语言Java入门------基础知识篇(三)类和对象-CSDN博客

编程语言Java入门------基础知识篇(四)包装类、数字处理类-CSDN博客

目录

[1. 接口、继承与多态](#1. 接口、继承与多态)

[1.1 接口(implement)](#1.1 接口(implement))

[1.1.1 接口的本质](#1.1.1 接口的本质)

[1.1.2 接口的核心特性](#1.1.2 接口的核心特性)

[1.1.3 接口的应用场景](#1.1.3 接口的应用场景)

[1.2 类的继承(extends)](#1.2 类的继承(extends))

[1.2.1 继承的基本概念](#1.2.1 继承的基本概念)

[1.2.2 继承的核心特性](#1.2.2 继承的核心特性)

[1.2.3 构造方法的继承](#1.2.3 构造方法的继承)

[1.2.4 继承的高级特性](#1.2.4 继承的高级特性)

[1.3 重写 Overload](#1.3 重写 Overload)

[1.3.1 方法重写基本概念](#1.3.1 方法重写基本概念)

[1.3.2 重写的严格规则](#1.3.2 重写的严格规则)

[1.3.3 重写的高级特性与细节](#1.3.3 重写的高级特性与细节)

[1.3.4 特殊场景处理](#1.3.4 特殊场景处理)

[1.4 Object类](#1.4 Object类)

[1.4.1 Object类概述](#1.4.1 Object类概述)

[1.4.2 核心方法详解](#1.4.2 核心方法详解)

[1.5 instanceof 判断对象类型](#1.5 instanceof 判断对象类型)

[1.5.1 基本语法与语义](#1.5.1 基本语法与语义)

[1.5.2 核心特性](#1.5.2 核心特性)

[1.5.3 高级用法](#1.5.3 高级用法)

[1.5.4 典型应用场景](#1.5.4 典型应用场景)

[1.6 重载Overload](#1.6 重载Overload)

[1.6.1 基本概念与语法](#1.6.1 基本概念与语法)

[1.6.2 重载的核心规则](#1.6.2 重载的核心规则)

[1.6.3 方法解析机制](#1.6.3 方法解析机制)

[1.6.4 高级特性](#1.6.4 高级特性)

[1.6.5 Overload与Override对比](#1.6.5 Overload与Override对比)

[1.7 多态(Polymorphism)](#1.7 多态(Polymorphism))

[1.7.1 多态的基本概念](#1.7.1 多态的基本概念)

[1.7.2 多态的实现形式](#1.7.2 多态的实现形式)

[1.7.3 多态的实现机制](#1.7.3 多态的实现机制)

[1.7.4 多态的高级应用](#1.7.4 多态的高级应用)

[1.7.5 多态的使用技巧](#1.7.5 多态的使用技巧)

[1.8 小结](#1.8 小结)


1. 接口、继承与多态

1.1 接口(implement)

1.1.1 接口的本质

接口(Interface)是面向对象编程中的一个重要概念,它定义了一组方法签名而不包含实现。它是一种完全抽象的"类模板"。在Java 8之后,接口可以包含默认方法和静态方法实现,但其核心目的仍然是定义契约。

1.1.2 接口的核心特性

1. 基本特性:

(1) 抽象性

只声明方法,不提供实现:接口仅定义方法的签名(名称、参数、返回类型),不包含方法体,具体实现由实现类完成。

关注"做什么",而非"怎么做":接口定义了一组行为规范,强调功能的抽象描述,而不关心具体的实现细节。

不能实例化:接口本身不能被直接创建对象,必须通过实现类来使用。

(2) 契约性

强制实现类提供方法:任何类实现接口时,必须实现接口中声明的所有抽象方法(除非是抽象类)。

标准化交互方式:接口规定了类之间的通信协议,确保不同的类可以按照统一的方式被调用。

增强代码可维护性:通过接口约束,可以更容易地替换不同的实现,而不会影响调用方代码。

(3) 多态支持

同一接口,不同实现:不同的类可以实现同一个接口,并通过接口类型引用调用,实现运行时多态。

提高代码灵活性:例如,List 接口可以有 ArrayList 和 LinkedList 两种实现,但调用方式一致。

适用于策略模式、依赖注入等:接口多态是许多设计模式(如工厂模式、观察者模式)的基础。

2. 技术特性

  • 方法声明

(1)抽象方法(默认):

接口中的方法默认是 public abstract,无需显式声明。

例如:void fly(); 就是一个抽象方法。

java 复制代码
interface Swimmer {
    void swim();
}

interface Flyer {
    void fly();
}

class Duck implements Swimmer, Flyer {  // 实现多个接口
    public void swim() { /*...*/ }
    public void fly() { /*...*/ }
}

这里面我们定义两个接口分别来代表鸭子的两种行为------游泳 swim 和飞 fly 。那么当我们定义一个Duck类时用关键字 implements 来继承接口。当继承接口以后,接口内的方法就必须实现。

(2)默认方法(Java 8+):

使用 default 关键字,提供默认实现,实现类可选择重写或直接使用。

主要用于接口演化,避免破坏已有代码。例如:

java 复制代码
default void log(String message) {
    System.out.println("Log: " + message);
}

(3)静态方法(Java 8+):

使用 static 关键字定义,属于接口本身,不能被子类继承或重写。

通常用于工具方法,例如:

java 复制代码
static boolean isValid(String input) {
    return input != null && !input.isEmpty();
}

(4)私有方法(Java 9+):(这个仅作了解,因为我用的时Java8)

用于在接口内部复用代码,避免默认方法重复逻辑。

只能被接口内的其他方法调用。

  • 变量声明

(1)自动为 public static final

接口中的变量默认是常量,即使不写修饰符也会自动加上 public static final

例如:int MAX_COUNT = 100; 等同于 public static final int MAX_COUNT = 100;

(2)主要用于配置或全局常量:

例如定义错误码、默认配置等。

  • 继承关系

(1)接口可以继承多个接口(多重继承):

例如:

java 复制代码
interface A { void methodA(); }
interface B { void methodB(); }
interface C extends A, B { void methodC(); }

实现 C 的类必须实现 methodA()methodB()methodC()

(2)类可以实现多个接口:

例如:class MyClass implements A, B, C { ... }

  • 访问修饰符

(1)方法默认 public

即使不写 public,接口方法仍然是公开的,不能是 privateprotected(私有方法除外)。

(2)接口本身可以是 public 或包私有:

如果不加 public,则接口仅在当前包内可见。

3. 高级特性

(1) 默认方法(Default Methods)

  • 解决接口演化问题:

    • 在 Java 8 之前,如果给接口新增方法,所有实现类都必须修改,否则编译失败。

    • 默认方法允许接口提供默认实现,避免破坏已有代码。

  • 可以被重写:

    • 实现类可以选择使用默认实现,也可以覆盖它。
  • 冲突处理:

    • 如果类实现了两个接口,且它们有相同的默认方法,必须显式重写,否则编译错误。

(2) 静态方法(Static Methods)

  • 属于接口本身:

    • 通过接口名直接调用,例如:MyInterface.staticMethod()
  • 不能被子类继承或重写:

    • 即使实现类定义了同名静态方法,也不会覆盖接口的静态方法。

(3) 函数式接口(Functional Interface)

  • 仅含一个抽象方法:

    • 例如 Runnable(仅 run())、Comparator(仅 compare())。
  • 可用 Lambda 表达式实现:

    • 例如:Runnable r = () -> System.out.println("Hello");
  • 可加 @FunctionalInterface 注解:

    • 用于编译器检查,确保接口符合函数式接口规范。

(4) 标记接口(Marker Interface)

  • 无任何方法:

    • 例如 SerializableCloneable,仅用于标记类具有某种能力。
  • 通过反射或类型检查使用:

    • 例如 if (obj instanceof Serializable) { ... }

1.1.3 接口的应用场景

  1. 定义通用行为(如 ComparableRunnable)。

  2. 实现回调机制(如事件监听器 EventListener)。

  3. 依赖注入(Spring 框架中广泛使用接口解耦)。

  4. 策略模式(通过不同实现类切换算法)。

  5. API 设计(如 JDBC 的 ConnectionStatement 接口)。

1.2 类的继承(extends)

1.2.1 继承的基本概念

继承是面向对象编程中最重要的特性之一,它允许新建类基于现有类进行构建。现有类称为父类/超类/基类,新建类称为子类/派生类。

生物学类比:

就像"猫"继承自动物类,具有动物的所有基本特征(呼吸、进食等),同时又有自己特有的行为(喵喵叫、抓老鼠)。

其语法形式为:

// 基础语法

class ParentClass {

// 父类成员

}

class ChildClass extends ParentClass { // 使用extends关键字

// 可以添加新成员

// 可以重写父类方法

}

1.2.2 继承的核心特性

  1. 成员继承

子类自动获得父类的非私有成员(字段和方法),包括:

  • public和protected修饰的成员

  • 默认访问修饰符(包私有)的同包成员

  • 不包括private成员和构造方法

java 复制代码
class Vehicle {
    protected String brand = "Ford";
    public void honk() {
        System.out.println("Tuut, tuut!");
    }
}

class Car extends Vehicle {
    private String modelName = "Mustang";
    
    public static void main(String[] args) {
        Car myCar = new Car();
        myCar.honk();  // 继承自Vehicle
        System.out.println(myCar.brand + " " + myCar.modelName);
    }
}
  1. super关键字

用于访问父类的成员,有四种使用方式:

  • 调用父类构造方法(必须放在子类构造方法的第一行):
java 复制代码
class Parent {
    Parent(int x) { /*...*/ }
}

class Child extends Parent {
    Child(int x, int y) {
        super(x);  // 调用父类构造
        // 子类初始化代码
    }
}
  • 访问父类被隐藏的字段:
java 复制代码
class Parent {
    String name = "Parent";
}

class Child extends Parent {
    String name = "Child";
    
    void printNames() {
        System.out.println(super.name);  // 输出Parent
        System.out.println(this.name);   // 输出Child
    }
}
  • 调用父类被重写的方法:
java 复制代码
@Override
public void someMethod() {
    super.someMethod();  // 先执行父类逻辑
    // 添加子类特有逻辑
}

1.2.3 构造方法的继承

  1. 创建子类对象时,构造方法的调用顺序:
  • 父类静态代码块(首次加载时)

  • 子类静态代码块(首次加载时)

  • 父类实例代码块

  • 父类构造方法

  • 子类实例代码块

  • 子类构造方法

java 复制代码
class Parent {
    static { System.out.println("Parent静态块"); }
    { System.out.println("Parent实例块"); }
    Parent() { System.out.println("Parent构造"); }
}

class Child extends Parent {
    static { System.out.println("Child静态块"); }
    { System.out.println("Child实例块"); }
    Child() { 
        // 隐含super();
        System.out.println("Child构造"); 
    }
}

// 输出顺序:
// Parent静态块
// Child静态块
// Parent实例块
// Parent构造
// Child实例块
// Child构造
  1. 构造方法注意事项
  • 隐式调用:如果子类构造方法没有显式调用super()或this(),编译器会自动插入super()

  • 必须第一行:super()或this()调用必须是构造方法的第一条语句

  • 无默认构造:如果父类没有无参构造,子类必须显式调用super(参数)

1.2.4 继承的高级特性

  1. 这里再强调一下Java四种修饰符再继承中的表现(基础篇(一)提到过):
修饰符 同类 同包 子类 不同包
private
default ✗*
protected
public
  1. final关键字
  • final类:不能被继承

    java

    java 复制代码
    final class FinalClass { /*...*/ }
    // class Child extends FinalClass {}  // 编译错误
  • final方法:不能被子类重写

    java 复制代码
    class Parent {
        final void finalMethod() { /*...*/ }
    }
    
    class Child extends Parent {
        // void finalMethod() {}  // 编译错误
    }
  • final变量:基本类型值不可变,引用类型引用不可变

    java 复制代码
    final int x = 10;
    // x = 20;  // 编译错误
    
    final List<String> list = new ArrayList<>();
    list.add("item");  // 允许
    // list = new LinkedList<>();  // 编译错误

1.3 重写 Overload

1.3.1 方法重写基本概念

方法重写(Override)是面向对象编程中子类重新定义父类已有方法的行为,也称为方法覆盖。这是实现运行时多态(动态绑定)的关键机制。

核心特点:

  • 发生在继承关系的子类中

  • 方法签名(名称+参数列表)必须完全相同

  • 子类方法提供新的实现逻辑

  • 通过父类引用调用时,实际执行子类的方法

java 复制代码
class Animal {
    public void makeSound() {
        System.out.println("动物发出声音");
    }
}

class Cat extends Animal {
    @Override  // 注解明确表示这是重写
    public void makeSound() {
        System.out.println("喵喵叫");
    }
}

// 使用
Animal myCat = new Cat();
myCat.makeSound();  // 输出"喵喵叫"(动态绑定)

1.3.2 重写的严格规则

  1. 方法签名一致性

必须完全匹配以下要素:

  • 方法名完全相同

  • 参数列表(类型、数量、顺序)完全相同

  • 返回类型可以是原返回类型的子类(协变返回)

java 复制代码
class Parent {
    Number getNumber() { return 0; }
}

class Child extends Parent {
    @Override
    Integer getNumber() { return 42; }  // Integer是Number的子类
}
  1. 访问权限的规则

子类方法的访问权限不能比父类更严格,但可以更宽松:

父类方法访问权限 允许的子类方法权限
private 不能重写
默认(package) protected, public
protected protected, public
public 只能是public

这里放一个错误示例:

java 复制代码
class Parent {
    public void method() {}
}

class Child extends Parent {
    @Override
    void method() {}  // 编译错误:不能降低访问权限
}

1.3.3 重写的高级特性与细节

  1. @Override注解
  • 作用:显式声明这是重写方法

  • 好处:

    • 编译器会检查是否真的重写了父类方法

    • 提高代码可读性

    • 防止因拼写错误导致意外创建新方法

重要示例:

java 复制代码
class Parent {
    void doWork() {}
}

class Child extends Parent {
    @Override
    void dooWork() {}  // 编译错误:没有真正重写

    // 没有@Override时,dooWork()会被当作新方法,导致逻辑错误
}
  1. 静态方法"重写"

静态方法不能被重写,只能被隐藏(看起来类似但机制不同):

java 复制代码
class Parent {
    static void staticMethod() {
        System.out.println("Parent static");
    }
}

class Child extends Parent {
    static void staticMethod() {  // 这是方法隐藏,不是重写
        System.out.println("Child static");
    }
}
// 测试
Parent p = new Child();
p.staticMethod();  // 输出"Parent static"(静态绑定)
  1. 私有方法重写

私有方法不能被重写,子类中定义同名方法实际上是新方法:

java 复制代码
class Parent {
    private void privateMethod() {
        System.out.println("Parent private");
    }
    
    void callPrivate() {
        privateMethod();  // 总是调用Parent的实现
    }
}

class Child extends Parent {
    // 这是全新的方法,不是重写
    private void privateMethod() {
        System.out.println("Child private");
    }
}

// 测试
Child c = new Child();
c.callPrivate();  // 输出"Parent private"

1.3.4 特殊场景处理

  1. 重写equals和hashCode

当重写equals时必须同时重写hashCode,遵守通用契约:

java 复制代码
class Person {
    private String id;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        // instanceof检查对象是否是指定类型或其子类型的示例
        if (!(o instanceof Person)) return false;  
        Person p = (Person) o;
        return id.equals(p.id);
    }

    @Override
    public int hashCode() {
        return id.hashCode();  // 必须与equals一致
    }
}
  1. 重写finalize方法(注:Java 9后finalize已被废弃,此处仅为演示)
java 复制代码
@Override
protected void finalize() throws Throwable {
    try {
        // 清理资源
    } finally {
        super.finalize();  // 必须调用父类实现
    }
}

1.4 Object类

Object 类是 Java 所有类的超类(父类),位于 java.lang 包中。每个 Java 类都直接或间接继承自 Object 类,它提供了所有对象的基本行为。

1.4.1 Object类概述

  1. 核心地位
  • 所有类的根父类(没有显式继承的类自动继承 Object)

  • 数组类型也是 Object 的子类

  • 泛型中的类型参数最终都会擦除到 Object

  1. 类定义
java 复制代码
// 以下为源码
public class Object {
    // 原生方法(由JVM实现)
    private static native void registerNatives();
    static {
        registerNatives();
    }
    
    // 其他方法...
}

1.4.2 核心方法详解

1. toString() 方法

作用:返回对象的字符串表示形式

默认实现:

java 复制代码
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

最佳实践:

  • 所有可实例化的类都应重写此方法

  • 应返回简洁但信息丰富的表示

  • 格式建议:ClassName[field1=value1, field2=value2]

java 复制代码
@Override
public String toString() {
    return "Person{" +
           "name='" + name + '\'' +
           ", age=" + age +
           '}';
}

在不重写toString() 方法前,使用该方法均返回该对象的地址。上面的例子改写后则返回字符串。

2. equals() 与 hashCode()

(1)equals() 方法

契约:

  1. 自反性:x.equals(x) 必须返回 true

  2. 对称性:x.equals(y)y.equals(x) 结果相同

  3. 传递性:如果 x.equals(y)y.equals(z),则 x.equals(z)

  4. 一致性:多次调用结果不变(除非对象被修改)

  5. 非空性:x.equals(null) 必须返回 false

重写步骤:

  1. 检查是否同一引用

  2. 检查是否为 null

  3. 检查是否同类(考虑继承时使用 getClass() 或 instanceof)

  4. 强制类型转换

  5. 比较关键字段

示例:

java 复制代码
@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);
}

(2)hashCode() 方法

契约:

  • 一致性:对象未修改时多次调用返回相同值

  • 相等对象必须产生相同哈希码

  • 不等对象最好产生不同哈希码(但不是必须)

重写建议:

  • 使用相同的字段计算

  • 使用 java.util.Objects.hash() 工具方法

java 复制代码
@Override
public int hashCode() {
    return Objects.hash(name, age);
}

ps:为什么重写 equals() 必须重写 hashCode()?

Java 对象契约明确规定:

  1. 如果 obj1.equals(obj2)true,则 obj1.hashCode() 必须等于 obj2.hashCode()

  2. hashCode() 相等的对象不一定 equals()true

哈希集合异常示例:

java 复制代码
class Person {
    String name;
    // 只重写equals没重写hashCode
    
    @Override
    public boolean equals(Object o) {
        //... 实现比较逻辑
    }
}

Person p1 = new Person("Alice");
Person p2 = new Person("Alice");

Set<Person> set = new HashSet<>();
set.add(p1);
set.contains(p2);  // 可能返回false,违反预期

3. clone() 方法

作用:创建并返回对象的副本

特点:

  • 必须实现 Cloneable 接口(标记接口)

  • 默认实现是浅拷贝

  • 通常应重写为深拷贝

java 复制代码
@Override
protected Object clone() throws CloneNotSupportedException {
    Person cloned = (Person) super.clone();
    cloned.birthDate = (Date) birthDate.clone(); // 深拷贝
    return cloned;
}

ps:如何实现深拷贝?

特性 浅拷贝 深拷贝
基本类型字段 值复制 值复制
引用类型字段 复制引用(共享对象) 递归创建新对象
修改影响 影响原对象 不影响原对象
实现复杂度 简单(默认clone行为) 复杂(需递归处理)

方式1:Cloneable 接口

步骤:

  1. 实现 Cloneable 标记接口

  2. 重写 clone() 方法(提升为public)

  3. 对每个引用字段递归调用 clone()

示例:

java 复制代码
class Department implements Cloneable {
    String name;
    Employee manager;
    
    @Override
    public Department clone() {
        try {
            Department cloned = (Department) super.clone();
            cloned.manager = this.manager.clone();  // 深拷贝关键
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();  // 不可能发生
        }
    }
}

缺点:

  • 需要所有引用类型都支持克隆

  • 容易遗漏深层字段

方式2:序列化法

原理:通过对象序列化/反序列化实现完全复制

示例:

java 复制代码
import java.io.*;

public class SerializationUtils {
    public static <T extends Serializable> T deepCopy(T object) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(object);
            
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            return (T) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

优点:自动处理整个对象图;不需要每个类单独实现

缺点:性能较低;所有相关类必须实现 Serializable

方式3:复制构造器/工厂

示例:

java 复制代码
class Project {
    private String title;
    private List<Task> tasks;
    
    // 复制构造器
    public Project(Project other) {
        this.title = other.title;
        this.tasks = new ArrayList<>();
        for (Task task : other.tasks) {
            this.tasks.add(new Task(task));  // 递归深拷贝
        }
    }
}

优点:代码控制力强;不需要实现特殊接口

缺点:需要为每个类编写复制逻辑;维护成本高

深拷贝方式选择:

  • 优先选择不可变对象:避免拷贝需求

  • 简单对象用Cloneable:结构简单时适用

  • 复杂对象用序列化:对象图复杂时推荐

  • 性能敏感场景用构造器:需要最高性能时使用

  • 使用第三方库:如Apache Commons Lang的SerializationUtils

4. getClass() 方法

作用:返回对象的运行时类对象

特点:

  • final 方法,不能重写

  • 返回 Class<?> 对象

  • 可用于反射

java 复制代码
// 中括号内是泛型的知识,后面会讲,这里仅做一个延申示例
Class<?> clazz = obj.getClass();
String className = clazz.getName();

1.5 instanceof 判断对象类型

instanceof 是 Java 中的一个二元运算符,用于检查对象是否是指定类型或其子类型的实例。它是 Java 类型系统的重要组成部分,广泛应用于类型检查和类型转换场景。

1.5.1 基本语法与语义

  1. 语法形式

object instanceof Type

  • object:要检查的对象引用

  • Type:类/接口类型或数组类型

  • 返回 boolean 值:true 表示对象是指定类型或其子类型的实例

  1. 基本示例
java 复制代码
// 超类的引用指向子类的继承
Object obj = new ArrayList<>(); 
// ArrayList可以理解成一个动态的数组,但因为有泛型所以可以在里面装任何对象
// 比如我装浮点型数字,整型数字,字符串等

System.out.println(obj instanceof List);   // true
System.out.println(obj instanceof ArrayList); // true
System.out.println(obj instanceof String);    // false
// instanceof是判断obj类型的运算符,而ArrayList继承List,所以返回true

1.5.2 核心特性

  1. 类型检查范围

instanceof 检查对象是否属于以下情况:

  • 指定类或其子类的实例

  • 指定接口的实现类的实例

  • 指定数组类型或其子类型的实例

java 复制代码
Number num = Integer.valueOf(10);

System.out.println(num instanceof Number);  // true (相同类)
System.out.println(num instanceof Integer); // true (子类)
System.out.println(num instanceof Comparable); // true (实现接口)
  1. null 处理

instanceofnull 值总是返回 false

java 复制代码
String str = null;
System.out.println(str instanceof String);  // false
  1. 编译时检查

编译器会进行静态类型检查,阻止明显不可能成立的比较:

java 复制代码
Integer i = 10;
// System.out.println(i instanceof String);  // 编译错误:不兼容的类型

1.5.3 高级用法

  1. 泛型类型检查

由于类型擦除,instanceof 不能检查具体的泛型类型参数:

java 复制代码
List<String> stringList = new ArrayList<>();

System.out.println(stringList instanceof List);       // true
// System.out.println(stringList instanceof List<String>);  // 编译错误
System.out.println(stringList instanceof List<?>);   // true
  1. 数组类型检查

可以检查数组类型及其维度:

java 复制代码
int[] arr = new int[10];

System.out.println(arr instanceof int[]);    // true
System.out.println(arr instanceof Object);  // true (所有数组都是Object子类)
System.out.println(arr instanceof long[]);  // false

1.5.4 典型应用场景

  1. 安全类型转换
java 复制代码
public void process(Object obj) {
    if (obj instanceof String) {
        String s = (String) obj;
        // 安全使用s
    }
}

2 多态方法处理

java 复制代码
void handleShape(Shape shape) {
    if (shape instanceof Circle) {
        Circle c = (Circle) shape;
        c.drawCircle();
    } else if (shape instanceof Rectangle) {
        Rectangle r = (Rectangle) shape;
        r.drawRectangle();
    }
}
  1. equals方法实现
java 复制代码
@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (!(obj instanceof MyClass)) return false;
    MyClass other = (MyClass) obj;
    // 比较字段...
}

1.5.5 使用性能与使用方法

  1. 性能考虑
  • instanceof 本身是高效操作(通常只是指针比较)

  • 过度使用可能表明设计问题(违反多态原则)

  1. 最佳实践
  • 优先使用多态:能用多态解决的问题不要用 instanceof

  • 检查顺序:将更具体的类型检查放在前面

  • 模式匹配:Java 16+ 应使用模式匹配简化代码

  • 防御性编程:在强制转换前总是先检查

  1. 反例
java 复制代码
// 不好的写法:过度使用instanceof
void process(Object obj) {
    if (obj instanceof TypeA) {
        // 处理A
    } else if (obj instanceof TypeB) {
        // 处理B
    } // 更多else if...
}

// 更好的设计:使用多态
interface Processor {
    void process();
}

class TypeA implements Processor { /*...*/ }
class TypeB implements Processor { /*...*/ }

1.6 重载Overload

方法重载是Java中实现编译时多态的重要机制,它允许在同一个类中定义多个同名方法,通过不同的参数列表来区分它们。

1.6.1 基本概念与语法

  1. 什么是重载?

方法重载(Overloading)是指:在同一个类中定义了多个方法,名称相同,但参数列表不同(类型、数量或顺序)且与返回类型和访问修饰符无关。

  1. 基本语法形式
java 复制代码
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; }
}

1.6.2 重载的核心规则

1. 合法重载的条件

必须满足以下至少一项参数列表差异:

参数类型不同:

java 复制代码
void print(int i) {}
void print(String s) {}

参数数量不同:

java 复制代码
void log(String msg) {}
void log(String msg, int level) {}

参数顺序不同(类型必须真正不同):

java 复制代码
void process(int a, String b) {}
void process(String a, int b) {}

2. 不构成重载的情况

  • 仅返回类型不同:
java 复制代码
int getValue() {}
double getValue() {}  // 编译错误
  • 仅访问修饰符不同:
java 复制代码
public void execute() {}
private void execute() {}  // 编译错误
  • 仅参数名称不同:(这个真的显而易见)
java 复制代码
void test(int a) {}
void test(int b) {}  // 编译错误
  • 仅抛出异常不同:
java 复制代码
void parse() throws IOException {}
void parse() throws SQLException {}  // 编译错误

1.6.3 方法解析机制

  1. 编译时确定

Java编译器在编译阶段就确定调用哪个重载方法,基于:

  • 方法名称

  • 参数表达式类型

  • 参数数量

  1. 匹配优先级

当存在多个可能匹配时,按以下顺序选择:

  • 精确匹配:参数类型完全一致

  • 基本类型自动转换 :按byte → short → int → long → float → double顺序

  • 自动装箱/拆箱

  • 可变参数

  1. 典型匹配过程
java 复制代码
class OverloadDemo {
    void test(int a) { System.out.println("int"); }
    void test(Integer a) { System.out.println("Integer"); }
    void test(long a) { System.out.println("long"); }
    void test(Object a) { System.out.println("Object"); }
    void test(int... a) { System.out.println("varargs"); }
}

// 调用示例
OverloadDemo demo = new OverloadDemo();
demo.test(5);    // 输出"int"(精确匹配)
demo.test(5L);   // 输出"long"
demo.test(5.0);  // 输出"Object"(自动装箱Double)
demo.test();     // 输出"varargs"

1.6.4 高级特性

  1. 可变参数重载
java 复制代码
void process(String... strs) {}
void process(String first, String... rest) {}  // 合法但危险

这个示例中可能对"合法但危险"这个说法感到疑惑。这里我们可以先从两个角度来分析。

从Java语法角度来看,它是合法的,因为两个方法的参数列表形式确实不同,而且满足方法重载的基本要求,至少在编译时Java编译器不会报错。但如果我这么调用该用哪个方法?

java 复制代码
process("hello");  // 该调用哪个方法?

编译器无法确定:

  • 可以匹配process(String... strs),将"hello"作为可变参数的唯一元素

  • 也可以匹配process(String first, String... rest),将"hello"作为first参数,rest为空数组

再或者我这么写,就直接会出现编译错误。

java 复制代码
// 都会产生"ambiguous method call"错误
process("a");
process("a", "b");  
process("a", "b", "c");

因此这个点要尤其注意。

  1. 自动装箱与重载
java 复制代码
void execute(int num) {}
void execute(Integer num) {}

execute(10);     // 调用int版本
execute(new Integer(10));  // 调用Integer版本
  1. 继承中的重载
java 复制代码
class Parent {
    void run(String input) {}
}

class Child extends Parent {
    // 这是重载不是重写!
    void run(int input) {}
}
  1. 静态方法重载

静态方法也可以重载,且遵循相同规则:

java 复制代码
class Utils {
    static void format(Date date) {}
    static void format(LocalDate date) {}
}

1.6.5 Overload与Override对比

特性 重载(Overload) 重写(Override)
发生位置 同一个类内 子类与父类之间
参数要求 必须不同 必须相同
返回类型 可以不同 必须相同或协变
访问权限 可以不同 不能更严格
异常抛出 可以不同 不能更宽泛
绑定时机 编译时静态绑定 运行时动态绑定
多态类型 编译时多态 运行时多态

1.7 多态(Polymorphism)

多态是面向对象编程的三大特性之一(封装、继承、多态),它允许不同类的对象对同一消息做出不同响应,极大地提高了代码的灵活性和可扩展性。

1.7.1 多态的基本概念

1. 什么是多态?

多态(Polymorphism)指同一操作作用于不同的对象,可以产生不同的执行结果。在Java中主要表现为:

  • 编译时多态:方法重载(静态绑定)

  • 运行时多态:方法重写(动态绑定)

2. 多态的必要条件:

  • 继承关系:存在父子类关系

  • 方法重写:子类重写父类方法

  • 向上转型:父类引用指向子类对象

1.7.2 多态的实现形式

1. 编译时多态(静态多态)

通过方法重载实现,在编译时确定调用哪个方法:

java 复制代码
class Calculator {
    int add(int a, int b) { return a + b; }
    double add(double a, double b) { return a + b; } // 重载
}

2. 运行时多态(动态多态)

通过方法重写实现,在运行时确定实际调用的方法:

java 复制代码
class Animal {
    void sound() { System.out.println("动物发声"); }
}

class Dog extends Animal {
    @Override
    void sound() { System.out.println("汪汪叫"); } // 重写
}

Animal myDog = new Dog(); // 向上转型
myDog.sound(); // 输出"汪汪叫"(运行时确定)

1.7.3 多态的实现机制

1 JVM方法调用原理:

  • 静态绑定:编译时确定(private/static/final方法和构造方法)

  • 动态绑定:基于对象实际类型确定(普通实例方法)

2. 虚方法表(VTable)

JVM通过虚方法表实现动态绑定:

  • 每个类有一个方法表

  • 包含该类所有可重写方法的实际入口地址

  • 调用时根据对象实际类型查找方法表

1.7.4 多态的高级应用

1. 接口多态

java 复制代码
interface USB {
    void transfer();
}

class Mouse implements USB {
    public void transfer() { System.out.println("鼠标数据传输"); }
}

class Keyboard implements USB {
    public void transfer() { System.out.println("键盘数据传输"); }
}

// 使用
USB device = new Mouse();
device.transfer(); // 多态调用
  1. 多态参数
java 复制代码
void drawShape(Shape shape) { // 接受任何Shape子类
    shape.draw();
}

drawShape(new Circle()); // 绘制圆形
drawShape(new Square()); // 绘制方形
  1. 工厂模式中的多态
java 复制代码
abstract class Product {
    abstract void use();
}

class ConcreteProduct extends Product {
    void use() { System.out.println("使用具体产品"); }
}

class Factory {
    Product create() { return new ConcreteProduct(); } // 返回抽象类型
}

1.7.5 多态的使用技巧

1. instanceof与类型转换

java 复制代码
if (animal instanceof Dog) {
    Dog dog = (Dog) animal; // 向下转型
    dog.bark();
}

2 避免过度使用instanceof,在instanceof方法讲解中提到过,这里不再赘述

3. 模板方法模式:

java 复制代码
abstract class Game {
    // 模板方法(final防止重写)
    final void play() {
        initialize();
        startPlay();
        endPlay();
    }

    abstract void initialize();
    abstract void startPlay();
    abstract void endPlay();
}

1.8 小结

最近在看面试题,刚好放几个出来看看你答不答的出来。

Q1: 为什么字段没有多态性?

java 复制代码
class Parent { String name = "Parent"; }
class Child extends Parent { String name = "Child"; }

Parent obj = new Child();
System.out.println(obj.name); // 输出"Parent"(字段静态绑定)

字段访问在编译时确定,不参与运行时多态

Q2: 静态方法能否实现多态?

不能,静态方法调用是静态绑定的:

java 复制代码
class Parent {
    static void method() { System.out.println("Parent"); }
}

class Child extends Parent {
    static void method() { System.out.println("Child"); }
}

Parent obj = new Child();
obj.method(); // 输出"Parent"(只看引用类型)

Q3: 构造方法中的多态问题:为什么在构造方法中调用可重写方法是危险的做法?

根本原因在于Java的初始化顺序问题。

(1) 对象构造的生命周期

Java对象构造遵循严格的初始化顺序:

  • 分配对象内存空间(所有字段设为默认值:0/null/false)

  • 调用父类构造方法(递归向上)

  • 初始化当前类的实例变量(按声明顺序)

  • 执行当前类构造方法体

(2) 问题发生的时机

当父类构造方法调用可重写方法时:

  • 子类字段尚未初始化(仍为默认值)

  • 但方法已经被子类重写,会调用子类版本

  • 导致子类方法操作未初始化的字段

举个例子

java 复制代码
class Parent {
    Parent() {
        printLength();  // 危险调用
    }
    
    void printLength() {
        System.out.println("Parent method");
    }
}

class Child extends Parent {
    private String text = "hello";  // 还未初始化
    
    @Override
    void printLength() {
        System.out.println(text.length());  // text此时为null!
    }
}

new Child();  // 抛出NullPointerException

Q4: instanceofgetClass() 有什么区别?

  • instanceof 检查类型继承关系(包含子类)

  • getClass() == SomeClass.class 检查精确类型匹配

java 复制代码
Number num = new Integer(10);

System.out.println(num instanceof Number);  // true
System.out.println(num.getClass() == Number.class);  // false

Q5: 为什么需要 instanceof 而不能直接强制转换?

直接强制转换可能抛出 ClassCastException

java 复制代码
Object obj = "Hello";
Integer num = (Integer) obj;  // 运行时抛出ClassCastException

Q6: 如何检查泛型的具体类型?

由于类型擦除,需要通过其他方式(如传递Class对象):

java 复制代码
<T> void checkType(Object obj, Class<T> type) {
    if (type.isInstance(obj)) {
        T t = type.cast(obj);
        // 处理t
    }
}

Q7: JVM如何实现方法重写?

通过**虚方法表(vtable)**实现:

  • 每个类有一个方法表,包含方法实际入口地址

  • 子类方法表包含继承的方法和重写的方法

  • 调用时根据对象实际类型查找方法表

Q8: 为什么静态方法不能重写?

因为方法绑定时机不同:

  • 静态方法:编译时静态绑定(根据引用类型)

  • 实例方法:运行时动态绑定(根据实际对象类型)

Q9: 构造方法能被重写吗?

不能,因为:

  1. 构造方法名必须与类名相同

  2. 子类构造方法必须调用父类构造(通过super)

  3. 本质上是不同的方法

Q10: 如何强制子类重写方法?

两种方式:

  • 声明为抽象方法

    java 复制代码
    abstract class Parent {
        abstract void mustOverride();
    }
  • 抛出UnsupportedOperationException

    java 复制代码
    class Parent {
        void mustOverride() {
            throw new UnsupportedOperationException("必须重写此方法");
        }
    }

不知道你答出来了没。毕竟这几个题看似很难,实际上一点也不简单。建议多看、多练、多理解。

相关推荐
程序员JerrySUN1 小时前
Linux 内核基础统简全解:Kbuild、内存分配和地址映射
java·linux·运维·服务器·嵌入式硬件·缓存·文件系统
lixzest1 小时前
快速梳理遗留项目
java·c++·python
xnglan2 小时前
使用爬虫获取游戏的iframe地址
开发语言·爬虫·python·学习
某个默默无闻奋斗的人2 小时前
【矩阵专题】Leetcode54.螺旋矩阵(Hot100)
java·算法·leetcode
zhysunny2 小时前
04.建造者模式的终极手册:从快餐定制到航天飞船的组装哲学
java·开发语言·建造者模式
郝学胜-神的一滴2 小时前
建造者模式:构建复杂对象的优雅方式
开发语言·c++·程序人生·建造者模式
AAIshangyanxiu2 小时前
最新基于R语言结构方程模型分析与实践技术应用
开发语言·r语言·结构方程模型·生态统计学
Layux3 小时前
使用钉钉开源api发送钉钉工作消息
java·spring boot·钉钉
Fly-ping4 小时前
【前端】JavaScript 的事件循环 (Event Loop)
开发语言·前端·javascript
范纹杉想快点毕业4 小时前
基于C语言的Zynq SOC FPGA嵌入式裸机设计和开发教程
c语言·开发语言·数据库·嵌入式硬件·qt·fpga开发·嵌入式实时数据库