我们知道Java面向对象编程有三个特征,即封装、继承和多态。 

封装隐藏了类的内部实现机制,从而可以在不影响使用者的前提下改变类的内部结构,同时保护了数据。  

继承是为了重用父类代码,同时为实现多态性作准备。那么什么是多态呢?  


今天的重点讨论内容就是分析Java的多态性:

方法的重写、重载与动态连接构成多态性。Java之所以引入多态的概念,原因之一是它在类的继承问题上和C++不同,后者允许多继承,这确实给其带来的非常强大的功能,但是复杂的继承关系也给C++开发者带来了更大的麻烦,为了规避风险,Java只许单继承,派生类与基类间有IS-A的关系(即“猫”is a “动物”)。这样做虽然保证了继承关系的简单明了,但是势必在功能上有很大的限制,所以,Java引入了多态性的概念以弥补这点的不足,此外,抽象类和接口也是解决单继承规定限制的重要手段。因此,多态也是面向对象编程的精髓所在。  


多态的一般理解:

多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。多态性增强了软件的灵活性和扩展性。


要理解多态性,首先要知道什么是“向上转型”,“向下转型”。  

我定义了一个子类Cat,它继承了Animal类,那么后者就是前者是父类。我可以通过  Cat c = new Cat(); 实例化一个Cat的对象,这个不难理解。但当我这样定义时:  Animal a = new Cat(); 这代表什么意思呢?  很简单,它表示我定义了一个Animal类型的引用,指向新建的Cat类型的对象。由于Cat是继承自它的父类Animal,所以Animal类型的引用是可以指向Cat类型的对象的。那么这样做有什么意义呢?因为子类是对父类的一个改进和扩充,所以一般子类在功能上较父类更强大,属性较父类更独特, 定义一个父类类型的引用指向一个子类的对象既可以使用子类强大的功能,又可以抽取父类的共性。  所以,父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的方法,它是无可奈何的;

eg:

class Animal{
	pubilc void eat(){
		System.out.println("eat");
	}
}
class Cat extends Animal{
	public void catchMouse(){
		System.out.println("catch mouse");
	}
}
class duodaiDemo{
	public static void main(String[] args) {
		<span style="color:#ff0000;">Animal a = new Cat();</span>
		a.eat();
	}
}




编译运行的结果是:

->eat

但是问题又来了虽然cat继承了animal的eat方法,可以调用a.eat()方法。但是anim中没有cat的特有方法catchmouse();如果这时调用a.catchMouse()

编译运行的结果是:

Java中为什么引入多态 java为什么有多态_子类

分析:

由于animal类是cat的父类cat继承了anmal的eat方法但是animal的对象a虽然是new的一个cat对象,但是它的类型是animal不能调用cat的catchMuse方法,这就是向上转向,cat--->animal;

要想实现catchMouse方法就必须要将a向下转型

cat c = (cat)a;

eg:

class Animal{
	public void eat(){
		System.out.println("eat");
	}
}
class Cat extends Animal{
	public void catchMouse(){
		System.out.println("catch mouse");
	}
}
class duodaiDemo{
	public static void main(String[] args) {
		Animal a = new Cat();
		a.eat();
		<span style="color:#ff0000;">Cat c = (Cat)a;</span>
		c.catchMouse();
	}
}

编译运行结果是:


Java中为什么引入多态 java为什么有多态_父类_02

向上转型向下转型实现了多态。


通过继承实现多态性:

javade 这种机制遵循一个原则:当超类对象引用变量引用子类对象是,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。如:如果a是类A的一个引用,那么,a可以指向类A的一个实例,或者说指向类A的一个子类;如果a是个接口的引用,那么,a必须指向实现了接口A的一个类的实例。继承父类进行方法的覆盖,同一个类中进行方法重载。多态存在的必要条件:1,要哟继承或实现,2,要有重写,3父类引用指向子类对象。

方法覆盖:如果在子类中定义一个方法,其名称、返回类型及参数签名正好与父类中某个方法的名称、返回类型及参数签名相匹配,那么可以说,子类的方法覆盖了父类的方法。
  * 子类的方法名称返回类型及参数签名 必须与父类的一致

eg:


class Fu{
	public void demo(<span style="color:#ff0000;">type1 t1,type2 t2</span>){
		//code;
	}
}
class Zi{
	public void demo(<span style="color:#ff0000;">type1 t1.type2 t2</span>){
		//recode;
	}
}




  * 子类方法不能缩小父类方法的访问权限

eg:

class Fu{
	<span style="color:#ff0000;">private</span> void demo(type1 t1,type2 t2){
		//code;
	}
}
class Zi extends Fu{
	<span style="color:#ff0000;">public</span> void demo(type1 t1.type2 t2){
		//recode;
	}
}




  *  子类方法不能抛出比父类方法更多的异常

eg:

class Fu throws <span style="color:#ff0000;">RuntimeException,interruptionException</span>{
	public void demo(type1 t1,type2 t2){
		//code;
	}
}
class Zi extends Fu throws <span style="color:#ff0000;">RuntimeException</span>{
	public void demo(type1 t1.type2 t2){
		//recode;
	}
}




  * 方法覆盖只存在于子类和父类之间,同一个类中只能重载

eg:

class Fu {
	public void demo(type1 t1,type2 t2){
		//code;
	}
	public void demo(type1 t1.type2 t2){
		//recode;
	}
}

以上的写法是错误的。


  * 父类的静态方法不能被子类覆盖为非静态方法

eg:

class Fu {
	public <span style="color:#ff0000;">staitc</span> void demo(type1 t1,type2 t2){
		//code;
	}
}
class Zi extends Fu{
	public void demo(type1 t1.type2 t2){
		//recode;
	}
}

以上代码是错误的书写



  * 子类可以定义与父类的静态方法同名的静态方法,以便在子类中隐藏父类的静态方法(满足覆盖约束), 而且Java虚拟机把静态方法和所属的类绑定,而把实例方法和所属的实例绑定。


  * 父类的非静态方法不能被子类覆盖为静态方法

eg:

class Fu  {
	public void demo(type1 t1,type2 t2){
		//code;
	}
}
class Zi extends Fu {
	public static void demo(type1 t1.type2 t2){
		//recode;
	}
}

以上代码错误



  * 父类的私有方法不能被子类覆盖


  * 父类的抽象方法可以被子类通过两种途径覆盖(即实现和覆盖)

eg:

abstract class Fu{
	abstract demo(type1 t1,type2 t2){
		//code;
	}
}
class Zi extends Fu {
	public void demo(type1 t1.type2 t2){
		//recode;
	}
}




  * 父类的非抽象方法可以被覆盖为抽象方法


重载则是在同一个类中实现的

重载是指使用了同名函数但是参数的个数或者循序不同。如构造函数有无参构造函数和有参构造函数这就是重载的体现

eg:

class Student{
	public void run(String s ,int i){}
	public void run(String s){}
	public void run(String s,String s){}
}

特别要注意的是千万不能定义同名同参不同返回类型的函数,那不单不是重载而且不能编译通过,因为java虚拟机读到这两个一样的方法,返回值不同到底该返回谁呢?

java虚拟机不知道。所以千万不要这样写:

class Student{
	public void run(String s ,int i){}
	public String run(String s ,int i){}
}



super 和 this


super:表示调用父类的构造函数。也是一个特殊语法,不是变量,没有什么类型。


子类继承了父类后子类的构造函数第一行隐藏了一行代码super();

eg:

abstract class Fu{
	Fu(){
		System.out.println("Fu");
	}
}
class Zi extends Fu {
	public void show(){
		System.out.println("Zi");
	}
}
class Demo{
	public static void main(String[] args){
		Zi z = new Zi();
		z.show();
	}
}

编译运行结果:

-->Fu

-->Zi

this:代表的是所在类的当前对象

eg:

class Student{
	private String name;
	Student(String name){
		<span style="color:#ff0000;">this</span>.name = name;
	}
	public String getName(){
		return name;
	}
}
class Demo{
	public static void main(String[] args){
		Student s = new Student("kch");
		Student s1 = new Student("kkk");
		System.out.println(s.getName());
		System.out.println(s1.getName());
	}
}

编译运行结果:

-->>kch

-->>kkk


通过接口实现多态性:

接口可以用于声明对象引用变量。一个接口引用变量可以指向实现该接口的任何类的任何对象。一个方法的参数可以使多态的,是的方法所接收的参数具有灵活性。接口的灵活性就在于:规定一个类必须要做什么,而不管你如何做。我们可以定义一个接口类型的引用变量来引用实现接口类的实例,当这个引用调用方法时,它会根据实际引用的类的实例来判断具体调用哪个方法,这和上诉的超类对对象引用访问子类对象的机制相似。

eg:

interface interA{
	void fun();
}
class B implements interA{
	public void fun(){
		System.out.println("B");
	}
}
class C implements interA{
	public void fun(){
		System.out.println("C");
	}
} 
class Demo{
	public static void main(String[] args){
		interA a;
		a = new B();
		a.fun();
		a =new C();
		a.fun();
	}
}

运行结果:

-->>B

-->>C

多态中调用成员方法,成员变量,静态成员方法的特点:

多态中调用方法的如下:

1:父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的方法,它是无可奈何的;:

2:同时,父类中的一个方法只有在在父类中定义而在子类中没有重写的情况下,才可以被父类类型的引用调用;对于父类中定义的方法,如果子类中重写了该方法,那么父类类型的引用将会调用子类中的这个方法,这就是动态连接

多态中变量如下:

1、使用父类类型的引用指向子类的对象;

2、该引用只能调用父类中定义的方法和变量;

3、变量不能被重写(覆盖),“重写”的概念只针对方法,如果在子类中“重写”了父类中的变量,那么在编译时会报错。

在编译时期:参阅引用型变量所属的类中是否有调用的方法。如果有,编译通过,如果没有,编译失败。

在运行期间;参阅对象所属类中是否有调用的方法。

简单总结:

 成员方法在多态调用时,编译看左边,运行看右边。

eg:

class Animal{
	public void eat(){
		System.out.println("animal eat");
	} 
}
class Cat extends Animal{
	public void eat(){
		System.out.println("cat eat");
	}
	public void catchMouse(){
		System.out.println("catch mouse");
	}
}
class Demo{
	public static void main(String[] args) {
		Animal a = new Cat();
		a.catchMouse();
	}
}

编译出错

class Animal{
	public void eat(){
		System.out.println("animal eat");
	} 
}
class Cat extends Animal{
	public void eat(){
		System.out.println("cat eat");
	}
	public void catchMouse(){
		System.out.println("catch mouse");
	}
}
class Demo{
	public static void main(String[] args) {
		Animal a = new Cat();
		a.eat();
	}
}

运行结果:

-->>cat eat

 成员变量在多态调用时,无论编译和运行,都参考左边(引用类型变量所属的类)。

eg:

class Animal{
 int a = 1;
	public void eat(){
		System.out.println("animal eat");
	} 
}
class Cat extends Animal{
	int a = 2;
	public void eat(){
		System.out.println("cat eat");
	}
	public void catchMouse(){
		System.out.println("catch mouse");
	}
}
class Demo{
	public static void main(String[] args) {
		Animal a = new Cat();
		System.out.println(a.a);
	}
}

运行结果:

--.>>1

 静态成员方法多态调用时,无论编译和运行,都参考左边(引用类型变量所属的类)。

eg:

class Animal{
 int a = 1;
	public static void eat(){
		System.out.println("animal eat");
	} 
}
class Cat extends Animal{
	int a = 2;
	public static void eat(){
		System.out.println("cat eat");
	}
	public void catchMouse(){
		System.out.println("catch mouse");
	}
}
class Demo{
	public static void main(String[] args) {
		Animal a = new Cat();
		a.eat();
	}
}

运行结果:

-->>animal eat


多态的好处:

1,可替换性:多态对已存在的代码具有可替换性。

2,可扩充性:增加新的子类不影响已存在的多态性,继承性,以及其他的特性和运行和操作

3,接口性:多态是超类通过方法签名,向子类提供了一个公有接口,有子类来完善或者覆盖它而实现的

4,灵活性:它在应用中体现了灵活多样的操作,提高了使用效率

5,简单性:多态简单对应用软件的代码编写和修改过程,尤其在处理大量对象的运算时,这个特点尤为突出和重要。



多态在在现实中普遍存在:

比如:

在Flash界面下按F1键弹出的就是Flash的帮助文档,如果在word下弹出的就是word帮助文档;在window下弹出的就是windows帮助和支持,同一个事件发生在不同的对象上产生不同的结果。这就是多态!