This material is licensed under the Creative Commons BY-NC-SA license, which means that you can use it and distribute it freely so long as you do not erase the names of the original authors. If you make changes in the material and want to distribute this altered version of the material, you have to license it with a similar free license. The use of the material for commercial use is prohibited without a separate agreement.
Authors: Arto Hellas, Matti Luukkainen
Translators to English: Emilia Hjelm, Alex H. Virtanen, Matti Luukkainen, Virpi Sumu, Birunthan Mohanathas, Etiënne Goossens
Extra material added by: Etiënne Goossens, Maurice Snoeren, Johan Talboom
The course is maintained by De Haagse Hogeschool
In java it’s possible to extend classes to add functionality. By extending a class, you take over all the attributes, methods and constructors of the other class. By reusing the class in the other class, you only have to write this code once, and can reuse it in other classes, as if it were part of that class. An example could be classes about animals.
In the animal kingdom are lots of different animals, like a Sparrow
, Seagull
, Cow
and Pig
. All of these animals can make sounds. We can group these animals together though, into Birds
and Mammals
. A major difference between these 2 groups of animals, is that birds can fly. We can model this in the following scheme
In this example, all animals can make a sound, so we will want to share this functionality among all classes
class Animal {
private String sound;
public Animal() {
this.sound = "";
}
public void setSound(String sound) {
this.sound = sound;
}
public void makeSound() {
System.out.println(this.sound + "!!!!");
}
}
With this class, all the animals share the functionality to make a sound. We can extend this to groups of animals, using the extends
keyword. We can add functionality to the Bird
and Mammal
classes, birds can fly, while mammals can walk (birds usually hop instead of walking)
class Bird extends Animal {
public Bird() {
}
public void fly() {
System.out.println("The bird is flying");
}
}
class Mammal extends Animal {
public Mammal() {
}
public void walk() {
System.out.println("The mammal is walking");
}
}
Now we can make some animals to go with these classes.
class Sparrow extends Bird {
public Sparrow() {
this.setSound("chirp");
}
}
class Seagull extends Bird {
public Seagull() {
this.setSound("Squaaa");
}
}
class Cow {
public Cow() {
this.setSound("moo");
}
}
class Pig {
public Pig() {
this.setSound("oink");
}
}
Now we can start using these classes. All classes can only do the things they are supposed to do
public static void main(String[] args) {
Cow cow = new Cow();
cow.makeSound();
cow.walk();
//cow.fly(); cows can't fly
Sparrow spwarrow = new Sparrow();
sparrow.makeSound();
sparrow.fly();
//sparrow.walk() sparrows can't walk
}
moo!!!!
the mammal is walking
chirp!!!!
The bird is flying
The class that is being extended, is called the superclass
. The class that is extending, is called the subclass
In the example of the animals,
Animal
is the superclass of Bird
and Mammal
Bird
is the superclass of Sparrow
and Seagull
Mammal
is the superclass of Cow
and Pig
Bird
, Mammal
, Sparrow
, Seagull
, Cow
and Pig
are all subclasses of Animal
Sparrow
andSeagull
are subclasses of Bird
Cow
and Pig
are subclasses of Mammal
In java, the superclass can be used by using the super
keyword. With super
, we can call the constructor of the superclass, or call a method in a superclass. Methods can also be called using the this
keyword.
If a superclass has a constructor with parameters, and no constructor without parameters, the superclass’s constructor must be called using super(...)
in the constructor of the subclass.
By calling the super constructor, we can shorten the example of the animal kingdom, by adding the sound to the constructor of the Animal class.
class Animal {
private String sound;
public Animal(String sound) {
this.sound = sound;
}
}
class Bird extends Animal {
public Bird(String sound) {
super(sound);
}
}
class Mammal extends Animal {
public Mammal(String sound) {
super(sound);
}
}
class Sparrow extends Bird {
public Sparrow() {
super("chirp");
}
}
class Seagull extends Bird {
public Seagull() {
super("Squaaa");
}
}
class Cow {
public Cow() {
super("moo");
}
}
class Pig {
public Pig() {
super("oink");
}
}
By adding the sound as a parameter of the Animal
class constructor, we enforce that the sound is always set. This makes for a better design, as this makes it hard to make an Animal that does not have a sound because the programmer forgot to add a sound.
Note: The super
constructor should always be called before all other code in a constructor. If you do not put the super
constructor first, java will throw an error while compiling.
Exercise 9-1: People
We’ve seen a lot of
Person
classes so far. In this exercise we’re going to work on printing different kinds of students, and reusing existing code. We will start off with aPerson
class, that stores a name and age. Then we’ll extend this class forStudent
s andTeacher
s, and add extra functionality9-1.1 Person class
Write a class
Person
with the attributesString name
andint age
. Also add the constructorpublic Person(String name, int age)
9-1.2 Printing a person
Add a method
printPerson
to thePerson
class that prints the name and age of this personpublic static void main(String[] args) { Person person = new Person("John Doe", 35); person.printPerson(); }
Name: John Doe Age: 35
10.1.3 Extending the Person to a student
Make a new class
Student
, that extends thePerson
class and adds an attributeint studentNumber
. Also add the constructorpublic Student(String name, int age, int studentNumber)
9-1.4 Adding a student printing method
Add a method
printStudent
to theStudent
class that prints the name, age and student number of this personpublic static void main(String[] args) { Student student = new Student("John Doe", 35, 1337); student.printStudent(); }
Name: John Doe Age: 35 Student number: 1337
Make sure you reuse the code of the
printPerson()
method in theprintStudent
method. do not copy/paste the code fromPerson
toStudent
Let’s take the example of shapes. There are a number of different geometrical shapes. In this example we’re focussing on the Rectangle
and Circle
. These shapes both have a color, a Rectangle
has a width
and height
, but a Circle
only has a radius. We can summarize this in the following diagram. With these shapes, we would like to be able to calculate the surface area and circumference.
We can start implementing the shape class
class Shape {
private Color color;
public Shape(Color color) {
this.color = color;
}
}
class Rectangle extends Shape {
private int width;
private int height;
public Rectangle(Color color, int width, int height) {
super(color);
this.width = width;
this.height = height;
}
public int getArea() {
return this.width * this.height;
}
public int getCircumference() {
return 2 * this.width + 2 * this.height;
}
}
class Circle extends Shape {
private int radius;
public Circle(Color color, int radius) {
super(color);
this.radius = radius;
}
public double getArea() {
return Math.PI * this.radius * this.radius;
}
public double getCircumference() {
return 2 * Math.PI * this.radius;
}
}
By using the same polymorphism we saw in week 10 with interfaces, we can now assign a new Circle into a Shape object variable
Shape circle = new Circle(Color.red, 10);
But, just like with interfaces, only the methods defined in Shape
are available for use
Shape circle = new Circle(Color.red, 10);
System.out.println(circle.getArea()); // won't work
This is a problem, as we would like to be able to use the generic superclass as a variable type too, like with interfaces. To fix this, we can add the getArea()
and getCircumference()
methods in the Shape
class, and overwrite this method in the subclasses. To overwrite a file, the method needs to have the same header, meaning the return type and parameters must be the same. In this case, we choose to use a double
returntype for the methods, so they are all the same
class Shape {
private Color color;
public Shape(Color color) {
this.color = color;
}
public double getArea() { return 0; }
public double getCircumference() { return 0; }
}
class Rectangle extends Shape {
private int width;
private int height;
public Rectangle(Color color, int width, int height) {
super(color);
this.width = width;
this.height = height;
}
public double getArea() {
return this.width * this.height;
}
public double getCircumference() {
return 2 * this.width + 2 * this.height;
}
}
class Circle extends Shape {
private int radius;
public Circle(Color color, int radius) {
super(color);
this.radius = radius;
}
public double getArea() {
return Math.PI * this.radius * this.radius;
}
public double getCircumference() {
return 2 * Math.PI * this.radius;
}
}
Now the methods in the subclasses overwrite the one in the superclass, and can be called, even if the variable used is a superclass type. This is called overriding
Circle circle1 = new Circle(Color.red, 10);
System.out.println(circle1.getArea()); //works
Shape circle2 = new Circle(Color.green, 10);
System.out.println(circle2.getArea()); // works too :)
Shape rect = new Rectangle(Color.blue, 10, 10); //also works
Circle circle3 = new Shape(Color.blue); // does not work...
This principle is called polymorphism. With this, we can have a variable of a superclass, containing an object of a subclass. When calling methods, java will automatically determine the object contained in the variable, and call the method of the lowest subclass applicable. This means if we have multiple levels of subclassing, A, B and C, the lowest subclass implementing a method will be called
class A {
public void print() {
System.out.println("A");
}
}
class B extends A {
public void print() {
System.out.println("B");
}
}
class C extends B {
}
public static void main(String[] args) {
A variable = new C();
variable.print();
}
B
By overwriting a method, the ‘old’ method in the superclass won’t be used anymore. It is possible to use the old method in the superclass to add new functionality and calling the super functionality, by using the super
keyword
class A {
public void print() {
System.out.println("A");
}
}
class B extends A {
public void print() {
super.print();
System.out.println("B");
}
}
class C extends B {
}
public static void main(String[] args) {
A variable = new C();
variable.print();
}
A
B
As mentioned before, when overriding a method, java matches the overriding of a method by the return value and parameters of the method. However if the method changes in the superclass, it must also be changed in the subclass. This is not done automatically, and can be forgotten by the programmer. This is why in the subclass, we can add an ‘annotation’, a small marker, to indicate this method is overriding another method. Then, if the method changes in the superclass, and is accidentally not changed in the subclass, the java compiler will give an error. This annotating can be done with the @Overrides
keyword, in front of the method
class A {
public void print() {
System.out.println("Printing in A");
}
}
class B extends A {
@Override
public void print() {
System.out.println("Override!");
}
}
This way, if the name of the print
method in class A
changes, and it is not changed in B
, java will give an error
Exercise 9-2: People improved
Now that we’ve seen overwriting methods, we can apply this to exercise 10-1, and overwrite a print method
9-2.1 Person class
Write a class
Person
with the attributesString name
andint age
. Also add the constructorpublic Person(String name, int age)
. You can copy the code from exercise 10-19-2.2 Printing a person
Add a method
Person
class that prints the name and age of this personpublic static void main(String[] args) { Person person = new Person("John Doe", 35); person.print(); }
Name: John Doe Age: 35
10.2.3 Extending the Person to a student
Make a new class
Student
, that extends thePerson
class and adds an attributeint studentNumber
. Also add the constructorpublic Student(String name, int age, int studentNumber)
. You can copy the code from exercise 10-19-2.4 Adding a student printing method
Add a method
Student
class that prints the name, age and student number of this personpublic static void main(String[] args) { Person person = new Student("John Doe", 35, 1337); person.print(); }
Name: John Doe Age: 35 Student number: 1337
Make sure you reuse the code of the
print()
method in theStudent
class, in theprintStudent
method. do not copy/paste the code fromPerson
toStudent
Exercise 9-3: Vectors
In math, there are vectors in 2D and 3D space. Both of these vectors can calculate a length of the vector. For 2D vectors, this is calculated as , but in 3D, this is calculated as
9-3.1 2D Vector class
Build a
Vector2D
class, with attributesdouble x
anddouble y
, a constructor, and a methoddouble getLength()
that calculates the length of this vector and returns itpublic static void main(String[] args) { Vector2D v1 = new Vector2D(10, 10); System.out.println("Length: " + v1.getLength()); }
Length: 14.142135623
9-3.2 3D Vector class
Build a
Vector3D
class that extendsVector2D
, with the attributedouble z
, and override the methoddouble getLength()
.public static void main(String[] args) { Vector3D v2 = new Vector3D(10, 10, 10); System.out.println("Length: " + v2.getLength()); }
Length: 24.49489742
In Exercise 10.3 there’s a problem though. It is possible to execute the following code
Vector2D vector = new Vector3D(10,10,10);
This would not make any sense though, as a Vector3D would be the same as a Vector2D.
In the shape example of last chapter, there’s also a big problem. It is possible in java to make a new Shape
Shape shape = new Shape(Color.green);
This would of course be nonsense, as a ‘shape’ does not really have a surface area or circumference. The methods in this class now return 0, but we can also remove the code for these methods, by turning this class into an abstract
class
class Shape {
private Color color;
public Shape(Color color) {
this.color = color;
}
public abstract double getArea();
public abstract double getCircumference();
}
An abstract class can contain abstract methods, which are methods that do not have an implementation. There is no code for these methods because it makes no sense, like in our Shape example. This also makes sure that there can be no new Shape objects, because it has abstract methods.
Shape shape = new Shape(Color.green); // won't compile, as Shape is an abstract class
In order to make an object, just extend the Shape class, and implement the abstract methods, as done before. This new class is not abstract anymore.
Exercise 9-4: Bank accounts
A bank has 2 kinds of accounts, a deposit account and a savings account. Deposit account costs 5 euro per year, and has no interest. A savings account costs 50 euro per year but has 5% interest. We are going to model this using an abstract class
9-4.1 Account class
An account stores the name of the type of the account (Savings or Deposit), and the amount of money on it. We will model these as attributes. Make an abstract class
Account
with the following properties
- attribute
String name
- attribute
double amount
- Constructor with parameters
String name
anddouble initialAmount
. The name attribute gets set to"name (initialAmount)"
, wherename
andinitialAmount
are the corresponding parameter values.- getter and setter for
amount
. Make sure the setter rounds the amount to 2 decimals- toString that returns
"name - amount"
, wherename
andamount
are the corresponding attribute values.- abstract
void calculateNextYear()
9-4.2 Savings and Deposit account
Make a class
DepositAccount
that extendsAccount
. Implement a constructor with 1 parameter,initialAmount
. The name is set to ‘Deposit’. The classDepositAccount
overwrites the calculateNextYear, which sets the amount to the amount - 5.Make a class
SavingsAccount
that extendsAccount
. Implement a constructor with 1 parameter,initialAmount
. The name is set to ‘Savings’. The classSavingsAccount
overwrites the calculateNextYear, which sets the amount to the (amount - 50) * 1.05.9-4-3 Using the accounts
Write a main method that tests the accounts. Make a program that outputs the following output (use the toString for the account to print the accounts)
Year 1 Savings (initial 500.0) - 472.5 Savings (initial 1500.0) - 1522.5 Deposit (initial 500.0) - 495.0 Deposit (initial 1500.0) - 1495.0 Year 2 Savings (initial 500.0) - 443.63 Savings (initial 1500.0) - 1546.13 Deposit (initial 500.0) - 490.0 Deposit (initial 1500.0) - 1490.0 Year 3 Savings (initial 500.0) - 413.31 Savings (initial 1500.0) - 1570.94 Deposit (initial 500.0) - 485.0 Deposit (initial 1500.0) - 1485.0
We’ve seen casting before, where we had a double, but wanted to store it in a integer variable. We can also cast objects. In our previous example of the animals, a Cow
is also an Animal
, so we can store it, but not all Animal
s are Cow
s, so we can’t store it that way. Let’s look at an example
Animal animal = new Cow(); // valid
Cow cow = (Cow)animal; // valid
Pig pig = (Pig)animal; // runtime exception
We can use casting on objects, but this casting is not always valid. If we try to cast a Cow
object to a Pig
object, java will give an error, as this is not supported behaviour.
Casting is only allowed to the right types, and to the sub or superclasses of an object. We can however, test if an object is an instance of a certain class.
Animal animal = (Animal) new Cow()
, this is done automaticallyCow
object, stored in an Animal
variable, can be casted to a Cow
, but not to a Pig
If we want to perform downcasting, we need to check what the type of the object is. We can do this using the instanceOf
operator
Animal animal = new Cow();
if(animal instanceOf Cow) {
System.out.println("This is a cow");
Cow cow = (Cow)animal;
//call specific cow method
} else if(animal instanceOf Pig) {
System.out.println("This is a pig");
}
as you can see in this example, we could test every object to see what type it is, casting it, and then calling methods on that object. This is however usually a sign of a bad design, and should be avoided as much as possible. This is why there will be no exercises for casting
When extending a class, it is possible to use the public methods and attributes of the superclass. It is however, not possible to access the private methods and attributes. Sometimes it is needed to access those methods from a subclass, but not from other classes. We can use the protected
keyword to access these methods and attributes
Modifier | Class | Package | Subclass | World |
---|---|---|---|---|
public |
Y | Y | Y | Y |
protected |
Y | Y | Y | N |
no modifier | Y | Y | N | N |
private |
Y | N | N | N |
As we can see, protected members are also accessable by other classes in the same package. It is mainly used to relax the strictnes of private
to work better in subclasses. We could take the example of Person
class. Suppose we have a Person
class with a Student
subclass.
end of week 9