You are on page 1of 6

Week 3: Inheritance and polymorphism

“Has a” vs “Is a” relationship


“Has-a” relationship is used in data abstraction and shows a composition relationship (eg. Circle contains a Point .)

“Is-a” relationship is more of a inheritance relationship, the class where some other class inherits from is called the superclass, while the class that inherits from the
superclass is called the subclass.

Filled Circle
Recall the Circle class that is defined last lecture:

%.2f is a placeholder which formats the display of the variable in the output. % is a format specifier which specifies the locale of the formatting, and then .2f basically
means returning the variable rounded to 2 decimal places as a floating point number in the output.

We want to implement a Filled Circle class based off the Circle class, and notice that Filled Circle inherits (”is-a”) from Circle , but does not depend on (”has-a”) Circle.

Abstraction principle
Basically, each piece of functionality (eg. method) in a program should be implemented in just one place in the source code. (In other words, try not to repeat similar
functions in different parts of the source code)

If the functional similarity is only part of the whole function body, we could still abstract it out.

Inheritance
Consider the class FilledCircle , it bears an “is-a” relationship with the class Circle , and thus inherits from Circle .

The extends keyword means that Filled Circle will inherit from Circle, this means that:

Week 3: Inheritance and polymorphism 1


All the properties of the Circle class will be inherited by the FilledCircle class

All the methods of the Circle class will be inherited by the FilledCircle class

Thus, all the properties of the superclass Circle can be omitted from the subclass Filled Circle’s definition. Filled Circle will have an additional color property. This also
means that getArea() method can be omitted too as it will be inherited.

For the constructor method, the super keyword calls the parent class Circle ’s constructor and passes in the radius property. super is used on the radius property as this
property is inherited from the superclass Circle to the subclass Filled Circle. (Note that since radius is an inherited property, it MUST be placed before color.)

For the toString() method, we can again call Circle ’s toString() method using super.toString() in order not to repeat similarities in the method body.

Note that super is NOT a reference, thus it cannot be assigned a value nor returned. (Unlike this )

Protected access modifier


When there is a relationship between two classes, there will tend to be an abstraction barrier. Eg. For “has-a”, we use private access modifiers to hide the properties and
methods of a specified class from the other classes.

Thus, for “is-a” we will use protected access modifiers. We cannot use private for the superclass properties as its subclass would not be able to access the property. Using
the protected keyword on the parent class properties will enable the child class AND all other classes within the same package (directory) access to the properties and but
restricts access from all other classes NOT within the same package

The object class


The Object class is the mother of all classes, and every existing class will inherit from the Object class.

All the methods defined under the Object class will be implicitly inherited from all the other subclasses. (eg. The toString() method can be called by all the classes without
explicit definition.

Method overriding
The idea of method overriding is that when a parent and child class both share the same method (eg. toString( ) ) with the same method signature, the the child class can
redefine the implementation of the shared method. Method signature comprises of:

Number of arguments

Data type of arguments

Order of arguments

Thus, given the idea of the Object class, method overriding is actually happening every time we define a unique toString() method implementation for every class.

For good practice, ALWAYS include the keyword @Override before every toString() method or any other method that exists in the parent class and is inherited. Even though
not including the @Override keyword will still enable the code to run and the new redefined method to work, if somehow we had a typo and wrote tostring() instead of
toString() , evaluating toString() will call the toString() from the parent class instead, which is different in implementation and will give a different output.

If we include the keyword @Override in a method that is meant to be overwritten but is not overwritten because of a typo, the code will not compile and hints that there are
issues with the method that is meant to be overwritten.

Therefore, after method overriding, the new implementation of the method will be evaluated whenever an object of the child class calls the overwritten method.

Compile-time vs Run-time type


Application

The variable c of data type Circle can actually reference an object of its subclass FilledCircle , thus creating a FilledCircle object.

Additionally, the toString() method called will be the implementation of that of the FilledCircle object, instead of the Circle object.

Conversely, a variable of data type FilledCircle cannot reference an object of its parent class Circle .

Week 3: Inheritance and polymorphism 2


Explanation

Consider the above assignment:


Circle circle = new FilledCircle(1.0, color.BLUE);

Compile time type and Run time type is always with reference to an object of a class (usually stored in a variable) instead of the class itself. In this case, the Circle object is
stored in the variable called circle .

The compile time type of circle would be Circle , the compile time type is:

Always determined by the left hand side of the assignment statement

Already determined during the declaration stage of the variable; it is the type of the variable that is being declared

Determines the method it CAN CALL during compilation instead of determining the BEHAVIOUR (output) of the method after it is called (In this case, the methods that
circle can call will be restricted to the methods defined under Circle .)

The run time type of circle would be FilledCircle , the run time type is:

Always determined by the right hand side of the assignment statement

Determined during the assignment stage of the variable; it is the type that the variable is referencing

Determines the ACTUAL method being called during run time. (In this case, during compilation [compile time], the toString() method of the Circle class is called. But
during execution [run-time], the actual method being called is the toString() method of the FilledCircle class.)

Polymorphism
The variable circle can be a Circle or a FilledCircle , thus it can take many forms, giving rise to the concept of polymorphism.

Consider a variable t of data type T. This variable t can be assigned with a reference to an object of subclass S with no compilation error.
T t = new S()

(Compile-time type: T , Run-time type: S )

Below shows a polymorphic example through the use of a more implicit form of assignment (using method calls)

In the new defined method called foo() , the compile-time type with reference to the variable (or more specifically parameter) c will be Circle .

When an argument of Circle class is passed into the method foo(), the method will run. When an argument of FilledCircle class is passed into the method foo() , the
method will still run. This is because passing an object of type FilledCircle as an argument is effectively doing an assignment of the form below Circle c = new

FilledCircle(1.0, color.BLUE) . Just that in this case the assignment is more implicit and is through a method call instead of manually assignment using the = operator.

Here is another example:

In this example, when foo() calls the getArea() method on c , no matter whether the argument data type is Circle or FilledCircle , the method will still run as the
compile time type is still Circle , however since the run time type will depend on the class of the argument supplied, the output might be different with different
arguments. (In this case the output is the same as the getArea() method is inherited and not overridden, also both the radius of Circle and FilledCircle object are the
same.)

Lets look at another example:

Week 3: Inheritance and polymorphism 3


For this example, when we include the getColor() method in the method body, it would not compile as the compile time is Circle , and getColor() is not a defined
method under the Circle class definition.

When we include the toString() method in the method body, the method will compile as toString() method is defined under the Circle class, which is the compile time
type. However, the actual output of foo() will DEPEND on whether the argument passed in is a Circle or FilledCircle . If its a Circle , the method will call Circle ’s
toString() . If its a FilledCircle , then the method will call FilledCircle ’s toString() . This is due to the effect of the run-time type, which will be dependent on the

argument due to the “delayed assignment”. This highlights the polymorphic nature.

Lastly, we will study this example:

For this example, we can see that this immutable list is polymorphic in nature as when we loop through the list and print out the elements, System.out.println() will call
the toString() method of Circle if the element is of Circle class and call the toString() method of FilledCircle if the element is of FilledCircle class. This is due to
the effect of the run-time type.

In essence, for methods, the parameter determines the compile time type and the actual argument passed determines the run time type.

Java memory model (Inheritance)


Similar concept to the past 2 lectures but just note that the subclass FilledCircle reference object actually “wraps around” the superclass Circle in the representation of the
memory model.

Week 3: Inheritance and polymorphism 4


Also, since the parameter of the foo method is the variable circle , it would reflect as circle in the call stack which will reference the FilledCircle object that is passed
in as the actual argument.

Lastly, the pointer from the foo() method denoting the reference to the FilledCircle object should physically point to FilledCircle 's memory address instead of the
Circle class that is contained in the FilledCircle object.

Method overloading
For method overriding, it will apply if the method header (method name) is exactly the same with the same method signature

If 2 methods have the same method header but a different method signature, then we will use method overloading.

Basically to invoke the overloaded method in JShell, just pass in the arguments differently. The compiler will look at the different arguments to decide which overloaded
method to call.

Static vs dynamic binding


Basically, static and dynamic binding are the different mechanisms by which compile time type and run time type are manifested.

Static binding occurs in the compilation stage and resolves the method TO CALL (including overloaded methods), consistent with the compile time type

Dynamic binding occurs in the run time stage and resolves the ACTUAL method called among all overriding methods, consistent with the run-time type.

Substitutability principle
The general idea of substitutability principle is that if a program or method accepts an object of data type T as the argument and can compile and run, then it can also
accept another object of data type S , where S is a subclass of T , and still compile and run normally without changing the desirable properties of the program.

For example, say we have this program:

void foo(Circle c) {
String s = c.toString();
System.out.println(s);
}

Since this foo() method takes in an object of class Circle as argument, it can always run when it is called by an object of class Circle . However, the actual argument
that will be passed into foo() will be unknown to foo() , for example, a FilledCircle or even a PatternedCircle (just a name example) can be thrown into foo() . As
long as the object passed in as argument is of Circle class or any of its subclasses, we will be sure that the foo() method will not break as any subclass of Circle is
still a circle; ie the method body still can be called successfully, but the actual output will still change.

For overriding methods, the return type may not always be the same. For example, Circle ’s method might just return a Circle while FilledCircle 's method might return a
FilledCircle instead.

Week 3: Inheritance and polymorphism 5


Lab 1 learning points
Comparing floating point numbers: Because of how floating point numbers are represented in Java, we are advised against comparing floating point numbers in this way:

if (double1 == double2) {
// do something
}

This code above can still run, but might lead to logical errors and imprecision. Instead, we will need to set a threshold value and check if the absolute difference between the
2 floating point numbers is smaller than some threshold. Which will be something like this:

private static final double threshold = 1E-15 // 10^-15


...
if (Math.abs(double1 - double2) <= threshold) {
// do something
}

Avoiding numbers which are assumed to be understood: consider the line below:

int numberOfMinutes = numberOfSeconds / 60;

In this case, the number 60 is considered to be a “magic number” as it just incidentally just appears in the code without prior initialisation to any variables, thus there is no
context of the number 60. We would need to rely on our prior knowledge of what 60 is, in this case relying on our concept of time that 1 minute constitutes 60 seconds. We
can instead do something like:

private static final int no_of_seconds_in_one_minute = 60;


...
int numberOfMinutes = numberOfSeconds / no_of_seconds_in_one_minute;

Thus, we assign these magic numbers to named variables to make them more clear. static and final are included at the front of the variable name as the variable holds a
constant value.

Week 3: Inheritance and polymorphism 6

You might also like