Java 一些相关概念深入搭配理解
Java 中的重载、覆写和多态
一、重载(Overloading)
重载指的是在同一个类中,方法的名字相同,但参数列表不同(参数的类型、数量、或顺序不同)。
特点:
方法名相同,但方法参数不同(类型、个数或顺序)。
返回值可以相同也可以不同。
重载和方法的修饰符(如
public
、static
)无关。
重载的意义:
重载可以实现类似功能的方法使用相同的名字,让代码更简洁易读。例如,println
方法就是重载的一个经典例子,可以打印字符串、数字、布尔值等。
举例代码:
class OverloadingExample {
// 重载方法:加法运算
public int add(int a, int b) { // 两个整数相加
return a + b;
}
public double add(double a, double b) { // 两个小数相加
return a + b;
}
public String add(String a, String b) { // 拼接两个字符串
return a + b;
}
public static void main(String[] args) {
OverloadingExample example = new OverloadingExample();
// 调用不同的add方法
System.out.println(example.add(3, 4)); // 输出 7
System.out.println(example.add(3.5, 4.5)); // 输出 8.0
System.out.println(example.add("Hello, ", "World!")); // 输出 Hello, World!
}
}
总结: 重载是同一个类中方法名相同,但参数不同,编译器根据传入参数的类型和数量,选择调用哪个方法。
二、覆写(Overriding)
覆写指的是在子类中修改父类的方法实现,从而改变父类的行为。
特点:
方法名、参数列表、返回值必须和父类方法一致。
子类方法的访问权限不能比父类更严格(如,父类是
public
,子类也必须是public
)。子类可以用自己的逻辑替换掉父类的方法逻辑。
被覆写的方法不能是
private
或static
。
覆写的意义:
覆写是实现多态的核心,它允许子类根据自己的需要来定制父类的行为。
举例代码:
// 父类
class Animal {
public void sound() {
System.out.println("动物发出某种声音");
}
}
// 子类
class Dog extends Animal {
@Override
public void sound() { // 覆写父类的方法
System.out.println("狗叫:汪汪汪");
}
}
class Cat extends Animal {
@Override
public void sound() { // 覆写父类的方法
System.out.println("猫叫:喵喵喵");
}
}
public class OverridingExample {
public static void main(String[] args) {
Animal myDog = new Dog(); // 父类引用指向子类对象
Animal myCat = new Cat();
myDog.sound(); // 输出 狗叫:汪汪汪
myCat.sound(); // 输出 猫叫:喵喵喵
}
}
总结: 覆写是子类修改父类方法的实现。通过覆写,你可以在子类中提供特定的实现逻辑。
三、多态(Polymorphism)
多态指的是同一个对象的不同表现形式,是基于继承和方法覆写实现的。
多态的实现:
父类引用指向子类对象。
调用方法时,编译期看父类有没有这个方法,运行时根据实际对象类型调用对应的方法(即子类覆写的方法)。
多态的意义:
多态让代码更加灵活、可扩展。例如,一个方法可以接受父类类型的参数,而不需要关心具体是哪个子类对象。
举例代码:
class Animal {
public void sound() {
System.out.println("动物发出某种声音");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("狗叫:汪汪汪");
}
}
class Cat extends Animal {
@Override
public void sound() {
System.out.println("猫叫:喵喵喵");
}
}
public class PolymorphismExample {
public static void makeSound(Animal animal) { // 参数是父类类型
animal.sound(); // 调用方法,实际执行的是子类覆写的方法
}
public static void main(String[] args) {
Animal dog = new Dog(); // 父类引用指向子类对象
Animal cat = new Cat();
makeSound(dog); // 输出 狗叫:汪汪汪
makeSound(cat); // 输出 猫叫:喵喵喵
}
}
核心点:
编译时:调用的方法是根据引用的类型(父类)来检查的。
运行时:真正执行的是对象实际类型(子类)的方法。
总结: 多态通过父类引用灵活调用子类的方法,能让程序更具扩展性。
四、重载、覆写、多态的区别总结
定义
同类中方法名相同,参数列表不同
子类中修改父类方法
父类引用指向子类对象,运行时调用子类方法
发生位置
同一个类
子类和父类之间
子类和父类之间
方法签名
参数列表必须不同
参数列表必须相同
基于覆写实现
返回值
可以不同
必须相同(或子类返回值是父类返回值的子类)
不影响多态
修饰符
没有限制
子类权限不能更严格
不影响多态
作用
功能类似的方法用相同名字,增强代码可读性
修改父类行为,提供子类特定实现
提高代码灵活性和扩展性
Java 的抽象类和接口
在 Java 中,抽象类(Abstract Class)和接口(Interface)都是实现多态和代码复用的核心机制,但它们的用途和设计思想有显著区别。以下是详细的对比分析和实际场景示例:
一、相似点
无法直接实例化 两者都不能直接创建对象(如
new AbstractClass()
或new Interface()
是非法的)。可定义抽象方法 都可以声明抽象方法(未实现的方法),子类/实现类必须实现这些方法。
支持多态 都可以作为父类型引用指向子类对象,实现运行时多态。
二、核心区别
继承方式
单继承(一个类只能继承一个抽象类)
多实现(一个类可实现多个接口)
成员变量
可以包含实例变量和常量
只能包含 public static final
常量
构造方法
可以有构造方法
不能有构造方法
方法实现
可以包含具体方法(非抽象方法)
Java 8 前只能有抽象方法,之后支持默认方法
设计目的
表示“是什么”(is-a 关系)
表示“能做什么”(has-a 能力)
三、使用场景
1. 抽象类的典型场景
场景:共享代码逻辑和状态 当多个相关类需要共享公共代码、字段或非公共方法时,抽象类更适合。例如:
// 抽象类:表示一种“图形”的通用逻辑
public abstract class Shape {
private String color;
public Shape(String color) {
this.color = color;
}
// 抽象方法:子类必须实现
public abstract double area();
// 具体方法:子类共享的代码
public String getColor() {
return color;
}
}
// 子类:圆形
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
// 子类:矩形
public class Rectangle extends Shape {
private double width, height;
public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
关键点:
抽象类
Shape
提供了共享字段color
和方法getColor()
,子类无需重复实现。构造方法初始化公共状态,确保子类必须通过
super()
调用父类构造方法。
2. 接口的典型场景
场景:定义行为契约 当需要为不同类定义一组能力(跨继承树)时,接口更灵活。例如:
// 接口:定义“可飞行”能力
public interface Flyable {
void fly();
}
// 接口:定义“可鸣叫”能力
public interface Honkable {
void honk();
}
// 类:飞机实现“飞行”
public class Airplane implements Flyable {
@Override
public void fly() {
System.out.println("Airplane flies with engines.");
}
}
// 类:鸭子同时实现“飞行”和“鸣叫”
public class Duck implements Flyable, Honkable {
@Override
public void fly() {
System.out.println("Duck flaps wings to fly.");
}
@Override
public void honk() {
System.out.println("Duck quacks.");
}
}
关键点:
Duck
可以同时实现多个接口,实现多重行为组合。接口的默认方法(Java 8+)可提供默认实现,减少重复代码:
public interface Flyable {
void fly();
// 默认方法:无需子类实现
default void emergencyLand() {
System.out.println("Emergency landing initiated.");
}
}
四、实际开发中的选择原则
选择抽象类的情况
多个类需要共享代码或状态。
需要定义非
public
方法或字段。需要构造方法初始化逻辑。
典型的模板方法模式(定义算法骨架)。
选择接口的情况
定义跨继承树的能力(如
Comparable
、Serializable
)。需要多重继承行为。
定义 API 契约(如 JDBC 的
Connection
接口)。结合 Lambda 表达式(函数式接口,如
Runnable
)。
五、经典示例对比
Java 集合框架中的抽象类 vs 接口
接口
List
定义列表的行为契约(如add()
,get()
),所有列表类必须遵守。抽象类
AbstractList
提供List
接口的通用实现(如基于索引的操作),ArrayList
和LinkedList
继承它以复用代码。
// 接口定义行为
public interface List<E> {
boolean add(E e);
E get(int index);
// ...
}
// 抽象类提供部分实现
public abstract class AbstractList<E> implements List<E> {
public boolean add(E e) {
add(size(), e); // 调用抽象方法
return true;
}
// 抽象方法:子类必须实现
public abstract void add(int index, E element);
}
// 具体类:复用 AbstractList 的逻辑
public class ArrayList<E> extends AbstractList<E> {
@Override
public void add(int index, E element) {
// 具体实现...
}
}
六、总结
抽象类:用于构建类的层次结构,强调代码复用和“是什么”。
接口:用于定义跨类的能力,强调多态和“能做什么”。
Java 8+:接口通过默认方法缩小了与抽象类的差距,但设计意图不同。
根据具体需求灵活选择两者,甚至结合使用(如接口定义行为,抽象类提供部分实现)。
Last updated
Was this helpful?