java学习面向对象之多态
如何理解多态,让我们举个例子来描述一下,因为单纯的说多态大家可能不理解:
1 abstract class Animal 2 { 3 4 public int age = 10; 5 6 abstract void eat(); 7 8 public void run() 9 { 10 11 System.out.println("I Run!"); 12 13 } 14 15 } 16 17 class Dog extends Animal 18 { 19 20 public void eat() 21 { 22 23 System.out.println("I eat meat"); 24 25 } 26 27 public void lookUpHome() 28 { 29 30 System.out.println("I can look up home~"); 31 32 } 33 34 } 35 36 class Cat extends Animal 37 { 38 39 public void eat() 40 { 41 42 System.out.println("I can eat Mouse"); 43 44 } 45 46 public void catchMouse() 47 { 48 49 System.out.println("I can catch Mouse"); 50 51 } 52 53 } 54 55 class DuoTaiDemo1 56 { 57 58 public static void main(String[] args) { 59 60 Dog d = new Dog(); 61 Cat c = new Cat(); 62 d.eat(); 63 c.eat(); 64 65 } 66 67 }
我们一般的思路在写DuoTaiDemo1这个类的时候,我们会通常这个样子写,但是我们发现我们这样子写是不是有点重复,同时new了一个继承自同一父类的子类,同时调用了继承自父类的方法。但是java是一种纯面向对象的语言,面向对象的特点是提高代码的复用性。java为了提高代码的复用性,提供了一下的写法:
1 class DuoTaiDemo1 2 { 3 4 public static void main(String[] args) { 5 6 eat(new Dog()); 7 eat(new Cat()); 8 9 } 10 11 public static void eat(Animal a) 12 { 13 14 a.eat(); 15 16 } 17 18 }
这个样子的话,我们仅需要传一个对象进去就能够,调用相关方法,这样是不是提高了代码的复用性呢?
在这里需要注意的一点是DuoTaiDemo1这个类的eat方法接受的参数是Animal型的,同时Dog 和 Cat也是继承自Animal的是其子类,所以这样写是合法的。那么也就是说,这个时候Cat即属于动物也属于猫,同时具有两种形态,这个就是面向对象当中所说的多态。
多态更形象一些可以这样子来写:
1 Animal c = new Cat();//一个对象两种形态
等式两边分属两个不同的类,具有不同的形态,右边继承自左边。
定义:多态就是某一类事物存在的多种形态。多态在代码中的体现:父类或者接口的引用指向其子类的对象。
多态的好处:
提高了代码的扩展性,前期定义的代码可以使用后期的内容。 这句话的好处在于,在前期定义的代码里面可以使用后期的内容,比如上述定义的猫啊、狗啊继承自动物类,在DuoTaiDemo1中的eat方法当中我们使用Animal类型的作为传入参数。这个时候我们可以定义一个猪类然后也让他继承自动物,这样子我们就可以在DuoTaiDemo1当中利用以前定义的方法,然后直接传入猪的对象,代码体现如下:
1 class Pig extends Animal 2 { 3 4 public void eat() 5 { 6 7 System.out.println("I can eat grass!"); 8 9 } 10 11 public void gongDi() 12 { 13 14 System.out.println("I can gong earth"); 15 16 } 17 18 } 19 20 class DuoTaiDemo1 21 { 22 23 public static void main(String[] args) { 24 25 eat(new Dog()); 26 eat(new Cat()); 27 eat(new Pig()); 28 } 29 30 public static void eat(Animal a) 31 { 32 33 a.eat(); 34 35 } 36 37 }
这样就叫做前期定义的代买可以使用后期定义的内容。
那么多态有哪些弊端呢?我们仍然用代码来体现:
1 class DuoTaiDemo1 2 { 3 4 public static void main(String[] args) { 5 6 eat(new Dog()); 7 eat(new Cat()); 8 eat(new Pig()); 9 } 10 11 public static void eat(Animal a) 12 { 13 14 a.eat(); 15 a.catchMouse();//这样子写是错误的,因为父类当中并没有定义这个方法 16 } 17 18 }
这里如果我们用传入的Animal a 对象来调用catchMouse()这个方法的话,就会报错,为什么因为Animal这个抽象类当中并没有预先定义这个方法,这个方法是子类的特有的方法,所以调用的时候就会报错。这个就是多态的局限性。
多态的缺陷:
前期定义的代码不能调用后期内容的特有方法
使用多态的前提:
1、必须有关系,继承或这实现。
2、必须有覆盖。
我们把代码现在改成这个样子:
1 class DuoTaiDemo1 2 { 3 4 public static void main(String[] args) { 5 6 Animal a = new Cat(); 7 a.eat();// 8 a.catchMouse(); 9 } 10 11 public static void eat(Animal a) 12 { 13 14 a.eat(); 15 a.catchMouse();// 16 } 17 18 }
我们知道在
1 byte a = 4;int x = a;//这个过程当中发生了自动类型提升。
同样在多态当中,也发生了自动类型的提升。子类型提升为父类型并且屏蔽掉了子类型当中特有的方法,限制对特有功能的使用。这种方式也叫做向上转型。如果我们想用子类当中特有的功能方法,我们必须向下转型。我们继续上述的代码:
1 class DuoTaiDemo1 2 { 3 4 public static void main(String[] args) { 5 6 Animal a = new Cat();//向上转型限制了对子类当中特有功能的访问 7 a.eat();// 8 Cat b = (Cat)a;//向下转型是为了访问子类当中特有的方法 9 /**对于转型,自始至终都是在对子类的类型做变化,同时也需要注意,狗 10 *只能对狗的对象引用做向下转型,一个类只能对其属于他的对象向下转型 11 *比如Cat b = (Dog)a;这种写法就是完全错误的,因为属于猫的怎么样也不 12 *可能变成狗。 13 */ 14 b.catchMouse(); 15 } 16 17 }
instanceof的使用
那么上述我们也讲过了,一个类型只能针对一个类型的引用做相应的判断。这个时候我们会发现在我们写主方法当中的eat方法的时候,要调用子类型对象的特有方法就会非常棘手,因为我们传入的是Animal这个父类的对象,这个是不确定的性的,可能未向上转型之前,我们的对象可能是狗、猫、猪,那么我们怎么判断如何向下转型,利用子类当中特有的方法呢?也就是说,这里我们需要做的一个就是如何判断一个对象到底属于哪个类的问题,这里我们就用到了instanceof这个关键字。这个关键字有什么作用呢,这个关键字的作用就在于能够帮我们判断一个对象到底属于那个类。代码示例:
1 class DuoTaiDemo1 2 { 3 4 public static void main(String[] args) { 5 6 Animal a = new Cat(); 7 if(a instanceof Animal) 8 System.out.println("a is an instance of Animal"); 9 else 10 System.out.println("a isn't an instance of Animal"); 11 if(a instanceof Cat) 12 System.out.println("a is an instence of Cat"); 13 else 14 System.out.println("a isn't an instance of Cat"); 15 16 } 17 18 }
在上述的代码当中,我们可以知道,一个类的对象是该类的实例,同样也属于该类往上的父类的实例。这样我们就可以改良之前我们写的方法:
1 class DuoTaiDemo1 2 { 3 4 public static void main(String[] args) { 5 6 Animal a = new Dog(); 7 method(a); 8 } 9 10 public static void method(Animal a) 11 { 12 13 a.eat(); 14 if(a instanceof Cat) 15 { 16 Cat b = (Cat)a; 17 b.catchMouse(); 18 } 19 else if (a instanceof Dog) 20 { 21 Dog b = (Dog)a; 22 b.lookUpHome(); 23 } 24 else 25 { 26 Pig b = (Pig)a; 27 b.gongDi(); 28 } 29 30 } 31 32 }
在想使用子类当中特有的方法时,一定要向下转型。当想屏蔽掉子类当中的特有方法,想使用父类当中定义的基本的方法时,就可以用向上转型,屏蔽掉子类当中特有的方法。
多态时,成员的特点:
成员变量:
编译时,参考引用型变量所属类型当中是否有该成员变量,如果有编译通过,如果没有编译失败。
运行时,也是参考引用型变量所属类型当中是否有该成员变量,并运行该所属类的成员变量。
简单说:编译和运行都参考等号的左边。
1 class Fu 2 { 3 4 int num = 6; 5 6 } 7 8 class Zi extends Fu 9 { 10 11 int num = 4; 12 13 } 14 15 class DuoTaiDemo2 16 { 17 18 public static void main(String[] args) { 19 20 /** 21 *引用型变量a 所属类型为Fu所以a调用num这个成员变量的时候,是参考Fu类来 22 *去确定的 23 */ 24 Fu a = new Zi();//6 25 Zi b = new Zi();//4 26 System.out.println(a.num); 27 System.out.println(b.num); 28 29 } 30 31 }
如果Fu类当中没有num这个成员变量的话,那么编译的时候,就会提示找不到符号num的错误。如果编译通过运行的时候,会参考应用型变量a所属的类型也就是Fu类型当中的成员变量num来调用。这个是基于什么原理呢,因为我们知道在初始化的时候,Zi的初始化,在内存空间当中成员变量是分两个部分的一个是this指想本类当中的成员变量,还有一个super关键字指向父类的成员变量,因为成员变量是共存的不存在覆盖关系,只有方法有覆盖的关系。
成员方法:
实例代码:
1 class Fu 2 { 3 4 void show() 5 { 6 7 System.out.println("Fu show"); 8 9 } 10 11 } 12 13 class Zi extends Fu 14 { 15 16 void show() 17 { 18 19 System.out.println("Zi show"); 20 21 } 22 23 } 24 25 class DuoTaiDemo3 26 { 27 28 public static void main(String[] args) { 29 30 Fu f = new Zi(); 31 f.show();//zi show 32 33 } 34 35 36 }
我们来画个内存图来理解下多态时候成员方法的调用是怎样的?
在我们创建子类对象的时候,我们知道在堆内存当红分配一个内存空间,之后主方法进栈,然后调用show方法,这个时候因为f是一个指向堆内存当中先前的对象的地址。所以在调用的时候,这个时候this绑定的对象就是之前创建的对象,因为成员方法是依赖于对象的,所以这个时候调用show就会去f指向地址所属类的方法区去寻找方法,如果找到了就调用这个方法如果没有找到就会去父类当中寻找。
以为我们知道多态对应的一类对象不一定固定,因为就像之前说的动物类型的引用变量可能是狗、可能是猪,这些只能够在运行的时候,才能实时的调用指定类型的成员方法,这个中方式也叫做动态绑定。
那么成员函数的特点是什么呢?
编译时:参考引用型变量所属的类当中是否有指定的成员方法,有的话编译通过,没有的话编译失败。
运行时,参考成员方法所属的类,而非父类,如果子类当中有方法,就进行调用,如果没有才会调用父类当中的方法。
简单说:编译看左边,运行看右边。
静态方法:
编译时:参考所属类当中是否有相应的静态方法。
运行时:参考所属类当中是否有相应的静态方法。
简单说:编译和运行都看左边。
这个为什么是这个样子的呢?因为我们知道静态方法的存在是不依赖于对象的,他是先于对象而存在的,所以说调用成员方法没有必要创建对象。直接使用就可以了,不需要绑定到指定的对象上面。