多态性是通过:
1 接口和实现接口并覆盖接口中同一方法的几不同的类体现的。
2 父类和继承父类并覆盖父类中同一方法的几个不同子类实现的.
一、基本概念
多态性:发送消息给某个对象,让该对象自行决定响应何种行为。通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。
java 的这种机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。
1. 如果a是类A的一个引用,那么,a可以指向类A的一个实例,或者说指向类A的一个子类。
2. 如果a是接口A的一个引用,那么,a必须指向实现了接口A的一个类的实例。
二、Java多态性实现机制
SUN目前的JVM实现机制,类实例的引用就是指向一个句柄(handle)的指针,这个句柄是一对指针:
一个指针指向一张表格,实际上这个表格也有两个指针(一个指针指向一个包含了对象的方法表,另外一个指向类对象,表明该对象所属的类型);
另一个指针指向一块从java堆中为分配出来内存空间。
三、总结
1、通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。
- DerivedC c2=new DerivedC();
- BaseClass a1= c2; //BaseClass 基类,DerivedC是继承自BaseClass的子类
- a1.play(); //play()在BaseClass,DerivedC中均有定义,即子类覆写了该方法
分析:
1、为什么子类的类型的对象实例可以覆给超类引用?
自动实现向上转型。通过该语句,编译器自动将子类实例向上移动,成为通用类型BaseClass;
2、a.play()将执行子类还是父类定义的方法。
子类的。在运行时期,将根据 a 这个对象引用 实际的类型来获取对应的方法。所以才有多态性。一个基类的对象引用,被赋予不同的子类对象引用,执行该方法时,将表现出不同的行为。
在a1=c2的时候,仍然是存在两个句柄,a1和c2,但是a1和c2拥有同一块数据内存块和不同的函数表。
2、不能把父类对象引用 赋给 子类对象引用变量
- BaseClass a2=new BaseClass();
- DerivedC c1=a2;//出错
在java里面,向上转型是自动进行的,但是向下转型却不是,需要我们自己定义强制进行。
- c1=(DerivedC)a2; 进行强制转化,也就是向下转型.
3、记住一个很简单又很复杂的规则,一个类型引用只能引用 ,引用类型自身含有的方法和变量。
你可能说这个规则不对的,因为父类引用指向子类对象的时候,最后执行的是子类的方法的。
其实这并不矛盾,那是因为采用了后期绑定,动态运行的时候又根据型别去调用了子类的方法。而假若子类的这个方法在父类中并没有定义,则会出错。
例如,DerivedC类在继承BaseClass中定义的函数外,还增加了几个函数(例如 myFun())
分析:
当你使用父类引用指向子类的时候,其实jvm已经使用了编译器产生的类型信息调整转换了。
这里你可以这样理解,相当于 把不是父类中含有的函数从虚拟函数表中设置为不可见的。注意有可能虚拟函数表中有些函数地址由于在子类中已经被改写了,所以对象虚拟函数表中虚拟函数项目地址已经被设置为子类中完成的方法体的地址了。
4、Java与C++多态性的比较
jvm关于多态性支持解决方法是和c++中几乎一样的,只是c++中编译器很多是把类型信息和虚拟函数信息都放在一个虚拟函数表中,但是利用某种技术来区别。
Java把类型信息和函数信息分开放。Java中在继承以后,子类会重新设置自己的虚拟函数表,这个虚拟函数表中的项目有由两部分组成。从父类继承的虚拟函数和子类自己的虚拟函数。
虚拟函数调用是经过虚拟函数表间接调用的,所以才得以实现多态的。Java的所有函数,除了被声明为final的,都是用后期绑定。
四. 1个行为,不同的对象,他们具体体现出来的方式不一样,
比如: 方法重载 overloading 以及 方法重写(覆盖)override
- class Human{
- void run(){输出 人在跑}
- }
- class Man extends Human{
- void run(){输出 男人在跑}
- }
- 这个时候,同是跑,不同的对象,不一样(这个是方法覆盖的例子)
- class Test{
- void out(String str){输出 str}
- void out(int i){输出 i}
- }
这个例子是方法重载,方法名相同,参数表不同
ok,明白了这些还不够,还用人在跑举例
- Human ahuman=new Man();
这样我等于实例化了一个Man的对象,并声明了一个Human的引用,让它去指向Man这个对象
意思是说,把 Man这个对象当 Human看了.
比如去动物园,你看见了一个动物,不知道它是什么, "这是什么动物? " "这是大熊猫! "
这2句话,就是最好的证明,因为不知道它是大熊猫,但知道它的父类是动物,所以,这个大熊猫对象,你把它当成其父类 动物看,这样子合情合理.这种方式下要注意 new Man();的确实例化了Man对象,所以 ahuman.run()这个方法 输出的 是 "男人在跑 "如果在子类 Man下你 写了一些它独有的方法 比如 eat(),而Human没有这个方法,在调用eat方法时,一定要注意 强制类型转换 ((Man)ahuman).eat(),这样才可以...
对接口来说,情况是类似的...
实例:
- package domatic;
- //定义超类superA
- class superA {
- int i = 100;
- void fun(int j) {
- j = i;
- System.out.println("This is superA");
- }
- }
- // 定义superA的子类subB
- class subB extends superA {
- int m = 1;
- void fun(int aa) {
- System.out.println("This is subB");
- }
- }
- // 定义superA的子类subC
- class subC extends superA {
- int n = 1;
- void fun(int cc) {
- System.out.println("This is subC");
- }
- }
- class Test {
- public static void main(String[] args) {
- superA a = new superA();
- subB b = new subB();
- subC c = new subC();
- a = b;
- a.fun(100);
- a = c;
- a.fun(200);
- }
- }
- /*
- * 上述代码中subB和subC是超类superA的子类,我们在类Test中声明了3个引用变量a, b,
- * c,通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。也许有人会问:
- * "为什么(1)和(2)不输出:This is superA"。
- * java的这种机制遵循一个原则:当超类对象引用变量引用子类对象时,
- * 被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,
- * 但是这个被调用的方法必须是在超类中定义过的,
- * 也就是说被子类覆盖的方法。
- * 所以,不要被上例中(1)和(2)所迷惑,虽然写成a.fun(),但是由于(1)中的a被b赋值,
- * 指向了子类subB的一个实例,因而(1)所调用的fun()实际上是子类subB的成员方法fun(),
- * 它覆盖了超类superA的成员方法fun();同样(2)调用的是子类subC的成员方法fun()。
- * 另外,如果子类继承的超类是一个抽象类,虽然抽象类不能通过new操作符实例化,
- * 但是可以创建抽象类的对象引用指向子类对象,以实现运行时多态性。具体的实现方法同上例。
- * 不过,抽象类的子类必须覆盖实现超类中的所有的抽象方法,
- * 否则子类必须被abstract修饰符修饰,当然也就不能被实例化了
- */
以上大多数是以子类覆盖父类的方法实现多态.
下面是另一种实现多态的方法-----------重写父类方法。
------------------------------------------------------------------------------------------------------------------------------------------------------------1.JAVA里没有多继承,一个类之能有一个父类。而继承的表现就是多态。一个父类可以有多个子类,而在子类里可以重写父类的方法(例如方法print()),这样每个子类里重写的代码不一样,自然表现形式就不一样。这样用父类的变量去引用不同的子类,在调用这个相同的方法print()的时候得到的结果和表现形式就不一样了,这就是多态,相同的消息(也就是调用相同的方法)会有不同的结果。举例说明:
- //父类
- public class Father{
- //父类有一个打孩子方法
- public void hitChild(){
- }
- }
- //子类1
- public class Son1 extends Father{
- //重写父类打孩子方法
- public void hitChild(){
- System.out.println("为什么打我?我做错什么了!");
- }
- }
- //子类2
- public class Son2 extends Father{
- //重写父类打孩子方法
- public void hitChild(){
- System.out.println("我知道错了,别打了!");
- }
- }
- //子类3
- public class Son3 extends Father{
- //重写父类打孩子方法
- public void hitChild(){
- System.out.println("我跑,你打不着!");
- }
- }
- //测试类
- public class Test{
- public static void main(String args[]){
- Father father;
- father = new Son1();
- father.hitChild();
- father = new Son2();
- father.hitChild();
- father = new Son3();
- father.hitChild();
- }
- }
- class AnimalClass//声明一个动物类
- {
- public void printSuper()
- {
- System.out.println("printSuper:——>AnimalClass");
- }
- public void print()
- {
- System.out.println("print:——>AnimalClass");
- }
- }
- class Dog extends AnimalClass//声明一个狗类,并继承自动物类 AnimalClass
- {
- public void printSub()
- {
- System.out.println("printSub:——>Dog ");
- }
- public void print()
- {
- System.out.println("print:——>Dog");
- }
- }
- public class TestAnimals
- {
- public static void main(String[] args)
- {
- AnimalClass animal = new Dog();//声明一个子类Dog对象,并让其基类(animal)引用变量指向它'new Dog()'。
- //animal.printSub();//编译器报错,这说明基类的引用不能访问子类的新增成员(对象和方法)
- /*
- D:\java\object>javac testanimals.java
- testanimals.java:25: 找不到符号
- 符号: 方法 printSub()
- 位置: 类 AnimalClass
- animal.printSub();
- ^
- 1 错误
- */
- //为了解决上面的问题,只能进行对象的强制转型
- Dog animalThree = (Dog)animal;
- animalThree.printSub();
- AnimalClass animalTwo = new Dog();
- animalTwo.print();/*为什么这个会打印出 print:——>Dog 这个结果,而不是 print:——>AnimalClass 呢?这个就是动态绑定机制。*/
- }
- }
- class Son extends Father {
- public void method1(){
- System.out.println("子类中的method1方法");
- }
- public void method2(){
- System.out.println("子类中的method2方法");
- }
- }
- //父类:
- class Father {
- public Father(){
- System.out.println("Father中的this " + this);
- }
- public void init(){
- //System.out.println("init方法");
- this.method1();
- this.method2();
- }
- public void method1(){
- System.out.println("父类中的method1方法");
- }
- private void method2(){//注意此句中的private
- System.out.println("父类中的method2方法");
- }
- }
- //测试类:
- public class ObjectClass {
- public static void main(String[] args) {
- Father fa = new Son();
- fa.init();
- //fa.method2();编译器报错,这说明fa引用虽然指向new Son()对象,但看到的只是继承过来的基类对象new Father()
- /*
- objectclass.java:54: method2() 可以在 Father 中访问 private
- fa.method2();
- ^
- 1 错误
- */
- /*不过我一直不明白,既然 看到的只是继承过来的基类对象new Father(),为何上面调用fa.init()方法是能对父类的method1()方法进行重写?希望高手拍砖释惑,谢谢~~!
- */
- /*今天总算把上面这个问题弄清楚了。之所以会出现上面的问题,是因为“动态绑定(值绑定)”机制在“做窜”(更主要是自己学艺不精)。
- */
- System.out.println("----------------------优美的分隔符-----------------------");
- Son s = (Son)fa;//强制转型
- s.init();
- s.method2();//经强制转型后,编译通过
- System.out.println("----------------------优美的分隔符-----------------------");
- Son s2 = new Son();
- s.init();
- s.method2();
- }
- }
- class Animal{
- public String name;
- Animal(String name){
- this.name = name;
- }
- }
- class Cat extends Animal{
- public String eyesColor;
- Cat(String n,String c){
- super(n);// 必须写在第一行
- eyesColor = c;
- }
- }
- class Dog extends Animal {
- public String furColor;
- Dog(String n, String c) {
- super(n);
- furColor = c;
- }
- }
- public class TestObjectChanage {
- public static void main(String[] args){
- Animal a = new Animal("name");
- Cat c = new Cat("catname","blue");
- Dog d = new Dog("dogname","furColor");
- System.out.println(a instanceof Animal);//true
- System.out.println(c instanceof Animal);//true
- System.out.println(d instanceof Animal);//true
- System.out.println(a instanceof Cat);//false
- a = new Dog("bigyellow","yellow");
- System.out.println(a.name); //bigyellow
- //System.out.println(a.furColor);// !error
- System.out.println(a instanceof Animal);//true
- System.out.println(a instanceof Dog);//true
- Dog d1 = (Dog)a;
- System.out.println(d1.furColor);// yellow
- }
- }
2).为什么super(…)或this(…)调用语句只能作为构造函数中的第一句出现?
1.分配成员变量的存储空间并进行默认的初始化,即new关键字产生对象后,对类中的成员变量对象中的成员变量进行初始化赋值。
2.绑定构造方法。
3.如有this()调用,则调用相应的重载构造方法(被调用的重载构造方法又从步骤2开始执行这些流程),被调用的重载构造方法的执行流程结束后,回到当前构造方法,当前构造方法直接跳转到步骤6执行。
4.显式或隐式追溯调用父类的构造方法(一直到Object类为止,Object是所有Java类的最顶层父类),父类的构造方法又从步骤2开始对父类执行这些流程,父类的构造方法的执行流程结束后,回到当前构造方法,当前构造方法继续往下执行。
5.进行实例变量的显式初始化操作,也就是执行在定义成员变量时就对其进行赋值的语句。
6.执行当前构造方法的方法体中的程序代码
- public class Extends {
- public static void main(String[] args) {
- Son s1=new Son(20,"张三","民大");
- System.out.println(s1.name);
- }
- }
- class Father{
- int age;
- String name;
- public Father(){
- }
- public Father(int age,String name){
- System.out.println("Step1");
- this.age=age;
- this.name=name;
- System.out.println("Step2");
- }
- }
- class Son extends Father{
- String school;
- public Son(){
- //super(); //如子类构造函数中没有显式的调用父类的构造函数,
- //则编译器默认调用父类无参数的构造函数
- }
- public Son(int age,String name){
- super(age,name);
- System.out.println("Step3");
- }
- public Son(int age,String name,String school){
- this(age,name); //this,super语句必须在构造函数的首部
- System.out.println("Step4");
- this.school=school;
- }
- }
- 输出结果为
- Step1
- Step2
- Step3
- Step4
- 张三
- 需要注意的是
- this和super不能再同一个构造函数中出现,且this和super只能出现在构造函数的第一行。