目录
一、多态的理解
1. 多态的产生条件
2. 重写
二、多态转型
1. 向上转型
2.向下转型
3. instanceof转换符
4. 动态绑定
5.静态绑定
简单来说,多态就是多种形态的意思,当要去完成某个行为的时候,不同的对象会产生出不同的状态。
比如说一个打印机可以打印黑白图片也可以打印彩色图片,虽然都是打印机,但是打印出来的东西却不相同。再比如说动物都要吃饭,但是每个动物吃的饭却也不一样,狗吃狗粮,猫吃猫粮。我们也可以将多态理解为同一件事情发生在不同的对象身上就会产生不同的结果。
多态产生必须满足三个条件:
1. 多态必须在继承体系内
2. 多态的子类必须要对父类的方法进行重写
3. 多态的子类必须要通过父类的引用来调用重写的方法
下面我们用一个例子来说明多态
class Animal{
public String name;
public Animal(String name){
this.name = name;
}
public void eat(){
System.out.println(this.name+"吃饭");
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(name+"吃鱼");
}
}
class Dog extends Animal{
public Dog(String name){
super(name);
}
@Override
public void eat() {
System.out.println(name + "吃狗粮");
}
}
class Bird extends Animal{
public Bird(String name){
super(name);
}
@Override
public void eat() {
System.out.println(name + "吃鸟粮");
}
public void fly(){
System.out.println(this.name+"正在飞");
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("小黑");
cat.eat();
Bird bird = new Bird("圆圆");
bird.eat();
bird.fly();
}
}
在上例中,我们在Animal中定义了一个eat方法,在cat dog bird子类中重写了父类的eat方法,在子类编写者在编写eat方法时,父类方法内部并不知道未来eat方法会指向哪个子类,此时每个子类调用父类方法会有不同的表现。
重写也称为覆盖,重写是子类对父类非静态、非private、非final、非构造方法进行重写的的过程,返回值和形参都不能改变,但是核心代码可以重写。
class Dog extends Animal{
public Dog(String name){
super(name);
}
public void bark(){
System.out.println("woof");
}
}
class dog extends Dog{
public dog(String name) {
super(name);
}
public void bark(){
System.out.println("boo");
}
}
class dog extends Dog{
public dog(String name) {
super(name);
}
public void bark(int num){
for (int i = 0; i < num; i++) {
System.out.println("boo");
}
}
}
在上例中第一个dog子类返回类型和形参均未改变,但是重写了内部方法,称为重写或覆盖。第二个dog子类重写了形参类型,称为重载。
重写和重载的区别就在于,是否修改了参数列表和访问类型,如果只是内部方法重写则为重写,参数类型一起修改则为重载。
向上转型的意思是创造一个子类对象,但将其当做父类对象来使用。
父类类型 对象名 = new 子类类型()
下面仍然举例来说明向上转换
class Animal{
public String name;
public Animal(String name){
this.name = name;
}
public void eat(){
System.out.println(this.name+"吃饭");
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(name+"吃鱼");
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("小黑");
Bird bird = new Bird("圆圆");
Animal animal = (Animal) cat; //向上转换,猫本来就是动物,转换成动物没有危险
animal.eat();
}
}
向下转换一般会带有类型出错的危险,向下转换一般只会强制转换父类的引用,不能强制转换父类的对象,父类的引用必须指向当前目标的对象,在向下转型后父类可以调用子类的所有成员。
子类类型 引用名 = (子类类型) 父类引用;
class Animal{
public String name;
public Animal(String name){
this.name = name;
}
public void eat(){
System.out.println(this.name+"吃饭");
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(name+"吃鱼");
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("小黑");
Bird bird = new Bird("圆圆");
Cat cat = (Animal)cat;// 向下转型出错
cat.eat();
}
}
Java中为了提高向下转型的安全性,引入 了 instanceof ,如果该表达式为true,则可以安全转换。
class Animal{
public String name;
public Animal(String name){
this.name = name;
}
public void eat(){
System.out.println(this.name+"吃饭");
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(name+"吃鱼");
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("小黑");
Bird bird = new Bird("圆圆");
Animal animal = cat;
if(animal instanceof Cat){
cat = (Cat)animal;
cat.eat();
}
}
}
使用instanceof操作符后,我们可以成功编译并输出结果。
在我们重写方法后,如何得知编译器运行的是哪个方法呢?这个时候我们就需要了解动态绑定的原理。动态绑定也是多态的基础。
在三个地方我们会使用到动态绑定:重写、向上转型、通过父类引用调用父类和子类重写的方法
class Animal{
public String name;
public Animal(String name){
this.name = name;
}
public void eat(){
System.out.println(this.name+"吃饭");
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(name+"吃鱼");
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("小黑");
Animal animal = cat;
//向上转型(自动类型转换)
//程序在编译阶段只知道 cat 是 Animal 类型
//程序在运行的时候才知道堆中实际的对象是 Animal 类型
animal.eat();
//程序在编译时 cat 被编译器看作 Animal 类型
//因此编译阶段只能调用 Animal 类型中定义的方法
//在编译阶段,cat 引用绑定的是 Animal 类型中定义的 eat 方法(静态绑定)
//程序在运行的时候,堆中的对象实际是一个 Cat 类型,而 Cat 类已经重写了 eat 方法
//因此程序在运行阶段对象中绑定的方法是 Cat 类中的 eat 方法(动态绑定)
}
}
静态绑定就是我们已经熟悉的重载,在此不做过多的赘述。