多态

来源:互联网 发布:excel数据分析方法五种 编辑:程序博客网 时间:2024/06/05 09:37

概念:

java引用变量有两种类型:一种是编译时类型,一种是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态(Polymorphism)。

编译期和运行期:

编译期:javac编译java代码的时候,编译成class字节码文件,它会检查语法错误。


father变量的类型由声明该变量时使用的类型Father类型决定。

运行时:java执行class字节码文件,变量的类型由指向的实际对象决定,同时它会遇到运行时期的各种错误。


father变量虽然声明的时候是Father类型,但是运行时,father变量指向的Child对象,那么它的类型就是Child类型。


但是子类不能指向父类,它会在编译期就报错:


class DuoTai{         //编译时错误:检查语法错误         //inti = new String();         //运行时错误:检查运行时错误         staticString str ;         publicstatic void m(){                   Stringstr2 = str.substring(0,2);         }         publicstatic void main(String[] args){                   m();         }}


多态性:

//TODO 编写普通创建案例和多态案例代码。查看多态案例执行重写的方法是父类的还是子类的

class DuoTai{    /*        father变量指向子类对象。        在编译时只检查father是Father类型变量        在运行时,father代表的Child类型变量    */    publicstatic void main(String[] args){        //多态案例        Fatherfather = new Child();        father.method();        //普通案例        Father= new Father();        father.method();    }}class Father{    publicvoid method(){        System.out.println("父类对象的方法");    }}class Child extends Father{    publicvoid method(){        System.out.println("子类对象的方法");    } }


 

在上面的案例中,我们创建了Child对象,但是使用它的父类Father类类型的变量来指向该Child对象。在子类对象中重写了父类的同名方法。当执行该Child对象的method方法时,执行的代码是子类对象的代码。

虽然看似定义时候,使用到的变量是Father类型,但是这仅仅是一个变量的引用,他真实指向内存中的对象是该类型的子类对象Child类型的对象!所以使用该变量去调用重写的method方法,实际上调用的还是Child对象的method方法。

在我们日常生活中,经常遇到这种情况,如:父亲可以代表孩子办理户口、证件,或者可以代替办理入学手续,但是实际上操作的对象还是孩子。这个时候,父亲就可以代表孩子。

小明和大明是兄弟,他爹给大明办完入学手续后又去给小明办理入学手续,那么他爹在不同的时候,执行相同的入学手续,但是实际操作的对象分别是小明和大明。

什么是多态:在不同的时刻,同一个变量,调用同一个方法,呈现的多种不同的行为特征,这就是多态。

//TODO代码 他爹给大明小明办理入学手续,不同时候打印不同语句

class RuXue{    publicstatic void main(String[] args){        Fatherfather ;        //给小明办理入学手续        father= new XiaoMing();        father.ruxue();        //给大明办理入学手续        father= new DaMing();        father.ruxue();    }}class Father{        Stringname = "父亲";        publicvoid ruxue(){            System.out.println(name+ "办理了入学手续!");        }}class XiaoMing extends Father{        Stringname = "小明";        publicvoid ruxue(){            System.out.println(name+ "办理了入学手续!");        }}class DaMing extends Father{        Stringname = "大明";        publicvoid ruxue(){            System.out.println(name+ "办理了入学手续!");        }}


这个时候,我们发现,父类类型的变量,指向了子类对象,这个时候父类可以代表子类,但是,当父类类型的变量去执行方法的时候,实际操作的对象的方法还是子类的!

一句话说,父类变量可以指向子类对象,并且代表子类对象!

//TODO创建多个子父类,然后验证父类变量指向子类对象

class RuXue{    publicstatic void main(String[] args){        Fatherfather ;        //给小明办理入学手续        father= new XiaoMing();        System.out.println(father.getClass().getName());         //给大明办理入学手续        father= new DaMing();        System.out.println(father.getClass().getName());     }}

class Father{        Stringname = "父亲";        publicvoid ruxue(){            System.out.println(name+ "办理了入学手续!");        }}



class XiaoMing extends Father{        Stringname = "小明";        publicvoid ruxue(){            System.out.println(name+ "办理了入学手续!");        }}


class DaMing extends Father{        Stringname = "大明";        publicvoid ruxue(){            System.out.println(name+ "办理了入学手续!");        }}


//TODO体验多态的实际用途,在不同时刻执行不同功能

import java.util.Scanner;class Shopping{    publicstatic void main(String[] args){        System.out.println("请选择您的支付方式:1、现金;2、支付宝;3微信");        Scannersc = new Scanner(System.in);        //从键盘获取一个选择的值int类型        intchoose = sc.nextInt();        Paypay = new Pay();        switch(choose)        {        case1:            pay= new Money();            break;        case2:            pay= new ZhiFuBao();            break;        case3:            pay= new WeiXin();            break;        }        pay.paying();    } }

import java.util.Scanner;class Shopping{    publicstatic void main(String[] args){        System.out.println("请选择您的支付方式:1、现金;2、支付宝;3微信");        Scannersc = new Scanner(System.in);        //从键盘获取一个选择的值int类型        intchoose = sc.nextInt();        Paypay = new Pay();        switch(choose)        {        case1:            pay= new Money();            break;        case2:            pay= new ZhiFuBao();            break;        case3:            pay= new WeiXin();            break;        }        pay.paying();    } }//支付方法class Pay{    publicvoid paying(){    }}//现金支付class Money extends Pay{    publicvoid paying(){        System.out.println("现金支付");    }}//支付宝支付class ZhiFuBao extends Pay{        publicvoid paying(){        System.out.println("支付宝支付");        }}//微信支付class WeiXin extends Pay{        publicvoid paying(){        System.out.println("微信支付");        }}


我们知道,子类继承父类后,除了具备父类的一些属性后,还可以自己进行扩展自有的业务。这个时候子类可以访问父类成员,但是父类成员绝对无法访问子类自有成员,那么当父类引用指向子类对象时候,只能调用在父类中定义过的方法,但无法调用子类额外拓展的方法!

该错误会在编译期产生。父类引用调用子类的方法,在运行时期,执行的是子类方法的代码。

理解:父类引用只能调用在父类中定义过的方法

编译期,检查语法错误,同时还检查多态性。并不检查运行期对象的方法。也就是说,编译期只管引用调用的编译期类型的属性或方法存在不存在,不管实际运行时,对象是否具备该方法。

 

class DuoTai{    publicstatic void main(String[] args){        //多态案例        Fatherfather = new Child();        //父类引用调用子类对象重写的方法不报错        father.method();        //父类引用调用子类自有的方法报错        father.method2();    }}class Father{    publicvoid method(){        System.out.println("父类对象的方法");    }}class Child extends Father{    Stringname;    String id;    //重写父类    publicvoid method(){        System.out.println("子类对象的方法");    }    //自有方法    publicvoid method2(){        System.out.println("子类对象的方法2");    }}


运行期,执行运行时对象的方法业务。编译通过后,在运行期,变量的类型是由对象的类型决定,那么运行时通过变量调用的方法是对象的方法。在多态情况下,虽然使用father类型变量调用子类的方法,但是实际上他指向的是子类,那么执行的方法也就是子类的方法。

多态情况下的属性调用:

父类中定义了和子类同名的属性,当使用父类引用时,去访问同名属性,那么访问的是父类的属性。

class DuoTai{    publicstatic void main(String[] args){        //多态案例        Fatherfather = new Child();        father.method();        //打印结果是:父类        System.out.println(father.name);       }}class Father{    Stringname = "父类";    String id;    publicvoid method(){        System.out.println("父类对象的方法");    }}class Child extends Father{    Stringname = "子类";    String id;    publicvoid method(){        System.out.println("子类对象的方法");    }    publicvoid method2(){        System.out.println("子类对象的方法2");    }}


注意:子父类中,方法一致叫重写,属性不叫重写。

解析:

         Java中多态的静态绑定和动态绑定。

         静态绑定:变量属于静态绑定。

         即便子父类定义了同名属性,当使用父类引用去访问该属性,实际访问的父类的属性。

         动态绑定:方法属于动态绑定。

         方法的动态绑定,当执行方法时候,就会执行引用所指向的实际内存中的对象方法(子类方法)

子类重写父类要遵循“两同两小一大”

“两同”即方法名相同,形参列表相同,实现重写的必要条件。

“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等

如下案例:

Zi和Fu是继承关系;Father和Child继承关系,当在father类中定义方法method()要求返回Fu类类型对象,子类重写 该方法后,可以返回Fu类类型对象或者子类型对象(在下面案例中,子类方法可以返回Fu或者Zi,因为Zi是继承Fu的)

 

 

但是:如果父类定义的返回值要求是Zi,结果子类重写该方法返回的是Fu,那就会报错!因为Zi继承Fu,Fu可以代表Zi,但是Zi无法代表Fu,也就是说方法的返回值也具有多态特性,其实方法传参的参数也可以具备多态特性!


class DuoTai{    public static void main(String[] args){        //多态案例        Father father = new Child();        father.method();        //System.out.println();    }}class Father{    String name = "父类";    String id;      Fu  method(){        System.out.println("父类对象的方法");        return null;    }}class Child extends Father{    String name = "子类";    String id;    public  Zi method(){        System.out.println("子类对象的方法");        Fu fu = new Fu();        Zi zi = new Zi();        return zi ;    }    public void method2(){        System.out.println("子类对象的方法2");    }} class Fu{}class Zi extends Fu{} 传参也可以多态:class DuoTai{    public static void main(String[] args){        //打印结果:Fu        method(new Fu());        //打印结果:Zi,就说明了传参也具备多态性        method(new Zi());    }    public static void method(Fu fu){        System.out.println(fu.getClass().getName());    }} class Fu{}class Zi extends Fu{} 


返回值的多态性:

“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等,如下案例中,我们子类重写父类方法,子类的访问权限是小于父类的,所以会报错!反之则不会!



//子类重写父类方法的权限比父类小,报错class Father{    public void  method(){        System.out.println("父类对象的方法");    }}class Child extends Father{      void method(){        System.out.println("子类对象的方法");    }}//子类重写父类方法的权限比父类大,正确class Father{    void method(){        System.out.println("父类对象的方法");    }}class Child extends Father{        publicvoid method(){        System.out.println("子类对象的方法");    }}   


 

 

那当父类引用指向子类对象时候,不能调用父类没有的方法,这个时候我们该怎么去调用子类对象的自有方法?

强制转换与自动转换:

 

   classDuoTai    {        public static void main(String[] args){            Father father  = new Father();            Childchild = new Child();            //自动转换,向上转换            father= child;            //编译看左边,发现father没有method2方法,报错            //father.method2();            //如果非要执行child2中的方法,那么强制转换            //强制转换,向下转换            Childchild2 = (Child)father;            //编译看左边,child类型对象有method2方法,Ok            child2.method2();        }  }    class Father    {        public   void method(){            System.out.println("父类对象的方法");        }    }    classChild extends Father    {         public  void method(){            System.out.println("子类对象的方法");        }        public   void method2(){            System.out.println("子类对象的方法2");        }    } 

注意:

当把父类类型引用,强制转换成子类类型引用,那么该引用指向的实际位置必须是子类对象。否则报错!

   

 class DuoTai    {        public static void main(String[] args){            Father father  = new Father();            /*父类类型强制转换成子类类型,编译不报错            但是,运行时            报错:类型转换错误ClassCastException*/            Child child = (Child)father;            child.method();        }    }    class Father    {        public   void method(){            System.out.println("父类对象的方法");        }    }    class Child extends Father    {         public  void method(){            System.out.println("子类对象的方法");        }        public   void method2(){            System.out.println("子类对象的方法2");        }    }

 

 

Instanceof运算符:

判断前一个操作数通常是一个引用类型变量指向的对象,是否是后一个操作数通常是类、接口的本身或它的子类的实例。如果是返回true,否则返回false。

为了维护程序的健壮性,通常我们可以使用instanceof关键字来判断类型关系。

class TestInstanceof{    publicstatic void main(String[] args){        Zi zi= new Zi();        //zi变量是Fu类型的对象的子类对象类型        booleanboo = zi instanceof Fu;        System.out.println(boo);        Fu fu= new Fu();               //fu变量是Fu类型的对象类型        boo =fu instanceof Fu;        System.out.println(boo);        boo =fu instanceof Zi;        System.out.println(boo);    }}class Fu{}class Zi extends Fu{}


在实际开发中常用的操作:

当程序接收一个对象时,可能这个对象是来自外界的,我并不知道该对象是什么类型,那么如果我随意把该对象直接拿来使用,可能就会出现错误,为了保证程序的安全,我需要在使用该对象是,使用instanceof运算符验证该对象是否是我需要的对象。

 


静态方法和成员变量在多态中的特性:

通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法,关于static方法,声明为static的方法有以下几条限制:

它们仅能调用其他的static 方法。

它们只能访问static数据。

它们不能以任何方式引用this 或super。

无论是static修饰的变量,还是static修饰的方法,我们都知道他们是属于类本身的,不是属于某一个对象的,当声明一个对象时,并不产生static变量和方法的拷贝。也就是说,用static修饰的变量和方法在类加载的时候,将该信息存放到静态区。

class DuoTai{    public static void main(String[] args){        /*            子类对象继承父类对象,            但是不会继承父类类型的静态成员        */        Father father1 = new Child();        Father father2 = new Child();        System.out.println(father1.name);        father1.name= "刘禅";        //这里name是该类的静态成员,直接属于类,被所有对象共享        System.out.println(father2.name);        System.out.println(father1.name);        father1.laopo= "孙尚香";        father2.laopo= "小乔";        //非静态成员属于实例变量的,创建一个实例对象,就会生成一个laopo属性        System.out.println(father1.laopo);        System.out.println(father2.laopo);    }}  class Father{    staticString name = "刘备";    Stringlaopo;    public void  method(){        System.out.println("父类对象的方法");    }}class Child extends Father{     public void method(){        System.out.println("子类对象的方法");    }}


:这里要说明的时,当子类没有与之同名的static变量(或方法时),子类的对象也可以操控这块内存空间。


但是子类对象并没有继承父类中static修饰的变量和方法。因为static修饰的变量和方法是属于父类本身的,父类静态成员都不属于父类对象,更不可能属于子类对象,但是他们都可以访问该属性。

但是,当存在继承关系时,父类中有一个static修饰的变量(或方法),而子类中也存在一个同名的static修饰的变量(或方法)时,他们到底是否满足“重写”,而最终体现出多态的效果呢??

下面来看一个例子。 
父类中有一个static修饰的方法和一个普通的方法,子类中也有一个同名的static方法和一个普通的方法。如下

class DuoTai{    publicstatic void main(String[] args){        /*当使用father调用method时候,发现执行的是父类的静态方法,并没有存在多态特性        */        Fatherfather = new Child();        father.method();    }}


 

 

class Father{    public static void  method(){        System.out.println("父类对象的方法");    }}

class Child extends Father{     public static void method(){        System.out.println("子类对象的方法");    }}


 

 

从结果可以看出:对于静态方法在子类中是不存在“重写”这一说的,就像前面我们提到的,用static关键字修饰的方法和变量都是属于类自己本身的,即使存在继承关系,子类并没有继承父类的static修饰的变量和方法,所以说即使子类和父类中都有同样的static方法和变量,他们是没有任何关系的,他们是相互独立的,他们是属于各自类本身的。因此也是不存在多态特性的。而对于普通方法的调用是存在“重写”而最终呈现出多态特性的,因为普通成员是属于对象。

同样的道理:对于static修饰的变量,当子类与父类中存在相同的static变量时, 实际访问的是属于各自类的静态数据。

多态是对象与对象之间的关系,与类、静态成员无关。

而在父类和子类中对于非static方法,是根据“动态引用”来调用相应的方法。

然而,接着会有这样的问题存在:Java子类不会继承父类的static变量和static方法。

1)先说static方法:子类会不会继承父类的static方法呢??答案是:不会的,但是是可以访问的。

2)接着来看父类的static修饰的变量,是否能够被子类继承呢?? 
答案:也是不可以的。但是也是可以被子类访问的。

小结

1)子类是不继承父类的static变量和方法的。因为这是属于类本身的。但是子类是可以访问的。 
2)子类和父类中同名的static变量和方法都是相互独立的,并不存在任何的重写的关系。

多态:一个父类引用在不同时刻指向不同的子类对象,多态是针对对象而言,而static成员属于类,不属于对象,那么就不存在多态特性!!

 

 

总结:

成员变量 :编译看左边(父类),运行看左边(父类).

 

   class DuoTai    {        public static void main(String[] args){            //编译看左边,运行看左边            Father father  = new Child();                       //打印结果父类name            System.out.println(father.name);        }    }    class Father    {        String name = "父类name";    }    classChild extends Father    {        String name = "子类name";    }

 

成员方法 :编译看左边(父类),运行看右边(子类)

  

  class DuoTai    {        public static void main(String[] args){            Fatherfather  = new Child();            //编译看左边,发现Father类没有method2方法,报错            //father.method2();            //运行看右边,执行的是Child对象的method方法            father.method();        }    }    class Father    {        public   void method(){            System.out.println("父类对象的方法");        }    }    class Child extends Father    {         public  void method(){            System.out.println("子类对象的方法");        }        public   void method2(){            System.out.println("子类对象的方法");        }    }

静态方法:编译看左边(父类),运行看左边(父类)

 

    class DuoTai    {        public static void main(String[] args){            Fatherfather  = new Child();            //编译看左边,发现Father有method方法            //运行看左边,打印结果父类对象的方法            father.method();        }    }    class Father    {        public static  void method(){            System.out.println("父类对象的方法");        }    }    class Child extends Father    {         public static void method(){            System.out.println("子类对象的方法");        }    } 


继承是实现类复用的重要手段,但是继承带来最大的一个坏处:破坏封装性。相比之下,组合也是实现类复用的重要方式,而采用组合方式来实现类复用则能提供更好的封装性。下面将详细的介绍继承和组合之间的联系与区别。

1、使用继承的注意点:

子类扩展父类时,子类可以从父类继承到成员变量和方法,如果访问权限可以,子类就可以直接访问父类成员和方法,相当于子类可以直接复用父类的成员,确实非常方便。

但是随之带来一个严重的问题,继承严重的破坏了父类的封装性。前面介绍封装时提到:每个类都应该封装内部信息和实现细节,而只是暴露必要的方法供其他类使用。但在继承关系中,子类可以直接访问父类的成员变量和方法,从而造成了子父类的严重耦合(依赖)。

从这个角度上看,父类的实现细节对子类不再透明,子类可以访问父类的成员变量和方法,并且可重写父类定义的方法,从而导致子类可以随意篡改父类的方法。

为了保证父类良好的封装性,不会被子类随意改写,设计父类通常尊徐如下规则:

尽量隐藏父类内部数据,尽量把父类所有的成员变量都设置为private访问类型,不要让子类直接访问父类的成员变量。

不要让子类可以随意访问、修改父类的方法。父类中仅仅作为辅助其他方法的方法,应该使用private访问控制符修饰,让子类无法访问该方法,但又不想让子类重写该方法,那么可以使用final修饰符修饰该方法;如果希望父类某个方法被重写,则用protected修饰。

尽量不要在父类构造器中调用被子类重写的方法。

·


final修饰的类不可以被继承,final修饰的方法不可以被重写,final修饰属性不可以被改变(即常量)

 

组合:

使用private修饰的构造器,从而保证子类无法调用该类的构造器,也就无法继承该类。当然,可以提供公开的方法让外界获取该类型实例。

上面说明了继承关系可能存在一些问题,以及如何处理这些问题。如果只是出于类复用的目的,就没必要使用继承了,完全可以使用组合来实现。

 

利用组合实现代码复用

如果需要重复使用一个类,除了继承之外,还可以把该类当成另一个类的组合成分,从而允许新的类直接复用该类的public 方法。不管是继承还是组合,都允许在新的类中直接复用旧类的方法。

如下代码使用人继承胳膊类,虽然达到了代码的复用,但是严重的违反了事实!

class  ZuHe{    publicstatic void main(String[] args)    {        Personp = new Person();        p.play();    }}class Person extends Arm{   }class Arm{    publicvoid play(){        System.out.println("欢迎来到王者农药");    }}


 

我们可以更改一下方式,将胳膊作为一个组件,组合到人的身体上。

 

class  ZuHe{    publicstatic void main(String[] args)    {        Personp = new Person(new Arm());        p.play();    }}class Person{    privateArm arm;    publicPerson(Arm arm){        this.arm= arm;    }    publicvoid play(){        arm.play();        System.out.println("全军出击!");    }}class Arm{    publicvoid play(){        System.out.println("欢迎来到王者农药");    }}

 

对于继承而言,子类可以直接获取父类的public方法,程序使用子类时,将可以直接访问该子类从父类哪里继承到的方法;而组合则是将旧类对象作为新类对象的成员变量组合进来。用以实现新类的功能,用户看到的是新类的方法,而不能看到被组合对象的方法。因此,通常需要在新类中使用private修饰被组合的旧类对象。

什么时候使用组合,什么时候使用继承?

组合:当描述的两个对象,是组合关系,那就使用组合。

继承:描述两个对象的时候,这两个对象逻辑上具有子父类关系,那就可以使用继承描述这两个对象。

 

0 0
原创粉丝点击