Java之多态
1. 思考
描述狗
class Dog
{
public void eat()
{
System.out.println("骨头");
}
public void lookHome()
{
System.out.println("看家");
}
}
实现狗eat功能
// 直接new就可以了
class DuoTaiDemo
{
public static void main(String[] args)
{
Dog d = new Dog();
d.eat();
}
那如果有很多狗呢?每次都要d.eat(),怎么做比较合适呢?
//接收Dog,让dog做事。
public static void method(Dog d)
{
d.eat();
}
接下来又来了个猫,描述猫
class Cat
{
public void eat()
{
System.out.println("鱼");
}
public void catchMouse()
{
System.out.println("抓老鼠");
}
}
如果让猫也吃饭呢?同样也可以如此
//接收Cat,让cat做事。
public static void method(Cat c)
{
c.eat();
}
发现,每多一个动物,都需要为这个动物单独定义一个功能,让这个动物的对象去做事。这个程序扩展性就很差。如何提高这个扩展性呢?发现既然是让动作去eat,无论是dog,还是cat,eat是它们共性,干脆,将eat进行抽取。抽取到父类Animal中。
//进行抽取。将共性的功能抽取到父类Animal中。
//由于怎么吃不知道,应该定义为抽象方法
abstract class Animal
{
public abstract void eat();
}
class Dog extends Animal
{
public void eat()
{
System.out.println("骨头");
}
public void lookHome()
{
System.out.println("看家");
}
}
class Cat extends Animal
{
public void eat()
{
System.out.println("鱼");
}
public void catchMouse()
{
System.out.println("抓老鼠");
}
}
那怎么解决上面的问题呢?
//只要建立animal的引用就可以接收所有的dog cat对象进来。让它们去eat。
//提高了程序的扩展性。
public static void method(Animal a)
{
a.eat();
}
Dog是Animal中的一种。
Dog d = new Dog();
Animal a = new Dog();
Cat c = new Cat();
Animal aa = new Cat();
这就是多态。多态技术的引出。解决什么问题?程序扩展性的问题。
2. 什么是多态
2.1 体现
父类的引用或者接口的引用指向了自己的子类对象。
Dog d = new Dog(); //Dog对象的类型是Dog类型。
Animal a = new Dog(); //Dog对象的类型右边是Dog类型,左边Animal类型。
2.2 好处
提高了程序的扩展性。
2.3 弊端
通过父类引用操作子类对象时,只能使用父类中已有的方法,不能操作子类特有的方法。
2.4 使用的前提
- 必须有关系:继承,实现。
- 通常都有重写操作。
2.5 子类的特有方法如何调用呢?
Animal a = new Dog(); //Animal是父类型,new Dog()是子对象。
但是父类型引用指向子类对象时,这就是让子类对象进行了类型的提升(向上转型)。
向上转型好处:
提高了扩展性,隐藏了子类型。
弊端:
不能使用子类型的特有方法。
如果要想使用子类的特有方法,只有子类型可以用。可以向下转型,强制转换。
Animal a = new Dog();
a.eat();
Dog d = (Dog)a; //将a转型为Dog类型。向下转型。
d.lookHome();
2.6 向下转型什么时候用?
当需要使用子类型的特有内容时。
注意:无论向上还是向下转型,最终都是子类对象做着类型的变化。
2.7 向下转型的注意事项
Animal a = new Dog();
//Cat c = (Cat)a; 向下转型因为不明确具体子类对象类型,所以容易引发ClassCastException异常。所以为了避免这个问题,需要在向下转型前,做类型的判断。
判断类型用的是关键字 instanceof
if(a instanceof Cat) //a指向的对象的类型是Cat类型。
{
//将a转型Cat类型。
Cat c = (Cat)a;
c.catchMouse();
}
else if(a instanceof Dog)
{
Dog d = (Dog)a;
d.lookHome();
}
2.6 向上转型什么时候用?
提高程序的扩展性,不关系子类型(子类型被隐藏)。
3. 多态中,成员调用的特点
3.1 成员变量。
当子父类中出现同名的成员变量时。
多态调用该变量时:
编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量。没有,编译失败。
运行时期:也是调用引用型变量所属的类中的成员变量。
简单记:编译和运行都参考等号的左边。(编译运行看左边。)
参考:
class Fu
{
int num = 3;
}
class Zi extends Fu
{
int num = 5;
}
class DuoTaiDemo3
{
public static void main(String[] args)
{
//测试成员变量的多态调用。
Fu f = new Zi();
System.out.println(f.num); //3
Zi z = new Zi();
System.out.println(z.num); //5
}
}
运行结果:
3.2 成员函数。
编译,参考左边,如果没有,编译失败。
运行,参考右边的对象所属的类。
简单记:编译看左边,运行看右边。
参考:
class Fu
{
void show()
{
System.out.println("fu show run");
}
}
class Zi extends Fu
{
void show()
{
System.out.println("zi show run..");
}
}
class DuoTaiDemo3
{
public static void main(String[] args)
{
//测试成员函数的多态调用。
Fu f = new Zi();
f.show();
}
}
运行结果:
对于成员函数是动态绑定到对象上。
3.3 静态函数。
编译和运行都参考左边。
静态函数是静态的绑定到类上。
参考:
class Fu
{
static void method()
{
System.out.println("fu static method run");
}
}
class Zi extends Fu
{
static void method()
{
System.out.println("zi static method run");
}
}
class DuoTaiDemo3
{
public static void main(String[] args)
{
//测试静态函数的多态调用。
Fu f = new Zi();
f.method();
// 注意:真正开发静态方法是不会被多态调用的,因为静态方法不所属于对象,而是所属于类。
Fu.method();
Zi.method();
}
}
运行结果:
结论:
- 对于成员变量和静态函数,编译和运行都看左边。
- 对于成员函数,编译看左边,运行看右边。