Java中使用多态的主要目的是提高代码的可重用性和扩展性,使得代码更加灵活和易于维护。通过多态,我们可以将不同的对象看做是同一种类型,从而使得我们可以使用同一种接口来操作这些对象,而不必关心具体的实现细节。
当父类的引用所指向的子类对象引用指向的对象不一样时。调用重写的方法,所表现出来的行为是不一样的,我们把这种思想叫做多态。上面所说的可能大家会觉得有点抽象,看到后面就懂了。
多态的基础是动态绑定,所以要了解多态前提我们还要了解动态绑定。
要想实现动态绑定,需要满足以上几个条件:
1.要发生向上转型
2.要发生重写
3.使用父类对象的引用去调用重写方法
完成了这三部分,就会发生动态绑定,而在这里,出现了重写以及向上转型这些概念。所以我们得先了解它们才能去了解动态绑定。进而了解多态。
方法重写的规则:
1.子类在重写父类的方法时,必须与父类方法原型一致:即返回值、方法名、参数列表要完全一致
2.被重写的方法的访问修饰限定符在子类中要大于等于父类的。
3.父类中被static或private或final修饰的方法以及构造方法都不能被重写。
4.在子类中重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验。
Animal animal = new Cat ( );
Dog dog = new Dog();
Animal animal = dog;//该代码发生了向上转换,将Dog对象转换为Animal类型
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Dog barks");
}
void fetch() {
System.out.println("Dog fetches a ball");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog(); // 创建Dog对象
Animal animal = dog; // 向上转型,将Dog对象转换为Animal类型
animal.sound();//调用子类的覆盖方法
// animal.fetch(); // 编译错误,因为Animal类中没有fetch方法
}
}
// 输出: Dog barks
通过以上代码发现一个问题,不能调用到子类特有的方法(因为编译时调用的是父类的方法),我们可以通过向下转型来调用到子类特有的方法(后面介绍)
静态绑定:也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型就确定了具体调用那个方法。典型代表函数重载。
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。
public class TestAnimal {
// 2. 方法传参:形参为父类型引用,可以接收任意子类的对象
public static void eatFood(Animal a){ //因为主方法的原因使用静态方法
a.eat(); //方法传参向上转型
}
// 3. 作返回值:返回任意子类对象的实例
public static Animal buyAnimal(String var){
return new Dog();
}
public static void main(String[] args) {
Animal cat = new Cat("元宝",2); // 1. 直接赋值:子类对象赋值给父类对象
Dog dog = new Dog("小七", 1);
animal.eat(); //直接赋值向上转型
eatFood(cat);
eatFood(dog);
Animal animal = buyAnimal("狗");
animal = buyAnimal("猫");
animal.eat();//方法返回向上转型
}
}
多态具体点就是去完成某个行为时,当不同的对象去完成时会产生出不同的状态。代码如下:
class Animal {
public void eat(){
System.out.println( "吃饭");
}
}
class Cat extends Animal{
@Override //注解
public void eat(){
System.out.println("吃鱼~~~");
}
}
class Dog extends Animal {
@Override
public void eat(){
System.out.println("吃骨头~~~");
}
}
public class TestAnimal {
public static void eat(Animal a){
a.eat(); //两次调用该方法,但是结果却不一样
}
public static void main(String[] args) {
Cat cat = new Cat();
Dog dog = new Dog();
eat(cat);
eat(dog);
}
}
//输出结果
吃鱼~~~
吃骨头~~~
此时在上述代码中当父类的引用所指向的子类对象引用指向的对象不一样时。调用重写的方法(eat),所表现出来的行为是不一样的(输出结果不一样),我们把它叫做多态。
Dog myDog = (Dog) animal;
那么以上代码为什么要强制类型转换呢?向上转型可以不用,因为是从小范围向大范围的转换。(可以类比整型里面的强制转换),我们现在提出一个问题:什么时候都可以向下转型吗?
答案是不,在Java中,向下转型(将父类引用转换为子类引用)一般需要先进行向上转型
见以下代码
class Animal {
void sound() {
System.out.println("Animal的sound");
}
void sun() {
System.out.println("Animal特有的sun");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Dog的sound");
}
void fetch() {
System.out.println("Dog特有的fetches ");
}
}
public class Mainn {
public static void main(String[] args) {
Animal animal = new Dog(); // 向上转型
Dog myDog = (Dog) animal; // 向下转型
myDog.sound(); // 输出: Dog barks,调用子类的覆盖方法
myDog.fetch(); // 输出: Dog fetches a ball,调用子类特有的方法
// 调用父类的方法
myDog.sound(); // 输出: Dog barks,调用子类的覆盖方法
myDog.sun();
}
}
如果上面的代码没有 Animal animal = new Dog();,向下转型将报错,同时注意必须确保父类引用所指向的对象确实是子类的实例。如果父类引用所指向的对象不是子类的实例,那么即使进行了向上转型,向下转型也是不安全的:见以下代码
class Parent {}
class Child extends Parent {}
class AnotherChild extends Parent {}public class Main {
public static void main(String[] args) {
Parent parent = new AnotherChild(); // 向上转型
// 这里如果尝试向下转型为Child,编译器将会报错
// Child child = (Child) parent;//不安全的向下转型
}
}//为了演示方便,这个代码是不完整的
因此,向下转型之前,你需要确保父类引用所指向的对象确实是你要转型的子类的实例。这通常通过instanceof操作符来检查:用来判断parent是否为Child的实例,若是,返回true,否则返回false
if (parent instanceof Child) {
Child child = (Child) parent; // 安全的向下转型
} else {
...... // 不能转换为Child
}
我们最后思考一个问题:向上转型的缺陷是不能调用到子类特有的方法,那么向下转型可以调用父类特有的方法吗?是可以的,同时向下转型后不会影响向上转型的操作。见以下代码
class Animal {
void sound() {
System.out.println("Animal的sound");
}
void sun() {
System.out.println("Animal特有的sun");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Dog的sound");
}
void fetch() {
System.out.println("Dog特有的fetches ");
}
}
public class Mainn {
public static void main(String[] args) {
Animal animal = new Dog(); // 向上转型
Dog myDog = (Dog) animal; // 向下转型
myDog.sound(); // 输出: Dog barks,调用子类的覆盖方法
myDog.fetch(); // 输出: Dog fetches a ball,调用子类特有的方法
myDog.sound(); // 输出: Dog barks,调用子类的覆盖方法
myDog.sun(); //观察到向下转型过程中可以调用父类的特有的方法
animal.sun(); //观察到向下转型后不会影响向上转型的操作
animal.sound();
}
}
//输出结果
Dog的sound
Dog特有的fetches
Dog的sound
Animal特有的sun
Animal特有的sun
Dog的sound
待更新~~~