Java多态
多态概述
多态是继封装、继承之后,面向对象的第三大特性。
多态现实意义理解:
现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学张三既是学生也是人,即出现两种形态。
Java作为面向对象的语言,同样可以描述一个事物的多种形态。如Student类继承了Person类,一个Student的对象便既是Student,又是Person。
Java中的多态是指允许不同类的对象对同一消息做出响应的能力。换句话说,多态允许我们通过一个通用的接口来使用不同的底层类型。多态是面向对象编程的核心概念之一,它允许程序更加灵活和可扩展。
多态主要通过以下两种方式实现:
-
1.方法重载(Overloading):在同一个类中定义多个同名方法,但它们的参数类型或数量不同。编译器根据方法的参数类型和数量来决定调用哪个方法。
-
2.方法重写(Overriding):子类重写父类的方法。当通过父类的引用来调用被重写的方法时,实际执行的是子类中的版本。这是实现运行时多态的关键。
-
3.接口实现:一个类实现一个接口,并提供接口中所有方法的具体实现。通过接口类型的引用指向实现类的对象,调用方法时会根据实际对象的类型来执行相应的方法。
对象的多态
对象的多态性是面向对象编程(OOP)中的一个核心概念,它指的是同一个操作作用于不同的对象,可以有不同的解释和不同的执行结果。换句话说,多态允许不同类的对象对同一消息做出响应。在 Java 中,对象的多态性主要通过继承和接口实现。
多态的实现
多态主要通过以下两种方式实现:
- 1.继承(Inheritance):通过继承,子类可以继承父类的属性和方法,并且可以重写(Override)父类的方法,或者添加新的方法和属性。当通过父类的引用指向子类对象时,调用的方法将根据实际对象的类型来决定,这就是多态。
- 2.接口(Interfaces):接口定义了一组方法规范,但不提供具体实现。类通过实现(Implement)接口来提供这些方法的具体实现。一个类可以实现多个接口,从而实现多态。
关键点
- 向上转型(Upcasting):将子类对象赋值给父类类型的引用。这是实现多态的关键步骤,因为这样可以调用在父类中声明的方法,而实际执行的是子类中重写的方法。
- 动态绑定(Dynamic Binding):Java 通过动态绑定在运行时决定调用哪个方法。这意味着实际调用的方法取决于对象的实际类型,而不是引用变量的类型。
示例
class Animal {
void makeSound() {
System.out.println("Some generic sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Bark");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Meow");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
Animal myAnimal = new Animal();
Animal myDog = new Dog();
Animal myCat = new Cat();
myAnimal.makeSound(); // 输出: Some generic sound
myDog.makeSound(); // 输出: Bark
myCat.makeSound(); // 输出: Meow
}
}
在这个例子中,Animal
是一个基类,Dog
和 Cat
是它的子类。每个子类都重写了 makeSound()
方法。在 main
方法中,我们创建了不同类型的 Animal
引用指向不同类型的对象。调用 makeSound()
方法时,实际执行的是对象实际类型的方法,这就是多态的体现。
总结
对象的多态性允许我们编写更加灵活和可扩展的代码。通过继承和接口,我们可以设计出能够以统一的方式处理不同对象的系统。多态性是面向对象设计中实现“开闭原则”(对扩展开放,对修改封闭)的关键技术之一。
向上转型
就是父类指向子类
父类 Animal 子类 dog
Animal animal = new Dog()
向下转型
- 语法:子类类型 引用名 = (子类类型) 父类引用;
- 只能强转父类的引用,不能强转父类的对象;
- 要求父类的引用必须指向的是当前目标类型的对象;
- 可以调用于子类类型中所有的成员。
在Java中,向上转型(Upcasting)和向下转型(Downcasting)是处理继承关系中对象类型转换的两种基本方式。它们是多态性的重要组成部分,允许我们以灵活的方式操作对象。
向上转型(Upcasting)
向上转型是将子类对象转换为父类类型的过程。这种转换是自动的,不需要显式地进行类型转换操作。向上转型利用了继承的特性,允许子类对象被视为父类类型的实例。
特点:
- 向上转型是安全的,因为子类是父类的一个特化,所以子类对象可以被视为父类对象。
- 向上转型后,只能访问父类中定义的属性和方法,无法直接访问子类特有的属性和方法。
示例:
java
自动换行
复制
class Animal {
public void eat() {
System.out.println("Animal eats");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("Dog barks");
}
}
public class TestUpcasting {
public static void main(String[] args) {
Dog myDog = new Dog();
Animal myAnimal = myDog; // 向上转型
myAnimal.eat(); // 可以调用父类的方法
// myAnimal.bark(); // 编译错误,因为 bark() 不是 Animal 类的方法
}
}
向下转型(Downcasting)
向下转型是将父类对象转换为子类类型的过程。这种转换不是自动的,需要显式地进行类型转换,并且必须确保转换的对象实际上是目标子类的实例,否则会抛出 ClassCastException
。
特点:
- 向下转型用于访问子类特有的属性和方法。
- 必须确保转换的安全性,否则需要使用
instanceof
关键字进行检查。
示例:
java
自动换行
复制
class Animal {
public void eat() {
System.out.println("Animal eats");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("Dog barks");
}
}
public class TestDowncasting {
public static void main(String[] args) {
Animal myAnimal = new Dog();
Dog myDog = (Dog) myAnimal; // 向下转型
myDog.bark(); // 可以调用 Dog 类特有的方法
}
}
在实际应用中,向下转型通常在需要访问子类特有功能时使用,比如在处理多态性时,根据对象的实际类型执行特定操作。
总结
- 向上转型:子类对象转为父类类型,是自动且安全的,但访问范围受限于父类。
- 向下转型:父类对象转为子类类型,需要显式转换且要确保类型安全,可以访问子类特有属性和方法。
Java的动态绑定机制
动态绑定(Dynamic Binding),也称为运行时绑定,是面向对象编程中多态性实现的关键机制之一。它允许程序在运行时决定调用哪个方法,而不是在编译时决定。这种机制使得程序能够更加灵活和可扩展,因为它允许子类覆盖(override)父类的方法,而调用者无需知道对象的具体类型。
动态绑定的工作原理
在Java等支持多态的语言中,当一个方法被调用时,实际调用哪个方法取决于对象的实际类型(运行时类型),而不是引用变量的类型(编译时类型)。这意味着,即使通过父类类型的引用调用方法,实际执行的也是对象实际类型的方法版本。
编译类型为A,运行类型为B
所以既可以使用A的方法也可以使用A的方法
动态绑定的条件
为了实现动态绑定,需要满足以下条件:
- 1.方法必须是多态的:即方法必须在父类中声明,并在子类中被覆盖(重写)。
- 2.方法调用必须通过引用变量进行:直接通过类名调用静态方法或构造函数不涉及动态绑定。
- 3.方法不能是私有的:私有方法不能被子类覆盖,因此它们不参与多态。
- 4.方法不能是静态的:静态方法是类方法,它们不参与对象的多态行为。
动态绑定的例子
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.makeSound(); // 输出 "Dog barks"
myCat.makeSound(); // 输出 "Cat meows"
}
}
1.当调用对象方法的时候,该方法会和对象的内存地址/运行类型绑定
2.当调用对象时,属性没有动态绑定时,哪里声明,哪里使用
动态绑定的理解: 我的理解就是基于继承,动态的(jvm根据自己的规则)选择调用子类,还是重写父类的方法,规则就是子类有选择子类,子类没用就选择父类
多态参数
方法定义的参数类型为父类类型,实参类型为子类类型
在Java中,多态参数是指方法定义的参数类型为父类类型,而实际传递给方法的参数是子类类型的对象。这种做法充分利用了Java的多态性,允许方法接收不同子类类型的对象,从而实现更通用和灵活的代码。
例子
假设我们有一个父类 Animal
和两个子类 Dog
和 Cat
:
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat meows");
}
}
现在,我们定义一个方法,其参数类型为 Animal
:
public void animalSound(Animal animal) {
animal.makeSound();
}
这个方法可以接收任何 Animal
类型的对象,包括 Dog
和 Cat
的实例:
public class TestPolymorphism {
public static void main(String[] args) {
Animal myDog = new Dog();
Animal myCat = new Cat();
animalSound(myDog); // 输出: Dog barks
animalSound(myCat); // 输出: Cat meows
}
}
多态参数的优势
- 1.灵活性:方法可以接受任何继承自父类的对象,这使得方法更加通用。
- 2.扩展性:如果未来有新的
Animal
子类被创建,无需修改animalSound
方法,就可以直接传递新的子类对象。 - 3.代码复用:通过多态参数,可以减少重复代码,因为不同的子类对象可以共享同一个方法实现。
注意事项
- 当使用多态参数时,方法内部可以调用在父类中定义的方法。如果子类重写了这些方法,那么实际调用的是子类中重写的方法版本。
- 如果需要访问子类特有的方法或属性,可能需要进行向下转型(使用
instanceof
关键字检查类型,然后进行类型转换)。
接口(Interface)和继承类(Class)
接口(Interface)和继承类(Class)在Java中都是实现代码复用和组织结构的重要机制,但它们在设计和使用上有明显的区别:
接口(Interface)
- 1.定义:接口是一种引用类型,它定义了一组方法规范,但不提供具体实现。接口可以包含常量、方法签名、默认方法、静态方法和私有方法。
- 2.实现:类通过关键字
implements
来实现一个或多个接口。实现接口的类必须提供接口中所有方法的具体实现,除非该类是抽象类。 - 3.多实现:一个类可以实现多个接口,这提供了实现多种行为的能力。
- 4.用途:接口主要用于定义不同类对象之间共有的行为,促进不同类之间的多态性。
- 5.版本兼容性:接口支持向后兼容的扩展,即可以在不破坏现有代码的情况下添加新的方法。
继承类(Class Inheritance)
- 1.定义:继承是一种机制,允许一个类(子类)继承另一个类(父类)的属性和方法。
- 2.实现:子类通过关键字
extends
来继承父类。子类继承父类的所有非私有成员,并可以添加新的成员或重写父类的方法。 - 3.单继承:在Java中,一个类只能直接继承一个父类,但可以通过继承链继承多个类的特性。
- 4.用途:继承主要用于代码复用和创建类之间的层次结构。
- 5.版本兼容性:修改父类可能会影响所有继承它的子类,因此需要谨慎进行。
关键区别
- 目的:接口主要用于定义一组行为规范,而继承主要用于代码复用和创建类之间的层次关系。
- 实现方式:类可以实现多个接口,但只能继承一个类。
- 成员访问:接口中的成员默认是
public
的,而继承的类可以访问父类的非私有成员。 - 设计灵活性:接口提供了更大的灵活性,允许不同的类实现相同的接口,而继承则创建了更严格的“是一个”关系。
示例
// 接口示例
interface Flyable {
void fly();
}
class Bird implements Flyable {
public void fly() {
System.out.println("Bird is flying");
}
}
// 继承示例
class Animal {
void eat() {
System.out.println("Animal eats");
}
}
class Dog extends Animal {
void bark() {
System.out.println("Dog barks");
}
}
接口多态的传递
接口多态是指通过接口类型来引用实现了该接口的对象,从而实现多态性。在Java中,接口提供了一种方式来定义一组方法规范,而具体的实现则由实现该接口的类来完成。接口多态的传递主要体现在以下方面:
接口多态的实现
-
定义接口:首先定义一个接口,其中包含一组方法签名(没有具体实现)。
interface Shape { void draw(); }
-
实现接口:一个或多个类实现这个接口,并提供接口中所有方法的具体实现。
class Circle implements Shape { public void draw() { System.out.println("Drawing Circle"); } } class Rectangle implements Shape { public void draw() { System.out.println("Drawing Rectangle"); } }
-
接口多态:通过接口类型的引用指向实现了接口的具体类的对象。
Shape shape1 = new Circle(); Shape shape2 = new Rectangle();
接口多态的传递
接口多态的传递意味着你可以将接口类型的引用传递给期望接口类型参数的方法。这样,方法可以接受任何实现了该接口的对象,从而实现多态。
public void drawShape(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
drawShape(new Circle()); // 输出: Drawing Circle
drawShape(new Rectangle()); // 输出: Drawing Rectangle
}
在这个例子中,drawShape
方法接受一个 Shape
类型的参数。由于 Circle
和 Rectangle
都实现了 Shape
接口,因此它们都可以作为参数传递给 drawShape
方法。
接口多态的优势
- 灵活性:方法可以接受任何实现了接口的对象,这使得方法更加通用和灵活。
- 扩展性:如果未来有新的类实现了该接口,无需修改方法,就可以直接传递新的实现类对象。
- 代码复用:通过接口多态,可以减少重复代码,因为不同的实现类可以共享同一个方法实现。