Java 多态、动态绑定、向上转型等概念的解析

Java三大基本特性

一、继承:Java的继承体系的特点是子类可以通过继承父类而调用父类的方法(父类方法的修饰符需要为public或者protected才能被继承),子类也可以在继承父类方法的基础上实现一些自己独有的方法,也可以重写父类的方法,这为多态的实现做了铺垫。通过继承可以不用去繁琐地实现一些别的类以及实现的方法,只要继承它即可,但是一个类只能有一个父类,如果想实现一些别的功能,可以再实现一些接口。
二、封装:每个类都有自己的属性和方法,其中被private修饰的方法是不能被直接访问和被继承的。每个类对外部而言它隐藏了自身的细节,只对外提供访问的接口,保护了自身的数据,这就是封装。
三、多态:多态是指一个引用类型在不同情况下的多种状态,也可以理解为一个父类引用调用不同子类中的实现方法。多态常见的实现形式是向上转型,向上转型是指用父类的引用来实现子类。此处涉及到了引用的绑定,绑定可以分为静态绑定和动态绑定,接下来我们将详细介绍动态绑定、静态绑定、向上转型等概念。

静态绑定

静态绑定是指:在编译期,加载类的时候,就已经确定的类和方法的引用关系。这些引用关系存放于方法区,方法区中有:类信息,静态变量,常量池,以及编译期间产生的一些数据。静态绑定是对一些在程序运行前已经确定对应关系的类和方法的预加载,静态绑定包括的内容的不仅仅是方法表,还有private,static,final修饰的属性和方法,其中的private不能被继承也不能被重写,在查看源码时,我们可以发现private是被final关键字修饰的,因此private修饰的方法也会被静态绑定;被static修饰的方法和属性是属于类的,它们存放在JVM方法区中,被该类的所有对象所共享。

动态绑定

之前介绍的静态绑定是指在程序运行前(对象的创建前)就以经确定了引用关系,且我们可以通过类去直接访问(比如static和final修饰的属性和方法)。剩下的那些方法就需要在程序运行中通过创建对象来绑定引用关系,即动态绑定。向上转型体现了动态绑定,接下来我们介绍一下向上转型。

向上转型

**什么是向上转型:**向上转型是指用父类的引用去实现不同子类的方法,体现了多态的特点。比如:Animal a = new Dog();a.eat();eat()的内容是狗类改写的动物类的方法,这里我们用动物类的引用来实现了狗类的方法,同理我们还可以用这个引用实现更多的子类方法。在JVM中,我们可以把这种引用看做一张方法表,我们用父类的方法表去引用子类的方法,一般父类的方法少于子类,向上转型只能实现父类中存在的同名方法,在子类改写父类方法后调用得到的是改写后的方法。
**自动转型的意义:**当一个类的被多个类继承并重写了方法之后,假设我们按照传统的思路去实现这每一个子类的方法,有n个子类,需要new n个子类对象,然后再分别通过n条方法调用语句,才能把这些方法执行完,这是不是很麻烦呢?如果我们可以通过一个方法解决,调用问题,是不是会方便很多。我们来看看下面的代码:

public class Poly {		
	/**
	 * 动物的行为方法
	 * @param a
	 */
	public void doing(Animal a)
	{
		a.eat();
	}		
	public static void main(String[] args)
	{
		Poly p = new Poly();
		p.fun2();
	}	
	/**
	 * 测试1:传参数,多种对象传入相同方法,自动转型为父类,调用改写后的方法
	 */
     public void fun1()
     {  	
 		doing(new Dog());
 		doing(new Cat());
 		doing(new Pig());
     }
	     /**
      * 测试2:申明一次,创建多次,调用子类重写的方法
      */
	 public void fun2()
	 {
		 Animal a;
		 a=new Dog();
		 a.eat();
		 a=new Cat();
		 a.eat();
		 a=new Pig();
		 a.eat();		 
	 }	
}

先看测试1的输出:
狗在吃饭
猫在吃饭
猪在吃饭
我们在fun1()方法中调用了三次doing()方法,在方法的调用处传入的参数是三个不同的对象,为什么在唯一的一个doing()方法中可以输出三种不同的结果呢?这三个子类都重写了动物类的吃的方法,这时我们看测试2,输出结果和测试1是一致的,测试2中用父类引用实现了子类的中重写的方法,这体现了自动转型的思想(子类在这个过程中自动转型为父类)。转型后只能调用父类中的同名方法,因为被子类重写,那么就调用被重写后的方法,这种可被重写的方法也称为虚方法。接下来继续回过头来看测试1,我们可以看出自动转型的优势所在,我们用了一个doing()方法实现了所有动物的eat()方法,这极大增加了代码的简洁性。或许现在只有三种动物我们看不出明显的优势,如果数量增加到一万种动物,我们依然可以用一个方法来执行所有动物的eat()方法。向上转型的局限性体现在:使用的方法只能局限于父类中存在的方法,灵活性有所欠缺。

向下转型

这时可能会有同学问,有没有向下转型呢?即父类转子类,我们这样思考,如果指着一只动物说:“这是狗!”它一定是狗吗?不一定的,所以向下转型会带来或多或少的问题。但也不是不可以向下转型,只要在确定那个动物是狗之后再指明它是狗就可以了,这里要加上对象的判断和强制转型(上面提到的向上转型是自动转的)。我们来看代码:

	 /**
	  * 测试3:向下转型
	  */
	 public void fun3()
	 {
		    doing1(new Dog());
	 		doing1(new Cat());
	 		doing1(new Pig());
	 }	 
	 /**
	  * 向下转型
	  * @param a
	  */
	 public void doing1(Animal a)
	 {
		 if(a instanceof Dog)
		 {
			 Dog dog = (Dog)a;
			 dog.eat();
		 }
		 if(a instanceof Cat)
		 {
			 Cat dog = (Cat)a;
			 dog.eat();
		 }
		 if(a instanceof Pig)
		 {
			 Pig dog = (Pig)a;
			 dog.eat();
		 }
	 }

输出和之前的相同,这里用到了判断,先判断它是什么类型的动物,再强制转化为相应的类型,其中的instanceof关键字表示判断,返回的是true或false,判断某对象是不是属于某个类。综合分析这段代码,发现,既然需要还原为原对象,为何不一开始就直接创建对象并调用方法呢?的确,向下转型是不太适合出现在程序的设计中的,我们应该尽量避免这种现象,这不仅容易导致程序出错,也使得代码冗杂。

猜你喜欢

转载自blog.csdn.net/mayifan_blog/article/details/85341284