1. Writing your own classes
Of course, we have already written various classes so far. But these classes so far had only static fields and methods, and we have not yet utilized our own classes to build instances. Objects themselves, of course, we have already built, from classes of the Java standard library. We now want to extend this knowledge. We would like to write classes and make instances of our own types.
This chapter is about electronic consumer devices, and most of the exercises build on each other. We will first build simple electrical devices such as radios and televisions; later we will realize abstractions and collect these electrical devices on a ship. And when Captain CiaoCiao and Bonny Brain go on vacation, everything must be turned off nicely. In this way, the topics of associations and inheritance can be practiced. As a reminder, an association is what we refer to as a "has" or "knows" relationship; an inheritance is an "is-a-kind-of" relationship.
Prerequisites
be able to create new classes
be able to set instance variables in classes
be able to implement methods
know visibilities
private
, package visible, andpublic
be able to use static methods and fields
being able to declare and use enumeration types
know how to implement simple and overloaded constructors
know types of associations
be able to implement 1:1 association
be able to use a
List
be able to use delegation of operations
be able to implement inheritance relations with
extends
be able to override methods
understand the two meanings of
super
be able to use dynamic binding
be able to use and declare interfaces
use default methods in interfaces
Data types used in this chapter:
1.1. Class declaration and object properties
For a new type, we write a new class in Java. Let’s create a few classes in this section and give the classes instance variables and methods.
1.1.1. Declare radio with instance variables and a main program ⭐
The first type of our collection of electrical devices is a radio. Radio has a state that we want to store.
Task:
Create a new class
Radio
.Provide the radio with the following instance variables:
isOn
, is the radio on or off?volume
, how loud does the radio play music?
What types of variables are useful? Make sure that the instance variables are not static!
Write an additional class
Application
, which creates aRadio
object in itsmain(…)
method. Assign and access the variables for testing.
Consider the naming conventions: Classes start with an uppercase letter and variables and methods with a lowercase letter; only constants are in uppercase. |
1.1.2. Implementing methods of a radio ⭐
Methods are to be put into the new class radio
so that the object "can" do something.
Task:
Add the following non-static methods:
void volumeUp()
/void volumeDown()
: change the instance variablevolume
by 1 and -1 respectively. The volume may only be in the range of 0 to 100.void on()
/void off()
/boolean isOn()
: access the instance variableisOn
; it is okay if a method is named like an instance variable. Theon()/off()
methods are supposed to print messages like "on"/"off" on the screen.public String toString()
: It should return information about the internal state as a string, where the string should take a form likeRadio[volume=2, is on]
.
In the
main(…)
method of theApplication
class, the object methods of the radio can be tested like this, for example:excerpt from Application.javaRadio grandmasOldRadio = new Radio(); System.out.println( grandmasOldRadio.isOn() ); // false grandmasOldRadio.on(); System.out.println( grandmasOldRadio.isOn() ); // true System.out.println( grandmasOldRadio.volume ); // 0 grandmasOldRadio.volumeUp(); grandmasOldRadio.volumeUp(); grandmasOldRadio.volumeDown(); grandmasOldRadio.volumeUp(); System.out.println( grandmasOldRadio.volume ); // 2 System.out.println( grandmasOldRadio.toString() ); // Radio[volume=2, is on] System.out.println( grandmasOldRadio ); // Radio[volume=2, is on] grandmasOldRadio.off();
1.1.3. Private Parts: Make instance variables private ⭐
The private details of implementation must not be public so that you can change the insides at any time.
Task:
Make all instance variables from
Radio
private.Decide if the methods can be made
public
.Are there any internal methods that should be
private
?
1.1.4. Create setters and getters ⭐
In the Java world, getters and setters are commonly utilized to define properties. These properties can be automatically accessed by many frameworks through their corresponding getter and setter methods.
Task:
Add a new private
double
instance variablefrequency
to theRadio
so that you can set the frequency.Modify the
toString()
method so that it takes the frequency into account.Writing these setters and getters is often tedious, so either generate them automatically using an IDE or use tools to put them in the bytecode automatically. Generate setters and getters for frequency using the IDE.
If you want to implement read-only operations and prevent properties from being modified externally, use getters without setters. If a variable is
final
, only getters will work. Generate only one getter for the statevolume
.
Setters and getters are an important naming convention. If a property |
1.2. Static variables methods
Class variables and static methods often lead to confusion for novice programmers. Yet, it is simple: either state is stored within individual objects or within the class object itself. If we have different objects with instance variables, the object methods can access the individual properties. A static method can only access class variables without explicitly specifying an object.
1.2.1. Convert station names to frequencies ⭐
So far, the radio has only instance variables and instance methods. Let’s add a static method that has no link to a concrete radio object.
Task:
Implement a static method
double stationNameToFrequency(String)
in the classRadio
that maps a station as a string to a frequency (for example, the well-known pirate radio station "Walking the Plank" has the frequency 98.3).If
null
is passed to the method, then the result shall be0.0
. Furthermore, for unknown station names, the return should be0.0
.
Example:
In the main program, we can write:
System.out.println( Radio.stationNameToFrequency( "Walking the Plank" ) ); // 98.3
String comparisons with stations can be implemented with |
1.2.2. Write log output with a tracer class ⭐
You use loggers to log program output and be able to trace it later—much like Captain CiaoCiao records in his logbook everything that happened on the seas, in the ports, and within the crew.
Task:
Create a new class
Tracer
.Add a static method
void trace(String)
that prints a passed string on the screen.Extend the program with two static methods
on()
andoff()
, which remember in an internal state whethertrace(String)
leads to output or not. At the beginning, thetracer
shall be switched off.Optional: Add a method
trace(String format, Object... args)
that internally callsSystem.out.printf(format, args)
when tracing is turned on.
Example:
We can then use the class like this:
Tracer.on();
Tracer.trace( "Start" );
int i = 2;
Tracer.off();
Tracer.trace( "i = " + i );
// Tracer.trace( "i = %d", i );
Tracer.on();
Tracer.trace( "End" );
The expected output is:
Start End
1.3. Simple enumerations
Enumerations are closed sets built in Java using the keyword enum
.
1.3.1. Give radio an AM-FM modulation ⭐
Modulation is vital in radio transmissions; there is AM (amplitude modulation) and FM (frequency modulation)[1].
Task:
Declare a new enumeration type
Modulation
with the valuesAM
andFM
in its own file.Add a private instance variable
Modulation modulation
toRadio
where the radio keeps the modulation.Set the
Modulation
via a newRadio
methodvoid setModulation(Modulation modulation)
, a getter can also exist.Customize the
toString()
method inRadio
.
1.3.2. Set valid start and end frequency for modulation ⭐
For broadcasting, three frequency ranges (called frequency bands) are classified, which encode over AM:
Longwave: 148.5 kHz to 283.5 kHz
Mediumwave: 526.5 kHz to 1606.5 kHz
Shortwave: shortwave broadcasting uses several bands between 3.2 MHz and 26.1 MHz.
Coded over FM:
Ultra-shortwave (FM): 87.5 MHz to 108 MHz.
Task:
Add two new private instance variables:
minFrequency
maxFrequency
When calling
setModulation(Modulation)
, the instance variablesminFrequency
andmaxFrequency
are to be set to their minimum and maximum value ranges, namely for AM 148.5 kHz to 26.1 MHz and for FM 87.5 MHz to 108 MHz.
1.4. Constructors
Constructors are special initialization routines that are automatically called by the virtual machine when an object is created. We often use constructors to assign states when creating objects, which we then store in the object.
1.4.1. Writing radio constructors ⭐
So far, our radio has had only a default constructor generated by the compiler. Let’s replace it with our own constructors:
Task:
Write a constructor for the class
Radio
so that you can initialize a radio with a frequency (double
). But you should still be able to create radios with the parameterless constructor!Alternatively, a
Radio
object should be able to be initialized with a station name (asString
) (usestationNameToFrequency(..)
internally for this). The station name is not stored, only the frequency.How can we use the constructor delegation with
this(…)
?
Example: We should be able to construct radios in the following way:
Radio r1 = new Radio();
Radio r2 = new Radio( 102. );
Radio r3 = new Radio( "BFBS" );
1.4.2. Implement copy constructor ⭐
If an object of the same type is assumed to be a template in the constructor of a class, we refer to this as a copy constructor.
Task:
Implement a copy constructor for
Radio
.
1.4.3. Realize factory methods ⭐
In addition to constructors, some classes provide an alternative variant to create objects, called factory methods. The following applies:
Constructors exist in principle, but they are private, and consequently, outsiders cannot create instances.
So that objects can be built, there are static methods, which internally call the constructor and return the instance.
Task:
Create a new class
TreasureChest
for a treasure chest.A treasure chest can contain gold doubloons and gemstones; create two public final instance variables
int goldDoubloonWeight
andint gemstoneWeight
. So, the object is immutable, the states cannot be changed later. Getters are not necessary.Write four static factory methods that return a
TreasureChest
object:TreasureChest newInstance()
TreasureChest newInstanceWithGoldDoubloonWeight(int)
TreasureChest newInstanceWithGemstoneWeight(int)
TreasureChest newInstanceWithGoldDoubloonAndGemstoneWeight(int, int)
Where would be the problem here with a usual constructor?
1.5. Associations
An association is a dynamic connection between two or more objects. We can characterize association in several ways:
Does only one side know the other, or do both sides know each other?
Is the lifetime of an object tied to the lifetime of an object?
With how many other objects does an object have an association? Is there a connection to only one other object or several? For 1:n or n:m relationships, we need containers, like arrays or dynamic data structures like the
java.util.ArrayList
.
1.5.1. Connect monitor tube with TV ⭐
So far, we have had one electrical appliance: a radio. It’s time to add a second electrical device and includes a 1:1 association.
Task:
Create a new class
TV
.The TV should get methods
on()
/off()
which write short messages to the console (an instance variable is not necessary for the example for now).Create a
TV
in themain(…)
method ofApplication
.Create a new class
MonitorTube
.The
MonitorTube
shall also geton()
/off()
methods with console messages.
A
TV
shall reference aMonitorTube
via a private instance variable. How can this be done in Java?Implement a unidirectional relationship between the TV and the monitor tube. About the life cycle: when the TV is built, the monitor tube should also be created, you don’t need to be able to replace the monitor tube.
When the TV is switched on/off, the monitor tube should also be switched on/off.
Optional: How can we implement a bidirectional relationship? There might be a problem?
In the end, this relation should be implemented:
1.5.2. Add radios with a 1:n association to the ship ⭐⭐
Captain CiaoCiao owns a whole fleet of ships, and they can take cargo. In the beginning, Captain CiaoCiao only wants to load radios onto his ship.
Task:
Create a new class
Ship
(withoutmain(…)
method).To enable
Ship
to hold radios, we use the data structurejava.util.ArrayList
. As private instance variable, we include inShip
:ArrayList<Radio> radios = new ArrayList<Radio>();
Write a new
load(…)
method inShip
to allow the ship to hold a radio.Build two ships in the
main(…)
method ofApplication
.Assign multiple radios to a
ship
in themain(…)
method.Write a
Ship
methodint countDevicesSwitchedOn()
that returns how many radios are switched on. Attention: It is not about the total number of radios on the ship, but about the number of radios switched on!Optional: Give the ship also a
toString()
method.What do we need to do if the ship also wants to load other electrical devices, such as ice machines or televisions?
Implementation goal: A ship references radios.
1.6. Inheritance
Inheritance models an is-a-type-of relationship and ties two types together very directly. The modeling is critical to form groups of related things.
1.6.1. Introduce abstraction into electrical devices via inheritance ⭐
Until now, radios and televisions have been disconnected. But there is one thing they have in common: they are all electronic consumption devices.
Task:
Create a new class
ElectronicDevice
for electronic devices.Derive the class
Radio
from the classElectronicDevice
—leaveTV
out of it for now.Pull into the superclass the common features of the potential electrical devices.
Write a new class
IceMachine
which is also an electrical device.
Nowadays, a development environment can automatically move properties into the superclass via refactoring; find out how. |
Task Objective: implement the following inheritance relationship.
1.6.2. Determine number of switched on electrical devices ⭐
Inheritance can be used to declare a parameter with a supertype, which then addresses a whole group of types with it, namely all subtypes as well.
Task:
Declare a new static method in the class
ElectronicDevice
:public static int numberOfElectronicDevicesSwitchedOn( ElectronicDevice... devices ) { // Returns the number of switched on devices, // that were passed to the method }
Example:
If, for example,
r1
andr2
are two radios that are turned on, andice
is an ice machine that is turned off,main(…)
may say, for example:int switchedOn = ElectronicDevice.numberOfElectronicDevicesSwitchedOn( r1, ice, r2 ); System.out.println( switchedOn ); // 2
1.6.3. Ship should hold any electronic device ⭐
So far, the ship can only store the type Radio
. Now, general electrical devices should be stored.
Task:
Change the type of the dynamic data structure from
Radio
toElectronicDevice
:private ArrayList<ElectronicDevice> devices = new ArrayList<ElectronicDevice>();
Furthermore, the method to add electrical devices has to change—why?
Task objective: ships store all types of electrical devices.
1.6.4. Take working radios on the ship ⭐
When radios are taken onto the ship, a message should appear on the console. Moreover, Captain CiaoCiao dislikes taking broken radios onto the ship.
Task:
When a radio is passed to the
load(…)
addition method, it should be checked if it has volume 0; in that case, it should not be included in the data structure.When a radio is added, the console output should be:
Remember to pay license fee!
.
1.6.5. Solve equivalence test with pattern variable ⭐
Java 14 introduces "pattern matching for instanceof" which can shorten code nicely.
Task:
Given is an older class
Toaster
with anequals(…)
method for a test for equivalence:com/tutego/exercise/oop/Toaster.javapublic class Toaster { int capacity; boolean stainless; boolean extraLarge; @Override public boolean equals( Object o ) { if ( !(o instanceof Toaster) ) return false; Toaster toaster = (Toaster) o; return capacity == toaster.capacity && stainless == toaster.stainless && extraLarge == toaster.extraLarge; } @Override public int hashCode() { return Objects.hash( capacity, stainless, extraLarge ); } }
Rewrite the
equals(…)
method to useinstanceof
with a pattern variable.
1.6.6. Fire alarm does not go off: Overriding methods ⭐
Fire is something Captain CiaoCiao doesn’t need on his ships at all. If there is a fire, it must be extinguished as soon as possible.
Task:
Implement a class
Firebox
for a fire detector as a subclass ofElectronicDevice
.Fire detectors should always be switched on after creation.
The
off()
method should be implemented with an empty body or with console output so that a fire detector cannot be turned off.
Example:
Task target: an overridden
off()
method that does not change theisOn
state. This can be tested like this:Firebox fb = new Firebox(); System.out.println( fb.isOn() ); // true fb.off(); System.out.println( fb.isOn() ); // true
1.6.7. toString() override ⭐
Give ElectronicDevice
and Radio
their own toString()
method.
1.6.8. Calling the methods of the superclass ⭐⭐
A radio has on()/off()
methods, and the TV
class also already has on()/off()
methods. However, the TV
is not yet an ElectronicDevice
. The reason is that the TV needs special treatment because of the monitor tube.
If TV
also extends the class ElectronicDevice
, a TV, therefore, overwrites the methods of the superclass ElectronicDevice
. But a problem arises:
If we omit the two methods, the tube would not be turned off, but the TV would pass as an electrical device when inherited.
If we leave the methods in the class, only the tube will be turned off, but the device is no longer switched on or off. After all, this state was managed by the superclass via the
on()/off()
methods.
Task:
Fix the problem that a
TV
is anElectronicDevice
, but the monitor tube is turned on/off.
1.7. Polymorphism and dynamic binding
A central feature of object-oriented programming languages is the dynamic resolution of method calls. This form of calls can be decided thereby not at compile-time, but at runtime, if the object type is known.
1.7.1. Holiday! Switch off all devices ⭐
Before Captain CiaoCiao lies down in the hammock with a Tropical Storm cocktail and enjoys his vacation, all electrical appliances on the ship must be turned off.
Task:
Implement a method
holiday()
in theship
class that turns off all the electrical devices in the list.public void holiday() { // call off() for all elements in the data structure }
The
main(…)
method ofApplication
may contain, for example:Radio bedroomRadio = new Radio(); bedroomRadio.volumeUp(); Radio cabooseRadio = new Radio(); cabooseRadio.volumeUp(); TV mainTv = new TV(); Radio crRadio = new Radio(); Firebox alarm = new Firebox(); Ship ship = new Ship(); ship.load( bedroomRadio ); ship.load( cabooseRadio ); ship.load( mainTv ); ship.load( crRadio ); ship.load( alarm ); ship.holiday();
1.7.2. The Big Move (NEW) ⭐
The fearless Captain CiaoCiao has decided to abandon his old ship and move onto a fresh barge. Since not all of his fellow pirates are capable of reading, a graphical loading list should be created with little pictures representing the various items.
Task:
Copy the following class
AsciiArt
as a nested class into the classShip
:public static class AsciiArt { public static final String RADIO = " .-.\n|o.o|\n|:_:|"; public static final String BIG_TV = """ .---..--------------------------------------..---. | ||.------------------------------------.|| | |.-.||| |||.-.| | o ||| ||| o | |`-'||| |||`-'| |.-.||| |||.-.| | O ||| ||| O | |`-'||`------------------------------------'||`-'| `---'`--------------------------------------'`---' _||_ _||_ /____\\ /____\\"""; public static final String TV = " \\ /\n _\\/_\n| |\n|____|"; public static final String SOCKET = """ ____ ____| \\ (____| `._____ ____| _|___ (____| .' |____/"""; }
Implement a new method
printLoadingList()
inShip
that implements the following rules:If the device is a
Radio
and the wattage is positive, a radio will be printed to the screen by accessingAsciiArt.RADIO
. Broken radios have 0 watts and must not be printed.If the device is a
TV
and the wattage is above 10,000, the image of a big TV (AsciiArt.BIG_TV
) will be printed.If the device is a
TV
(regardless of the wattage), the image of a normal TV (AsciiArt.TV
) is printed.If none of the above cases apply, the image of a socket (
AsciiArt.SOCKET
) will be printed.
Solve the task with the language feature Pattern Matching for switch.
1.8. Abstract classes and abstract methods
Abstract classes are something strange at first sight: What to do with classes you can’t make objects of? And what about abstract methods? A class without implemented methods has nothing to offer, after all!
Both concepts are crucial. Supertype and subtype always have a contract; a subtype must have at least what a supertype specifies and must not break semantics. If a superclass is abstract or if methods are abstract, the subclasses from which objects can be built promise to provide that functionality.
1.8.1. TimerTask as an example for an abstract class ⭐⭐
Captain CiaoCiao records each robbery by video and analyzes the sequences in the debriefing. However, the finest 8K quality videos quickly fill the hard drive. He wants to determine in time whether he needs to buy a new hard drive.
A java.util.Timer
can perform tasks repeatedly. To achieve this, the Timer
class has a schedule(…)
method for adding a task. The task is of type java.util.TimerTask
.
Task:
Write a subclass of
TimerTask
that prints a message on the screen whenever the number of free bytes on the file system falls below a certain limit (e.g., less than 1000 MB).Returns the free bytes:
long freeDiskSpace = java.io.File.listRoots()[0].getFreeSpace();
The
Timer
should execute thisTimerTask
every 2 seconds.
TimerTask
and its own subclass.Bonus: Integrate a message in the tray with:
import javax.swing.*;
import java.awt.*;
import java.awt.TrayIcon.MessageType;
import java.net.URL;
try {
String url =
"https://cdn4.iconfinder.com/data/icons/common-toolbar/36/Save-16.png";
ImageIcon icon = new ImageIcon( new URL( url ) );
TrayIcon trayIcon = new TrayIcon( icon.getImage() );
SystemTray.getSystemTray().add( trayIcon );
trayIcon.displayMessage( "Warning", "Hard drive full", MessageType.INFO );
}
catch ( Exception e ) { e.printStackTrace(); }