Interfaces and Inheritance Subtyping
Inheritance
Inheritance is a mechanism where you derive a class from another class for a hierarchy of classes that share a set of attributes and methods.
Benefits of Inheritance
Inheritance minimizes the amount of duplicate code in software by sharing common code amongst classes: When the same code exists in two classes, The common code can be added to a superclass that the two classes inherit from. This also makes software easier to maintain since when a change is necessary it only has to be applied in one class instead of two.
Inheritance can also make application code more flexible to change because classes that inherit from a common superclass can be used interchangeably.
Class Hierarchy
Example
If the game code uses variables of type Item
, say, for keeping an Inventory
, then it is easy to add more types of “Items” later on, without changing the code for the Inventory.
Because classes that inherit from Item
can be used interchangeably with Item
.
JavaFX Class Hierarchy
Libraries like JavaFX contain many classes. Typically, the “base code” is shared: held in common base classes like Node
, or Shape
. For example
- A
Node
has alocation
,rotation
,scale
- A
Shape
isfilled
or notfilled
As a result of this class hierarchy all classes have alocation
,rotation
andscale
since they extendNode
.
Inheritance
Syntax
// Extends Syntax
class MountainBike extends Bicycle {
// new fields and methods for MountainBike.
// MountainBike is the subclass
// Bicycle is the superclass.
}
Diagram for the entire structure:
Extending Classes
When extending from a class you can access the fields and methods of that class and override methods to change their implementation.
When writing the code for a superclass, you can control whether the classes that extend the superclass will be able to access it's methods and fields:
- methods of the child class cannot access
private
fields/methods of the parent class. - If a method of the superclass you are writing should not be overridden you can mark it as
final
. - If the class should not be extended at all you can mark the entire class as
final
.
If there are two fields with the same name in a subclass and superclass you can differentiate the two with this.fieldName
and super.fieldName
respectively.
If there are two methods with the same signature in a subclass and superclass, the method in the subclass is overriding the method in the superclass which means that you are giving the method a different implementation from the superclass. You can access both implementations in the subclass with this.methodName()
and super.methodName()
respectively.
this
for the current object andsuper
for fields/methods of the parent classsuper()
in the constructor invokes the constructor of the parent class.
Inheritance Example
// Inheritance Example
public class Item {
private Room place;
public Item(Room place) {
this.place = place;
}
public Room getPlace() {
return this.place;
}
public boolean isPortable() {
return false;
}
}
public class Key extends Item {
private Door door;
public Key(Room place, Door door) {
super(place); // calls the constructor of the superclass: Item
this.door = door; // sets a value for door.
}
@Override
public boolean isPortable() {
return true; // overriding a method of a superclass to have a different result.
}
public boolean opens(Door door) {
return this.door.equals(door);
}
}
Polymorphism
Polymorphism
Polymorphism is an OOP feature that allows sub-classes to have methods of the same name (of the same superclass) but with a different implementation.
Example
// Polymorphism Example
class Animal {
public void animalSound() {
System.out.println("The animal makes a sound"); // original implementation.
}
}
class Pig extends Animal {
@Override
public void animalSound() {
System.out.println("The pig says: wee wee"); // different implementation for Pig
}
}
class Dog extends Animal {
@Override
public void animalSound() {
System.out.println("The dog says: bow wow"); // different implementation for Dog.
}
}
Overloading vs Overriding
Overloading (also called static polymorphism)
- Methods in a class with the same name but different signature (different number or type of parameters)
- Use sparingly, and only if confusion is unlikely
Overriding (also called dynamic polymorphism)
- Methods of a subclass with the same signature of a method of the parent class
- Use
@Override
tags in front of overriding method (not when overloading) - Improves maintainability: fewer mistakes! Good practice
Constructors
Method without a return type, with the same name as the class that creates a new object of the class.
- Constructors can be overloaded but are not inherited
- All classes have a constructor
- When omitted, the class has an implicit (default) empty-argument constructor
- Every constructor calls another constructor
- When omitted, implicitly calls super() (most frequent case)
- Only valid if superclass has a constructor without parameters!
Good practices
- Constructor initializes all fields that need a non-default value
- Constructor does not perform extensive computation
Example
public class Point2D {
protected Point2D() {
// empty
}
public Point2D(int x, int y) {
this(); // calls empty constructor.
move(x, y);
}
}
public class Point3D extends Point2D {
public Point3D() {
// empty
}
public Point3D(int x, int y, int z) {
super(x, y); // calls Point2D constructor
this.z = z;
}
}
Abstract Methods And Classes
An abstract method is a method without a body
public abstract class SomeAbstractClass {
public abstract void doSomething(int someNumber);
protected abstract double computeSomething(int numberOne, double numberTwo);
}
abstract
methods must be in anabstract
class.abstract
classes are “incomplete”/“partial” and typically contain “base” functionality- Subclasses must either implement the
abstract
methods or also beabstract
- You cannot instantiate an
abstract
class but anabstract
class has a constructor
Interfaces
An interface
is a special type: only has specification, no implementation.
- All variables are
public final static
- Methods in interfaces do not have a body, unless they are marked with
default
. - Classes can
extend
one class andimplement
multiple interfaces - Interfaces can
extend
multiple interfaces
Syntax
interface MyInterface {
public static int myInt = 0;
/**
* Specification 1 ...
*/
public void myMethod1(); // <-- a semicolon instead of the method body
/**
* Specification 2 ...
*/
public int myMethod2(int i, int j);
}
Implementation Example
interface Item {
Room getPlace();
boolean isPortable();
}
public class Key implements Item {
private Room place;
private Door door;
public Key(Door door) {
this.door = door;
}
public Room getPlace() {
return place;
}
public boolean isPortable() {
return true;
}
public boolean opens(Door door) {
return this.door.equals(door);
}
}
implemented methods have the same signature(method names, result, parameter types) and visibility
implementation has additional methods, fields and constructor.
Java Data-structures implementation
Java has no native list data structures, only arrays. The Collections
library includes many useful data structures including Lists
.
- All list types have a common interface
List
List
defines many methods. Some of them are:
interface List {
boolean add(Object e);
boolean remove(Object e);
boolean contains(Object e);
Object get(int index);
Object set(int index, Object e);
Object remove(int index);
int size();
}
UML Notation
For implementing an interface a dotted arrow is drawn instead of the solid arrow for extending.
Combining Interfaces And Inheritance
Since interfaces extend from each other the correct notation in uml is a solid arrow from the subinterface to the superinterface.
Multiple Inheritance
- In many programming languages, a class can extend multiple classes (ex. C++)
- This leads to the diamond problem:
In the Werewolf
class the speak()
method is ambiguous.
Inheritance vs Interface.
Feature | Interface | Class | abstract Class |
---|---|---|---|
method implementation necessary | no | yes | no if abstract method. |
method implementation possible | limited; with default. | yes | yes |
non-final fields | no | yes | yes |
private/protected members | no | yes | yes |
Options for extending/implenting:
- Classes can implement multiple interfaces
- Extending interfaces can be combined with extending one class.
Purpose of abstract classes vs interfaces:
- Abstract classes are a basis for subclasses with shared behaviour
- Interfaces are specifications, describing the behavior an implementing class will have.
Inheritance vs Composition
Also called the OOP composite reuse principle.
composite reuse principle
is the principle that classes should achieve polymorphic behavior and code reuse by their composition (by containing instances of other classes that implement the desired functionality) rather than inheritance from a base or parent class. (Wikipedia)
Composition | Inheritance |
---|---|
rely on other object(s) to provide (some) functionality | inherit fields and methods from another class |
Composition is often more appropriate | Use when there is a clear parent-child relationship of concepts (is-a relationship) |
Use when only using parts of the functionality of another class (has-a or uses-a relationship) | Use to alter the behavior of a class |
Use when you want to reuse the entire interface of the superclass |
Object Class
All classes are derived from the base class Object
. All classes inherit its methods:
public boolean equals(Object o) // check for “equality”
public int hashCode() // compute unique representation.
public String toString() // create a String representation
- These methods have a default implementation.
- Often it is a good idea to override these inherited methods
- The default
toString()
method returns a String that represents the internal reference to the instance:SomeObjectClassname@hashcodenumber
.
Types
Reference types (Objects) carry a subtyping relation: one type can be a subtype of another.
Subtyping is a partial order (meaning reflexive and transitive)
- Reflexive: every type is a subtype of itself:
A
is a subtype ofA
- Transitive: if
A
is a subtype ofB
andB
is a subtype ofC
, thenA
is a subtype ofC
. - if
S
is a subtype ofT
, then we useS
whenT
is expected (Whenever a value of a given type is expected, a subtype can be used)
Subtyping in Java
In each of these cases the class B
is a subtype of A
. And therefore B
can be used when A
is expected.
class B
implements A
implements
keyword should be used.
class B
extends A
extends
keyword should be used.
interface B
extends A
extends
keyword should be used.
Static vs Dynamic Type Of an Expression
Static type: that which the compiler can infer during “compile-time”, Also called declared type.
The Java compiler will not do (potentially complicated) type inferencing: it will treat variables as Type
when they are declared as Type
even if at runtime it will contain a different type.
Dynamic type: that which the value actually has during “run-time”, Also called actual type or run-time type.
- The dynamic type is always a subtype of the static type
- What the compiler considers correct is based on the static type.
Dynamic Type Test
How to find out the dynamic type of an expression expr during run time?
- Type test:
instanceof Type
- This yields true if the dynamic type of an expression is a subtype of
Type
null instanceof Type
is alwaysfalse
.
Key k1 = new Key();
Item i1 = k1;
Item i2 = new FireArm();
Item i3 = null;
System.out.println(k1 instanceof Key); // true
System.out.println(k1 instanceof Item); // true
System.out.println(i1 instanceof Key); // true
System.out.println(i1 instanceof Item); // true
System.out.println(i2 instanceof Key); // false
System.out.println(i2 instanceof Item); // true
System.out.println(i3 instanceof Item); // false
Casting
How to tell the compiler the actual type of an expression expr? To be able to call methods that are not available for the super type.
Type cast: (Type) expr
changes the static type to Type
- Only correct if dynamic type of expr is a subtype of Type
- You cannot change the dynamic type of an expression
Watch the parentheses
((Key) i1).opens(door)
is correct:((Key) i1)
has type “Key”.(Key) i1.opens(door)
is wrong: tries to cast the result from the method.
Example
public class Player {
private Item item;
// other code
/** Tests if item is a Key. */
public boolean hasKey() {
return item instanceof Key;
}
/** Returns the item if it is a key, otherwise null. */
public Key getKey() {
return item instanceof Key ? (Key) item : null;
}
/** Fires the firearm, if item is a firearm. */
public boolean fire() {
return item instanceof FireArm && ((FireArm) item).fire();
}
}