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:

hide circle
hide empty members
set namespaceSeparator none
skinparam classAttributeIconSize 0
skinparam classAttributeIconSize 0
skinparam genericDisplay old
skinparam defaultFontName monospaced
skinparam class {
    BackgroundColor LightYellow
    BackgroundColor<<interface>> AliceBlue
}

interface SecurityDevice <<interface>> {
    + {abstract} <<abstract>> activate(): void
    + {abstract} <<abstract>> deactivate(): void
    + {abstract} <<abstract>> getStatus(): String
}

class Camera {
    + <<override>> activate(): void
    + <<override>> deactivate(): void
    + <<override>> getStatus(): String
    + streamVideoFeed(resolution: String): void
    + setNightVision(enabled: boolean): void
}

class MotionSensor {
    + <<override>> activate(): void
    + <<override>> deactivate(): void
    + <<override>> getStatus(): String
    + setSensitivity(level: int): void
    + getLastDetectionTime(): long
}

class Alarm {
    + <<override>> activate(): void
    + <<override>> deactivate(): void
    + <<override>> getStatus(): String
    + triggerPanicSiren(): void
    + setVolume(decibels: int): void
}

class CentralController {
    -devices: SecurityDevice[]
    +main(args: String[]): void
    +armAll(): void
}

SecurityDevice <|..down.. Camera : "implements"
SecurityDevice <|..down.. MotionSensor : "implements"
SecurityDevice <|..down.. Alarm : "implements"

CentralController .down.> SecurityDevice : "depends on"

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.

Listing 5.13 These are all valid and will compile
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.

  1. SecurityDevice device = new Camera();
    device.activate();
    
  2. SecurityDevice device = new Camera();
    device.streamVideoFeed("1080p");
    
  3. Camera cam = new Camera();
    cam.streamVideoFeed("720p");
    
  4. Camera cam = new SecurityDevice();
    
  5. SecurityDevice device = new MotionSensor();
    device.setSensitivity(5);
    
  6. MotionSensor sensor = new MotionSensor();
    sensor.activate();
    
  7. Object item = new Alarm();
    item.activate();
    
  8. MotionSensor sensor = new MotionSensor();
    SecurityDevice generic = sensor;
    
  9. Camera cam = new Alarm();
    
  10. Alarm siren = new Alarm();
    siren.setVolume(90);
    
Sample Solutions
  1. Yes: activate() is defined in the SecurityDevice interface.

  2. No: The compiler only looks at the type of the variable (SecurityDevice), which determines which methods can be called. The interface does not contain streamVideoFeed(), so it is not allowed..

  3. Yes: The variable type is Camera, so unique camera methods are accessible.

  4. No: Interfaces cannot be instantiated using the new keyword.

  5. No: setSensitivity() is unique to the MotionSensor class and is invisible to a SecurityDevice reference.

  6. Yes: MotionSensor implements the interface, so it possesses the activate() method.

  7. No: The Object class does not have an activate() method.

  8. Yes: Upcasting a specific object to a general interface is always compatible.

  9. No: A Camera and an Alarm are sibling classes; one cannot be assigned to the other as they are not compatible.

  10. Yes: The variable type is Alarm, which contains the setVolume() 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

hide circle
hide empty members
set namespaceSeparator none
skinparam classAttributeIconSize 0
skinparam classAttributeIconSize 0
skinparam genericDisplay old
skinparam defaultFontName monospaced
skinparam class {
    BackgroundColor LightYellow
    BackgroundColor<<interface>> AliceBlue
}

class Note {
  - content : String
  + Note(content : String)
  + saveNote() : void
}

class EditedPhoto {
  - fileName : String
  - filterApplied : String
  + EditedPhoto(fileName : String, filterApplied : String)
  + savePhoto() : void
}

class UserProfile {
  - username : String
  - email : String
  + UserProfile(username : String, email : String)
  + saveProfile() : void
}

class Driver {
  + main(args : String[]) : {static} void
}

' Dependency arrows (dependsOn)
Driver ..> Note : depends on
Driver ..> EditedPhoto : depends on
Driver ..> UserProfile : depends on

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

  1. Yes, Polymorphism: A Savable variable can reference a Note object because they are compatible types.

  2. Yes, Direct access: Note has a save() method.

  3. No, Interfaces are abstract; they cannot be instantiated with new.

  4. Yes, The Savable interface defines save(), so it is accessible.

  5. No, The variable is of type Savable. The type of the variable determines what you are allowed to do. Since getFileName is not declared in Savable, this will not compile. Remember, the compiler doesn’t know the object type, so it can’t see getFileName().

  6. Yes, EditedPhoto implements Savable, so it must inherits/override the save() method.

  7. No, You cannot use a variable of a specific type (UserProfile) to reference something of type Savable. Compatibility only works the other way.

  8. Yes, Array elements of an interface type can reference any implementing class object.

  9. Yes, Every element in the items array is guaranteed to have a save() method.

  10. No, Type mismatch: A Note is a Savable, but a Savable is not necessarily a Note. 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.

Listing 5.14 Original Driver
 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
Listing 5.15 Original Driver
 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

hide circle
hide empty members
set namespaceSeparator none
skinparam classAttributeIconSize 0
skinparam classAttributeIconSize 0
skinparam genericDisplay old
skinparam defaultFontName monospaced
skinparam class {
    BackgroundColor LightYellow
    BackgroundColor<<interface>> AliceBlue
}

interface Savable {
  + save() : void
}

class Note {
  - content : String
  + Note(content : String)
  + save() : void
}

class EditedPhoto {
  - fileName : String
  - filterApplied : String
  + EditedPhoto(fileName : String, filterApplied : String)
  + save() : void
}

class UserProfile {
  - username : String
  - email : String
  + UserProfile(username : String, email : String)
  + save() : void
}

class Driver {
  + main(args : String[]) : {static} void
}

' Implementation (Realization)
Savable <|.. Note : implements
Savable <|.. EditedPhoto : implements
Savable <|.. UserProfile : implements

' Dependency
Driver ..> Savable : depends on

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!