理解多态思想
学了继承关系,我们知道继承关系是一种”is A”的关系,也就说子类是父类的一种特殊情况;
问题: 子类的对象是动物?
既然子类是一种特殊的父类,那么我们可不可以认为狗对象/猫对象就是动物类型的对象.
Class Animal{};//动物类
Class Dog extends Animal{};//狗类
Class Cat extends Animal{};//猫类
Animal a = new Dog();
Animal b = new Cat();
当编译类型和运行类型不同的时候,多态就产生了;
对象有两种类型
(1)编译类型:声明变量的类型,Animal,表示把对象看做什么类型。
(2)运行类型:对象的真实类型。
编译类型必须是运行类型的父类/或相同;
所谓多态:对象有多种形态,对象可以存在不同的形式;
Animal a = null;
a = new Dog(); //a此时表示Dog类型的形态
a = new Cat(); //a此时表示Cat类型的形态
多态的前提:
1.可以是继承关系(类和类)
2.也可以是实现关系(接口和实现类)
在开发中多态一般都指第二种。
多态的特点:把子类对象赋给父类变量,在运行时期会表现出具体的子类特征(调用子类的方法).
多态的好处
需求:给饲养员提供一个饲养动物的方法,用于喂养动物的方法,用于喂养动物。
没有多态:针对于不同的动物,我们得提供不同的feed方法来喂养。代码如下:
class Person
{
public void feed(Dog g){
System.out.println("开始喂食");
g.feed();
}
public void feed(Cat c){
System.out.println("开始喂食");
c.feed();
}
}
class Animal
{
}
class Dog extends Animal
{
public void feed(){
System.out.println("小狗吃骨头");
}
}
class Cat extends Animal
{
public void feed(){
System.out.println("小花猫吃鱼");
}
}
class PolymorphicDemo
{
public static void main(String[] args)
{
Person p = new Person();
Dog d = new Dog();
Cat c = new Cat();
p.feed(d); // 喂狗
p.feed(c); // 喂猫
}
}
存在多态:统一了喂养动物的行为。代码如下:
class Person
{
public void feed(Animal a){
System.out.println("开始喂食");
a.feed();
}
}
class Animal
{
public void feed(){
System.out.println("小宠物都爱吃食物");
}
}
class Dog extends Animal
{
public void feed(){
System.out.println("小狗吃骨头");
}
}
class Cat extends Animal
{
public void feed(){
System.out.println("小花猫吃鱼");
}
}
class PolymorphicDemo
{
public static void main(String[] args)
{
Person p = new Person();
Animal d = new Dog();
Animal c = new Cat();
p.feed(d); // 喂狗
p.feed(c); // 喂猫
}
}
从上述例子,可以得知多态的作用:把不同的子类对象当做父类类型来看待,可以屏蔽不同子类对象之间的差异,从而写出通用的代码达到编程,以适应需求的不断变化。
多态时的方法调用
多态时方法的调用问题:
前提:必须存在多态情况;
如下几种情况的代码:存在父类SuperClass,子类SubClass,方法doWork
情况1:doWork方法存在于SuperClass(父类)中,不存在SubClass(子类)中。
// 定义父类
class SuperClass
{
public void dowork(){
System.out.println("SuperClass");
}
}
// 定义子类
class SubClass extends SuperClass
{
}
class PolymorphicDemo2
{
public static void main(String[] args)
{
SuperClass sub = new SubClass(); // 多态
sub.dowork(); // SuperClass
}
}
---------- 运行java ----------
SuperClass
输出完成 (耗时 0 秒) - 正常终止
执行结果:编译通过,执行SuperClass的doWork方法。
从SubClass中去找doWork方法,找不到,再去父类SuperClass中去找。
情况2:doWork方法存在于SubClass(子类)中,不存在SuperClass(父类)中。
// 定义父类
class SuperClass
{
}
// 定义子类
class SubClass extends SuperClass
{
public void dowork(){
System.out.println("SubClass");
}
}
class PolymorphicDemo2
{
public static void main(String[] args)
{
SuperClass sub = new SubClass(); // 创建子类对象
sub.dowork(); // 编译报错
}
}
---------- 编译java ----------
PolymorphicDemo2.java:22: 错误: 找不到符号
sub.dowork(); // SuperClass
^
符号: 方法 dowork()
位置: 类型为SuperClass的变量 sub
1 个错误
输出完成 (耗时 0 秒) - 正常终止
此时的执行结果:编译错误:
编译时期:会去编译类型(SuperClass)类中去寻找doWork方法,但是找不到。除非找到,编译才能通过。
情况3:doWork方法公同存在于SubClass(子类)和SuperClass(父类)中。
// 定义父类
class SuperClass
{
public void dowork(){
System.out.println("SuperClass");
}
}
// 定义子类
class SubClass extends SuperClass
{
public void dowork(){
System.out.println("SubClass");
}
}
class PolymorphicDemo2
{
public static void main(String[] args)
{
SuperClass sub = new SubClass(); // 多态
sub.dowork();
}
}
---------- 运行java ----------
SubClass
输出完成 (耗时 0 秒) - 正常终止
此时执行结果:编译通过,执行SubClass的doWork方法。因为子类继承了父类,覆盖了dowork方法,子类对象赋给父类变量,在运行时期会表现出具体的子类特征(调用子类的方法)。
情况4:doWork方法公同存在于SubClass(子类)和SuperClass(父类)中,但是doWork是static修饰的静态方法,此时这种情况我们称为隐藏,而不是覆盖。
// 定义父类
class SuperClass
{
public static void dowork(){
System.out.println("SuperClass");
}
}
// 定义子类
class SubClass extends SuperClass
{
// 这里的dowork方法使用了static修饰,因此这里的dowork与父类中的dowork不再是覆盖,而是隐藏关系
public static void dowork(){
System.out.println("SubClass");
}
}
class PolymorphicDemo2
{
public static void main(String[] args)
{
SuperClass sub = new SubClass(); // 多态
sub.dowork();
}
}
---------- 运行java ----------
SuperClass
输出完成 (耗时 0 秒) - 正常终止
此时执行结果:编译通过,执行SuperClass的doWork方法。
上述代码包含了隐藏现象:
满足继承的访问权限下,隐藏父类静态方法:若子类定义的静态方法的签名和父类中的静态方法的签名相同,那么此时就是隐藏父类的方法。注意:仅仅是静态方法。
在父类和子类中都存在了一个static修饰的dowork方法,这里的dowork是隐藏的概念,而不是覆盖 !
总结:
子类中和父类的dowork方法使用了static修饰,因此子类的dowork与父类中的dowork不再是覆盖,而是隐藏关系;
静态方法的调用只需要类即可。
虽然我们的代码,使用的是sub对象来调用的dowork方法,
SuperClass sub = new SubClass(); // 多态
sub.dowork();
下图是反编译后的代码,可以看到,在编译时,并没有使用对象去调用,而是用的父类类名去调用的。
因此,如果使用对象来调用静态方法,其实使用的是对象的编译类型来调用静态方法,和对象没有关系。