目录
什么是多态呢?
多态的条件
演示说明
多态中成员访问的特点
成员变量
成员方法
多态的好处:
多态的缺点:
强制类型转换
什么是多态呢?
俗话说,龙生九子,各不相同。龙的九子都继承自龙,九子却有着各自的特点。在Java中,多态是指不同类的对象在调用同一个方法时,所呈现出的多种不同的行为。
例:
我们可以说猫是猫:Cat c = new Cat();
也可以说猫是动物:Animal a = new Cat();
在这里,猫在不同的时刻展现出不同的形态(猫也是猫,动物也是猫),这就叫做多态。
多态的条件
1.有继承或实现关系
class Cat extends Animal
2.有方法重写
@Override
3.有父类引用指向子类对象
Animal a = Cat();
演示说明
如果只用概念来描述多态,就会不太好理解,下面我们就用代码来演示一下
下面有三段代码
1.父类Animal类
public class Animal {
int age = 10;
public void eat(){
System.out.println("动物吃东西");
}
}
2.子类Cat类 (继承关系、方法重写)
public class Cat extends Animal {
int age = 20;
int weight = 30;
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void shout() {
System.out.println("猫喵喵叫");
}
}
3.测试类(父类引用指向子类对象)
public class Text {
public static void main(String[] args) {
Animal a = new Cat();
}
}
像这样的特点,就叫多态。
多态中成员访问的特点
那多态又有什么不一样呢?
首先来看一下多态中成员变量的访问特点
成员变量
在测试类中,已经有一个父类引用指向子类对象,也就是说,对象的类型是父类类型,但是对象是子类的对象。那我们就用这个对象来调用一下成员变量,看看会发生什么。
当我们去访问子类中的成员变量时,发现weight居然报错了,这就说明,weight是通不过编译的。我们先暂时将weight成员变量注释掉,继续运行下去,看看会发生什么。
运行结果为10,也就是说age的变量为10。
但是在子类中age的变量是20,父类中的变量才是10。说明多态中成员变量的访问特点为:访问父类中的成员变量。那么刚才weight成员变量报错的原因,也就可以说得通了。多态中访问的是父类中的成员变量,而父类中并没有成员变量weight,所以才会报错。
小口诀
编译看左边,运行看左边
左边指的是父类,编译时看左边有无对应的成员变量,运行的结果也是父类中的成员变量。
成员方法
同样的,用这个对象来调用子类中的两个方法,一个子类中重写父类的eat方法,一个子类中特有的shout方法。
我们发现,子类中特有的shout方法报错了。我们先暂时将shout方法注释掉,继续运行下去,看看运行的结果会是什么。
这次的运行结果跟成员变量的运行结果不同。调用的成员方法是子类中的成员方法,而不是父类中的成员方法。
这说明多态中成员方法的访问特点为:访问子类中的成员方法。
小口诀
编译看左边,运行看右边
左边指的是父类,编译时成员方法如果在父类中找不到,就会报错。运行时的结果是右边子类中重写的成员方法。
成员变量和成员方法访问特点不同的原因:
成员方法可以重写,但是成员变量不可以重写。
多态的好处:
多态可以消除类之间的耦合关系,大大提高了程序的可扩展性和可维护性。
这样说会比较抽象,下面用一个例子来体现多态的好处
我们来新建一个这样的类
public class AnimalUse {
public void animalUse(Cat c) {
c.eat();
}
}
这个类中提供了一个animalUse方法,需要传进去一个对象,然后就可以调出这个对象的eat方法
再新建一个Dog类
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
我们在不用多态的情况下完成以下操作:
以下为测试类:
public class Text {
public static void main(String[] args) {
AnimalUse an = new AnimalUse();
Cat c = new Cat();
an.animalUse(c);
}
}
运行后即可调用Cat中的eat方法。
如果我还想调用一个Dog类中的eat方法,那么就要在AnimalUse类中,再增添一个方法,参数为Dog,如下所示
public class AnimalUse {
public void animalUse(Cat c) {
c.eat();
}
public void animalUse(Dog d){
d.eat();
}
利用方法重载的方式,再写一个方法,在测试类中,再new一个Dog类的对象
成功运行。
虽然运行起来没毛病,但是如果还要再添加其他类的话,需要修改的地方就会很多。
再来用多态来修改一下这些代码
public class AnimalUse {
public void animalUse(Animal a) {
a.eat();
}
}
AnimalUse类我们来这样修改,将参数改为父类类型。那么我们以后要添加类,无论是Pig还是Duck还是Chicken,只要是继承自Animal类,都可以通过多态的方式,将参数传入animalUse方法。我们不修改测试类,再来运行一遍。
运行成功。
那么Cat型的对象为什么可以填入Animal型方法参数呢?
这个地方做了个自动的类型提升
public class Text {
public static void main(String[] args) {
byte a=2;
fun(a);
}
public static void fun(int a){
a=a+1;
System.out.println(a);
}
}
在这里,byte型的变量也可以放入int型的参数里,这里就做了个自动的类型提升,将byte提升为int型。上面同理,Cat型继承自Animal,Cat型的对象放入Animal型的参数里,也会做一个自动类型提升。
回到整体,为了规范,我们在测试类中的类型也要修改,修改为父类引用指向子类对象。
运行成功。
多态的缺点:
多态的缺点也非常明显,就是不能调用子类特有的方法。
如果想调用子类的shout方法(子类特有的方法),那我们应该如何调用呢?
Cat c = new Cat();
c.shout();
创建一个Cat类对象,这样当然是可以的,但我们要在这里提到的是,多态中的类型转换。
在我们的整数类型中,如果要将10转为byte型要怎么做呢?
byte b = (byte)10;
强制类型转换
在多态中,也是可以使用强制类型转换的。
我们在测试类中这样修改
public class Text {
public static void main(String[] args) {
AnimalUse an = new AnimalUse();
Animal a1 = new Cat();
Cat c = (Cat)a1;
c.shout();
}
}
子类中特有的shout方法调用成功。