同专栏基础知识篇写在这里,有兴趣的可以去看看:
编程语言Java入门——基础知识篇(三)类和对象-CSDN博客
编程语言Java入门——基础知识篇(四)包装类、数字处理类-CSDN博客
目录
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)
无任何方法:
例如
Serializable
、Cloneable
,仅用于标记类具有某种能力。
通过反射或类型检查使用:
例如
if (obj instanceof Serializable) { ... }
。
1.1.3 接口的应用场景
1. 定义通用行为(如 Comparable
、Runnable
)。
2. 实现回调机制(如事件监听器 EventListener
)。
3. 依赖注入(Spring 框架中广泛使用接口解耦)。
4. 策略模式(通过不同实现类切换算法)。
5. API 设计(如 JDBC 的 Connection
、Statement
接口)。
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(); // 继承自Vehicle
System.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); // 输出Parent
System.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 {
@Override
Integer getNumber() { return 42; } // Integer是Number的子类
}
2. 访问权限的规则
子类方法的访问权限不能比父类更严格,但可以更宽松:
父类方法访问权限 | 允许的子类方法权限 |
---|---|
private | 不能重写 |
默认(package) | protected, public |
protected | protected, public |
public | 只能是public |
这里放一个错误示例:
class Parent {
public void method() {}
}
class Child extends Parent {
@Override
void method() {} // 编译错误:不能降低访问权限
}
1.3.3 重写的高级特性与细节
1. @Override注解
作用:显式声明这是重写方法
好处:
编译器会检查是否真的重写了父类方法
提高代码可读性
防止因拼写错误导致意外创建新方法
重要示例:
class Parent {
void doWork() {}
}
class Child extends Parent {
@Override
void 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;
@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一致
}
}
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() 方法
契约:
自反性:
x.equals(x)
必须返回 true对称性:
x.equals(y)
与y.equals(x)
结果相同传递性:如果
x.equals(y)
且y.equals(z)
,则x.equals(z)
一致性:多次调用结果不变(除非对象被修改)
非空性:
x.equals(null)
必须返回 false
重写步骤:
检查是否同一引用
检查是否为 null
检查是否同类(考虑继承时使用 getClass() 或 instanceof)
强制类型转换
比较关键字段
示例:
@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 对象契约明确规定:
如果
obj1.equals(obj2)
为true
,则obj1.hashCode()
必须等于obj2.hashCode()
但
hashCode()
相等的对象不一定equals()
为true
哈希集合异常示例:
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
接口(标记接口)默认实现是浅拷贝
通常应重写为深拷贝
@Override
protected Object clone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
cloned.birthDate = (Date) birthDate.clone(); // 深拷贝
return cloned;
}
ps:如何实现深拷贝?
特性 | 浅拷贝 | 深拷贝 |
---|---|---|
基本类型字段 | 值复制 | 值复制 |
引用类型字段 | 复制引用(共享对象) | 递归创建新对象 |
修改影响 | 影响原对象 | 不影响原对象 |
实现复杂度 | 简单(默认clone行为) | 复杂(需递归处理) |
方式1:Cloneable 接口
步骤:
实现
Cloneable
标记接口重写
clone()
方法(提升为public)对每个引用字段递归调用
clone()
示例:
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:序列化法
原理:通过对象序列化/反序列化实现完全复制
示例:
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 {
@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. 接口多态
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"; // 还未初始化
@Override
void 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: 构造方法能被重写吗?
不能,因为:
构造方法名必须与类名相同
子类构造方法必须调用父类构造(通过super)
本质上是不同的方法
Q10: 如何强制子类重写方法?
两种方式:
声明为抽象方法
abstract class Parent { abstract void mustOverride(); }
抛出UnsupportedOperationException
class Parent { void mustOverride() { throw new UnsupportedOperationException("必须重写此方法"); } }
不知道你答出来了没。毕竟这几个题看似很难,实际上一点也不简单。建议多看、多练、多理解。