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

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

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

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

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

目录

1. 接口、继承与多态

1.1 接口(implement)

1.1.1 接口的本质

1.1.2 接口的核心特性

1.1.3 接口的应用场景

1.2 类的继承(extends)

1.2.1 继承的基本概念

1.2.2 继承的核心特性

1.2.3 构造方法的继承 

1.2.4 继承的高级特性

1.3 重写 Overload

1.3.1 方法重写基本概念

1.3.2 重写的严格规则

1.3.3 重写的高级特性与细节

1.3.4 特殊场景处理

1.4 Object类

1.4.1 Object类概述

1.4.2 核心方法详解

1.5 instanceof 判断对象类型

1.5.1 基本语法与语义

1.5.2 核心特性

1.5.3 高级用法

1.5.4 典型应用场景

1.6 重载Overload

1.6.1 基本概念与语法

1.6.2 重载的核心规则

1.6.3 方法解析机制

1.6.4 高级特性 

1.6.5 Overload与Override对比

1.7 多态(Polymorphism)

1.7.1 多态的基本概念

1.7.2 多态的实现形式

1.7.3 多态的实现机制

1.7.4 多态的高级应用

1.7.5 多态的使用技巧

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(); 就是一个抽象方法。

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 关键字,提供默认实现,实现类可选择重写或直接使用。

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

default void log(String message) {System.out.println("Log: " + message);
}

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

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

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

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)接口可以继承多个接口(多重继承):

例如:

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,接口方法仍然是公开的,不能是 private 或 protected(私有方法除外)。

(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成员和构造方法

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();  // 继承自VehicleSystem.out.println(myCar.brand + " " + myCar.modelName);}
}

 2. super关键字

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

  • 调用父类构造方法(必须放在子类构造方法的第一行):
class Parent {Parent(int x) { /*...*/ }
}class Child extends Parent {Child(int x, int y) {super(x);  // 调用父类构造// 子类初始化代码}
}
  • 访问父类被隐藏的字段:
class Parent {String name = "Parent";
}class Child extends Parent {String name = "Child";void printNames() {System.out.println(super.name);  // 输出ParentSystem.out.println(this.name);   // 输出Child}
}
  • 调用父类被重写的方法:
@Override
public void someMethod() {super.someMethod();  // 先执行父类逻辑// 添加子类特有逻辑
}

1.2.3 构造方法的继承 

1. 创建子类对象时,构造方法的调用顺序:

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

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

  • 父类实例代码块

  • 父类构造方法

  • 子类实例代码块

  • 子类构造方法

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构造

2. 构造方法注意事项

  • 隐式调用:如果子类构造方法没有显式调用super()或this(),编译器会自动插入super()

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

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

1.2.4 继承的高级特性

1. 这里再强调一下Java四种修饰符再继承中的表现(基础篇(一)提到过):

修饰符同类同包子类不同包
private
default✗*
protected
public

2. final关键字

  • final类:不能被继承

    java

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

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

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

核心特点:

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

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

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

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

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. 方法签名一致性

必须完全匹配以下要素:

  • 方法名完全相同

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

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

class Parent {Number getNumber() { return 0; }
}class Child extends Parent {@OverrideInteger getNumber() { return 42; }  // Integer是Number的子类
}

2. 访问权限的规则

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

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

这里放一个错误示例: 

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

1.3.3 重写的高级特性与细节

1. @Override注解

  • 作用:显式声明这是重写方法

  • 好处:

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

    • 提高代码可读性

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

重要示例:

class Parent {void doWork() {}
}class Child extends Parent {@Overridevoid dooWork() {}  // 编译错误:没有真正重写// 没有@Override时,dooWork()会被当作新方法,导致逻辑错误
}

2. 静态方法"重写"

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

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"(静态绑定)

3. 私有方法重写

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

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,遵守通用契约:

class Person {private String id;@Overridepublic 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);}@Overridepublic int hashCode() {return id.hashCode();  // 必须与equals一致}
}

2. 重写finalize方法(注:Java 9后finalize已被废弃,此处仅为演示)

@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

2. 类定义

// 以下为源码
public class Object {// 原生方法(由JVM实现)private static native void registerNatives();static {registerNatives();}// 其他方法...
}

1.4.2 核心方法详解

1. toString() 方法

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

默认实现:

public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

最佳实践:

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

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

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

@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. 比较关键字段

示例:

@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() 工具方法

@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

哈希集合异常示例:

class Person {String name;// 只重写equals没重写hashCode@Overridepublic 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 接口(标记接口)

  • 默认实现是浅拷贝

  • 通常应重写为深拷贝

@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()

示例:

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

缺点:

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

  • 容易遗漏深层字段

方式2:序列化法

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

示例:

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:复制构造器/工厂

示例:

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<?> 对象

  • 可用于反射

// 中括号内是泛型的知识,后面会讲,这里仅做一个延申示例
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 表示对象是指定类型或其子类型的实例

2. 基本示例

// 超类的引用指向子类的继承
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 检查对象是否属于以下情况:

  • 指定类或其子类的实例

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

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

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 (实现接口)

2. null 处理

instanceof 对 null 值总是返回 false

String str = null;
System.out.println(str instanceof String);  // false

3. 编译时检查

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

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

1.5.3 高级用法

1. 泛型类型检查

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

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

2. 数组类型检查

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

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. 安全类型转换

public void process(Object obj) {if (obj instanceof String) {String s = (String) obj;// 安全使用s}
}

2 多态方法处理

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();}
}

3. equals方法实现

@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 本身是高效操作(通常只是指针比较)

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

2. 最佳实践

  • 优先使用多态:能用多态解决的问题不要用 instanceof

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

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

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

3. 反例

// 不好的写法:过度使用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)是指:在同一个类中定义了多个方法,名称相同,但参数列表不同(类型、数量或顺序)且与返回类型和访问修饰符无关。

2. 基本语法形式

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. 合法重载的条件

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

参数类型不同:

void print(int i) {}
void print(String s) {}

参数数量不同:

void log(String msg) {}
void log(String msg, int level) {}

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

void process(int a, String b) {}
void process(String a, int b) {}

2. 不构成重载的情况

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

1.6.3 方法解析机制

1. 编译时确定

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

  • 方法名称

  • 参数表达式类型

  • 参数数量

2. 匹配优先级

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

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

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

  • 自动装箱/拆箱

  • 可变参数

3. 典型匹配过程

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. 可变参数重载

void process(String... strs) {}
void process(String first, String... rest) {}  // 合法但危险

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

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

process("hello");  // 该调用哪个方法?

编译器无法确定:

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

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

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

// 都会产生"ambiguous method call"错误
process("a");
process("a", "b");  
process("a", "b", "c");

 因此这个点要尤其注意。

2. 自动装箱与重载

void execute(int num) {}
void execute(Integer num) {}execute(10);     // 调用int版本
execute(new Integer(10));  // 调用Integer版本

3. 继承中的重载

class Parent {void run(String input) {}
}class Child extends Parent {// 这是重载不是重写!void run(int input) {}
}

4. 静态方法重载

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

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. 编译时多态(静态多态)

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

class Calculator {int add(int a, int b) { return a + b; }double add(double a, double b) { return a + b; } // 重载
}

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

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

class Animal {void sound() { System.out.println("动物发声"); }
}class Dog extends Animal {@Overridevoid 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. 接口多态

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(); // 多态调用

2. 多态参数

void drawShape(Shape shape) { // 接受任何Shape子类shape.draw();
}drawShape(new Circle()); // 绘制圆形
drawShape(new Square()); // 绘制方形

3. 工厂模式中的多态

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与类型转换

if (animal instanceof Dog) {Dog dog = (Dog) animal; // 向下转型dog.bark();
}

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

3. 模板方法模式:

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

1.8 小结

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

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

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

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

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

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

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) 问题发生的时机

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

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

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

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

举个例子 

class Parent {Parent() {printLength();  // 危险调用}void printLength() {System.out.println("Parent method");}
}class Child extends Parent {private String text = "hello";  // 还未初始化@Overridevoid printLength() {System.out.println(text.length());  // text此时为null!}
}new Child();  // 抛出NullPointerException

 Q4: instanceof 和 getClass() 有什么区别?

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

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

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

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

直接强制转换可能抛出 ClassCastException

Object obj = "Hello";
Integer num = (Integer) obj;  // 运行时抛出ClassCastException

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

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

<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: 如何强制子类重写方法?

两种方式:

  • 声明为抽象方法

    abstract class Parent {abstract void mustOverride();
    }
  • 抛出UnsupportedOperationException

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

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

     

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
    如若转载,请注明出处:http://www.pswp.cn/diannao/92029.shtml
    繁体地址,请注明出处:http://hk.pswp.cn/diannao/92029.shtml
    英文地址,请注明出处:http://en.pswp.cn/diannao/92029.shtml

    如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

    相关文章

    【39】MFC入门到精通——C++ /MFC操作文件行(读取,删除,修改指定行)

    文章目录1 通过关键词&#xff0c;读取某一行 &#xff08;3种方法&#xff09;2 删除 指定行3 修改 指定行1 通过关键词&#xff0c;读取某一行 &#xff08;3种方法&#xff09; 通过定位关键词&#xff0c;读取某一行信息,返回CString //通过定位关键词&#xff0c;读取某…

    5 种可行的方法:如何将 Redmi 联系人备份到 Mac

    将 Redmi 联系人备份到 Mac 是防止因手机损坏、丢失或更换设备而导致数据丢失的重要措施。虽然云服务提供了便利性&#xff0c;但拥有离线备份可以提供额外的安全性&#xff0c;而无需完全依赖互联网。如果您想知道如何将 Redmi 联系人备份到 Mac&#xff0c;本文将为您介绍 5 …

    LeRobot 具身智能机械臂 SO-ARM100 从搭建到训练全流程

    今天给大家分享一下 LeRobot 具身智能机械臂 SO-ARM100 的完整使用流程&#xff0c;包括设备组装、环境配置、远程控制、数据录制到模型训练的全过程。适合刚入门具身智能的小伙伴参考学习。 一、前期准备与资源获取 在开始之前&#xff0c;我们需要准备好相关的资源和工具&a…

    LINUX720 SWAP扩容;新增逻辑卷;逻辑卷扩容;数据库迁移;gdisk

    SWAP空间扩展 方法一 增加硬盘或分区扩展 swap -s mkswap /dev/sdd6 blkid /dev/sdd6 swapon /dev/sdd6 swapon -s vim /etc/fstab /dev/sdd6 swap swap defaults 0 0 开机自动扩容 swap -s [rootweb ~]# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sd…

    Python 进程间通信:TCP安全加密数据传输

    最近在写安全方面的程序&#xff0c;有需求&#xff0c;就做了这些TCP加密数据传输类。 utils.safeUtils的内容详见&#xff1a; SafeObj&#xff1a;Python 高安全性加密数据容器类-CSDN博客SafeKey&#xff1a;Python 高安全性加密密码容器类-CSDN博客 如有任何问题或漏洞欢迎…

    Windows批量修改文件属性方法

    标题使用icacls命令&#xff08;推荐批量操作&#xff09;打开管理员权限的命令提示符&#xff08;CMD&#xff09;执行以下命令&#xff1a;cmd icacls "文件夹路径" /grant 用户名:(OI)(CI)F /T /C 参数说明&#xff1a;(OI)&#xff1a;对象继承 - 适用于文件夹(C…

    Entity Component System架构

    ECS架构 1 简介 在当今快速发展的软件开发领域&#xff0c;游戏开发、实时模拟等场景对系统的性能、灵活性和可扩展性提出了极高的要求。传统的面向对象架构在面对复杂且动态变化的实体时&#xff0c;往往会出现代码耦合度高、扩展性差等问题。​ ECS&#xff08;Entity - Com…

    .vscode 扩展配置

    一、vue快捷键配置 在项目.vscode下新建vue3.0.code-snippets 每当输入vue3.0后自动生成代码片段 {"Vue3.0快速生成模板": {"scope": "vue","prefix": "Vue3.0","body": ["<template>"," &…

    一个基于阿里云的C端Java服务的整体项目架构

    1.背景介绍 总结一下工作使用到的基于通常的公有云的项目整体架构&#xff0c;如何基于公有云建设安全可靠的服务&#xff0c;以阿里云为例的整体架构&#xff1b;1. 全局流量治理层&#xff08;用户请求入口&#xff09;1.1 域名与 DNS 解析域名注册与备案&#xff1a;通过阿里…

    《剥开洋葱看中间件:Node.js请求处理效率与错误控制的深层逻辑》

    在Node.js的运行时环境中&#xff0c;中间件如同一系列精密咬合的齿轮&#xff0c;驱动着请求从进入到响应的完整旅程&#xff0c;而洋葱模型则是这组齿轮的传动系统。它以一种看似矛盾的方式融合了顺序与逆序、分离与协作——让每个处理环节既能独立工作&#xff0c;又能感知全…

    GaussDB union 的用法

    1 union 的作用union 运算符用于组合两个或更多 select 语句的结果集。2 union 使用前提union 中的每个 select 语句必须具有相同的列数这些列也必须具有相似的数据类型每个 select 语句中的列也必须以相同的顺序排列3 union 语法select column_name(s) from table1 union sele…

    构建足球实时比分APP:REST API与WebSocket接入方案详解

    在开发足球实时比分应用时&#xff0c;数据接入方式的选择直接影响用户体验和系统性能。本文将客观分析REST API和WebSocket两种主流接入方案的技术特点、适用场景和实现策略&#xff0c;帮助开发者做出合理选择。一、REST API&#xff1a;灵活的数据获取方案核心优势标准化接口…

    Linux文件系统三要素:块划分、分区管理与inode结构解析

    理解文件系统 我们知道文件可以分为磁盘文件和内存文件&#xff0c;内存文件前面我们已经谈过了&#xff0c;下面我们来谈谈磁盘文件。 目录 一、引入"块"概念 解析 stat demo.c 命令输出 基本信息 设备信息 索引节点信息 权限信息 时间戳 二、引入"分区…

    基于paddleDetect的半监督目标检测实战

    基于paddleDetect的半监督目标检测实战前言相关介绍前提条件实验环境安装环境项目地址使用paddleDetect的半监督方法训练自己的数据集准备数据分割数据集配置参数文件PaddleDetection-2.7.0/configs/semi_det/denseteacher/denseteacher_ppyoloe_plus_crn_l_coco_semi010.ymlPa…

    计算机网络:(十)虚拟专用网 VPN 和网络地址转换 NAT

    计算机网络&#xff1a;&#xff08;十&#xff09;虚拟专用网 VPN 和网络地址转换 NAT前言一、虚拟专用网 VPN1. 基础概念与作用2. 工作原理3. 常见类型4. 协议对比二、NAT&#xff1a;网络地址转换1. 基础概念与作用2. 工作原理与类型3. 优缺点与问题4. 进阶类型三、VPN 与 N…

    数位 dp

    数位dp 特点 问题大多是指“在 [l,r][l,r][l,r] 的区间内&#xff0c;满足……的数字的个数、种类&#xff0c;等等。” 但是显然&#xff0c;出题人想要卡你&#xff0c;rrr 肯定是非常大的&#xff0c;暴力枚举一定超时。 于是就有了数位 dp。 基本思路 数位 dp 说白了…

    Selector的用法

    Selector的用法 Selector是基于lxml构建的支持XPath选择器、CSS选择器&#xff0c;以及正则表达式&#xff0c;功能全面&#xff0c;解析速度和准确度非常高 from scrapy import Selectorbody <html><head><title>HelloWorld</title></head>&…

    Netty封装Websocket并实现动态路由

    引言 关于Netty和Websocket的介绍我就不多讲了,网上一搜一大片。现如今AI的趋势发展很热门,长连接对话也是会经常接触到的,使用Websocket实现长连接,那么很多人为了快速开发快速集成就会使用spring-boot-starter-websocket依赖快速实现,但是注意该实现是基于tomcat的,有…

    行为型设计模式:解释器模式

    解释器模式 解释器模式介绍 解释器模式使用频率不算高&#xff0c;通常用来描述如何构建一个简单“语言”的语法解释器。它只在一些非常特定的领域被用到&#xff0c;比如编译器、规则引擎、正则表达式、SQL 解析等。不过&#xff0c;了解它的实现原理同样很重要&#xff0c;能…

    SaTokenException: 未能获取对应StpLogic 问题解决

    &#x1f4dd; Sa-Token 异常处&#xff1a;未能获取对应StpLogic&#xff0c;typeuser&#x1f9e8; 异常信息 cn.dev33.satoken.exception.SaTokenException: 未能获取对应StpLogic&#xff0c;typeuser抛出位置&#xff1a; throw new SaTokenException("未能获取对应S…