Java面向对象的三大特征——封装、继承和多态,多态比较难以理解,也通常被考察,所以掌握多态及其实现机制是尤为重要的。本篇主要总结了多态的定义,编译时多态和运行时多态,方法重载和重写的区别、关于多态的例子这些问题。
*多态是什么?
简言之,多态就是父类对象的引用指向子类的对象。这样说没错,似乎太抽象了。可下面这种说法:
程序中的引用类型变量所指向的具体类型和通过该引用类型变量发出的方法调用在编译时并不确定,而是在程序运行期间才能确定,即一个引用变量到底会指向哪一个类的实例对象,该引用变量发出的方法调用到底是哪一个类中的方法,必须在程序运行期间才能决定!(在编译时不确定,要等到运行时才能确定,这样就会导致多种状态的效果,这就是多态啦!)
*编译时多态和运行时多态?
**区别
一般情况下说的多态指的是运行时多态,也就是父类引用指向的具体对象以及调用的具体方法必须要等到运行时才能确定。
而编译时多态就是在编译时就已经确定了指向的具体对象,而且要调用的方法也是已知的,也就是这些操作在编译阶段就已经确定,不像运行时多态必须要等到运行时阶段才能确定。另外编译时多态也叫静态多态,运行时多态也叫做动态多态!
**编译时多态的例子——方法重载
方法重载——在同一个类中存在同名不同参(参数个数或类型不同)的方法。
一个叫相同的方法名因为参数的不同就产生了多种状态,但是这个当你调用的时候需要传入具体的参数来确定调用哪一个方法,这样的话在编译阶段已经确定下来,所以对于一个类中方法的重载其实就是编译时多态。
说到了方法重载,顺便提一下重写和覆盖!!!
*方法重载、重写和覆盖?
首先明确一点——重写其实就是覆盖,所以主要是要弄清楚的是重载和重写的区别!
**方法重载特点:
1.一个类中的方法;
2.在使用重载时只能通过相同的方法名、不同的参数形式实现。不同的参数类型、不同的参数个数或者不同的参数顺序(参数类型必须不一样);
3.不能通过访问权限、返回类型、抛出的异常进行重载;
4.方法的异常类型和数目不会对重载造成影响。
**方法重写特点:
1.在继承关系中出现,子类中的方法重写父类中的方法;
2.重写方法的参数列表必须完全与被重写的方法的相同,否则不能称其为重写;
3.重写方法的访问修饰符一定要大于被重写方法的访问修饰符;例如父类中如果是protected,子类的就必须是protected或者public;
4.重写的方法的返回值必须和被重写的方法的返回值一致;
5.重写的方法所抛出的异常必须和被重写的方法的所抛出的异常一致,或者是其子类;
6.被重写的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行重写;
7.静态方法不能被重写为非静态的方法(会编译出错)。
*多态三要素?
1.要有继承关系 2.子类要重写父类的方法 3.父类引用指向子类对象
*举个多态的例子
父类Animal与其子类Cat,子类的eat和sleep方法重写了Animal中对应的方法,在main中父类引用指向子类对象,满足了多态三要素。所以在调用这两个方法时,应该会调用子类Cat的方法,但静态方法结果并非我们所想的。
class Animal { //父类
String name = "animal";
static int age = 20; //静态属性
public void eat() {
System.out.println("动物 eat");
}
protected static void sleep () { //静态方法
System.out.println("动物 sleep");
}
public void run () {
System.out.println("动物 run");
}
}
class Cat extends Animal { //子类
String name = "cat";
static int age = 12; //静态属性
@Override
public void eat() {
System.out.println("猫 eat");
}
public static void sleep() { //父类的静态方法可以被子类继承,但是不能重写。
System.out.println("猫 run");//这里与父类的方法名相同但是实现不同,所以并未继承!
}
public void fight() {
System.out.println("猫 fight");
}
}
写个测试的方法:
public static void main(String[] args) {
Animal animal = new Cat();
System.out.println(animal.name + " " + animal.age); //animal 20
animal.eat(); //猫 eat
animal.sleep(); //动物 sleep
animal.run(); //动物 run
}
根据测试结果,验证了多态的一套规则:
1.如果是访问成员变量,编译的话就是看父类,运行时还是看父类。
2.如果是访问方法,编译看父类,运行看子类。
3.如果是静态方法,编译和运行都是看父类。
子类Cat中还有一个独有的fight方法,通过animal对象是访问不到的,这时要animal向下转型为Cat,即将父类强制转成子类:
Cat cat = (Cat) animal;
cat.fight();
*多态的好处?
把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用编程,以适应需求的不断变化。