Can use basic inheritance
Given below is an extract from the -- Java Tutorial, with slight adaptations.
A class that is derived from another class is called a subclass (also a derived class, extended class, or child class). The class from which the subclass is derived is called a superclass (also a base class or a parent class).
A subclass inherits all the members (fields, methods, and nested classes) from its superclass. Constructors are not members, so they are not inherited by subclasses, but the constructor of the superclass can be invoked from the subclass.
Every class has one and only one direct superclass (single inheritance), except the Object
class, which has no superclass, . In the absence of any other explicit superclass, every class is implicitly a subclass of Object
. Classes can be derived from classes that are derived from classes that are derived from classes, and so on, and ultimately derived from the topmost class, Object
. Such a class is said to be descended from all the classes in the inheritance chain stretching back to Object
. Java does not support multiple inheritance among classes.
The java.lang.Object
class defines and implements behavior common to all classes—including the ones that you write. In the Java platform, many classes derive directly from Object
, other classes derive from some of those classes, and so on, forming a single hierarchy of classes.
The keyword extends
indicates one class inheriting from another.
Here is the sample code for a possible implementation of a Bicycle
class and a MountainBike
class that is a subclass of the Bicycle
:
public class Bicycle {
public int gear;
public int speed;
public Bicycle(int startSpeed, int startGear) {
gear = startGear;
speed = startSpeed;
}
public void setGear(int newValue) {
gear = newValue;
}
public void applyBrake(int decrement) {
speed -= decrement;
}
public void speedUp(int increment) {
speed += increment;
}
}
public class MountainBike extends Bicycle {
// the MountainBike subclass adds one field
public int seatHeight;
// the MountainBike subclass has one constructor
public MountainBike(int startHeight, int startSpeed, int startGear) {
super(startSpeed, startGear);
seatHeight = startHeight;
}
// the MountainBike subclass adds one method
public void setHeight(int newValue) {
seatHeight = newValue;
}
}
A subclass inherits all the fields and methods of the superclass. In the example above, MountainBike
inherits all the fields and methods of Bicycle
and adds the field seatHeight
and a method to set it.
Accessing superclass members
If your method overrides one of its superclass's methods, you can invoke the overridden method through the use of the keyword super
. You can also use super
to refer to a (although hiding fields is discouraged).
Consider this class, Superclass
and a subclass, called Subclass
, that overrides printMethod()
:
public class Superclass {
public void printMethod() {
System.out.println("Printed in Superclass.");
}
}
public class Subclass extends Superclass {
// overrides printMethod in Superclass
public void printMethod() {
super.printMethod();
System.out.println("Printed in Subclass");
}
public static void main(String[] args) {
Subclass s = new Subclass();
s.printMethod();
}
}
Printed in Superclass.
Printed in Subclass
Within Subclass
, the simple name printMethod()
refers to the one declared in Subclass
, which overrides the one in Superclass
. So, to refer to printMethod()
inherited from Superclass
, Subclass
must use a qualified name, using super
as shown.
Subclass constructors
A subclass constructor can invoke the superclass constructor. Invocation of a superclass constructor must be the first line in the subclass constructor.
The syntax for calling a superclass constructor is super()
(which invokes the no-argument constructor of the superclass) or super(parameters)
(to invoke the superclass constructor with a matching parameter list).
The following example illustrates how to use the super
keyword to invoke a superclass's constructor. Recall from the Bicycle
example that MountainBike
is a subclass of Bicycle
. Here is the MountainBike
(subclass) constructor that calls the superclass constructor and then adds some initialization code of its own (i.e., seatHeight = startHeight;
):
public MountainBike(
int startHeight, int startSpeed, int startGear) {
super(startSpeed, startGear);
seatHeight = startHeight;
}
Note: If a constructor does not explicitly invoke a superclass constructor, the Java compiler automatically inserts a call to the no-argument constructor of the superclass. If the superclass does not have a no-argument constructor, you will get a compile-time error. Object
does have such a constructor, so if Object
is the only superclass, there is no problem.
Access modifiers (simplified)
Access level modifiers determine whether other classes can use a particular field or invoke a particular method. Given below is a simplified version of Java access modifiers, assuming you have not yet started placing your classes in different packages i.e., all classes are placed in the root level. A full explanation of access modifiers is given in a later topic.
There are two levels of access control:
At the class level:
public
: the class is visible to all other classes- no modifier: same as
public
At the member level:
public
: the member is visible to all other classesprotected
: same aspublic
- no modifier: same as
public
private
: the member is not visible to other classes (but can be accessed in its own class)
Background: Suppose we are creating a software to manage various tasks a person has to do. Two types of such tasks are,
- Todos: i.e., things that needs to be done some day e.g., 'Read the book Lord of the Rings'
- Deadlines: i.e., things to be done by a specific date/time e.g., 'Read the text book by Nov 25th'
The Task
class is given below:
public class Task {
protected String description;
public Task(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
- Write a
Todo
class that inherits from theTask
class.- It should have an additional
boolean
fieldisDone
to indicate whether the todo is done or not done. - It should have a
isDone()
method to access theisDone
field and asetDone(boolean)
method to set theisDone
field.
- It should have an additional
- Write a
Deadline
class that inherits from theTodo
class that you implemented in the previous step. It should have,- an additional
String
fieldby
to store the details of when the task to be done e.g.,Jan 25th 5pm
- a
getBy()
method to access the value of theby
field, and a correspondingsetBy(String)
method. - a constructor of the form
Deadline(String description, String by)
- an additional
The expected behavior of the two classes is as follows:
public class Main {
public static void main(String[] args) {
// create a todo task and print details
Todo t = new Todo("Read a good book");
System.out.println(t.getDescription());
System.out.println(t.isDone());
// change todo fields and print again
t.setDone(true);
System.out.println(t.isDone());
// create a deadline task and print details
Deadline d = new Deadline("Read textbook", "Nov 16");
System.out.println(d.getDescription());
System.out.println(d.isDone());
System.out.println(d.getBy());
// change deadline details and print again
d.setDone(true);
d.setBy("Postponed to Nov 18th");
System.out.println(d.isDone());
System.out.println(d.getBy());
}
}
Read a good book
false
true
Read textbook
false
Nov 16
true
Postponed to Nov 18th
Hint
Can use Object class
As you know, all Java objects inherit from the Object
class. Let us look at some of the useful methods in the Object
class that can be used by other classes.
The toString
method
Every class inherits a toString
method from the Object
class that is used by Java to get a string representation of the object e.g., for printing. By default, it simply returns the type of the object and its address (in hexadecimal).
Suppose you defined a class called Time
, to represent a moment in time. If you create a Time
object and display it with println:
class Time {
int hours;
int minutes;
int seconds;
Time(int hours, int minutes, int seconds) {
this.hours = hours;
this.minutes = minutes;
this.seconds = seconds;
}
}
Time t = new Time(5, 20, 13);
System.out.println(t);
Time@80cc7c0
(the address part can vary)
You can override the toString
method in your classes to provide a more meaningful string representation of the objects of that class.
Here's an example of overriding the toString
method of the Time
class:
class Time{
//...
@Override
public String toString() {
return String.format("%02d:%02d:%02d\n",
this.hours, this.minutes, this.seconds);
}
}
Time t = new Time(5, 20, 13);
System.out.println(t);
05:20:13
@Override
is an optional annotation you can use to indicate that the method is overriding a method from the parent class.
The equals
method
There are two ways to check whether values are equal: the ==
operator and the equals
method. With objects you can use either one, but they are not the same.
- The
==
operator checks whether objects are identical; that is, whether they are the same object. - The
equals
method checks whether they are equivalent; that is, whether they have the same value.
The definition of identity is always the same, so the ==
operator always does the same thing.
Consider the following variables:
Time time1 = new Time(9, 30, 0);
Time time2 = time1;
Time time3 = new Time(9, 30, 0);
- The assignment operator copies references, so
time1
andtime2
refer to the same object. Because they are identical,time1 == time2
istrue
. - But
time1
andtime3
refer to different objects. Because they are not identical,time1 == time3
isfalse
.
By default, the equals
method inherited from the Object
class does the same thing as ==
. As the definition of equivalence is different for different classes, you can override the equals
method to define your own criteria for equivalence of objects of your class.
Here's how you can override the equals
method of the Time
class to provide an equals
method that considers two Time
objects equivalent as long as they represent the same time of the day:
public class Time {
int hours;
int minutes;
int seconds;
// ...
@Override
public boolean equals(Object o) {
Time other = (Time) o;
return this.hours == other.hours
&& this.minutes == other.minutes
&& this.seconds == other.seconds;
}
}
Time t1 = new Time(5, 20, 13);
Time t2 = new Time(5, 20, 13);
System.out.println(t1 == t2);
System.out.println(t1.equals(t2));
false
true
Note that a proper equals
method implementation is more complex than the example above. See the article How to Implement Java’s equals Method Correctly by Nicolai Parlog for a detailed explanation before you implement your own equals
method.
Suppose you have the following classes Task
, Todo
, Deadline
:
public class Task {
protected String description;
public Task(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
public class Todo extends Task {
protected boolean isDone;
public Todo(String description) {
super(description);
isDone = false;
}
public void setDone(boolean done) {
isDone = done;
}
public boolean isDone() {
return isDone;
}
}
public class Deadline extends Todo {
protected String by;
public Deadline(String description, String by) {
super(description);
this.by = by;
}
public void setBy(String by) {
this.by = by;
}
public String getBy() {
return by;
}
}
Override the toString
method of the three classes to produce the following behavior.
public class Main {
public static void main(String[] args) {
// create a todo task and print it
Todo t = new Todo("Read a good book");
System.out.println(t);
// change todo fields and print again
t.setDone(true);
System.out.println(t);
// create a deadline task and print it
Deadline d = new Deadline("Read textbook", "Nov 16");
System.out.println(d);
// change deadline details and print again
d.setDone(true);
d.setBy("Postponed to Nov 18th");
System.out.println(d);
}
}
description: Read a good book
is done? No
description: Read a good book
is done? Yes
description: Read textbook
is done? No
do by: Nov 16
description: Read textbook
is done? Yes
do by: Postponed to Nov 18th
You can use the super.toString
from the subclass to invoke the behavior of the method you are overriding. This is useful here because the overriding method is simply adding onto the behavior of the overridden method.
Hint
Can use polymorphism in Java
Java is a strongly-typed language which means the code works with only the object types that it targets.
The following code PetShelter
keeps a list of Cat
objects and make them speak
. The code will not work with any other type, for example, Dog
objects.
public class PetShelter {
private static Cat[] cats = new Cat[]{
new Cat("Mittens"),
new Cat("Snowball")};
public static void main(String[] args) {
for (Cat c: cats){
System.out.println(c.speak());
}
}
}
Mittens: Meow
Snowball: Meow
The Cat
class
This strong-typing can lead to unnecessary verbosity caused by repetitive similar code that do similar things with different object types.
If the PetShelter
is to keep both cats and dogs, you'll need two arrays and two loops:
public class PetShelter {
private static Cat[] cats = new Cat[]{
new Cat("Mittens"),
new Cat("Snowball")};
private static Dog[] dogs = new Dog[]{
new Dog("Spot")};
public static void main(String[] args) {
for (Cat c: cats){
System.out.println(c.speak());
}
for(Dog d: dogs){
System.out.println(d.speak());
}
}
}
Mittens: Meow
Snowball: Meow
Spot: Woof
The Dog
class
A better way is to take advantage of polymorphism to write code that targets a superclass so that it works with any subclass objects.
The PetShelter2
uses one data structure to keep both types of animals and one loop to make them speak. The code targets the Animal
superclass (assuming Cat
and Dog
inherits from the Animal
class) instead of repeating the code for each animal type.
public class PetShelter2 {
private static Animal[] animals = new Animal[]{
new Cat("Mittens"),
new Cat("Snowball"),
new Dog("Spot")};
public static void main(String[] args) {
for (Animal a: animals){
System.out.println(a.speak());
}
}
}
Mittens: Meow
Snowball: Meow
Spot: Woof
The Animal
, Cat
, and Dog
classes
Explanation: Because Java supports polymorphism, you can store both Cat
and Dog
objects in an array of Animal
objects. Similarly, you can call the speak
method on any Animal
object (as done in the loop) and yet get different behavior from Cat
objects and Dog
objects.
Suggestion: try to add an Animal
object (e.g., new Animal("Unnamed")
) to the animals
array and see what happens.
Polymorphic code is better in several ways:
- It is shorter.
- It is simpler.
- It is more flexible (in the above example, the
main
method will work even if we add more animal types).
Exercises
The Main
class below keeps a list of Circle
and Rectangle
objects and prints the area (as an int
value) of each shape when requested.
Add the missing variables/methods to the code below so that it produces the output given.
public class Main {
//TODO add your methods here
public static void main(String[] args) {
addShape(new Circle(5));
addShape(new Rectangle(3, 4));
addShape(new Circle(10));
printAreas();
addShape(new Rectangle(4, 4));
printAreas();
}
}
78
12
314
78
12
314
16
Circle
class and Rectangle
class is given below but you'll need to add a parent class Shape
.
public class Circle extends Shape {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public int area() {
return (int)(Math.PI * radius * radius);
}
}
public class Rectangle extends Shape{
private int height;
private int width;
public Rectangle(int height, int width){
this.height = height;
this.width = width;
}
@Override
public int area() {
return height * width;
}
}
You may use an array of size 100 to store the shapes.
Partial solution
Hint
Can use abstract classes and methods
In Java, an abstract method is declared with the keyword abstract
and given without an implementation. If a class includes abstract methods, then the class itself must be declared abstract.
The speak
method in this Animal
class is abstract
. Note how the method signature ends with a semicolon and there is no method body. This makes sense as the implementation of the speak
method depends on the type of the animal and it is meaningless to provide a common implementation for all animal types.
public abstract class Animal {
protected String name;
public Animal(String name){
this.name = name;
}
public abstract String speak();
}
As one method of the class is abstract
, the class itself is abstract
.
An abstract class is declared with the keyword abstract
. Abstract classes can be used as reference type but cannot be instantiated.
This Account
class has been declared as abstract although it does not have any abstract methods. Attempting to instantiate Account
objects will result in a compile error.
public abstract class Account {
int number;
void close(){
//...
}
}
Account a;
OK to use as a type
a = new Account();
Compile error!
In Java, even a class that does not have any abstract methods can be declared as an abstract class.
When an abstract class is subclassed, the subclass should provide implementations for all of the abstract methods in its superclass or else the subclass must also be declared abstract.
The Feline
class below inherits from the abstract class Animal
but it does not provide an implementation for the abstract method speak
. As a result, the Feline
class needs to be abstract too.
public abstract class Feline extends Animal {
public Feline(String name) {
super(name);
}
}
The DomesticCat
class inherits the abstract Feline
class and provides the implementation for the abstract method speak
. As a result, it need not be (but can be) declared as abstract.
public class DomesticCat extends Feline {
public DomesticCat(String name) {
super(name);
}
@Override
public String speak() {
return "Meow";
}
}
Animal a = new Feline("Mittens");
Compile error!Feline
is abstract.Animal a = new DomesticCat("Mittens");
OK.DomesticCat
can be instantiated and assigned to a variable ofAnimal
type (the assignment is allowed by polymorphism).
Exercises
The Main
class below keeps a list of Circle
and Rectangle
objects and prints the area (as an int
value) of each shape when requested.
public class Main {
private static Shape[] shapes = new Shape[100];
private static int shapeCount = 0;
public static void addShape(Shape s){
shapes[shapeCount] = s;
shapeCount++;
}
public static void printAreas(){
for (int i = 0; i < shapeCount; i++){
shapes[i].print();
}
}
public static void main(String[] args) {
addShape(new Circle(5));
addShape(new Rectangle(3, 4));
addShape(new Circle(10));
addShape(new Rectangle(4, 4));
printAreas();
}
}
Circle of area 78
Rectangle of area 12
Circle of area 314
Rectangle of area 16
Circle
class and Rectangle
class is given below:
public class Circle extends Shape {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public int area() {
return (int)(Math.PI * radius * radius);
}
@Override
public void print() {
System.out.println("Circle of area " + area());
}
}
public class Rectangle extends Shape {
private int height;
private int width;
public Rectangle(int height, int width){
this.height = height;
this.width = width;
}
@Override
public int area() {
return height * width;
}
@Override
public void print() {
System.out.println("Rectangle of area " + area());
}
}
Add the missing Shape
class as an abstract class with two abstract methods.
Partial solution
Statements about abstract classes
Can use interfaces in Java
The text given in this section borrows some explanations and code examples from the -- Java Tutorial.
In Java, an interface is a reference type, similar to a class, mainly containing method signatures. Defining an interface is similar to creating a new class except it uses the keyword interface
in place of class
.
Here is an interface named DrivableVehicle
that defines methods needed to drive a vehicle.
public interface DrivableVehicle {
void turn(Direction direction);
void changeLanes(Direction direction);
void signalTurn(Direction direction, boolean signalOn);
// more method signatures
}
Note that the method signatures have no braces ({ }
) and are terminated with a semicolon.
Interfaces cannot be instantiated—they can only be implemented by classes. When an instantiable class implements an interface, indicated by the keyword implements
, it provides a method body for each of the methods declared in the interface.
Here is how a class CarModelX
can implement the DrivableVehicle
interface.
public class CarModelX implements DrivableVehicle {
@Override
public void turn(Direction direction) {
// implementation
}
// implementation of other methods
}
An interface can be used as a type e.g., DrivableVehicle dv = new CarModelX();
.
Interfaces can inherit from other interfaces using the extends
keyword, similar to a class inheriting another.
Here is an interface named SelfDrivableVehicle
that inherits the DrivableVehicle
interface.
public interface SelfDrivableVehicle extends DrivableVehicle {
void goToAutoPilotMode();
}
Note that the method signatures have no braces and are terminated with a semicolon.
Furthermore, Java allows multiple inheritance among interfaces. A Java interface can inherit multiple other interfaces. A Java class can implement multiple interfaces (and inherit from one class).
The design below is allowed by Java. In case you are not familiar with UML notation used: solid lines indicate normal inheritance; dashed lines indicate interface inheritance; the triangle points to the parent.
Staff
interface inherits (note the solid lines) the interfacesTaxPayer
andCitizen
.TA
class implements bothStudent
interface and theStaff
interface.- Because of point 1 above,
TA
class has to implement all methods in the interfacesTaxPayer
andCitizen
. - Because of points 1,2,3, a
TA
is aStaff
, is aTaxPayer
and is aCitizen
.
Interfaces can also contain constants and static methods.
This example adds a constant MAX_SPEED
and a static method isSpeedAllowed
to the interface DrivableVehicle
.
public interface DrivableVehicle {
int MAX_SPEED = 150;
static boolean isSpeedAllowed(int speed){
return speed <= MAX_SPEED;
}
void turn(Direction direction);
void changeLanes(Direction direction);
void signalTurn(Direction direction, boolean signalOn);
// more method signatures
}
Interfaces can contain default method implementations and nested types. They are not covered here.
The Main
class below passes a list of Printable
objects (i.e., objects that implement the Printable
interface) for another method to be printed.
public class Main {
public static void printObjects(Printable[] items) {
for (Printable p : items) {
p.print();
}
}
public static void main(String[] args) {
Printable[] printableItems = new Printable[]{
new Circle(5),
new Rectangle(3, 4),
new Person("James Cook")};
printObjects(printableItems);
}
}
Circle of area 78
Rectangle of area 12
Person of name James Cook
Classes Shape
, Circle
, and Rectangle
are given below:
public abstract class Shape {
public abstract int area();
}
public class Circle extends Shape implements Printable {
private int radius;
public Circle(int radius) {
this.radius = radius;
}
@Override
public int area() {
return (int)(Math.PI * radius * radius);
}
@Override
public void print() {
System.out.println("Circle of area " + area());
}
}
public class Rectangle extends Shape implements Printable {
private int height;
private int width;
public Rectangle(int height, int width){
this.height = height;
this.width = width;
}
@Override
public int area() {
return height * width;
}
@Override
public void print() {
System.out.println("Rectangle of area " + area());
}
}
Add the missing Printable
interface. Add the missing methods of the Person
class given below.
public class Person implements Printable {
private String name;
// todo: add missing methods
}
Partial solution