Java学习:第八章多态

多态:

    向上转型

★    多态和动态绑定

    多态和构造器

枚举:

特殊的类 

 [访问权限] enum 枚举类型名

 {枚举常量表列}

 深入理解Java枚举类型(enum)-CSDN博客

enum Note      //  枚举类型
{
    CarmeloeAnthony, KobeBryant, KawhiLeonard
} //;可以没有


public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world!");
        Note n1 = Note.CarmeloeAnthony;//CarmeloeAnthony
        System.out.println(n1);  //
        System.out.println(n1.ordinal()); //枚举常量序号0
    }
}

 // case 后面,常量标示名前不能有枚举类型名

//        public void describe(Note degree) {
//            switch(degree) {
//                case CarmeloeAnthony:    println("not spicy at all.");
//                    break;
//                case KobeBryant :
//                case KawhiLeonard: println("a little hot.");
//                default:     println("maybe too hot.");
//            }
//        }

  归纳与分析

⑴  使用多态的三个条件:继承、覆盖与向上转型

⑵  当调用父类里面被覆盖方法的时候, 实际是哪个对象, 就调用这个对象(子类对象)的与之相关的覆盖方法

向上转型:

enum number    //  枚举类型
{CarmeloeAnthony, KobeBryant, KawhiLeonard}

//Wind.java
class player {
    public void play(
            number n) {
        System.out.println("MVP.play()" + n);
    }
}

class Win extends player {
    public void play(
            Note n) {
        System.out.println("Win.play()" + n);
    }
}


public class exp1 {
    public static void tune(player i) {
        i.play(number.CarmeloeAnthony);
    }

    public static void main(String[] args) {
        Win flute = new Win();  //
        tune(flute);    //  向上转型

        flute.play(number.KobeBryant); 
    }

}

 Win类“向上转换成”player类

★  归纳:多态的优点(与前面缺点形成对比)

⑴  减少工作量

⑵  少做少错

◆  总结:多态是将改变的事物与未改变的事物分离开来的重要技术(保证变化所造成的影响,局限在可以控制的范围)

 绑定:

     将一个方法调用和一个方法主体关联起来的过程称为绑定

 绑定(Banding)的分类:

⑴     静态绑定(前期绑定,编译时绑定)

★     在编译的时候,由编译器和连接器绑定

⑵     动态绑定(后期绑定、运行时绑定)

★     在运行的时候,根据对象类型进行绑定

除了staticfinalprivate方法,其他方法都是动态绑定,可以实现多态调用。

class PrivateOverride {
    private void f() {
        System.out.println("super f( )");
    }

    public static void main(String[] args) {
        PrivateOverride po = new Derived();
        po.f();   // super f()私有方法:不存在与对象的动态绑定
    }
}

class Derived extends PrivateOverride {
    private void f() {
        System.out.println("sub f( )");
    }

    public static void main(String[] args) {
        PrivateOverride po = new Derived();;
        //po.f();   //父类的私有函数不能调用
    }
}

静态方法:不存在与对象的绑定

class StaticSuper {
    public static String staticGet() {
        return "Base staticGet()";
    }

    public String dynamicGet() {
        return "Base dynamicGet()";
    }
}

class StaticSub extends StaticSuper {
    public static String staticGet() {
        return "Derived staticGet()";
    }

    public String dynamicGet() {
        return "Derived dynamicGet()";
    }
}

//★     静态方法:不存在与对象的绑定

public class StaticPolymorphism {
    public static void main(String[] args) {
        StaticSuper sup = new StaticSub();
        System.out.println(sup.staticGet());
        System.out.println(sup.dynamicGet());
    }

}
class  P
{  public  void  a(){  System.out.println("P.a()");  }
    public  void  b() { System.out.println("P.b()");  }
}
class  U  extends  P
{// 实现对父类方法的覆盖
    public  void  b() { System.out.println("U.b()");  }
    public  void  c() { System.out.println("U.c()");  }
}
class  A
{  public  static  void  main(String[]  args)
    {  // 向上转型
        P  pr = new  U();    pr.a();  // 调用的是P.a()
        pr.b();   //调用的是U.b()   pr.c();// 编译错误
    }
}


public class exp3 {
    public static void main(String[] args){
        A a = new A();
        a.main(null);
    }
}

★  向上转型的归纳:父类  实例引用 = new  子类();

  向上转型时,实例引用可以调用父类中所有的方法(包括未在子类中覆盖的方法)

  实例可以调用子类中覆盖父类的方法(即多态)

  但是不可以调用子类中特有的方法(即父类中没有的方法

4、   注意:只有普通的方法调用可以是动态绑定的,域不存在动态绑定

    覆盖函数的访问权限与父类有关,只能放松

构造器和多态 

1、   构造器的调用顺序(复习)

 构造器的调用原则:调用子类构造器之前,先触发基类构造器调用

⑴  首先:只有基类的构造器,才具有恰当的知识和权限来对自己的元素进行初始化

⑵  过程:new操作将首先导致分配存储空间, 但在调用构造器初始化的时候,它发现自己没有能力对来自基类的成员进行初始化,于是只好上溯

反复递归(属于两上两下的第二次上下):

⑴  不断上溯,一直到这个层次的根

  不断触发基类构造器的调用

⑵  调转方向不断下溯

  首先创建(初始化)基类对象,然后以此为基础创建(初始化)下一层的子类对象,如此操作一直到最底层

class Bread     // 面包
{
    Bread() {
        System.out.println("Bread()");
    }
}

class Meal     // 一餐
{
    Meal() {
        System.out.println("Meal()");
    }
}

class Lunch extends Meal    // 午餐
{
    Lunch() {
        System.out.println("Lunch()");
    }
}

class Portablelunch extends Lunch    // 便携式
{
    Portablelunch() {
        System.out.println("Portablelunch()");
    }
}

class Sandwich extends Portablelunch   //三明治
{
    private Bread b = new Bread();

    Sandwich() {
        System.out.println("Portablelunch()");
    }

    public static void main(String[] args) {
        new Sandwich();
    }
}


public class exp4 {
    public static void main(String[] args){
        Sandwich fun = new Sandwich();
        fun.main(null);
    }
}

构造器内部的多态方法的行为 :

⑴     首先,我们知道:动态绑定的调用是在运行时才决定的

     因为编译器无法知道:方法所要绑定的对象是基类对象还是派生类对象

⑵     如果将这种思路推广到:在基类构造器内部调用一个动态绑定方法,那么会发生什么情况?

     结果难以预料,因为被覆盖的方法在子类对象被完全构造之前就会被调用。

class Glyph   //  雕像,象形文字   P163
{
    void draw() {
        System.out.println("Glyph.draw()");
    }

    Glyph() {
        System.out.println("Glyph()  before  draw()");
        draw();
        System.out.println("Glyph()  after  draw()");
    }
}

class RoundGlyph extends Glyph {
    private int radius = 1;

    RoundGlyph(int r) {
        radius = r;
        System.out.println("RoundGlyph.radius=" + radius);
    }

    void draw()   // 覆盖虚函数
    {
        System.out.println("RoundGlyph.draw(), radius=" + radius);
    }
}


public class PolyConstructors {
    public static void main(String[] args) {
        new RoundGlyph(5);
    }
}

  分析其执行过程:

⑴  当类的加载和静态初始化完毕(第一次上下完毕)

  主类的这条语句将被首先执行:new  RoundGlyph5

◆  根据构造器的调用原则,编译器将不断上溯直至该类的根,然后开始调用其基类构造器

⑵  在调用基类构造器Glyph()过程中,将会发生draw()方法的调用

      

◆  根据动态绑定的理论,我们知道,这时候将会调用派生类的draw()方法

  因为:new的是RoundGlyph而不是Glyph

⑶  这时我们将看到一个悲剧:

      

◆  派生类的draw()方法所涉及的数据成员radius还来不及初始化

  掉进了一个两难境地的陷阱(派生类初始化依赖于基类,而基类却又依赖于派生类的初始化)

  归纳:完整的两上两下过程

  在第二次上溯的过程中,当new操作分配了存储空间以后,接下来要完成的是自动初始化:

     将分配给对象的存储空间初始化为二进制的零

  在第二次下溯的过程中,才完成指定初始化和构造器初始化

  注意:初始化的第一基本原则仍然满足

 协变返回类型:

     只要子类方法与超类方法具有相同的方法签名,或者子类方法的返回类型是超类方法的子类型,就可以覆盖

class Grain          // 谷类  P164
{
    public String toString() {
        return "Grain";
    }
}

class Wheat extends Grain          // 小麦
{
    public String toString() {
        return "Wheat";
    }
}

class Mill             // 磨坊
{
    Grain process() {
        return new Grain();
    }
}

class WheatMill extends Mill      // 小麦磨坊
{
    Wheat process()  //  覆盖
    {
        return new Wheat();
    }
}

class CovariantReturn {
    public static void main(String[] args) {
        Mill m = new Mill();
        Grain g = m.process();
        System.out.println(g);
        m = new WheatMill();  //  向上转型
        g = m.process();//  向上转型
        System.out.println(g); //多态
    }
}


public class exp5 {
    public static void main(String[] args){
        CovariantReturn fun = new CovariantReturn();
        fun.main(null);
    }
}

 因为满足了继承、覆盖和向上转型三个条件,多态的实现成为可能

用继承进行设计:

     当我们在使用现成的类来建立新类时,如果首先考虑使用继承技术,反而会

加重我们的设计负担

     更好的方式是首先选择组合,尤其是不能十分确定应该使用哪一种方式时

2、   向下转型与运行时类型识别 

  Java的类型检测更为严格,所有的转型都会受到严格检查

  换句话说,Java在运行期间,仍然会对转型进行检查,以保证它的确是我们希望的那种类型。(运行时类型识别,RITT(向下转型必须使用强制类型转换)

  若不是,返回ClassCastException异常不能直接把父类引用赋给子类引用,也不能用子类引用指向父类对象。

class Useful { //P167
    public void f( ) { }
}
class MoreUseful extends Useful {
    public void f( ) { }
    public void u( ) { }
}
public class RTTI {
    public static void main(String[] args) {
        Useful[ ] x = {  new Useful( ),  new MoreUseful( )  };
        x[0].f( );
        //! x[1].u( );
        ((MoreUseful)x[1]).u( );   // Downcast/RTTI
        ((MoreUseful)x[0]).u( );   // Exception thrown
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值