5.8. Additional Practice Exercises¶
Question 1
Where do you see common interfaces in your daily life? Think about devices, systems, or even people.
Try to find different things that offer a similar behavior.
The goal is to consider how interfaces provide consistency despite internal differences.
Question 2
Consider the UML diagram below:
It is important to remember the relationship between the type of the variable (label on the variable) and the type of the object (the actual memory allocated). These types do not always have to match, but they need to be compatible.
SecurityDevice device1 = new Camera();
Camera device2 = new Camera();
MotionSensor device3 = new MotionSensor();
SecurityDevice device4 = new MotionSensor();
Now, imagine the code below is found in the main method
of the CentralController class. For each numbered block
of code, say whether or not that code will compile and explain
your answer.
SecurityDevice device = new Camera(); device.activate();
SecurityDevice device = new Camera(); device.streamVideoFeed("1080p");
Camera cam = new Camera(); cam.streamVideoFeed("720p");
Camera cam = new SecurityDevice();
SecurityDevice device = new MotionSensor(); device.setSensitivity(5);
MotionSensor sensor = new MotionSensor(); sensor.activate();
Object item = new Alarm(); item.activate();
MotionSensor sensor = new MotionSensor(); SecurityDevice generic = sensor;
Camera cam = new Alarm();
Alarm siren = new Alarm(); siren.setVolume(90);
Sample Solutions
Yes:
activate()is defined in the SecurityDevice interface.No: The compiler only looks at the type of the variable (
SecurityDevice), which determines which methods can be called. The interface does not containstreamVideoFeed(), so it is not allowed..Yes: The variable type is
Camera, so unique camera methods are accessible.No: Interfaces cannot be instantiated using the new keyword.
No:
setSensitivity()is unique to theMotionSensorclass and is invisible to aSecurityDevicereference.Yes:
MotionSensorimplements the interface, so it possesses theactivate()method.No: The
Objectclass does not have anactivate()method.Yes: Upcasting a specific object to a general interface is always compatible.
No: A
Cameraand anAlarmare sibling classes; one cannot be assigned to the other as they are not compatible.Yes: The variable type is
Alarm, which contains thesetVolume()method.
5.8.1. Practice Activity¶
Practice Activity
Note
Solutions to the coding parts of this activity are provided if you toggle “Show Solutions” above.
Download Starter Code
We recommend that students download the starter code and follow along with the activity on Odin - updating their code where appropriate.
Use the following command to download the starter code for
this chapter and places it into a subdirectory called
cs1302-interface-practice:
sh -c "$(curl -fsSl https://cs1302uga.github.io/cs1302-book/_bundle/cs1302-interface-practice.sh)"
- downloading cs1302-interface-practice bundle...
- verifying integrity of downloaded files using sha256sum...
- extracting downloaded archive...
- removing intermediate files...
subdirectory cs1302-interface-practice successfully created
Understanding Starter Code
Consider the three classes below. Each have their own unique characteristics, but they also have a similar behavior. Take a minute to identify the similar behavior.
Note
Getter and setter methods left out intentionally. You should assume they exist throughout this question.
1package cs1302.interface.example;
2
3public class Note {
4 private String content;
5
6 public Note(String content) {
7 this.content = content;
8 } // Note
9
10 public void saveNote() {
11 // Logic to save text to a database or file
12 System.out.println("Note saved successfully: " + content);
13 } // saveNote
14} // Note
1package cs1302.interface.example;
2
3public class EditedPhoto {
4 private String fileName;
5 private String filterApplied;
6
7 public EditedPhoto(String fileName, String filterApplied) {
8 this.fileName = fileName;
9 this.filterApplied = filterApplied;
10 } // EditedPhoto
11
12 public void savePhoto() {
13 // Logic to save image bytes or update image path
14 System.out.println("Photo '" + fileName + "' saved with " + filterApplied + " filter.");
15 } // savePhoto
16} // EditedPhoto
1package cs1302.interface.example;
2
3public class UserProfile {
4 private String username;
5 private String email;
6
7 public UserProfile(String username, String email) {
8 this.username = username;
9 this.email = email;
10 } // UserProfile
11
12 public void saveProfile() {
13 // Logic to persist user data
14 System.out.println("User profile for " + username + " has been updated and saved.");
15 } // SaveProfile
16} // UserProfile
Solution
All three have unique behaviors, but they are all contain a method that allows the user to save.
Drawing the UML
Now, imagine that we write a driver program that creates objects of each type and calls their save methods:
1package cs1302.interface.example;
2
3public class Driver {
4 public static void main(String[] args) {
5 Note myNote = new Note("Finish the Java project!");
6 myNote.saveNote();
7
8 EditedPhoto myPhoto = new EditedPhoto("sunset.jpg", "Sepia");
9 myPhoto.savePhoto();
10
11 UserProfile myUser = new UserProfile("JavaDev2026", "dev@example.com");
12 myUser.saveProfile();
13 } // main
14} // Driver
Take a few minutes to draw the UML diagram for all four of these classes.
Make sure and include dependency arrows labeled with dependsOn where
appropriate.
Solution
Planning for an Interface
In the scenario above, we have three disparate classes with a common function (save). How could we incorporate an interface to reduce code redundancy and eliminate dependencies in the driver program? For this question, you can simply describe your proposed solution without writing code or drawing diagrams. We are simply coming up with a plan.
Solution
Please note that the names for the entities (interface, methods, etc) may differ in your solution. As long as you create an interface and incorporate it correctly, the solution would be correct with different names.
The best course of action in this scenario would be to add an interface
called Savable (or similar) that includes a save method. Then,
each of our three classes would implement the interface and change
the name of their corresponding save method to save.
An interface defines a common method signature (like save())
that all implementing classes must provide. This means all
different classes (like Note, EditedPhoto, UserProfile) have
a consistent way to be saved. Because they share the same method
name and type, we can write code that treats all these different
objects the same way — for example, by calling save() on any
object without knowing its exact class. This unifies saving
behavior and makes the code easier to write, read, and maintain.
Terminology
What does it mean for a class to “implement” an interface?
Solution
When a class implements an interface, it promises to provide
concrete code for all the methods declared in that interface. The
class agrees to follow the contract defined by the interface. This
means any object of that class can be treated as the interface
type, and the interface’s methods can be called on it — enabling
polymorphism. It’s like saying, “I have this capability
(save()), and here’s how I do it.”
Creating the Interface
Write code to define an interface called Savable with one
method, save.
Solution
/**
* Interface representing a savable object.
*/
public interface Savable {
void save();
} // Savable
Incorporating the Interface
Modifying the existing classes (Note, EditedPhoto, UserProfile)
to implement this interface and rename their save methods to save()
would result in code similar to below. Before moving through all of the
tabs, try to write out one of the classes on your own:
public class Note implements Savable {
private String content;
public Note(String content) {
this.content = content;
} // Note
@Override
public void save() {
System.out.println("Note saved: " + content);
} // save
} // Note
public class EditedPhoto implements Savable {
private String fileName;
private String filterApplied;
public EditedPhoto(String fileName, String filterApplied) {
this.fileName = fileName;
this.filterApplied = filterApplied;
} // EditedPhoto
@Override
public void save() {
System.out.println("Photo saved: " + fileName + " (" + filterApplied + ")");
} // save
} // EditedPhoto
public class UserProfile implements Savable {
private String username;
private String email;
public UserProfile(String username, String email) {
this.username = username;
this.email = email;
} // UserProfile
@Override
public void save() {
System.out.println("Profile saved for: " + username);
} // save
} // UserProfile
Test Yourself: Compatibility Exercise
Now that we have our interface relationship set up, let’s look at compatibility. For each numbered block of code below, determine if the block will compile or will cause a compiler error. Explain your answers.
public class CompatibilityTest {
public static void main(String[] args) {
// 1.
Savable item1 = new Note("Study for Java Exam");
// 2.
Note item2 = new Note("Clean the room");
item2.save();
// 3.
Savable item3 = new Savable();
// 4.
Savable item4 = new EditedPhoto("vacation.jpg", "Sepia");
item4.save();
// 5.
Savable item5 = new EditedPhoto("portrait.png", "Vivid");
item5.getFileName(); // assume getFileName exists in the EditedPhoto class
// 6.
EditedPhoto item6 = new EditedPhoto("selfie.png", "None");
item6.save();
// 7.
UserProfile item7 = item5;
// 8.
Savable[] items = new Savable[3];
items[0] = new Note("Task 1");
items[1] = new UserProfile("Admin", "admin@web.com");
// 9.
items[0].save();
// 10.
Note item10 = new Savable();
} // main
} // CompatibilityTest
Solution
# Compiles? Explanation
Yes, Polymorphism: A
Savablevariable can reference aNoteobject because they are compatible types.Yes, Direct access:
Notehas asave()method.No, Interfaces are abstract; they cannot be instantiated with new.
Yes, The
Savableinterface definessave(), so it is accessible.No, The variable is of type
Savable. The type of the variable determines what you are allowed to do. SincegetFileNameis not declared inSavable, this will not compile. Remember, the compiler doesn’t know the object type, so it can’t seegetFileName().Yes,
EditedPhotoimplementsSavable, so it must inherits/override thesave()method.No, You cannot use a variable of a specific type (
UserProfile) to reference something of typeSavable. Compatibility only works the other way.Yes, Array elements of an interface type can reference any implementing class object.
Yes, Every element in the items array is guaranteed to have a
save()method.No, Type mismatch: A
Noteis aSavable, but aSavableis not necessarily aNote. Also, you cannot instantiate the interface.
Updating the Driver
Update the original Driver class seen below. Be sure to use Savable
as the data type wherever possible and remember to update method names.
1public class Driver {
2 public static void main(String[] args) {
3 Note myNote = new Note("Finish the Java project!");
4 myNote.saveNote();
5
6 EditedPhoto myPhoto = new EditedPhoto("sunset.jpg", "Sepia");
7 myPhoto.savePhoto();
8
9 UserProfile myUser = new UserProfile("JavaDev2026", "dev@example.com");
10 myUser.saveProfile();
11 } // main
12} // Driver
Solution
1public class Driver {
2 public static void main(String[] args) {
3 Savable myNote = new Note("Finish the Java project!");
4 myNote.saveNote();
5
6 Savable myPhoto = new EditedPhoto("sunset.jpg", "Sepia");
7 myPhoto.savePhoto();
8
9 Savable myUser = new UserProfile("JavaDev2026", "dev@example.com");
10 myUser.saveProfile();
11 } // main
12} // Driver
Updating the UML
Draw the UML diagram for the updated code.
Solution
Thought Exercise
Why is the updated design that uses an interface better than the original?
Solution
Polymorphism: In the driver class, you can now create an array
of type Savable and save all objects in a single loop,
regardless of whether they are notes or photos.
FewerDependencies: The Driver class only needs to know about the
Savable interface, not the specific implementation details of
each class. If we add more classes that implement Savable in
the future, the Driver class won’t have to change!
Implementing Bulk Save
Implement a method called saveAll that takes an array of Savable
references and saves all of them - regardless of the type of the
underlying objects. Here is what a call to saveAll would look like:
1public static void main(String[] args) {
2 // Initialize an array of type Savable
3 Savable[] itemsToSave = new Savable[3];
4
5 // Assign different concrete types to the Savable array
6 itemsToSave[0] = new Note("Buy groceries");
7 itemsToSave[1] = new EditedPhoto("mountain.png", "Grayscale");
8 itemsToSave[2] = new UserProfile("TechGuru", "guru@code.com");
9
10 // Pass the array to the bulk save method
11 saveAll(itemsToSave);
12} // main
Solution
1public static void saveAll(Savable[] items) {
2 // Iterate through the array using an enhanced for-loop
3 for (Savable item : items) {
4 if (item != null) {
5 item.save();
6 } // if
7 } // for
8 System.out.println("Bulk save operation complete.");
9} // saveAll
New Implementing Class
Add a new class that implements Savable. It can be anything you
want! See if you can get the syntax correct without looking
back at other examples.
Solution
There are many correct solutions to this activity. Compare your final code with one of the existing examples to see if you wrote the syntax correctly. Then, compile everything on Odin.
Final Discussion
Now that we have a fourth implementing class, will we need to
update saveAll or is that method already compatible with
our new class?
Solution
saveAll is already compatible because it was written
to work with all types that are compatible with Savable!