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 Technische Informatica Breda
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 overwriting-methods-1: People improved
Now that we’ve seen overwriting methods, we can apply this to exercise 10-1, and overwrite a print method
overwriting-methods-1.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-1overwriting-methods-1.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
overwriting-methods-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)
. You can copy the code from exercise 10-1overwriting-methods-1.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 overwriting-methods-2: 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 \(\sqrt{x²+y²}\), but in 3D, this is calculated as \(\sqrt{x²+y²+z²}\)
overwriting-methods-2.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
overwriting-methods-2.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: 17.32050808