790 likes | 810 Views
Learn about inheritance, polymorphism, and method overriding. Understand the is-a relationship in classes through examples like Clock, AnalogClock, and Turtle. Find out when to derive a subclass and explore private field inheritance.
E N D
Inheritance • Inheritance is a fundamental object-oriented technique that supports reuse of software. • In this lecture: • Inheritance • Polymorphism, widening & narrowing • Method overriding • protected and final modifiers • A word about multiple inheritance
Inheritance • Inheritance allows us to derive a new class from an existing one • The existing class is called the superclass or base-class. • derived class is called the subclass or derived-class. • Instances of the derived class inherit all the properties and functionality that is defined in the base class. • Usually, the derived class adds more functionality and properties.
Example setTime() getSeconds()getMinutes() getHours() secondElapsed() ... Clock AlarmClock AnalogClock getSecondsPointerAngle()getMinutesPointerAngle() getHoursPointerAngle() setAlarm() setAlarmState()
The is a relationship • Inheritance creates an is-a relationship. AnalogClockis aClock, AlarmClockis a Clock. • Everything that can be done with a Clock object can also be done with an AlarmClock object. • An AnalogClock is a special kind of Clock. It has all the functionality of a clock and some more. • The subclass instances are more specific than the instances of the superclass.
Example moveForwards(float size) turnLeft(float teta)turnRight(float teta) tailUp() ... Turtle drawPolygon(int n, float size) drawSquare(int n) IntelligentTurtle
/** * A logo turtle that knows how to draw composite * figures such as polygons. */ public class IntelligentTurtle extends Turtle { /** * Draws a perfect polygon. * @param n The number of edges * @param edgeSize The size of each edge */ public void drawPolygon(int n, float edgeSize) { float teta = 360.0f / n; for (int i=0; i<n; i++) { moveForwards(edgeSize); turnLeft(teta); } } } The extends Keyword
// Draws a perfect polygon whose number of edges is // given by the user and the size of is edge is fixed class PerfectPolygon { static final float EDGE_SIZE = 100.0f; /** * Draws a perfect polygon. */ public static void main(String[] args) { int n = Integer.parseInt(args[0]); IntelligentTurtle painter = new IntelligentTurtle(); painter.tailDown(); painter.drawPolygon(n, EDGE_SIZE); } } Example
When to derive a subclass? • Derived class should normally extend the functionality of the superclass • In certain cases, a derived class would change some of the functionality of the superclass • Thumb Rule: Subclass only when it is reasonable from the point of view of the abstractions!
Point Employee TextField Pixel Manager PasswordField Examples Frame Dialog FileDialog
What is Inherited? • When you derive a class from a given base class: • The subclass inherits all the fields of the base class • It inherits all the methods of the base class • You have to declare the constructors of the subclass from scratch • Private fields and methods are inherited but cannot be accessed directly from the code of the subclass
Switch Example Switch • Switch() • boolean isOn() • setOn(boolean) • isOn
/** * An electronic switch that can be on/off. */ public class Switch { // Records the state of the switch private boolean isOn; /** * Checks if this switch is on. * @return true if the switch is on. */ public boolean isOn() { return isOn; } /** * Sets the state of the switch on/off. */ public void setOn(boolean state) { isOn = state; } } Switch Code
on off Switch vs. Adjustable Switch an adjustable switch has a “level of current” dial pressing the adjustable switch turns it on
Inheriting AdjustableSwitch from Switch • Switch() • isOn() • setOn(boolean) Switch • AdjustableSwitch() • setLevel(float) • getLevel() AdjustableSwitch
AdjustableSwitch Code /** * An adjustable electronic switch. */ public class AdjustableSwitch extends Switch { // The level of current (0-100) private float level; // . . . getLevel() and setLevel() }
AdjustableSwitch Code (cont.) /** * Sets the level of current * @param level the required level of current(0%-100%) */ public void setLevel(float level) { if (level < 0.0f || level > 100.0f) { throw new IllegalArgumentException(); } this.level = level; } /** * Returns the level of current of the switch. */ public float getLevel() { return level; }
AdjustableSwitch Example AdjustableSwitch • setOn(boolean) • isOn() • AdjustableSwitch() • setLevel(float) • getLevel() • isOn • level
Private Fields - Inherited but not Accessible • Notice that an AdjustableSwitch object has a state variable isOn • This variable is inherited from Switch • However, it cannot be accessed directly from the code of AdjustableSwitch because it is defined as private in Switch - i.e., it is encapsulated
Inheritance: a Basis for Code Reusability • Fast implementation - we need not write the implementation of AdjustableSwitch from scratch, we just implement the additional functionality. • Ease of use - If someone is already familiar with the base class, then the derived class will be easy to understand. • Less debugging - debugging is restricted to the additional functionality. • Ease of maintenance - if we need to correct/improve the implementation of Switch, AdjustableSwitch is automatically corrected as well. • Compactness - our code is more compact and is easier to understand.
Example: Changing the Base Class • Suppose that we want to add the property of ‘maximal power’ to our representation of a switch. • This property is suitable in adjustable switches as well (the inheritance supports our abstraction!). • Inheritance allows us to add this property only in the base class. The changes will automatically take place in the inherited class.
Changing the Switch Class /** * An electronic switch that can be on/off. */ public class Switch { // Records the state of the switch private boolean isOn; // The maximal power of this switch private float maxPower;
Changing the Switch Class /** * Constructs a new switch. * @param power the maximal power of the switch. */ public Switch(float maxPower) { if (maxPower <= 0.0f) { throw new IllegalArgumentException(); } this.maxPower = maxPower; } /** * Returns the maximal power of this switch. */ public float getMaxPower() { return maxPower; }
Constructors Must Be Redefined! • Note that the constructor of Switch receives a parameter - maxPower - and initializes the private field with the same name. • We have to define a constructor for AdjustableSwitch that receives the same parameter and initializes this field. But this field is private, so there is no direct access to it. • In general, when we invoke a constructor of a subclass, we first have to construct the superclass “part” of the subclass. We do this by using the super keyword.
/** * Constructs an adjustable switch. * @param power the maximal power of the switch. * @exception IllegalArgumentException if the power * is negative. */ public AdjustableSwitch(float power) { super(power); } AdjustableSwitch - Redefinition of Constructor The first line of the constructor calls the constructor of the superclass (Switch) that receives a float parameter to initialize the state variable defined in the superclass
Calling super(...) • The constructor of a derived class MUST initialize the state of the object from the point of view of its parent class. Thus the first line in the constructor must be a call to one of the constructors in the superclass using super(...). You cannot make a later call to super(...) because to can invoke a constructor only once! • If you do not call super(...) in the first line of the constructor, the compiler automatically places a call to the empty constructor of the superclass. • If there is no empty constructor in the superclassthe code will not compile!
Automatic Default Construction • In the previous implementation of Switch and AdjustableSwitch we didn’t include any constructors. • The compiler automatically added an empty (default) constructor to each of the classes. • In addition, the compiler had put in the first line of the empty constructor of Switch a call to the empty constructor of AdjustableSwitch.
Automatic Default Construction // ...in class Switch (automatically added) public Switch() { } // and in class AdjustableSwitch (automatically added) public AdjustableSwitch() { super(); }
Default Construction Using Other Constructors • Sometimes the programmer may wish to define a default constructor by herself, which calls other constructors with default arguments of her choice
Default Construction - Delegation private static final DEFAULT_POWER = 60.0f; /** * Constructs a new switch. * @param power the maximal power of the switch. */ public Switch(float maxPower) { if (maxPower <= 0.0f) { throw new IllegalArgumentException(); } this.maxPower = maxPower; } /** * Constructs a switch with default power. */ public Switch() { this(DEFAULT_POWER); }
Overriding • In certain case, when we derive a class, we want to change some of the functionality defined in the superclass. • Example: We want clients to be able to open a protected file only if it is unlocked • File() • isOpen() • open() • close() File • RestrictedFile(long key) • isLocked() • lock() • unlock(long key) RestrictedFile
File Example /** * Part of a File implementation */ public class File { // true if the file is opened private boolean isOpen; /** * Checks if the file is open. * @return true iff the file is open */ public boolean isOpen() { return isOpen; } // other methods/variables... }
File Example /** * Opens the file. */ public void open() { // … other operations isOpen = true; } /** * Closes the file. */ public void close() { // … other operations isOpen = false; } }
RestrictedFile Example /** * Represents a restricted file, which can be opened * only if it is unlocked. In order to unlock * the file a key is needed. */ public class RestrictedFile extends File { // Password for unlocking the file private long key; // The state of the file - locked/unlocked private boolean isLocked; /** * Constructs a new restricted file. * @param key The key to unlock the file */ public RestrictedFile(long key) { this.key = key; isLocked = true; }
RestrictedFile Example /** * Checks if the file is locked. */ public boolean isLocked() { return isLocked; } /** * Locks the file. */ public void lock() { isLocked = true; }
RestrictedFile Example /** * Unlock the file. The file will be unlocked only * if the given key matches. * @param key the key to unlock the file */ public void unlock(long key) { if (this.key == key) { isLocked = false; } else // ... } So far the implementation is useless if we do not change the implementation of open()
RestrictedFile Example /** * Open the file. The file will be opened only if it * is unlocked. */ public void open() { if (!isLocked()) { super.open(); } else // ... }
Overriding in RestrictedFile • RestrictedFile inherits the interface of File, but changes the functionality of the method open(). • We say that RestrictedFileoverrides the method open(). • Notice the call to super.open() - we invoke the method open() of the superclass on this object.
Rules of Overriding • When you derive a class B from a class A, the interface of class B will be a superset of that of class A (except for constructors) • You cannot remove a method from the interface by subclassing (explain why...) • However, class B can override some of the methods that it inherits and thus change their functionality. • The contract of a method states what is expected from an overriding implementation of the method.
The Object Class • Java defines the class java.lang.Object that is defined as a superclass for all classes. • If a class doesn’t specify explicitly which class it is derived from, then it will be implicitly derived from class Object. • So, in the previous example, RestrictedFile was derived from File which in turn was derived from Object. • We can depict the relationship between this classes in the following diagram, that is called class hierarchy diagram.
Hierarchy Diagram Object File RestrictedFile
The Object Class • The Object class defines a set of methods that are inherited by all classes. • One of these is the toString() method that is used whenever we want to get a String representation of an object. • When you define a new class, you can override the toString() method in order to have a suitable representation of the new type of objects as Strings. • The contract of the toString() method says that you are expected to return a String that represents your object
Point: Example of Overriding toString() /** * Represents a point on a grid. */ public class Point { // The coordinates of the point private int x,y; /** * Constructs a new point. * @param x,y The coordinates of the point. */ public Point(int x, int y) { this.x = x; this.y = y; } public String toString() { return “Point: (“ + x + ”,” + y + ”)”; }
Point: Example of Overriding toString() // Example of overriding the toString() method class PrintingPointExample { public static void main(String[] args) { Point p = new Point(2,3); System.out.println(p); //System.out.println(p.toString()); } } The output of the program will be: Point: (2,3)
Polymorphism • Recall the is-a relationship induced by inheritance: A RestrictedFileis aFile which is anObject. • We can view a RestrictedFile object from 3 different points of views: • As a RestrictedFile. This is the most narrow point of view (the most specific). This point of view ‘sees’ the full functionality of the object. • As a File. This is a wider point of view (a less specific one). We forget about the special characteristics the object has as a RestrictedFile (we can only open and close the file). • As an plain Object.
Polymorphism • We view an object by using an object reference. • A variable of type ‘reference to File’ can only refer to an object which is aFile. File f = new File(); • But a RestrictedFile is also a File, so f can also refer to a RestrictedFile object. File f = new RestrictedFile(); • The type of the reference we use determines the point of view we will have on the object.
Polymorphism • If we refer to a RestrictedFile object using a RestrictedFile reference we have the RestrictedFile point of view - we see all the methods that are defined in RestrictedFile and up the hierarchy tree. • RestrictedFile f = new RestrictedFile(12345); • f.close(); • f.lock(); • f.unlock(12345); • String s = f.toString();
Polymorphism • If we refer to a RestrictedFile object using a File reference we have the File point of view - which lets us use only methods that are defined in class File and up the hierarchy tree. • File f = new RestrictedFile(12345); • f.close(); • f.lock(); • f.unlock(12345); • String s = f.toString();
Polymorphism • If we refer to a RestrictedFile object using an Object reference we have the Object point of view - which let us see only methods that are defined in class Object. • Object f = new RestrictedFile(12345); • f.close(); • f.lock(); • f.unlock(12345); • String s = f.toString();
Polymorphism RestrictedFile • toString() • ... • isOpen() • open() • close() • lock() • unlock(key) • isLocked() • ... • isOpen • isLocked • key
Widening • Changing our point of view of an object, to a wider one (a less specific one) is called widening. File myFile; myFile = new RestrictedFile(12345); RestrictedFile referenceRestrictedFile point of view File referenceFile point of view Widening