多态
如果子类重写了父类的方法,则调用的子类的方法;如果子类没有重写父类的方法,则调用的是父类的方法。这就是多态。
简单的讲,当调用子父类同名参数的方法时,实际执行的是子类重写的方法,这也被称为虚拟方法调用
直接看例子:
class Person {
String name;
int age;
public void eat() {
System.out.println("吃东西");
}
public void say() {
System.out.println("说话");
}
}
class Man extends Person {
Boolean isHandsome;
@Override
public void eat() {
System.out.println("男人吃的多");
}
public void earnMoney() {
System.out.println("男人要赚钱");
}
}
class Woman extends Person {
Boolean isBeauty;
@Override
public void eat() {
System.out.println("女人吃的少");
}
public void goShopping() {
System.out.println("女人喜欢购物");
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person();
Man man = new Man();
man.eat(); // 男人吃的多
man.age = 20;
man.earnMoney(); //男人要赚钱
//对象的多态性,父类的引用指向子类的对象
Person p2 = new Man();
Person p3 = new Woman();
/*****多态*****/
p2.eat(); // 男人吃的多
p2.say(); // 说话
}
}
注意这里的57、58行,可以明显的看到,p2的类型是父类Person,而赋值内容却是Man子类,而且运行eat()方法时运行的是子类的Man的eat()方法,并不是父类Person的eat()方法。
触发多态的条件:
- 发生类的继承
- 方法的重写
- 父类的引用指向子类的对象(向上转型)
Person p2 = new Man();
在这个语句中,父类的引用指向的是子类的对象,因此这里发生了对象的多态。
而且可以看到p2.eat();
执行的是子类的重写的方法。
多态中当调用子父类同名参数的方法时,实际执行的是子类重写的方法,那么父类能不能调用子类特有的方法呢?
答案是不能的,当父类执行 p2.earnMoney();
是会报错的。
当发生多态时:编译看左边,运行看右边
编译看左边:这个的意思是在Person p2 = new Man();
等号左边中,p2对象所能调用的方法只能是p2的类型Person中的方法,而不能调用它引用的Man对象的方法。这就是p2.earnMoney();
为啥会报错的原因。
运行看右边:这个的意思是p2调用方法时,是调用Person p2 = new Man();
等号右边Man对象中重写的方法,如果Man没有重写Person中的方法,那么p2调用的是Person自身的方法。 因此p2.eat();
运行的是子类Man的eat()方法,因为在子类中重写了父类的eat()方法;而p2.say();
调用的是父类中的方法,因为子类没有重写,子类中也没有这方法,所以p2调用不到子类的,所以会调用自己的。
总之,类型是在编译阶段确定的,而方法的调用是在运行时确定的
class Person {
String name = "Person";
void say() {
System.out.println(this.name);
}
}
class Man extends Person {
String name = "Man";
void say() {
System.out.println(this.name);
}
}
public class Test {
public static void main(String[] args) {
Person p = new Man();
System.out.println(p.name); // Person
p.say(); // Man
}
}
上面可以看出,p.name调用的是父类的name而不是子类的。 但是在p.say()
方法中虽然运行的是运行的是子类的say()方法,但是这个方法里面的this是子类对象,而不是父类对象,所以20行输出的是子类的name。
多态的应用:
class Animal {
public void eat () {
System.out.println("动物,进食");
}
public void shout() {
System.out.println("动物,叫");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
@Override
public void shout() {
System.out.println("狗,汪汪汪");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("狗吃鱼");
}
@Override
public void shout() {
System.out.println("猫,喵喵喵");
}
}
public class Test {
public static void main(String[] args) {
Test test = new Test();
// 有多态性
test.func(new Dog()); // Animal animal = new Dog();
test.func(new Cat()); // Animal animal = new Cat();
// 没多态性
test.funcDog(new Dog());
test.funcCat(new Cat());
}
public void func(Animal animal) { // 声明的是Animal 而使用的是子类
// 这里只能调用Animal中的方法,可是运行中是运行的子类重写的方法
animal.eat();
animal.shout();
}
public void funcDog(Dog dog) {
dog.eat();
dog.shout();
}
public void funcCat(Cat cat) {
cat.eat();
cat.shout();
}
}
在这个案例中,func可以传入Animal所以的子类对象,这里就相当于Animal animal = new 子类();
也是一个多态的发生。 如果没得多态,那么只能写成funcDog
、funcCat
方法传入指定的类型,所以如果有一百个类就要写一百个方法,这样就非常鸡肋。
向下转型
其实多态的发生就是向上转型的发生,那么向下转型是怎么发生呢?
就上面的例子中,p2只能调用Person中有的方法,而运行的时候是调用的子类重写的方法。 这里就有个问题,就是p2不能调用子类特有的方法,如上面的earnMoney
方法,在Person p2 = new Man();
向上转型中p2就调用不了earnMoney
方法,可是要怎么才能调用呢?
- 定义类型肯定是要子类类型
- 要进行一个强制类型转化
Man m1 = (Man)p2;
m1.earnMoney();
这时才可以调用子类的earnMoney
方法,其实很容易理解,m1的类型就是Man,而且引用的对象也被强制转化为Man,所以能调用earnMoney
方法。
但是强制类型转化,就有可能出现转换问题,如:
Woman w1 = (Woman)p2;
w1.goShopping();
这里就会发生类中错误,p2引用的时Man对象,而Man对象不能转化为Woman。
instanceof
为了避免在向下转型之前出现类型异常,一般要进行类型判断
a instanceof A
: 判断对象a是否是A的实例。 如果是返回true,不是返回false
所以一旦返回true,就向下转型,否则不进行向下转型
if(p2 instanceof Woman){
Woman w1 = (Woman)p2;
w1.goShopping();
System.out.println("p2 是一个Woman实例");
}
if(p2 instanceof Man){
Man m2 = (Man)p2;
m1.earnMoney();
System.out.println("p2 是一个Man实例");
}
这样就避免了类型错误的出现
补充:
如果A是B的父类
那么 a instanceof B 返回true, 则a instanceof A 也返回true
如:
if (p2 instanceof Person) {
System.out.println("Person");
}
if (p2 instanceof Object) {
System.out.println("Object");
}
上面例子中会打印Person
,Object
常见的问题
问题一: 编译时通过,运行时报错
举例一:
Person p4 = new Woman();
Man m3 = (Man) p4;
举例二:
Person p4 = new Person();
Man m4 = (Man) p4;
问题一: 编译时通过,运行时也通过
Object obj = new Woman();
Person p = (Person) obj;
问题三: 编译不过
Man m5 = new Woman(); // 不是父子类关系