Polymorphism

Polymorphism is the third essential feature of an object oriented programming language, after data abstraction and inheritance. It is also called dynamic binding (or late binding, or tun time binding).

upcasting: taking an object reference and treating it as a reference to its base type is called.

The Twist

Method-call Binding

Connecting a method call to a method body is called binding.

  • Early Binding: binding is performed before the program is run. (The procedural languages (C) has only early binding.)
  • Late Binding: binding occurs at run time, based on the type of the object.

There is a mechanism to determine the type of the object at run time and to call the appropriate method. (The compiler still doesn't know the object type)

All method binding in Java uses late binding unless the method is static or final, or private (private methods are implicitly final).

Pitfall: "overriding" private Methods

public class PrivateOverride {
  private void f() {
    print("private f()");
  }
  publc static void main (String[]  args) {
    PrivateOverride po = new Derived();
    po.f();
  }
}

class Derived extends PrivateOverride {
  public void f() {
    print("public f()");
  }
}

/* Output:
private f()
*/
In the base class, the private method f() is automatically final, and is also hidden from the derived class. So Derived's f() in this case is a brand new method. It's not even overloaded, since the base class version of f() isn't visible in Derived. It's just a coincidence that the two methods are with the same name.

Pitfall: Fields and static Methods

Only ordinary method calls can be polymorphic. If you access a field directly, that access will be resolved at compile time. When an object of derived class is upcast to s super reference, any field accesses are resolved by the compiler, are thus not polymorphic. Thus this object actually contains two fields: its own and the one that it gets from parent.

class Super {
  public int field = 0;
  public int getField() {return field;}
}

class Sub extends Super {
  public int field = 1;
  public int getField() {return field;}
  public int getSuperField() {return super.field;}
}

public class FieldAccess {
  public static void main(String[] args) {
    Super sup = new Sub(); // upcasting
    print("sup.field = " + sup.filed + ", sup.getField()" + sup.getField());
    Sub sub = new Sub();
    print("sub.field = " + sub.field + ", sub.getField() = " +
          sub.getField() + ", sub.getSuperField() = " + sub.getSuperField());
  }
}

/* Output:
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
If a method is static, it doesn't behave polymorphically, because static method are associated with the class, and not the individual object.

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(); // upcasting
    print(sup.staticGet());
    print(sup.dynamicGet());
  }
}

/* Output:
Base staticGet()
Derived dynamicGet()
*;<span style="font-size:18px;">
</span>

Constructors and Polymorphism

Order of Constructor Calls

class Bread {
  Bread() { print("Bread"); }
}

class Cheese {
  Cheese() { print("Cheese"); }
}

class Lettuce {
  Lettuce()) { print("Lettuce"); }
}

class Meal {
  Meal() { print("Meal"); }
}

class Lunch extends Meal {
  Lunch() { print("Lunch"); }
}

class PortableLunch extends Lunch {
  PortableLunch() { print("PortableLunch"); }
}

public class Sandwich extends PortableLunch {
  private Bread b = new Bread();
  private Cheese c = new Cheese();
  private Lettuce l = new Lettuce();
  public Sandwich() { print("Sandwich"); }
  public static void main (String[] args) {
    new Sandwich();
  }
}
/* Output
Meal
Lunch
PortableLunch
Bread
Cheese
Lettuce
Sandwich
*/
The order of constructors calls for a complex object is as follows:

  1. The base class constructor is called. This step is repeated recursively such that the root of the hierarchy is constructed first, followed by the next derived class, until the most derived class is reached.
  2. Member initializers are called in the order of declaration.
  3. The body of the derived class constructor is called.

Inheritance and Cleanup

If you have cleanup issues with inheritance, you must override dispose() in the derived class if you have any special cleanup that must happen as part of garbage collection. When you override dispose() in an inherited class, it's important to remember to call the base class version of dispose(), otherwise the base class cleanup will not happen.

Class Characteristic {
  private String s;
  Characteristic(String s) {
    this.s = s;
    print("Creating Characteristic " + s);
  }
  protected void dispose() {
    print("disposing Characteristic " + s);
  }
}

Class Description {
  private String s;
  Description(String s) {
    this.s = s;
    print("Creating Description " + s);
  }
  protected void dispose() {
    print("disposing Description " + s);
  }
}

Class LivingCreature {
  private Characteristic p = new Characteristic("is alive");
  private Description t = new Description("Basic Living Creature");
  LivingCreature() { print("LivingCreature()") };
  protected void dispose() {
    print("LivingCreature dispose");
    t.dispose();
    p.dispose();
  }
}

Class Animal extends LivingCreature {
  private Characteristic p = new Characteristic("has heart");
  private Description t = new Description("Animal not Vegetable");
  Animal() { print("Animal()") };
  protected void dispose() {
    print("Animal dispose");
    t.dispose();
    p.dispose();
    super.dispose();
  }

  public static void main (String[] args) {
    Animal animal = new Animal();
    print("Bye!");
    animal.dispose();
  }
}
/* Output
Creating Characteristic is alive
Creating Description Basic Living Creature
LivingCreature()
Creating Characteristic has heart
Creating Description Animal not Vegetable
Animal()
Bye!
Animal dispose
disposing Description Animal not Vegetable
disposing Characteristic has heart
LivingCreature dispose
disposing Description Basic Living Creature
disposing Characteristic is alive
The order of disposal should be he reverse order of initialization, in case one subobject is dependent on another.

  • For fields, this means the reverse of the order of declaration, since fields are initialized in declaration order.
  • For base classes (same in C++ for destructors), you should perform the derived class clean up first, then the base class cleanup.

Behavior of Polymorphic Methods inside Constructors

class Glyph {
  void draw() { print("Glyph.draw()"); }
  Glyph() {
    print("Glyph() before draw()");
    draw();
    print("Glyph() after draw()");
  }
}

class RoundGlyph extends Glyph {
  private int radius = 1;
  RoundGlyph(int r) {
    radius = r;
    print("RoundGlyph.RoundGlyph(), radius = " + radius);
  }
  void draw() {
    print("RoundGlyph.draw(), radius = " + radius);
  }
}

public class PolyConstructors {
  public static void main(String[] args) {
    new RoundGlyph(5);
  }
}
/* Output
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
*/
The actual process of initialization is:

  1. The storage allocated for the object is initialized to binary zero before anything else happens.
  2. The base class constructors are called as described previously. At this point, the overridden draw() method is called (yes, before the RoundGlyph constructor is called), which discovers a radius value of 0, due to Step 1.
  3. Member initializers are called in the order of declaration.
  4. The body of the derived class constructor is called.

A general guideline for constructors: The only safe methods to call inside a constructor are those that are final in the base class. (It also applies to private methods, which are automatically final.)

Covariant return Types

Java SE5 adds covariant return types, which means that an overridden method in a derived class can return a type derived from the type returned by the base class method.

class Grain {}
class Wheat extends Grain {}

class Mill {
  Grain process() { return new Grain(); }
}
class WheatMill extends Mill {
  Wheat process() { return new Wheat(); }
}
The WheatMill.process has override the same method in Mill, and the return type of derived class method (Wheat) is derived from the return type of the base class method (Grain), this is allowed since JSE5.

Downcasting and Runtime Type Information

class Useful {
  public void f() {}
  public void g() {}
}

class MoreUseful extends Useful {
  @Override
  public void f() {}
  @Override
  public void g() {}
  public void u() {}
}

public class RTTI {
  public static void main(String[] args) {
    Useful[] x = {new Useful(), new MoreUseful()};
    x[0].f();
    x[1].g();
    // compile time: method not found in Useful:
    //! x[1].u();
    ((MoreUseful) x[1]).u();  // Downcasting/RTTI
    ((MoreUseful) x[0]).u();  // Exception thrown
  }
}
In C++, you must perform a special operation in order to get a type sate downcast.

But in Java, every cast is checked! Even though it looks like you are just performing an ordinary parenthesized cast, at run time this cast is checked to ensure that it is in fact the type you think it is. If it isn't, you get a ClassCastException. This act of checking types at run time is called runtime type information (RTTI).




















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值