概述
首先开始我们以下面的程序来引出今天所讲的多态
一、动态绑定
代码如下
public class Main {
public static void main(String[] args) {
Zi b = new Zi();
b.view();
}
}
class Fu {
public int m = 1;
public void common() {
System.out.println("这是Fu的common方法");
}
public void view() {
common(); // 这里是关键,父类和子类都有common方法,那么调用
//哪个呢,这里根据下面运行结果就知道是调用的子类的方法
//具体原理是什么呢,跟动态绑定有关
}
}
class Zi extends Fu {
public int m = 2;
public void common() {
System.out.println("这是Zi的common方法");
}
public void look() {
view(); // 子类没有该方法,这里涉及继承,子类对象会通过继承链
// 找到父类对象中的该方法调用
}
}
输出结果为:这是Zi的common方法
接下来我们把子类的common方法注释起来又会发生什么
要想搞懂上面两个程序运行的结果,我们就先要了解下面这些
Java允许程序员不必在编制程序时就确定调用哪一个方法,而是在程序运行的过程中,当方法被调用时,系统根据当时对象本身所属的类来确定调用哪个方法,这种技术被称为后期(动态)绑定。当然这会降低程序的运行效率,所以只在子类对父类方法进行覆盖时才使用
(这里我们贴上动态绑定的定义:动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。程序运行过程中,把函数(或过程)调用与响应调用所需要的代码相结合的过程称为动态绑定。
Java中只有static,final,private和构造方法,以及成员变量是静态绑定,其他的都属于动态绑定,而private的方法其实也是final方法(隐式),而构造方法其实是一个static方法(隐式),所以可以看出把方法声明为final,第一可以让他不被重写,第二也可以关闭它的动态绑定。
所以根据这些,我们就可以解释上面的例子为什么是调用的子类的方法,例子中public方法是动态绑定,因为实例对象是子类,所以调用的是子类方法。第一个是子类重写了父类方法,所以运行结果为"这是子类的common方法";而对于第二个来说也是调用子类方法,只不过子类没有重写父类的方法,所以根据继承链找到父类的实现调用,运行结果为"这是父类的common方法"
这里就可以引出我们要讲的多态,多态的实现关键就是靠这种动态绑定(Java中的大多数方法都是属于动态绑定,也就是实现多态的基础。)
二、多态详解
1.什么是多态
面向对象的三大特性:封装、继承、多态。从一定角度来看,封装和继承几乎都是为多态而准备的。这是我们最后一个概念,也是最重要的知识点。
多态的定义:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用) ,简单的说:就是用基类的引用指向子类的对象。
多态的技术称为:动态绑定(dynamic binding),是指在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
2.多态的前提
- 要有继承关系
- 要有方法重写
- 要有父类引用指向子类对象
多态的前提条件就决定了只有成员方法才有多态,成员变量是没有的
对于成员变量来说
无论是实例成员变量还是静态成员变量,都没有多态这一特性,通过引用变量来访问它包含的成员变量时,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量,成员变量是静态绑定的(只根据对象的当前表示类型决定使用那个变量)
对于成员方法来说:
调用方法时,成员方法支持动态绑定(根据对象的实际类型确定执行那个方法),这里注意静态方法不算
这个口诀可以帮我们理解,但我们一定要弄懂其中原理
- 成员变量:编译看左边(父类),运行看左边(父类)
- 成员方法:编译看左边(父类),运行看右边(子类)
- 静态方法:编译看左边(父类),运行看左边(父类)
(静态和类相关,算不上重写,所以,访问还是左边的)
只有非静态的成员方法,编译看左边,运行看右边
我们来个实例解释一下上面这段话
public class Main {
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.m); //与父类一致
f.method1(); //与父类一致
f.method2(); //编译时与父类一致,运行时与子类一致
System.out.println("-------------------");
Zi z = new Zi();
System.out.println(z.m);
z.method1();
z.method2();
}
}
class Fu {
public int m = 1;
public static void method1() {
System.out.println("这是Fu的静态method方法");
}
public void method2() {
System.out.println("这是Fu的method方法");
}
}
class Zi extends Fu {
public int m = 2;
public static void method1() {
System.out.println("这是Zi的静态method方法");
}
public void method2() {
System.out.println("这是Zi的method方法");
}
}
运行结果:
分析:
Fu f = new Zi(); ----------首先了解变量F到底是什么
把这句子分2段:Fu f;这是声明一个变量f为Fu这个类,那么知道了f肯定是Fu类。然后我们f=newZi();中建立一个子类对象赋值给了f,结果是什么??
结果是,拥有了被Zi类函数覆盖后的Fu类对象 ----------f
也就是说:
一、只有子类的函数覆盖了父类的函数这一个变化,但是f肯定是Fu这个类,也就是说f不可能变成其他比如Zi这个类等等(突然f拥有了Zi类特有函数,成员变量等都是不可能的)。所以f所代表的是函数被复写后(多态的意义)的一个Fu类,而Fu类原来有的成员变量(不是成员函数不可能被复写)没有任何变化。那么获得结论:A:成员变量:编译和运行都看Fu
二、但是f的Fu类函数被复写了。那么获得结论:B:非静态方法:编译看Fu,运行看Zi
三、对于静态方法:编译和运行都看Fu!!(这个又是怎么来的)
其实很简单,首先我们要理解静态情况下发生了什么?
当静态时,Fu类的所有函数跟随Fu类加载而加载了。也就是Fu类的函数(是先于对象建立之前就存在了,无法被后出现的Zi类对象所复写的,所以没发生复写,那么获得结论:C:静态方法:编译和运行都看Fu
下面也是一个典型的多态案例
class Demo1_Polymorphic {
public static void main(String[] args) {
Animal a = new Cat(); //父类引用指向子类对象
System.out.println(a.color);
a.eat();
}
}
class Animal {
public String color = "黑色";
public void eat() {
System.out.println("动物吃饭");
}
}
class Cat extends Animal {
public String color = "白色";
public void eat() {
System.out.println("猫吃鱼");
}
}
这里前提是我们知道a是Animal类引用指向的Cat对象,所以它的成员变量编译是跟Animal类绑定在一起的,方法是跟它运行时的Cat对象绑定在一起的
程序输出结果为黑色,猫吃鱼。那么这个是如何实现的呢,首先,对于color这个成员变量来说,它是静态绑定的,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量,所以我们这里访问的就是Animal类所定义的成员变量 —color=“黑色”,而对于eat()这个方法来说,是根据对象的实际类型(Cat类)确定执行这个方法,所以我们得到的是—“猫吃鱼”。(根据上面的口诀也很快能得出答案)
通过这些,基本上对多态有了详细的了解,成员变量和成员方法多态是不同的表现的,这也就是成员在被子类重写时,变量称为"隐藏",而方法称为"覆盖"的主要原因。
然后我们这里要特别强调一点,就是如果要实现多态,父类一定要有被重写的方法,这里我们以下面为例:
这里我们就明显看到了报错,说这个方法未定义,所以要想用多态,多态的第二条前提是十分重要的(要有方法重写,重点是父类要有被重写的方法,子类不重写父类方法还可以通过继承运行,但是父类没有被重写的方法是会完全报错的,编译都通不过,干脆就是错的)。
这个就是子类没有重写父类方法,但是程序没错,还是可以完整运行,跟刚刚上面那个是不同的。
三、多态中向上转型和向下转型
对象的向上转型:父类 父类对象 = 子类实例
1.父类有的方法,都可以调用,如果被子类重写了,则会调用子类的方法。
2. 父类没有的方法,而子类存在,则不能调用。
3.向上转型只对方法有影响,对属性没影响。属性不存在重写。
对象的向下转型:子类 子类对象 = (子类)父类实例
为什么要发生向下转型?当父类需要调用子类的扩充方法时,才需要向下转型。(这是因为多态的弊端就是不能使用子类的特有功能)
下面这个实例讲述了向上转型和向下转型的具体操作
运行结果:
四、多态的好处和弊端
-
A:多态的好处
1.提高了代码的维护性(继承保证)
2.提高了代码的扩展性(由多态保证) -
B:多态的弊端
不能使用子类的特有属性和行为。 -
C:常用应用场景
可以当作形式参数,可以接收任意子类对象
五、这里讲一下静态绑定和动态绑定的区别
静态绑定(前期绑定):即在程序执行前,即编译的时候已经实现了该方法与所在类的绑定,像C就是静态绑定,针对Java简单的可以理解为程序编译期的绑定
具体过程就是执行这个方法,只要到这个类的方法表里拿出这个方法在内存里的地址,然后就可以执行了。
Java中只有static,final,private和构造方法,以及成员变量是静态绑定,其他的都属于动态绑定,而private的方法其实也是final方法(隐式),而构造方法其实是一个static方法(隐式),所以可以看出把方法声明为final,第一可以让他不被重写,第二也可以关闭它的动态绑定。
动态绑定(后期绑定):运行时根据对象的类型进行绑定,Java中的大多数方法都是属于动态绑定,也就是实现多态的基础。
Java实现了后期绑定,则必须提供一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。也就是说,编译的时候该方法不与所在类绑定,编译器此时依然不知道对象的类型,但方法调用机制能自己去调查,找到正确的方法主体。Java里实现动态绑定的是JVM.
最后
以上就是独特钻石为你收集整理的Java动态绑定和多态性详解的全部内容,希望文章能够帮你解决Java动态绑定和多态性详解所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复