330 likes | 468 Views
Polymorphism. Upcasting revisited. Taking an object reference and treating it as a reference to its base type is called upcasting because of the way inheritance trees are drawn with the base class at the top. Forgetting the object type.
E N D
Upcasting revisited Taking an object reference and treating it as a reference to its base type is called upcasting because of the way inheritance trees are drawn with the base class at the top.
Forgetting the object type Why should anyone intentionally forget the type of an object? This is what happens when you upcast. It is nice if you could forget that there are derived classes, and write your code to talk only to the base class.
Method-call binding Connecting a method call to a method body is called binding. When binding is performed before the program is run (by the compiler and linker, if there is one), it’s called early binding. And the late binding means that the binding occurs at run time, based on the type of object. Late binding is also called dynamic binding or runtime binding.
All method binding in Java uses late binding unless the method is static or final (private methods are implicitly final). This means that ordinarily you don’t need to make any decisions about whether late binding will occur—it happens automatically.
Why would you declare a method final? As noted in the last chapter, it prevents anyone from overriding that method. Perhaps more important, it effectively “turns off” dynamic binding, or rather it tells the compiler that dynamic binding isn’t necessary. This allows the compiler to generate slightly more efficient code for final method calls. However, in most cases it won’t make any overall performance difference in your program, so it’s best to only use final as a design decision, and not as an attempt to improve performance.
Producing the right behavior “send a message to an object and let the object figure out the right thing to do.” you can write your code to talk to the base class and know that all the derived-class cases will work correctly using the same code.
Classic Shape Example The shape example has a base class called Shape and various derived types: Circle, Square, Triangle, etc.
Extensibility Because of polymorphism, you can add as many new types as you want to the system without changing the method. In a well-designed OOP program, most or all of your methods will follow the model of this method and communicate only with the base-class interface.
Such a program is extensible because you can add new functionality by inheriting new data types from the common base class. The methods that manipulate the base-class interface will not need to be changed at all to accommodate the new classes.
Pitfall: “overriding” private methods Only non-private methods may be overridden, but you should watch out for the appearance of overriding private methods, which generates no compiler warnings, but doesn’t do what you might expect. To be clear, you should use a different name from a private base-class method in your derived class.
Pitfall: fields and static methods For one thing, you’ll generally make all fields private and so you won’t access them directly, but only as side effects of calling methods. In addition, you probably won’t give the same name to a base-class field and a derived-class field, because its confusing. If a method is static, it doesn’t behave polymorphically.
Constructors and polymorphism Constructors are different from other kinds of methods. This is also true when polymorphism is involved. Even though constructors are not polymorphic (they’re actually static methods, but the static declaration is implicit), it’s important to understand the way constructors work in complex hierarchies and with polymorphism
Order of constructor calls A constructor for the base class is always called during the construction process for a derived class, chaining up the inheritance hierarchy so that a constructor for every base class is called. This makes sense because the constructor has a special job: to see that the object is built properly.
A derived class has access to its own members only, and not to those of the base class (whose members are typically private). Only the base-class constructor has the proper knowledge and access to initialize its own elements. Therefore, it’s essential that all constructors get called; otherwise the entire object wouldn’t be constructed.
It will silently call the default constructor if you don’t explicitly call a base-class constructor in the derived-class constructor body. If there is no default constructor, the compiler will complain.
When you inherit, you know all about the base class and can access any public and protected members of the base class. You must be able to assume that all the members of the base class are valid when you’re in the derived class. The only way to guarantee this is for the base-class constructor to be called first. Then when you’re in the derived-class constructor, all the members you can access in the base class have been initialized
Inheritance and cleanup If you do have cleanup issues, you must be diligent and create a dispose( ) method (the name I have chosen to use here; you may come up with something better) for your new class. And 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( ), since otherwise the base-class cleanup will not happen.
Behavior of polymorphic methods inside constructors If you call a dynamically-bound method inside a constructor, the overridden definition for that method is used. However, the effect of this call can be rather unexpected because the overridden method will be called before the object is fully constructed. This can conceal some difficult-to-find bugs
Actual process of initialization 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 zero, due to Step 1. 3. Member initializers are called in the order of declaration. 4. The body of the derived-class constructor is called.
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.
Designing with inheritance Once you learn about polymorphism, it can seem that everything ought to be inherited, because polymorphism is such a clever tool. This can burden your designs. A better approach is to choose composition first, especially when it’s not obvious which one you should use.
Substitution vs. extension t would seem that the cleanest way to create an inheritance hierarchy is to take the “pure” approach. That is, only methods that have been established in the base class are overridden in the derived class.
This can be called a pure “is-a” relationship because the interface of a class establishes what it is. Inheritance guarantees that any derived class will have the interface of the base class and nothing less. If you follow this diagram, derived classes will also have no more than the base-class interface. This can be thought of as pure substitution
That is, the base class can receive any message you can send to the derived class because the two have exactly the same interface.
This can be termed an “is-like-a” relationship, because the derived class is like the base class—it has the same fundamental interface—but it has other features that require additional methods to implement
Downcasting and runtime type information to move back down the inheritance hierarchy—you use a downcast. There must be some way to guarantee that a downcast is correct, so that you won’t accidentally cast to the wrong type and then send a message that the object can’t accept. This would be quite unsafe
in Java, every cast is checked. You get a ClassCastException if error occurred. This act of checking types at run time is called runtime type identification (RTTI).
Summary Polymorphism means “different forms.” In object-oriented programming, you have the same interface from the base class, and different forms using that interface: the different versions of the dynamically bound methods. To use polymorphism—and thus object-oriented techniques—effectively in your programs, you must expand your view of programming to include not just members and messages of an individual class, but also the commonality among classes and their relationships with each other.