Professional Documents
Culture Documents
Inheritance, Interfaces,
Abstract, Polymorphism
Inheritance
Inheritance - extends
- Terminology:
- superclass (parent, base) - the more generic class (Animal here)
- subclass (child, derived) - the more specific class (Dog here)
For example, if we have this Animal A Dog subclass inherits all properties
base class: and behavior of 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!
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..)
//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
class Organism {}
● 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
interface Shape {
double computeArea(); //note the ‘;’ here, instead of implementation!
double computePerimeter();
void enlarge(double factor);
}
A class may implement one or more interfaces (but it may extend only one base class)
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
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
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
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
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)
double totalArea = 0;
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
- 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
Example:
class Animal {
//prints ‘animal sound’
void makeNoise() {
new Animal().makeNoise();
System.out.println("animal sound");
}
//prints ‘woof’ (calls overridden method!)
}
new Dog().makeNoise();
● 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 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:
@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");
}
}
The default implementation is not very useful, but at least it tells us the type:
dog.toString(); //-> week4.Dog@60e53b93
Object.toString()
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
- it will also be automatically called when a String value is expected - like when
adding anything to an existing String:
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