需求:创建动物类,猫类,狗类。猫类和狗类都去继承动物类,在狗类和猫类中根据自己的根据进行方法的重写。
public class ReviewDuoTai{
public static void main(String[] args) {
Cat01 a1 = new Cat01();
Cat01 a2 = new Cat01();
a1.eat();
a1.drink();
a1.sleep();
a2.eat();
a2.drink();
a2.sleep();
System.out.println("========================");
Dog01 s1 = new Dog01();
Dog01 s2 = new Dog01();
s1.eat();
s1.drink();
s1.sleep();
s2.eat();
s2.drink();
s2.sleep();
}
}
//需求:创建动物类,猫类,狗类。猫类和狗类都去继承动物类,在狗类和猫类中根据自己的根据进行方法的重写
class Animal01{
public void eat(){
System.out.println("动物会吃"); }
public void drink(){
System.out.println("动物会喝"); }
public void sleep(){
System.out.println("动物会睡"); }
}
class Cat01 extends Animal01{
@Override
public void eat() {
System.out.println("猫猫吃猫粮"); }
@Override
public void drink() {
System.out.println("猫猫喝水");
}
@Override
public void sleep() {
System.out.println("猫猫睡觉");
}
}
class Dog01 extends Animal01{
@Override
public void eat() {
System.out.println("修狗吃狗粮");
}
@Override
public void drink() {
System.out.println("修狗喝水");
}
@Override
public void sleep() {
System.out.println("修狗睡觉");
}
}
这么多方法都要一个一个调用,太麻烦了。有没有一个好办法可以省事点呢?
我们可以创建一个工具类
class AnimalTool233{
public static void AnimalTool233(Animal01 cs){
//cs既可以接收Dog类的引用类型还可以接收Cat类的引用类型
//因为Dog和Cat都继承Animal类型
//形参的参数名cs
cs.eat();
cs.drink();
cs.sleep();
}
}
然后再传入参数
public class ReviewDuoTai{
public static void main(String[] args) {
Cat01 a1 = new Cat01();
Cat01 a2 = new Cat01();
AnimalTool233.AnimalTool233(a1);
AnimalTool233.AnimalTool233(a2);
System.out.println("========================");
Dog01 s1 = new Dog01();
Dog01 s2 = new Dog01();
AnimalTool233.AnimalTool233(s2);
AnimalTool233.AnimalTool233(s2);
}
}
输出结果
猫猫吃猫粮
猫猫喝水
猫猫睡觉
猫猫吃猫粮
猫猫喝水
猫猫睡觉
========================
修狗吃狗粮
修狗喝水
修狗睡觉
修狗吃狗粮
修狗喝水
修狗睡觉
这就体现出多态的好处了:
这个案例中,通过定义一个AnimalTool233的工具类,静态方法AnimalTool233就是通过Animal01类型的引用参数去接受Dog01和Cat01类型的实例,然后调用这两种不同类型的实例的通用方法,即eat(),sleep()和drink()方法。
由于Dog01和Cat01都继承自Animal01类,并且重写了这三个通用方法,所以这两个不同类型的实例都能够正常调用通用方法。
通过这种方式,我们可以在不改变代码的情况下,轻松地扩展并加强程序的功能,而无需为每种新类型编写特定的方法。同时,由于多态性的存在,我们可以更容易地重构代码,更改类的继承关系或添加新的子类,而无需重新编写现有代码。
通过多态的使用,我们能够将不同类型的对象作为一个通用的类型(Animal01),并且在这个通用类型的基础上调用通用方法,从而提高了程序的可维护性、灵活性和可扩展性。
在Java中,多态虽然有很多优点,但它也存在一些弊端,其中之一就是无法直接调用子类特有的方法。
再看一个案例:
class Animal {
public void eat() {
System.out.println("动物正在吃");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("狗正在吃");
}
public void bark() {
System.out.println("狗在叫");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Dog();
a.eat(); //输出:狗正在吃
a.bark(); //编译错误:无法解析的符号(即找不到bark()方法)
}
}
在这个例子中,我们定义了一个Animal父类和一个Dog子类,并且在main()方法中将Dog的实例赋值给Animal类型的引用a。由于方法的重写,当a调用eat()方法时,会输出“狗正在吃”。但是,当我们尝试调用a的bark()方法时,编译器会报错,因为它无法确定a所指向的对象类型是否有此方法。
在多态的情况下,编译器只能看到a引用的类型是Animal,所以无法判断a是否真正指向了Dog类型的对象,在编译期间也无法确定bark()方法是否存在。因此,在编译时会发生错误,导致无法调用这个方法。
知识补充:
静态绑定中,对于一个方法调用,编译器会根据引用变量的类型来决定使用哪个方法的实现,这个决定在编译期就已经确定了。也就是说,在编写代码时,编译器已经知道哪个方法将被调用,因为它只考虑了引用类型,而没有考虑引用所指向的具体对象类型。
动态绑定中,对于一个方法调用,编译器并不知道应该使用哪个方法的实现,它只能在程序运行时动态地查找合适的方法实现。这意味着,当我们调用一个方法时,编译器并不知道方法的具体实现,而是在运行时根据对象的实际类型动态地查找合适的方法实现。
以多态为例,如果我们有一个指向子类对象的父类引用,则该引用可以引用任何子类的对象。当我们通过该引用调用一个方法时,在编译期间编译器只能看到该引用的静态类型,而不知道它的实际类型。因此,编译器无法在编译时就确定调用哪个方法的实现,必须在运行时动态地查找合适的方法实现。
简而言之,在动态绑定中,方法调用的分配是在运行时进行的,而在静态绑定中,方法调用的分配是在编译时进行的。
这就是为什么在Java中方法调用是动态绑定的,因为它允许我们在程序运行时根据对象的实际类型动态地选择方法的实现,实现多态特性。