Topics allocated to the week will appear in this tab.
Detailed Table of Contents
Guidance for the item(s) below:
Given below are some OOP and Java topics you are expected to know already (indicated by the icon ). As we have a few days before the first lecture, use that time to go through these topics and self-test your knowledge via the pre-lecture quiz on Canvas.
Can describe OOP at a higher level
Object-Oriented Programming (OOP) is a programming paradigm. A programming paradigm guides programmers to analyze programming problems, and structure programming solutions, in a specific way.
Programming languages have traditionally divided the world into two parts—data and operations on data. Data is static and immutable, except as the operations may change it. The procedures and functions that operate on data have no lasting state of their own; they’re useful only in their ability to affect data.
This division is, of course, grounded in the way computers work, so it’s not one that you can easily ignore or push aside. Like the equally pervasive distinctions between matter and energy and between nouns and verbs, it forms the background against which you work. At some point, all programmers—even object-oriented programmers—must lay out the data structures that their programs will use and define the functions that will act on the data.
With a procedural programming language like C, that’s about all there is to it. The language may offer various kinds of support for organizing data and functions, but it won’t divide the world any differently. Functions and data structures are the basic elements of design.
Object-oriented programming doesn’t so much dispute this view of the world as restructure it at a higher level. It groups operations and data into modular units called objects and lets you combine objects into structured networks to form a complete program. In an object-oriented programming language, objects and object interactions are the basic elements of design.
Some other examples of programming paradigms are:
Paradigm | Programming Languages |
---|---|
Procedural Programming paradigm | C |
Functional Programming paradigm | F#, Haskell, Scala |
Logic Programming paradigm | Prolog |
Some programming languages support multiple paradigms.
Java is primarily an OOP language but it supports limited forms of functional programming and it can be used to (although not recommended to) write procedural code. e.g. se-edu/addressbook-level1
JavaScript and Python support functional, procedural, and OOP programming.
Can describe how OOP relates to the real world
An object in Object-Oriented Programming (OOP) has state and behavior, similar to objects in the real world.
Every object has both state (data) and behavior (operations on data). In that, they’re not much different from ordinary physical objects. It’s easy to see how a mechanical device, such as a pocket watch or a piano, embodies both state and behavior. But almost anything that’s designed to do a job does, too. Even simple things with no moving parts such as an ordinary bottle combine state (how full the bottle is, whether or not it’s open, how warm its contents are) with behavior (the ability to dispense its contents at various flow rates, to be opened or closed, to withstand high or low temperatures).
It’s this resemblance to real things that gives objects much of their power and appeal. They can not only model components of real systems, but equally as well fulfill assigned roles as components in software systems.
OOP views the world as a network of interacting objects.
A real world scenario viewed as a network of interacting objects:
You are asked to find out the average age of a group of people Adam, Beth, Charlie, and Daisy. You take a piece of paper and pen, go to each person, ask for their age, and note it down. After collecting the age of all four, you enter it into a calculator to find the total. And then, use the same calculator to divide the total by four, to get the average age. This can be viewed as the objects You
, Pen
, Paper
, Calculator
, Adam
, Beth
, Charlie
, and Daisy
interacting to accomplish the end result of calculating the average age of the four persons. These objects can be considered as connected in a certain network of certain structure that dictates how these objects can interact. For example, You
object is connected to the Pen
object, and hence You
can use the Pen
object to write.
OOP solutions try to create a similar object network inside the computer’s memory – a sort of virtual simulation of the corresponding real world scenario – so that a similar result can be achieved programmatically.
OOP does not demand that the virtual world object network follow the real world exactly.
Our previous example can be tweaked a bit as follows:
Main
to represent your role in the scenario.Pen
and Paper
with an object called AgeList
that is able to keep a list of ages.Every object has both state (data) and behavior (operations on data).
The state and behavior of our running example are as follows:
Object | Real World? | Virtual World? | Example of State (i.e. Data) | Examples of Behavior (i.e. Operations) |
---|---|---|---|---|
Adam | Name, Date of Birth | Calculate age based on birthday | ||
Pen | - | Ink color, Amount of ink remaining | Write | |
AgeList | - | Recorded ages | Give the number of entries, Accept an entry to record | |
Calculator | Numbers already entered | Calculate the sum, divide | ||
You/Main | Average age, Sum of ages | Use other objects to calculate |
Every object has an interface and an implementation.
Every real world object has,
The interface and implementation of some real-world objects in our example:
Similarly, every object in the virtual world has an interface and an implementation.
The interface and implementation of some virtual-world objects in our example:
Adam
: the interface might have a method getAge(Date asAt)
; the implementation of that method is not visible to other objects.Objects interact by sending messages. Both real world and virtual world object interactions can be viewed as objects sending messages to each other. The message can result in the sender object receiving a response and/or the receiver object’s state being changed. Furthermore, the result can vary based on which object received the message, even if the message is identical (see rows 1 and 2 in the example below).
Same messages and responses from our running example:
World | Sender | Receiver | Message | Response | State Change |
---|---|---|---|---|---|
Real | You | Adam | "What is your name?" | "Adam" | - |
Real | as above | Beth | as above | "Beth" | - |
Real | You | Pen | Put nib on paper and apply pressure | Makes a mark on your paper | Ink level goes down |
Virtual | Main | Calculator (current total is 50) | add(int i): int i = 23 | 73 | total = total + 23 |
Can explain the abstraction aspect of OOP
The concept of Objects in OOP is an abstraction mechanism because it allows us to abstract away the lower level details and work with bigger granularity entities i.e. ignore details of data formats and the method implementation details and work at the level of objects.
You can deal with a Person
object that represents the person Adam and query the object for Adam's age instead of dealing with details such as Adam’s date of birth (DoB), in what format the DoB is stored, the algorithm used to calculate the age from the DoB, etc.
Can explain the encapsulation aspect of OOP
Encapsulation protects an implementation from unintended actions and from inadvertent access.
-- Object-Oriented Programming with Objective-C, Apple
An object is an encapsulation of some data and related behavior in terms of two aspects:
1. The packaging aspect: An object packages data and related behavior together into one self-contained unit.
2. The information hiding aspect: The data in an object is hidden from the outside world and are only accessible using the object's interface.
Can explain the relationship between classes and objects
Writing an OOP program is essentially writing instructions that the computer will use to,
A class contains instructions for creating a specific kind of objects. It turns out sometimes multiple objects keep the same type of data and have the same behavior because they are of the same kind. Instructions for creating a 'kind' (or ‘class’) of objects can be done once and those same instructions can be used to objects of that kind. We call such instructions a Class.
Classes and objects in an example scenario
Consider the example of writing an OOP program to calculate the average age of Adam, Beth, Charlie, and Daisy.
Instructions for creating objects Adam
, Beth
, Charlie
, and Daisy
will be very similar because they are all of the same kind: they all represent ‘persons’ with the same interface, the same kind of data (i.e. name
, dateOfBirth
, etc.), and the same kind of behavior (i.e. getAge(Date)
, getName()
, etc.). Therefore, you can have a class called Person
containing instructions on how to create Person
objects and use that class to instantiate objects Adam
, Beth
, Charlie
, and Daisy
.
Similarly, you need classes AgeList
, Calculator
, and Main
classes to instantiate one each of AgeList
, Calculator
, and Main
objects.
Class | Objects |
---|---|
Person | objects representing Adam, Beth, Charlie, Daisy |
AgeList | an object to represent the age list |
Calculator | an object to do the calculations |
Main | an object to represent you (i.e., the one who manages the whole operation) |
Can define Java classes
As you know,
new
operator instantiates objects, that is, it creates new instances of a class. Here's a class called Time
, intended to represent a moment in time. It has three attributes and no methods.
public class Time {
private int hour;
private int minute;
private int second;
}
You can give a class any name you like. The Java convention is to use format for class names.
The code is placed in a file whose name matches the class e.g., the Time
class should be in a file named Time.java
.
When a class is public
(e.g., the Time
class in the above example) it can be used in other classes. But the that are private
(e.g., the hour
, minute
and second
attributes of the Time
class) can only be accessed from inside the Time
class.
The syntax for is similar to that of other methods, except:
static
is omitted.When you invoke new
, Java creates the object and calls your constructor to initialize the instance variables. When the constructor is done, it returns a reference to the new object.
Here is an example constructor for the Time
class:
public Time() {
hour = 0;
minute = 0;
second = 0;
}
This constructor does not take any arguments. Each line initializes an instance variable to 0
(which in this example means midnight).
Now you can create Time
objects.
Time time = new Time();
Like other methods, constructors can be .
You can add another constructor to the Time
class to allow creating Time
objects that are initialized to a specific time:
public Time(int h, int m, int s) {
hour = h;
minute = m;
second = s;
}
Here's how you can invoke the new constructor:
Time justBeforeMidnight = new Time(11, 59, 59);
this
keywordThe this
keyword is a reference variable in Java that refers to the . You can use this
the same way you use the name of any other object. For example, you can read and write the instance variables of this
, and you can pass this
as an argument to other methods. But you do not declare this
, and you can’t make an assignment to it.
In the following version of the constructor, the names and types of the parameters are the same as the instance variables (parameters don’t have to use the same names, but that’s a common style). As a result, the parameters shadow (or hide) the instance variables, so the keyword this
is necessary to tell them apart.
public Time(int hour, int minute, int second) {
this.hour = hour;
this.minute = minute;
this.second = second;
}
this
can be used to refer to a constructor of a class within the same class too.
In this example the constructor Time()
uses the this
keyword to call its own constructor Time(int, int, int)
public Time() {
this(0, 0, 0); // call the overloaded constructor
}
public Time(int hour, int minute, int second) {
// ...
}
You can add methods to a class which can then be used from the objects of that class. These instance methods do not have the static
keyword in the method signature. Instance methods can access attributes of the class.
Here's how you can add a method to the Time
class to get the number of seconds passed till midnight.
public int secondsSinceMidnight() {
return hour*60*60 + minute*60 + second;
}
Here's how you can use that method.
Time t = new Time(0, 2, 5);
System.out.println(t.secondsSinceMidnight() + " seconds since midnight!");
Define a Circle
class so that the code given below produces the given output. The nature of the class is as follows:
private
):
int x
, int y
: represents the location of the circledouble radius
: the radius of the circleCircle()
: initializes x
, y
, radius
to 0Circle(int x, int y, double radius)
: initializes the attributes to the given valuesgetArea()
: int
int
value (not double
). Calculated as Pi * (radius)2double
to an int
using (int)
e.g., x = (int)2.25
gives x
the value 2
.Math.PI
to get the value of PiMath.pow()
to raise a number to a specific power e.g., Math.pow(3, 2)
calculates 3
2
public class Main {
public static void main(String[] args) {
Circle c = new Circle();
System.out.println(c.getArea());
c = new Circle(1, 2, 5);
System.out.println(c.getArea());
}
}
0
78
Hint
Can define getters and setters
As the instance variables of Time
are private, you can access them from within the Time
class only. To compensate, you can provide methods to access attributes:
public int getHour() {
return hour;
}
public int getMinute() {
return minute;
}
public int getSecond() {
return second;
}
Methods like these are formally called “accessors”, but more commonly referred to as getters. By convention, the method that gets a variable named something
is called getSomething
.
Similarly, you can provide setter methods to modify attributes of a Time
object:
public void setHour(int hour) {
this.hour = hour;
}
public void setMinute(int minute) {
this.minute = minute;
}
public void setSecond(int second) {
this.second = second;
}
Consider the Circle
class below:
public class Circle {
private int x;
private int y;
private double radius;
public Circle(){
this(0, 0, 0);
}
public Circle(int x, int y, double radius){
this.x = x;
this.y = y;
this.radius = radius;
}
public int getArea(){
double area = Math.PI * Math.pow(radius, 2);
return (int)area;
}
}
Update it as follows so that code given below produces the given output.
public class Main {
public static void main(String[] args) {
Circle c = new Circle(1,2, 5);
c.setX(4);
c.setY(5);
c.setRadius(6);
System.out.println("x : " + c.getX());
System.out.println("y : " + c.getY());
System.out.println("radius : " + c.getRadius());
System.out.println("area : " + c.getArea());
c.setRadius(-5);
System.out.println("radius : " + c.getRadius());
c = new Circle(1, 1, -4);
System.out.println("radius : " + c.getRadius());
}
}
x : 4
y : 5
radius : 6.0
area : 113
radius : 0.0
radius : 0.0
Hint
Can explain class-level members
While all objects of a class have the same attributes, each object has its own copy of the attribute value.
All Person
objects have the name
attribute but the value of that attribute varies between Person
objects.
However, some attributes are not suitable to be maintained by individual objects. Instead, they should be maintained centrally, shared by all objects of the class. They are like ‘global variables’ but attached to a specific class. Such variables whose value is shared by all instances of a class are called class-level attributes.
The attribute totalPersons
should be maintained centrally and shared by all Person
objects rather than copied at each Person
object.
Similarly, when a normal method is being called, a message is being sent to the receiving object and the result may depend on the receiving object.
Sending the getName()
message to the Adam
object results in the response "Adam"
while sending the same message to the Beth
object results in the response "Beth"
.
However, there can be methods related to a specific class but not suitable for sending messages to a specific object of that class. Such methods that are called using the class instead of a specific instance are called class-level methods.
The method getTotalPersons()
is not suitable to send to a specific Person
object because a specific object of the Person
class should not have to know about the total number of Person
objects.
Class-level attributes and methods are collectively called class-level members (also called static members sometimes because some programming languages use the keyword static
to identify class-level members). They are to be accessed using the class name rather than an instance of the class.
Can use class-level members
The content below is an extract from -- Java Tutorial, with slight adaptations.
When a number of objects are created from the same class blueprint, they each have their own distinct copies of instance variables. In the case of a Bicycle
class, the instance variables are gear, and speed. Each Bicycle object has its own values for these variables, stored in different memory locations.
Sometimes, you want to have variables that are common to all objects. This is accomplished with the static
modifier. Fields that have the static
modifier in their declaration are called static fields or class variables. They are associated with the class, rather than with any object. Every instance of the class shares a class variable, which is in one fixed location in memory. Any object can change the value of a class variable, but class variables can also be manipulated without creating an instance of the class.
Suppose you want to create a number of Bicycle objects and assign each a serial number, beginning with 1 for the first object. This ID number is unique to each object and is therefore an instance variable. At the same time, you need a field to keep track of how many Bicycle
objects have been created so that you know what ID to assign to the next one. Such a field is not related to any individual object, but to the class as a whole. For this you need a class variable, numberOfBicycles
, as follows:
public class Bicycle {
private int gear;
private int speed;
// an instance variable for the object ID
private int id;
// a class variable for the number of Bicycle
// objects instantiated
private static int numberOfBicycles = 0;
...
}
Class variables are referenced by the class name itself, as in Bicycle.numberOfBicycles
This makes it clear that they are class variables.
The Java programming language supports static methods as well as static variables. Static methods, which have the static
modifier in their declarations, should be invoked with the class name, without the need for creating an instance of the class, as in ClassName.methodName(args)
The static
modifier, in combination with the final
modifier, is also used to define constants. The final modifier indicates that the value of this field cannot change. For example, the following variable declaration defines a constant named PI
, whose value is an approximation of pi (the ratio of the circumference of a circle to its diameter):
static final double PI = 3.141592653589793;
Here is an example with class-level variables and class-level methods:
public class Bicycle {
private int gear;
private int speed;
private int id;
private static int numberOfBicycles = 0;
public Bicycle(int startSpeed, int startGear) {
gear = startGear;
speed = startSpeed;
numberOfBicycles++;
id = numberOfBicycles;
}
public int getID() {
return id;
}
public static int getNumberOfBicycles() {
return numberOfBicycles;
}
public int getGear(){
return gear;
}
public void setGear(int newValue) {
gear = newValue;
}
public int getSpeed() {
return speed;
}
// ...
}
Explanation of System.out.println(...)
:
out
is a class-level public attribute of the System
class.println
is an instance level method of the out
object.Consider the Circle
class below:
public class Circle {
private int x;
private int y;
private double radius;
public Circle(){
this(0, 0, 0);
}
public Circle(int x, int y, double radius){
setX(x);
setY(y);
setRadius(radius);
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = Math.max(radius, 0);
}
//TODO: Add your getMaxRadius() method here
}
Update it as follows so that code given below produces the given output.
maxRadius
variable to store the maximum radius value of the Circle
objects in existence thus far.getMaxRadius()
method that uses the above-mentioned variable to return the maximum radius value of the Circle
objects in existence thus far.setRadius
method to ensure maxRadius
value is updated (if needed) when the radius of an existing Circle
object is changed.public class Main {
public static void main(String[] args) {
Circle c = new Circle();
System.out.println("max radius used so far : " + Circle.getMaxRadius());
c = new Circle(0, 0, 10);
System.out.println("max radius used so far : " + Circle.getMaxRadius());
c = new Circle(0, 0, -15);
System.out.println("max radius used so far : " + Circle.getMaxRadius());
c.setRadius(12);
System.out.println("max radius used so far : " + Circle.getMaxRadius());
}
}
max radius used so far : 0.0
max radius used so far : 10.0
max radius used so far : 10.0
max radius used so far : 12.0
Hint
Can explain the meaning of enumerations
An Enumeration is a fixed set of values that can be considered as a data type. An enumeration is often useful when using a regular data type such as int
or String
would allow invalid values to be assigned to a variable.
Suppose you want a variable called priority
to store the priority of something. There are only three priority levels: high, medium, and low. You can declare the variable priority
as of type int
and use only values 2
, 1
, and 0
to indicate the three priority levels. However, this opens the possibility of an invalid value such as 9
being assigned to it. But if you define an enumeration type called Priority
that has three values HIGH
, MEDIUM
and LOW
only, a variable of type Priority
will never be assigned an invalid value because the compiler is able to catch such an error.
Priority
: HIGH
, MEDIUM
, LOW
Can use Java enumerations
You can define an enum type by using the enum
keyword. Because they are constants, the names of an enum type's fields are in uppercase letters e.g., FLAG_SUCCESS
by convention.
Defining an enumeration to represent days of a week (code to be put in the Day.java
file):
public enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY
}
Some examples of using the Day
enumeration defined above:
Day today = Day.MONDAY;
Day[] holidays = new Day[]{Day.SATURDAY, Day.SUNDAY};
switch (today) {
case SATURDAY:
case SUNDAY:
System.out.println("It's the weekend");
break;
default:
System.out.println("It's a week day");
}
Note that while enumerations are usually a simple set of fixed values, Java enumerations can have behaviors too, as explained in this tutorial from -- Java Tutorial
Define an enumeration named Priority
. Add the missing describe
method to the code below so that it produces the output given.
public class Main {
// Add your method here
public static void main(String[] args) {
describe("Red", Priority.HIGH);
describe("Orange", Priority.MEDIUM);
describe("Blue", Priority.MEDIUM);
describe("Green", Priority.LOW);
}
}
Red indicates high priority
Orange indicates medium priority
Blue indicates medium priority
Green indicates low priority
Hint
Can explain the meaning of inheritance
The OOP concept Inheritance allows you to define a new class based on an existing class.
For example, you can use inheritance to define an EvaluationReport
class based on an existing Report
class so that the EvaluationReport
class does not have to duplicate data/behaviors that are already implemented in the Report
class. The EvaluationReport
can inherit the wordCount
attribute and the print()
method from the base class Report
.
A superclass is said to be more general than the subclass. Conversely, a subclass is said to be more specialized than the superclass.
Applying inheritance on a group of similar classes can result in the common parts among classes being extracted into more general classes.
Man
and Woman
behave the same way for certain things. However, the two classes cannot be simply replaced with a more general class Person
because of the need to distinguish between Man
and Woman
for certain other things. A solution is to add the Person
class as a superclass (to contain the code common to men and women) and let Man
and Woman
inherit from Person
class.
Inheritance implies the derived class can be considered as a sub-type of the base class (and the base class is a super-type of the derived class), resulting in an is a relationship.
Inheritance does not necessarily mean a sub-type relationship exists. However, the two often go hand-in-hand. For simplicity, at this point let us assume inheritance implies a sub-type relationship.
To continue the previous example,
Woman
is a Person
Man
is a Person
Inheritance relationships through a chain of classes can result in inheritance hierarchies (aka inheritance trees).
Two inheritance hierarchies/trees are given below. Note that the triangle points to the parent class. Observe how the Parrot
is a Bird
as well as it is an Animal
.
Multiple Inheritance is when a class inherits directly from multiple classes. Multiple inheritance among classes is allowed in some languages (e.g., Python, C++) but not in other languages (e.g., Java, C#).
The Honey
class inherits from the Food
class and the Medicine
class because honey can be consumed as a food as well as a medicine (in some oriental medicine practices). Similarly, a Car
is a Vehicle
, an Asset
and a Liability
.
Can explain method overloading
Method overloading is when there are multiple methods with the same name but different type signatures. Overloading is used to indicate that multiple operations do similar things but take different parameters.
Type signature: The type signature of an operation is the type sequence of the parameters. The return type and parameter names are not part of the type signature. However, the parameter order is significant.
Example:
Method | Type Signature |
---|---|
int add(int X, int Y) | (int, int) |
void add(int A, int B) | (int, int) |
void m(int X, double Y) | (int, double) |
void m(double X, int Y) | (double, int) |
In the case below, the calculate
method is overloaded because the two methods have the same name but different type signatures (String)
and (int)
.
calculate(String): void
calculate(int): void
Can explain method overriding
Method overriding is when a sub-class changes the behavior inherited from the parent class by re-implementing the method. Overridden methods have the same name, same type signature, and same return type.
Consider the following case of EvaluationReport
class inheriting the Report
class:
Report methods | EvaluationReport methods | Overrides? |
---|---|---|
print() | print() | Yes |
write(String) | write(String) | Yes |
read():String | read(int):String | No. Reason: the two methods have different signatures; This is a case of overloading (rather than overriding). |
Exercises
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.
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.
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 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 classespublic
At the member level:
public
: the member is visible to all other classesprotected
: same as public
public
private
: the member is not visible to other classes (but can be accessed in its own class)A very beginner-friendly video about implementing Java inheritance.
Background: Suppose we are creating a software to manage various tasks a person has to do. Two types of such tasks are,
The Task
class is given below:
public class Task {
protected String description;
public Task(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
Todo
class that inherits from the Task
class.
boolean
field isDone
to indicate whether the todo is done or not done.isDone()
method to access the isDone
field and a setDone(boolean)
method to set the isDone
field.Deadline
class that inherits from the Todo
class that you implemented in the previous step. It should have,
String
field by
to store the details of when the task to be done e.g., Jan 25th 5pm
getBy()
method to access the value of the by
field, and a corresponding setBy(String)
method.Deadline(String description, String by)
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 explain OOP polymorphism
Polymorphism:
The ability of different objects to respond, each in its own way, to identical messages is called polymorphism. -- Object-Oriented Programming with Objective-C, Apple
Polymorphism allows you to write code targeting superclass objects, use that code on subclass objects, and achieve possibly different results based on the actual class of the object.
Assume classes Cat
and Dog
are both subclasses of the Animal
class. You can write code targeting Animal
objects and use that code on Cat
and Dog
objects, achieving possibly different results based on whether it is a Cat
object or a Dog
object. Some examples:
Animal
and still be able to store Dog
and Cat
objects in it.Animal
object as a parameter and yet be able to pass Dog
and Cat
objects to it.Dog
or a Cat
object as if it is an Animal
object (i.e., without knowing whether it is a Dog
object or a Cat
object) and get a different response from it based on its actual class e.g., call the Animal
class's method speak()
on object a
and get a "Meow"
as the return value if a
is a Cat
object and "Woof"
if it is a Dog
object.Polymorphism literally means "ability to take many forms".
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:
main
method will work even if we add more animal types).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 implement abstract classes
Abstract class: A class declared as an abstract class cannot be instantiated, but it can be subclassed.
You can declare a class as abstract when a class is merely a representation of commonalities among its subclasses in which case it does not make sense to instantiate objects of that class.
The Animal
class that exists as a generalization of its subclasses Cat
, Dog
, Horse
, Tiger
etc. can be declared as abstract because it does not make sense to instantiate an Animal
object.
Abstract method: An abstract method is a method signature without a method implementation.
The move
method of the Animal
class is likely to be an abstract method as it is not possible to implement a move
method at the Animal
class level to fit all subclasses because each animal type can move in a different way.
A class that has an abstract method becomes an abstract class because the class definition is incomplete (due to the missing method body) and it is not possible to create objects using an incomplete class definition.
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");
Feline
is abstract.Animal a = new DomesticCat("Mittens");
DomesticCat
can be instantiated and assigned to a variable of Animal
type (the assignment is allowed by polymorphism).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 explain interfaces
An interface is a behavior specification i.e. a collection of . If a class , it means the class is able to support the behaviors specified by the said interface.
There are a number of situations in software engineering when it is important for disparate groups of programmers to agree to a "contract" that spells out how their software interacts. Each group should be able to write their code without any knowledge of how the other group's code is written. Generally speaking, interfaces are such contracts. --Oracle Docs on Java
Suppose SalariedStaff
is an interface that contains two methods setSalary(int)
and getSalary()
. AcademicStaff
can declare itself as implementing the SalariedStaff
interface, which means the AcademicStaff
class must implement all the methods specified by the SalariedStaff
interface i.e., setSalary(int)
and getSalary()
.
A class implementing an interface results in an is-a relationship, just like in class inheritance.
In the example above, AcademicStaff
is a SalariedStaff
. An AcademicStaff
object can be used anywhere a SalariedStaff
object is expected e.g. SalariedStaff ss = new AcademicStaff()
.
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 interfaces TaxPayer
and Citizen
.TA
class implements both Student
interface and the Staff
interface.TA
class has to implement all methods in the interfaces TaxPayer
and Citizen
.TA
is a Staff
, is a TaxPayer
and is a Citizen
.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
Can explain substitutability
Every instance of a subclass is an instance of the superclass, but not vice-versa. As a result, inheritance allows substitutability: the ability to substitute a child class object where a parent class object is expected.
An AcademicStaff
is an instance of a Staff
, but a Staff
is not necessarily an instance of an AcademicStaff
. i.e. wherever an object of the superclass is expected, it can be substituted by an object of any of its subclasses.
The following code is valid because an AcademicStaff
object is substitutable as a Staff
object.
Staff staff = new AcademicStaff(); // OK
But the following code is not valid because staff
is declared as a Staff
type and therefore its value may or may not be of type AcademicStaff
, which is the type expected by variable academicStaff
.
Staff staff;
...
AcademicStaff academicStaff = staff; // Not OK
Can explain dynamic and static binding
Dynamic binding ( ): a mechanism where method calls in code are at , rather than at compile time.
Overridden methods are resolved using dynamic binding, and therefore resolves to the implementation in the actual type of the object.
Consider the code below. The declared type of s
is Staff
and it appears as if the adjustSalary(int)
operation of the Staff
class is invoked.
void adjustSalary(int byPercent) {
for (Staff s: staff) {
s.adjustSalary(byPercent);
}
}
However, at runtime s
can receive an object of any subclass of Staff
. That means the adjustSalary(int)
operation of the actual subclass object will be called. If the subclass does not override that operation, the operation defined in the superclass (in this case, Staff
class) will be called.
Static binding (aka early binding): When a method call is resolved at compile time.
In contrast, overloaded methods are resolved using static binding.
Note how the constructor is overloaded in the class below. The method call new Account()
is bound to the first constructor at compile time.
class Account {
Account() {
// Signature: ()
...
}
Account(String name, String number, double balance) {
// Signature: (String, String, double)
...
}
}
Similarly, the calculateGrade
method is overloaded in the code below and a method call calculateGrade("A1213232")
is bound to the second implementation, at compile time.
void calculateGrade(int[] averages) { ... }
void calculateGrade(String matric) { ... }
Can explain how substitutability operation overriding, and dynamic binding relates to polymorphism
Three concepts combine to achieve polymorphism: substitutability, operation overriding, and dynamic binding.
Can explain the Collections framework
This section uses extracts from the -- Java Tutorial, with some adaptations.
A collection — sometimes called a container — is simply an object that groups multiple elements into a single unit. Collections are used to store, retrieve, manipulate, and communicate aggregate data.
Typically, collections represent data items that form a natural group, such as a poker hand (a collection of cards), a mail folder (a collection of letters), or a telephone directory (a mapping of names to phone numbers).
The collections framework is a unified architecture for representing and manipulating collections. It contains the following:
Interfaces: These are abstract data types that represent collections. Interfaces allow collections to be manipulated independently of the details of their representation.
Example: the List<E>
interface can be used to manipulate list-like collections which may be implemented in different ways such as ArrayList<E>
or LinkedList<E>
.
Implementations: These are the concrete implementations of the collection interfaces. In essence, they are reusable data structures.
Example: the ArrayList<E>
class implements the List<E>
interface while the HashMap<K, V>
class implements the Map<K, V>
interface.
Algorithms: These are the methods that perform useful computations, such as searching and sorting, on objects that implement collection interfaces. The algorithms are said to be polymorphic: that is, the same method can be used on many different implementations of the appropriate collection interface.
Example: the sort(List<E>)
method can sort a collection that implements the List<E>
interface.
A well-known example of collections frameworks is the C++ Standard Template Library (STL). Although both are collections frameworks and the syntax look similar, note that there are important philosophical and implementation differences between the two.
The following list describes the core collection interfaces:
Collection
— the root of the collection hierarchy. A collection represents a group of objects known as its elements. The Collection interface is the least common denominator that all collections implement and is used to pass collections around and to manipulate them when maximum generality is desired. Some types of collections allow duplicate elements, and others do not. Some are ordered and others are unordered. The Java platform doesn't provide any direct implementations of this interface but provides implementations of more specific subinterfaces, such as Set
and List
. Also see the Collection
API.
Set
— a collection that cannot contain duplicate elements. This interface models the mathematical set abstraction and is used to represent sets, such as the cards comprising a poker hand, the courses making up a student's schedule, or the processes running on a machine. Also see the Set
API.
List
— an ordered collection (sometimes called a sequence). List
s can contain duplicate elements. The user of a List
generally has precise control over where in the list each element is inserted and can access elements by their integer index (position). Also see the List
API.
Queue
— a collection used to hold multiple elements prior to processing. Besides basic Collection
operations, a Queue
provides additional insertion, extraction, and inspection operations. Also see the Queue
API.
Map
— an object that maps keys to values. A Map
cannot contain duplicate keys; each key can map to at most one value. Also see the Map
API.
Others: Deque
, SortedSet
, SortedMap
Can use the ArrayList class
The ArrayList
class is a resizable-array implementation of the List
interface. Unlike a normal array
, an ArrayList
can grow in size as you add more items to it. The example below illustrates some of the useful methods of the ArrayList
class using an ArrayList
of String
objects.
import java.util.ArrayList;
public class ArrayListDemo {
public static void main(String args[]) {
ArrayList<String> items = new ArrayList<>();
System.out.println("Before adding any items:" + items);
items.add("Apple");
items.add("Box");
items.add("Cup");
items.add("Dart");
print("After adding four items: " + items);
items.remove("Box"); // remove item "Box"
print("After removing Box: " + items);
items.add(1, "Banana"); // add "Banana" at index 1
print("After adding Banana: " + items);
items.add("Egg"); // add "Egg", will be added to the end
items.add("Cup"); // add another "Cup"
print("After adding Egg: " + items);
print("Number of items: " + items.size());
print("Index of Cup: " + items.indexOf("Cup"));
print("Index of Zebra: " + items.indexOf("Zebra"));
print("Item at index 3 is: " + items.get(2));
print("Do we have a Box?: " + items.contains("Box"));
print("Do we have an Apple?: " + items.contains("Apple"));
items.clear();
print("After clearing: " + items);
}
private static void print(String text) {
System.out.println(text);
}
}
Before adding any items:[]
After adding four items: [Apple, Box, Cup, Dart]
After removing Box: [Apple, Cup, Dart]
After adding Banana: [Apple, Banana, Cup, Dart]
After adding Egg: [Apple, Banana, Cup, Dart, Egg, Cup]
Number of items: 6
Index of Cup: 2
Index of Zebra: -1
Item at index 3 is: Cup
Do we have a Box?: false
Do we have an Apple?: true
After clearing: []
Add the missing methods to the class given below so that it produces the output given.
Use an ArrayList
to store the numbers.
public class Main {
//TODO: add your methods here
public static void main(String[] args) {
System.out.println("Adding numbers to the list");
addNumber(3);
addNumber(8);
addNumber(24);
System.out.println("The total is: " + getTotal());
System.out.println("8 in the list : " + isFound(8) );
System.out.println("5 in the list : " + isFound(5) );
removeNumber(8);
System.out.println("The total is: " + getTotal());
}
}
Adding numbers to the list
[3]
[3, 8]
[3, 8, 24]
The total is: 35
8 in the list : true
5 in the list : false
[3, 24]
The total is: 27
Hint
Can use the HashMap class
HashMap
is an implementation of the Map
interface. It allows you to store a collection of key-value pairs. The example below illustrates how to use a HashMap<String, Point>
to maintain a list of coordinates and their identifiers e.g., the identifier x1
is used to identify the point 0,0
where x1
is the key and 0,0
is the value.
import java.awt.Point;
import java.util.HashMap;
import java.util.Map;
public class HashMapDemo {
public static void main(String[] args) {
HashMap<String, Point> points = new HashMap<>();
// put the key-value pairs in the HashMap
points.put("x1", new Point(0, 0));
points.put("x2", new Point(0, 5));
points.put("x3", new Point(5, 5));
points.put("x4", new Point(5, 0));
// retrieve a value for a key using the get method
print("Coordinates of x1: " + pointAsString(points.get("x1")));
// check if a key or a value exists
print("Key x1 exists? " + points.containsKey("x1"));
print("Key x1 exists? " + points.containsKey("y1"));
print("Value (0,0) exists? " + points.containsValue(new Point(0, 0)));
print("Value (1,2) exists? " + points.containsValue(new Point(1, 2)));
// update the value of a key to a new value
points.put("x1", new Point(-1,-1));
// iterate over the entries
for (Map.Entry<String, Point> entry : points.entrySet()) {
print(entry.getKey() + " = " + pointAsString(entry.getValue()));
}
print("Number of keys: " + points.size());
points.clear();
print("Number of keys after clearing: " + points.size());
}
public static String pointAsString(Point p) {
return "[" + p.x + "," + p.y + "]";
}
public static void print(String s) {
System.out.println(s);
}
}
Coordinates of x1: [0,0]
Key x1 exists? true
Key x1 exists? false
Value (0,0) exists? true
Value (1,2) exists? false
x1 = [-1,-1]
x2 = [0,5]
x3 = [5,5]
x4 = [5,0]
Number of keys: 4
Number of keys after clearing: 0
The class given below keeps track of how many people signup to attend an event on each day of the week. Add the missing methods so that it produces the output given.
Use an HashMap
to store the number of entries for each day.
public class Main {
private static HashMap<String, Integer> roster = new HashMap<>();
//TODO: add your methods here
public static void main(String[] args) {
addToRoster("Monday"); // i.e., one person signed up for Monday
addToRoster("Wednesday"); // i.e., one person signed up for Wednesday
addToRoster("Wednesday"); // i.e., another person signed up for Wednesday
addToRoster("Friday");
addToRoster("Monday");
printRoster();
}
}
Monday => 2
Friday => 1
Wednesday => 2
Hint
Can explain error handling
Well-written applications include error-handling code that allows them to recover gracefully from unexpected errors. When an error occurs, the application may need to request user intervention, or it may be able to recover on its own. In extreme cases, the application may log the user off or shut down the system. -- Microsoft
Can explain exceptions
Exceptions are used to deal with 'unusual' but not entirely unexpected situations that the program might encounter at runtime.
Exception:
The term exception is shorthand for the phrase "exceptional event." An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions. –- Java Tutorial (Oracle Inc.)
Examples:
Can explain Java Exceptions
Given below is an extract from the -- Java Tutorial, with some adaptations.
There are three basic categories of exceptions In Java:
Error
, RuntimeException
, and their subclasses. Suppose an application prompts a user for an input file name, then opens the file by passing the name to the constructor for java.io.FileReader. Normally, the user provides the name of an existing, readable file, so the construction of the FileReader
object succeeds, and the execution of the application proceeds normally. But sometimes the user supplies the name of a nonexistent file, and the constructor throws java.io.FileNotFoundException
. A well-written program will catch this exception and notify the user of the mistake, possibly prompting for a corrected file name.
Error
and its subclasses. Suppose that an application successfully opens a file for input, but is unable to read the file because of a hardware or system malfunction. The unsuccessful read will throw java.io.IOError
. An application might choose to catch this exception, in order to notify the user of the problem — but it also might make sense for the program to print a stack trace and exit.
RuntimeException
and its subclasses. These usually indicate programming bugs, such as logic errors or improper use of an API. Consider the application described previously that passes a file name to the constructor for FileReader. If a logic error causes a null to be passed to the constructor, the constructor will throw NullPointerException
. The application can catch this exception, but it probably makes more sense to eliminate the bug that caused the exception to occur.
Errors and runtime exceptions are collectively known as unchecked exceptions.
Can explain how exception handling is done typically
Most languages allow code that encountered an "exceptional" situation to encapsulate details of the situation in an Exception object and throw/raise that object so that another piece of code can catch it and deal with it. This is especially useful when the code that encountered the unusual situation does not know how to deal with it.
The extract below from the -- Java Tutorial (with slight adaptations) explains how exceptions are typically handled.
When an error occurs at some point in the execution, the code being executed creates an exception object and hands it off to the runtime system. The exception object contains information about the error, including its type and the state of the program when the error occurred. Creating an exception object and handing it to the runtime system is called throwing an exception.
After a method throws an exception, the runtime system attempts to find something to handle it in the . The runtime system searches the call stack for a method that contains a block of code that can handle the exception. This block of code is called an exception handler. The search begins with the method in which the error occurred and proceeds through the call stack in the reverse order in which the methods were called. When an appropriate handler is found, the runtime system passes the exception to the handler. An exception handler is considered appropriate if the type of the exception object thrown matches the type that can be handled by the handler.
The exception handler chosen is said to catch the exception. If the runtime system exhaustively searches all the methods on the call stack without finding an appropriate exception handler, the program terminates.
Advantages of exception handling in this way:
Can use Java Exceptions
A program can catch exceptions by using a combination of the try
, catch
blocks.
try
block identifies a block of code in which an exception can occur.catch
block identifies a block of code, known as an exception handler, that can handle a particular type of exception. The writeList()
method below calls a method process()
that can cause two type of exceptions. It uses a try-catch construct to deal with each exception.
public void writeList() {
print("starting method");
try {
print("starting process");
process();
print("finishing process");
} catch (IndexOutOfBoundsException e) {
print("caught IOOBE");
} catch (IOException e) {
print("caught IOE");
}
print("finishing method");
}
Some possible outputs:
No exceptions | IOException | IndexOutOfBoundsException |
---|---|---|
starting method starting process finishing process finishing method | starting method starting process caught IOE finishing method | starting method starting process caught IOOBE finishing method |
You can use a finally
block to specify code that is guaranteed to execute with or without the exception. This is the right place to close files, recover resources, and otherwise clean up after the code enclosed in the try
block.
The writeList()
method below has a finally
block:
public void writeList() {
print("starting method");
try {
print("starting process");
process();
print("finishing process");
} catch (IndexOutOfBoundsException e) {
print("caught IOOBE");
} catch (IOException e) {
print("caught IOE");
} finally {
// clean up
print("cleaning up");
}
print("finishing method");
}
Some possible outputs:
No exceptions | IOException | IndexOutOfBoundsException |
---|---|---|
starting method starting process finishing process cleaning up finishing method | starting method starting process caught IOE cleaning up finishing method | starting method starting process caught IOOBE cleaning up finishing method |
The try
statement should contain at least one catch
block or a finally block and may have multiple catch
blocks.
The class of the exception object indicates the type of exception thrown. The exception object can contain further information about the error, including an error message.
You can use the throw
statement to throw an exception. The throw statement requires a object as the argument.
Here's an example of a throw
statement.
if (size == 0) {
throw new EmptyStackException();
}
In Java, Checked exceptions are subject to the Catch or Specify Requirement: code that might throw checked exceptions must be enclosed by either of the following:
try
statement that catches the exception. The try
must provide a handler for the exception.throws
clause that lists the exception.Unchecked exceptions are not required to follow to the Catch or Specify Requirement but you can apply the requirement to them too.
Here's an example of a method specifying that it throws certain checked exceptions:
public void writeList() throws IOException, IndexOutOfBoundsException {
print("starting method");
process();
print("finishing method");
}
Some possible outputs:
No exceptions | IOException | IndexOutOfBoundsException |
---|---|---|
starting method finishing method | starting method | starting method |
Java comes with a collection of built-in exception classes that you can use. When they are not enough, it is possible to create your own exception classes.
The Main
class below parses a string descriptor of a rectangle of the format "WIDTHxHEIGHT"
e.g., "3x4"
and prints the area of the rectangle.
public class Main {
public static void printArea(String descriptor){
//TODO: modify the code below
System.out.println(descriptor + "=" + calculateArea(descriptor));
}
private static int calculateArea(String descriptor) {
//TODO: modify the code below
String[] dimensions = descriptor.split("x");
return Integer.parseInt(dimensions[0]) * Integer.parseInt(dimensions[1]);
}
public static void main(String[] args) {
printArea("3x4");
printArea("5x5");
}
}
3x4=12
5x5=25
Update the code of printArea
to print an error message if WIDTH
and/or HEIGHT
are not numbers e.g., "Ax4"
calculateArea
will throw the unchecked exception NumberFormatException
if the code tries to parse a non-number to an integer.
Update the code of printArea
to print an error message if the descriptor is missing WIDTH
and/or HEIGHT
e.g., "x4"
calculateArea
will throw the unchecked exception IndexOutOfBoundsException
if one or both dimensions are missing.
Update the code of calculateArea
to throw the checked exception IllegalShapeException
if there are more than 2 dimensions e.g., "5x4x3"
and update the printArea
to print an error message for those cases. Here is the code for the IllegalShapeException.java
public class IllegalShapeException extends Exception {
//no other code needed
}
Here is the expected behavior after you have done the above changes:
public class Main {
//...
public static void main(String[] args) {
printArea("3x4");
printArea("3xy");
printArea("3x");
printArea("3");
printArea("3x4x5");
}
}
3x4=12
WIDTH or HEIGHT is not a number: 3xy
WIDTH or HEIGHT is missing: 3x
WIDTH or HEIGHT is missing: 3
Too many dimensions: 3x4x5
Partial solution