You are on page 1of 44

JAVA Development

Inheritance, Interfaces,
Abstract, Polymorphism
Inheritance
Inheritance - extends

- A class can extend another class:

class Dog extends Animal { ... }

- Terminology:
- superclass (parent, base) - the more generic class (Animal here)
- subclass (child, derived) - the more specific class (Dog here)

- Effect: the subclass inherits all members (fields,methods) of the superclass


- constructors are not inherited, but can be accessed
- the subclass can also define its own members (besides inherited ones)

- Purpose: another way to reuse code (beside composition..)


Inheritance - extends example

For example, if we have this Animal A Dog subclass inherits all properties
base class: and behavior of Animal:

class Animal { class Dog extends Animal {


int age; int breed;
String name; }

void eat(Food food) { //…


System.out.println(name + Dog dog = new Dog();
" is eating " + food); dog.breed = 1; //has own properties
}
} //also inherited all members of Animal
dog.eat(new Food("meat"));
dog.age = 2;
Inheritance - instanceof, cast

If Dog is a subclass of Animal, we say that an instance of a Dog is an Animal.

- You can perform this “is-a” check using the instanceof operator.
- Once you know that a variable declared of a more generic type actually holds a value of a
more specific subtype, you can use casting - with (subtype) - to treat it as of that type
- Warning: if the value is not really of that subtype, casting will result in an exception!

Note: using instanceof / casting is strongly discouraged !


(when it appears in code, it’s usually a sign of bad design, and can almost always be avoided)

Animal animal = new Dog(); //we built a Dog instance, but var type only knows of Animal
boolean isAnimal = animal instanceof Animal; //true
boolean isDog = animal instanceof Dog; //true
boolean isCat = animal instanceof Cat; //false (if Dog doesn’t extend Cat..)

Dog animalAsDog = (Dog) animal; //casting (forcing) an Animal instance to Dog


Cat animalAsCat = (Cat) animal; //compiles,but Exception at runtime! (a Dog is not a Cat)
Inheritance - ‘is-a’ for variables

A variable of type T can be assigned instances of any subclass of T, meaning


anything that “is-a” instance of T:

//Both assignments are valid (as a Cat/Dog is an Animal)


Animal a;
a = new Dog("Azor");
a = new Cat("Spot");
//after this the variable ‘a’ holds an instance of Cat, but code can see/use it only
//as the declared type Animal; as this is a base class/more generic, it means
//we cannot access Cat specific members through it (at least not without a forced cast to Cat)

//But not the other way around (as a generic Animal is not a Dog)
Dog d = new Animal(); //=> compiler error: incompatible types
Inheritance - ‘is a’ for methods params

The same goes for methods: a method parameter of type T can receive any value
that “is a” T (any subtype of T):

void clean(Animal a){…} //method declared to handle any type of Animal, in generic way

clean(new Dog("Fido")); //method receives a Dog, but should handle it only as an Animal
clean(new Cat("Spot")); //same here: it shouldn’t care about the exact Animal type received
Inheritance - multi level

You can extend classes that This generates many “is-a”


extend other classes: relationships:

class Organism {}

class Plant extends Organism {} ● A Plant is an Organism


class Animal extends Organism {} ● An Animal is an Organism

class Dog extends Animal {} ● A Dog is an Animal (& Organism)


class Cat extends Animal {} ● A Cat is an Animal (& Organism)

class ShowDog extends Dog {} ● A ShowDog is a Dog (& Animal &


Organism)
Inheritance - hierarchy

A tree of related classes is called


a class hierarchy.

Note: generally, it’s a good idea to avoid


deep class hierarchies.
super keyword

● somehow similar to this but instead of access to the whole current instance, it
refers/gives access only to fields/methods and constructors from parent class!
○ especially needed when there is a member with same name in current subclass

● can access all non-private members (public, default, protected)


● cannot access static members through it, since it’s strictly tied to the instance
(and static members are not really inherited)
super - example

class Animal { class Dog extends Animal {


boolean alive = true;
Animal() {System.out.println("Animal built");} Dog() {
void eat(){System.out.println("Animal eats");} super(); //call parent constructor
System.out.println("Dog built");
}
}

public static void main(String[] args) { void eat() {


Dog dog = new Dog(); super.eat(); //call parent method
dog.eat(); System.out.println("Dog eats");
dog.printAlive(); }
}
void printAlive() {
Output: //access parent field
Animal built System.out.println(
Dog built "Alive? " + super.alive);
Animal eats }
Dog eats }
Alive? true
Interfaces
Interfaces - definition

- Like classes, interfaces describe a behavior through a set of methods:


Eg: “A shape is something that: can compute its area & perimeter, can be enlarged.”

interface Shape {
double computeArea(); //note the ‘;’ here, instead of implementation!
double computePerimeter();
void enlarge(double factor);
}

- But interfaces contain no method implementations, only the declarations!


- They also cannot have state, so no fields!
Interfaces - usage

A class may implement one or more interfaces (but it may extend only one base class)

class MyClass [extends BaseClass]


implements Interface1, Interface2,… {
//implementations for methods of all interfaces…
}

A class implementing an interface must define implementations for all the methods
declared in the interface.
- An interface basically defines a contract for classes implementing it
- A class implementing multiple interfaces just means that it has to fulfill multiple
contracts at the same time (define all missing methods)
Interfaces - usage example

class Square implements Shape {


double edge;

//implementations for methods of Shape


public double computeArea() { return this.edge * this.edge; }
public double computePerimeter() { return this.edge * 4; }
public void enlarge(double factor) { this.edge *= factor; }
}

The “is-a” relationship applies for interfaces as well:


● A Square is a Shape
Interfaces - default methods (Java 8)

Starting from Java 8, interfaces may also contain method implementations!


- these are called default methods, and must be prefixed by ‘default’ keyword.
- classes implementing the interface may still choose to provide alternate implementations for
such methods, but are not forced to do so (will use the default implementation in this case)

Note: even if interfaces can have method implementations now, they still cannot have fields!

interface Shape {
double computeArea(); //an abstract method, requires implementation by classes..

//a default method (supported since Java 8); it requires the 'default' prefix here;
//it doesn't require implementation by classes (but may be overridden if needed)
default boolean isBigger(Shape other) {
//default methods may call non-default methods in their body
return this.computeArea() > other.computeArea();
}
}
Abstract Classes
Abstract classes - declaration

abstract - a modifier that can be added to class and method definitions:

abstract class Shape {


abstract double computeArea(); //no implementation allowed here!
}

Rules:
- abstract methods cannot have an implementation (just like in interfaces)
- a class with any abstract methods must also be declared abstract
- even when it just inherits some abstract methods from a parent, but does
not implement them (leaving them for children to implement)
Abstract classes - use

Like a base class + an interface at the same time!

Useful when you want to:


- reuse some base functionality (methods+fields, like base classes)
- but also require a common behavior for all subclasses (like interfaces):

abstract class Animal { class Dog extends Animal {


String name;
//must implement abstract methods
//some abstract methods (no impl here) String makeNoise() {
abstract String makeNoise(); return "woof woof";
}
//some regular ones (with impl) }
void printNoise() {
System.out.println(name + //…
" makes noise: " + makeNoise()); Dog dog = new Dog();
} dog.makeNoise(); //call method from Dog
} dog.printNoise(); //call method from base class
Abstract classes - no instantiation

Abstract classes cannot be instantiated directly! (but their subclasses can be..)

Also, classes declared as abstract are not actually required to contain abstract methods (they just may).
=> this means we could use ‘abstract’ on regular classes (no abstract methods) just to prevent instantiation:

//base class declared abstract just to prevent instantiation (avoid building a generic Animal)
//it doesn’t contain any abstract methods!
abstract class Animal {
int age;
void printAge(){/*…*/}
}
class Dog extends Animal {} //derived class, not abstract -> this one can be instantiated

//…
//Animal a1 = new Animal(); //compile error: cannot instantiate abstract class!
Animal a2 = new Dog(); //this is allowed

Note: there are other (better?) ways to just prevent class instantiation (like making constructors protected..)
Polymorphism
Polymorphism

Polymorphism is the capability of objects with the same interface to behave


differently at runtime based on their type.
“same interface, different behavior” (based on actual instance’s subtype)

How it works:
● multiple classes inherit the same base class/interface
● each subclass implements a method differently
● when the method is called, each object may do a different thing
● a caller just knows about the base class, but does not know what the runtime
behavior will be (or what are the actual types of the instances it uses)

Note: Polymorphism depends on inheritance!


Polymorphism - example

Example 1: Compute the area of a list of shapes:


interface Shape {
double computeArea(); //any kind of Shape needs to defined this
}

class Square implements Shape {


double edge;
Square(double edge) { this.edge = edge; }
double computeArea() { return edge * edge; } //square specific implementation
}

class Circle implements Shape {


double radius;
Circle(double radius) { this.radius = radius; }
double computeArea() { return 3.14 * radius * radius; } //circle implementation
}
Polymorphism - example

//have an array with a few different shapes


Shape[] shapes = { new Square(10), new Circle(4), … };

double totalArea = 0;

for (Shape shape : shapes) {


//we don’t know/care how each shape’s area is computed!
totalArea += shape.computeArea();
}
Polymorphism - other example

Example 2: Publish a notification

abstract class MessageNotifier { abstract void notify(String message); }

class SMSMessageNotifier extends MessageNotifier {…}


class EmailMessageNotifier extends MessageNotifier {…}
class FacebookMessageNotifier extends MessageNotifier {…}

A caller does not know what will actually happen, he just wants to send a notification
when appropriate:

MessageNotifier notifier = …

if (shouldGreet) { notifier.notify("Hello!"); }
Overloading,
Overriding
Methods - signature, overloading

- A method’s signature is the combination of its: {name + parameters}


- for the parameters, what matters is: the number, order and types (but not
the names)

- Rule: methods defined in same scope (class) must have unique signatures!
- return type is not part of the signature -> we cannot have 2 methods
different just by return type (as compiler wouldn’t know which one to call)

- We are allowed to define multiple methods with same name, as long as they
have different signatures - meaning different parameter lists (type/order)
- this is named ‘overloading’ (of the initial method)
Methods - overloading examples

Examples: /*------ VALID OVERLOADING ------*/


//different params number:
//ORIGINAL method; signature is: print(int) static void print ( int n, int m) {
static void print ( int n) { System.out.println("2 numbers: " + n + ", " + m);
System.out.println("A number: " + n); }
}
//different param types:
static void print ( String s, int n) {
System.out.println("string and number: " + s + ", " + n);
/*------ INVALID OVERLOADING ------*/
}
//same params but just different names
//different param order:
static void print ( int x) { //-> compile error: already defined
static void print ( int n, String s) {
/*...*/
System.out.println("number and string: " + n + ", " + s);
}
}
//same name+params, just different return type
static int print ( int x) { //-> compile error: already defined //different param types; can also have different return type
/*...*/ static String print ( String s) {
} System.out.println("A string: " + s); return s;
}
Methods - Overriding

● If a subclass inherits a method from a superclass, then it may choose to


“override” the inherited method (if not marked as ‘final’ in superclass)

● Override = ‘redefining’ a method implementation by redeclaring it with


exactly the same definition (name+parameters+return type) as in
superclass, but with a different implementation (body)

● Benefits: ability to redefine a behavior that's specific to the subclass type


Overriding

Example:

class Animal {
//prints ‘animal sound’
void makeNoise() {
new Animal().makeNoise();
System.out.println("animal sound");
}
//prints ‘woof’ (calls overridden method!)
}
new Dog().makeNoise();

//this also prints ‘woof’!


class Dog extends Animal {
//(as the object is really a Dog, even if
//overrides method in base class, as it
//variable/reference is declared as Animal)
//has same signature
Animal animal = new Dog();
void makeNoise() {
animal.makeNoise();
System.out.println("woof woof");
}
}
Overriding - rules

● signatures must match:


○ name + parameters - should be exactly the same as for the original method;
○ return type - should be the same (or a subtype) as the return type of original;

● access level cannot be more restrictive than for original method


■ example: public methods from base class cannot be overridden with private level
■ but protected methods can be overridden with public level

● cannot override:
○ constructors
○ methods declared final
○ methods declared static (but can be re-declared)
○ methods which are not visible/cannot be inherited
■ example: a subclass in a different package can only override the non-final methods declared
public or protected;
‘final’ keyword

‘final’ keyword can be used in multiple contexts, like for:


- field/variable -> constant, can be assigned a value only once (no reassignments)
- method parameter -> immutable, reassignment not allowed (in method body)
- method -> not allowed to override this method (in subclasses)
- class -> not allowed to extend this class

class Base { class SomeClass {


void method1() {} //regular method final int field1 = 1; //final field
final void method2() {} //final method
} void method1(final int p1, int p2){ //final param
class Sub1 extends Base {
//p1++; //NOT allowed (final param)
void method1() {/*...*/} //overridden
p2++; //allowed (but NOT recommended!)
//void method2(){/*...*/} //NOT allowed (final method)
} //this.field1 = 2; //NOT allowed (final field)

final class Sub2 extends Sub1 {} //ok, final class final int localVar = 2;
//class Sub3 extends Sub2{} //NOT allowed (final class) //localVar = 3; //NOT allowed (final var))
}
}
Overriding - not for fields

Overriding does not apply to fields inherited from a parent (only to methods)
- But: it’s possible to declare a new field in a subclass, with the same name and type as in parent
- this will basically ‘hide’ the parent field while in the scope of the subclass
- both fields exist in the subclass, and may hold different values; they are both accessible:
- with “this.” (or no prefix) -> you access the field copy from subclass
- with “super.” prefix -> you access the hidden field from parent class!
- While allowed, this is really NOT RECOMMENDED! (as it can lead to very confusing code)
class Base {
int field = 0;
void method1() { System.out.println("base: someField= " + someField); }
}
class SubClass extends Base {
int field = 1; //field with same name, just hides field from parent - NOT recommended!
void method2(int field) { //also a method parameter with same name!
System.out.println("field param: " + field); //using no prefix => the method param!
System.out.println("field from this class: " + this.someField); //using this. => the field of this class
System.out.println("field from base class: " + super.someField); //using super. => the hidden field of parent!
}
}

new SubClass().method2(2); //prints 3 DIFFERENT values for field! (2, 1, 0)
Inheritance - static members

Subclasses “inherit” static members, but don’t actually have different copies of them
per each class level:

public class Printer {static void print(String s){/*...*/} }


public class BetterPrinter extends Printer {}

Printer.print("hello world"); //> 1: hello world


BetterPrinter.print("hello again"); //> 2: hello again

- BetterPrinter.print() refers to the same method as Printer.print()


- BetterPrinter.count is the same field as Printer.count
Overriding - not for static members

Overriding does not apply to static methods inherited from a parent


- But: it’s possible to declare a new static method with exact same definition (signature)
- this will just create a new static method on the subclass
- both methods exist and are accessible in the subclass:
- with subclass name (or no prefix) -> you access the subclass method
- with parent class prefix -> you access the parent class method
- This means there is no polymorphism! (for static methods)
class BaseClass { static void m() { System.out.print("I’m in the base class"); } }

class SubClass extends BaseClass {


static void m() { System.out.print("I’m in the sub class"); }

public static void main(String[] args) {


m(); //calls method from same class (SubClass)
SubClass.m(); //same if we use class name
BaseClass.m(); //calls method from BaseClass
}
}
‘@Override’ annotation

@Override
- it’s an annotation (a special label, not an executable statement but considered by the compiler)
- may be used to mark methods which:
- override other methods (from a base class)
- implement abstract methods (from an interface or abstract base class; there is no
separate @Implements annotation...)
- effect: results in a compiler error if the method does not really override another method
- it’s optional (not required by compiler), but strongly recommended to use it, because:
- it makes your code easier to understand by (other) humans
- it prevents mistakes: makes sure you are really overriding your expected method
- it makes code resilient to change: if someone later changes the signature of the
based method or even deletes it, the methods which used to override it will raise
compile errors, and must be updated/fixed (instead of becoming useless or wrong..)
‘@Override’ annotation

class Animal {
void makeNoise() {
System.out.println("generic animal sound");
}
}

class Dog extends Animal {

@Override //optional; ok here! (valid override of base class method)


void makeNoise() {
System.out.println("woof woof");
}

// @Override //results in compile error! (method overrides nothing from base)


void sleep() {
System.out.println("(dog sleeps)");
}
}
java.lang.Object

java.lang.Object - root of the class hierarchy class MyClass {/*...*/}

//ANY class is also of type Object:


MyClass myClass = new MyClass();
- every class has Object as a superclass: System.out.println(myClass instanceof Object);
- all objects, including arrays, inherit
//even arrays! (and String)
the methods of this class int[] array = {1, 2, 3};
System.out.println(array instanceof Object);
- primitives are excluded from this rule System.out.println("string" instanceof Object);

//but NOT primitives:


- contains a list of generic methods which //System.out.println(123 instanceof Object);
are then inherited by all objects (toString, //-> compile error!

equals, hashCode,...) //valid code, no casting needed ('is-a' relation)


- some of them are used internally by Java Object o1 = new MyClass();
Object o2 = "a string";
compiler or standard libraries;
- they can also be used and/or overridden //all classes inherit Object's methods
myClass.toString();
as needed by your code myClass.equals(myClass);
Object.toString()
Object.toString()

The .toString() method is used to convert any object to a String value.

It is declared in the Object class, so it is available for all objects:

public class Object


public String toString() {
return this.getClass().getName() + "@" +
Integer.toHexString(this.hashCode());
}
}

The default implementation is not very useful, but at least it tells us the type:
dog.toString(); //-> week4.Dog@60e53b93
Object.toString()

Override this method in your classes to make it more meaningful:

class Point {
int x, y;

@Override
public String toString() {
return "Point( x=" + x + ", y=" + y + ")";
}
}

Tip: IntelliJ IDEA can generate it: right click / Generate… / toString()
Object.toString() - usage

- to use it, you may call it explicitly:


System.out.println(new Point(0, 0).toString());

- it will also be automatically called when a String value is expected - like when
adding anything to an existing String:

Point p = new Point(3, 4);


System.out.println("The point is: " + p);

//is equivalent to:


System.out.println("The point is: " + p.toString());
Questions?
Extra reading:

http://tutorials.jenkov.com/java/inheritance.html
http://tutorials.jenkov.com/java/abstract-classes.html
http://tutorials.jenkov.com/java/interfaces.html
http://tutorials.jenkov.com/java/interfaces-vs-abstract-classes.html
https://www.journaldev.com/1775/multiple-inheritance-in-java

http://www3.ntu.edu.sg/home/ehchua/programming/java/j3b_oopinheritancepolymorphism.html

You might also like