多态顾名思义就是多种状态,那么在Java中可以解释为,不同的对象做相同的行为时,呈现出不同的状态。
比如说,狗和猫都做“叫”这个行为,那么狗是“汪汪叫”,猫是“喵喵叫”。这就是所谓的不同对象做相同行为时,呈现出了不同的状态。
那么在Java中如何做到多态呢?
在介绍实现多态的条件之前,我们需要讲一些前置的知识作为铺垫。
当一个类继承了某个父类的时候,才能够实现向上转型。
比如我们现在创建两个类,一个类是父类A,一个类是继承了父类A后的子类B。
class A{
int a;
int b;
}
class B extends A{
int c;
int d;
}
在这种情况下,我们去创建一个B
类的对象b
。那么这个’b’的类型是B。此时我们将其进行强转操作,将b
的类型B向上强转为父类A。过程如下:
B b = new B();
A b1 = (A)b;
即,我们创建了一个B类对象b,然后将b转型为了父类A,并让一个类A的引用指向转型后的b。
上述两行代码可以合并,合并后更加直观:
A a = (A)new B();
这种由子类类型强转为父类类型的过程,叫做向上转型。
那么向上转型后,会有什么变化呢?
首先,我们准备两个类:
class Animal{
public String name;
public int age;
public void eat(){
System.out.println("动物在吃东西。");
}
}
class Dog extends Animal{
public String color;
public void eat(){
System.out.println("狗在吃东西。");
}
public void bark(){
System.out.println("狗在叫。");
}
}
此时,我们执行如下代码:
public static void main(String[] args) {
Animal dog1 = (Animal) new Dog();
System.out.println(dog1.name);
System.out.println(dog1.age);
System.out.println(dog1.color);//这一行出现了报错。
dog1.eat();
dog1.bark();//这一行报错。
}
我们发现,由于我们发生了向上转型,所以**子类特有的变量和方法无法访问了。**那么除此以外还有什么变化呢?我们观察到Animal
中的eat()
方法输出的是"动物在吃东西
。“,而现在输出的却是"狗在吃东西。
”。
这就说明,当子类重写父类方法后,执行的是子类重写后的方法。
那么我们可以做出如下总结:
当发生向上转型后,
(1)父类中没有,子类中有的变量和方法,无法访问。
(2)父类中有,子类中没有的变量和方法,访问的是父类的变量和方法。
(3)父类中有,子类中也有的变量和方法,优先调用子类的,如果子类没有,才会调用父类的。
我们看下面这段代码。
先准备三个类:
class Animal{
public String name;
public int age;
public void eat(){
System.out.println("动物在吃东西。");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗在吃东西。");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫在吃东西。");
}
}
然后我们定义一个这样的方法。
public static void test(Animal animal){
animal.eat();
}
接着我们在main函数中写下如下代码:
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
test(dog);
test(cat);
}
然后运行上述代码:
看完结果后,我们从两个方面来理解。
第一个方面:
在main函数中,我们发现,两个不同的类竟然都能做参数,这大大的提高了函数的复用性。按照我们的理解,参数应该只能接受相同类型的传参,那么我们应该写两个test函数,一个test负责Dog类的传入,另一个test负责Cat类的传入。
而现在由于父类的存在,二个不同的子类都发生了向上转型,使得在传入的过程中,两个不同的子类向上转型为了相同的父类。因此,我们只需要写一个test函数。
第二个方面:
在test函数中,我们发现都是调用了Animal类的eat()
方法,这个函数中就输出了一句话,但是不同的子类调用,却输出了不同的语句。这就反映出了:不同的对象进行相同的行为,呈现出了不同的状态。这就是多态。
接下来,我们来总结一下实现多态的条件。
刚刚代码中的Dog
和Cat
都继承于Animal
类。
Dog
和Cat
类中都重写了Animal
中的eat
方法。
在test
函数中,我们的参数是父类Animal
引用。我们通过这个引用调用了父类Animal
中的eat
方法。
将一个子类对象经过向上转型之后当成父类方法使用,这个时候就无法调用子类的方法了,但有的时候我们可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。
代码如下:
Dog dog = new Dog();
//向上转型
Animal d1 = (Animal)dog;
//向下转型
Dog d2 = (Dog)d1;
但是,这里有一个安全性问题,我知道,子类只有一个父类,但是一个父类有很多子类。因此,子类向上转型的时候,转化后的父类是唯一的。但是,在你向下转型的时候,可能忘记了该类对应哪个子类,此时如果向下转化为了另外一个子类就出现了安全性问题。
比如,我们刚刚代码中的d1
引用是由Dog
转化过来的,而我们将其向下转化的时候,可能转化为了Cat
。此时就出现了安全性问题。
//向上转型
Animal d1 = (Animal)dog;
//向下转型
Dog d2 = (Dog)d1;
//转化为了错误的子类,出现问题。
Cat d3 = (Cat)d1;
此时,你运行的话,终端会报错,如下:
意思就是这个包下的Dog
类无法被转型为Cat
类。
为了解决向下转型的安全性问题,就出现了instanceof
关键字。
Java中为了提高向下转型的安全性,引入了instanceof ,如果该表达式为true,则可以安全转换。
使用方法如下:
if(d1 instanceof Dog) {
Dog d2 = (Dog) d1;
}
else if(d1 instanceof Cat)
{
Cat d3 = (Cat)d1;
}
**多态可以提高代码的简洁程度。**如果不用多态的话,就如刚刚的多态的代码示例一样,你要写很多个具有相同功能,只是参数类型不同的方法。有了多态以后,只需要将父类作为参数,其余子类向上转型即可。
**同时提高代码的可扩展能力。**如果你想对父类中的某个方法扩展,那你可以重新写一个扩展后的子类,然后在子类中的方法里重写扩展后的代码。调用的时候,只需要将其向上转型为父类,继续传入原来的方法即可。
使用了多态后,代码的运行效率降低。
我们最好不要在构造方法中调用重写的方法。 因为,子类构造的时候,优先调用父类构造方法,如果你在构造方法中调用了子类重写的方法。那么根据刚刚的解释,此时,将会执行子类中重写后的方法,如果说你子类方法中用到了某些成员变量,由于这个时候,你还没有执行父类构造方法,所以你的子类成员变量还是初始值。因此,你子类方法中,此时是用初始值执行后续代码的。
比如:
class Animal{
public String name;
public int age;
public Animal(){
eat();
}
public void eat(){
System.out.println("动物在吃东西。");
}
}
class Dog extends Animal{
public int a;
public Dog(){
super();
a = 10;
}
public void eat(){
System.out.println(a);
System.out.println("狗在吃东西。");
}
}
父类构造中你调用了eat()方法,此时将会执行Dog中重写后的方法,这个方法中输出了a的值,而这个a在子类构造方法中赋值为了10,所以按逻辑来讲,我们这里期望的是输出10。但实际上这里输出0,因为你父类调用这个方法的时候,这个a还没来得及赋值。
验证结果如下: