W11. Interface & Implementation, Final Methods & Classes, Interfaces, Enumerations, Java Collections Framework
1. Summary
1.1 Interface & Implementation Design
When designing object-oriented programs, a critical question arises: what should be inherited from a base class—just the interface (method signatures) or both the interface and its implementation (actual code)? This distinction is crucial for writing maintainable, bug-resistant code.
1.1.1 The Problem with Inheriting Implementation
Consider a scenario where multiple airplane models need to implement a fly() method. If we create a concrete base class Airplane with a default fly() implementation, all subclasses automatically inherit that implementation:
class Airplane {
public void fly() {
// Standard Airbus flying algorithm
}
}
class AirbusA extends Airplane { }
class AirbusB extends Airplane { }
class Boeing extends Airplane { }This creates a serious problem: Boeing now uses Airbus’s flying algorithm! The compiler won’t detect this error because the code is syntactically correct. The bug is semantic—the wrong behavior is inherited silently.
1.1.2 The Solution: Separate Interface from Implementation
The proper approach is to separate the interface from the implementation using abstract classes and methods:
abstract class Airplane {
public abstract void fly(); // Interface only
protected void defaultFly() { // Optional default implementation
// Standard flying algorithm
}
}
class AirbusA extends Airplane {
public void fly() { defaultFly(); } // Uses default
}
class Boeing extends Airplane {
public void fly() {
// Boeing's own flying algorithm
}
}Now the compiler forces each subclass to explicitly implement fly(). Airbus models can choose to use the default algorithm, while Boeing must provide its own. This prevents silent inheritance of incorrect behavior.
1.1.3 Design Guidelines for Base Classes
When designing a base class, follow these principles:
- Interface only: Make the method
abstractif you want subclasses to provide their own implementation. Hide any default implementation in a separate helper method (likedefaultFly()). - Interface and implementation: Make the method
virtual(explicitly in C++/C#, implicitly in Java) if you want to provide a default implementation that subclasses can optionally override. - Fixed behavior: Make the method non-virtual (C++/C#) or
final(Java) if subclasses should not modify the behavior.
1.2 Final Methods
Final methods are methods that cannot be overridden by subclasses. They provide two key benefits: guaranteed behavior and performance optimization.
1.2.1 Preventing Method Overriding
The final keyword prevents subclasses from changing a method’s behavior:
class Base {
public final void method() {
System.out.println("Base's method");
}
}
class Derived extends Base {
public void method() { // ERROR: Cannot override final method
System.out.println("Derived's method");
}
}This ensures that method() will always behave exactly as defined in Base, regardless of the dynamic type of the object.
1.2.2 Performance Benefits
Final methods enable early binding (static dispatch) instead of late binding (dynamic dispatch):
- Late binding: The method to call is determined at runtime based on the object’s dynamic type. This requires extra overhead (virtual method table lookup).
- Early binding: The method to call is determined at compile time based on the static type. This is faster.
When a method is final, the compiler knows it cannot be overridden, so it can:
- Generate more efficient code for method calls
- Inline the method body directly into the caller, eliminating method call overhead entirely for small methods
1.3 Final Classes
A final class cannot be subclassed (inherited from). This is useful when you want to prevent extension of a class entirely.
final class Base {
// Class implementation
}
class Derived extends Base { // ERROR: Cannot subclass final class
}Key properties:
- Declaring a class as
finalimplicitly makes all its methodsfinal - It’s illegal to declare a class as both
abstractandfinal(this would be contradictory—an abstract class is incomplete and must be extended)
1.4 Interfaces
An interface is a pure abstraction that defines a contract of behavior without any implementation. Interfaces represent an alternative way of thinking about object-oriented design: focusing on what objects can do rather than what objects are.
1.4.1 Two Views of the World
- Class-based approach: The world consists of objects with state and relationships. Emphasis on “what things are.”
- Interface-based approach: All entities are defined by their behavior (what they can do). Emphasis on “what things can do.”
Interfaces embody the interface-based approach and provide a cleaner alternative to multiple inheritance.
1.4.2 Defining Interfaces
An interface declares method signatures without implementations:
interface Features {
int numOfLegs();
boolean canFly();
boolean canSwim();
}Properties of interfaces:
- No method bodies (classes provide implementations)
- All methods are implicitly
publicandabstract - Cannot be instantiated (no
newoperator) - Cannot have instance variables (only constants)
- Represents a contract: “any class implementing this interface must provide these methods”
1.4.3 Implementing Interfaces
A class implements an interface using the implements keyword:
class Lion implements Features {
public int numOfLegs() { return 4; }
public boolean canFly() { return false; }
public boolean canSwim() { return true; }
}
Features f = new Lion(); // Treat Lion as a set of features
if (f.canFly()) { /* ... */ }The class must implement all methods declared in the interface, or the code won’t compile.
1.4.4 Multiple Interfaces
A class can implement multiple interfaces, providing a safer alternative to multiple inheritance:
class Person implements iBodyParams, iSkills, iRelations {
// Must implement all methods from all three interfaces
}
Person john = new Person();
iSkills johnsSkills = john; // View John as a set of skillsThis allows objects to be viewed from different perspectives (facets) depending on the context.
1.4.5 Interface Inheritance
Interfaces can extend other interfaces:
interface SpeedFeatures {
float maxSpeed();
float maxAcceleration();
}
interface EngineFeatures extends SpeedFeatures {
float numOfCyls();
float enginePower();
}
class Car implements EngineFeatures {
// Must implement methods from both interfaces
}Classes can also inherit interface implementations:
interface HasLegs {
int noLegs();
}
class Mammal implements HasLegs {
public int noLegs() { return 4; }
}
class Lion extends Mammal {
// Inherits the noLegs() implementation
}1.4.6 Interfaces with Inheritance
Interfaces and class inheritance can be combined:
interface ColorFeatures {
Color color();
Border border();
}
abstract class Shape {
abstract void draw();
}
class Rectangle extends Shape {
void draw() { /* ... */ }
}
class ColoredRectangle extends Rectangle implements ColorFeatures {
// Inherits from Rectangle AND implements ColorFeatures
public Color color() { /* ... */ }
public Border border() { /* ... */ }
}Interfaces are orthogonal to the inheritance mechanism—they provide a separate dimension of abstraction.
1.4.7 Type Checks with Interfaces
The instanceof operator works with interfaces:
interface Printable { void print(); }
interface Movable { void move(); }
class Rectangle extends Shape implements Printable, Movable {
// Implementation
}
Shape a = new Rectangle();
if (a instanceof Printable) {
((Printable)a).print();
}
if (a instanceof Movable) {
((Movable)a).move();
}Interfaces can even be empty, serving as “tags” or markers to identify objects with certain properties.
1.4.8 Nested Interfaces
Interfaces can be nested inside classes:
class SomeClass {
public interface Nested {
boolean isNotNegative(int x);
}
}
class MyClass implements SomeClass.Nested {
public boolean isNotNegative(int x) {
return x >= 0;
}
}
SomeClass.Nested obj = new MyClass();1.4.9 Interfaces as Facets
Interfaces can be viewed as different facets or views of an object, representing different clients’ perspectives on the same underlying object. Each interface provides a specific view tailored to a particular use case.
1.5 Interfaces vs Abstract Classes
Understanding the distinction between interfaces and abstract classes is fundamental:
Similarities:
- Both represent abstractions
- Neither can be instantiated directly
Differences:
- Interface: Pure abstraction of behavior. Defines what an object can do, but not how or what state it has. (Note: Modern Java allows default methods and constants, but the conceptual purpose remains behavioral abstraction.)
- Abstract class: Can contain:
- Abstract method declarations (interface)
- Concrete method implementations (behavior)
- Instance variables (state)
When to use which:
- Use interfaces when you want to define a capability or behavior that unrelated classes might share
- Use abstract classes when you want to provide common state and behavior for closely related classes
1.6 Enumerations
Enumerations (enums) are a special type used to define a fixed set of named constants. They make code more readable and type-safe compared to using arbitrary integer constants.
1.6.1 Why Enumerations?
Consider modeling traffic light states. The conventional approach uses integers:
final int GREEN = 0;
final int YELLOW = 1;
final int RED = 2;
int trafficLight = GREEN;
trafficLight = 777; // Nothing prevents this!The problem: why these specific numbers? What prevents invalid values like 777?
The enumeration approach:
enum Lights {
GREEN,
YELLOW,
RED
}
Lights tl = Lights.GREEN;
tl = 777; // Compiler ERROR: type mismatchNow the compiler enforces type safety. You cannot assign arbitrary values to tl.
1.6.2 Basic Enumerations
Enumerations define a set of named constants (called enumerators):
enum Compass {
NORTH,
SOUTH,
EAST,
WEST
}
enum Day {
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
}Note: Enumerator names are traditionally written in UPPERCASE (a convention from C and assembly language, not a requirement).
Behind the scenes, enumerators are associated with integer values starting from 0 (SUNDAY = 0, MONDAY = 1, etc.), but you generally don’t need to know or use these values.
1.6.3 Using Enumerations
Enumerations work naturally with switch statements:
public void tellItLikeItIs(Day day) {
switch (day) {
case MONDAY:
System.out.println("Mondays are bad.");
break;
case FRIDAY:
System.out.println("Fridays are better.");
break;
default:
System.out.println("Midweek days are so-so.");
break;
}
}1.6.4 Java Enum Extensions
Java enums are much more powerful than enums in languages like C or C++. In Java, enums are actually classes! This enables advanced features:
1. Enums can have fields and constructors:
enum Coin {
PENNY(1), NICKEL(5), DIME(10), QUARTER(25);
private final int value;
Coin(int value) {
this.value = value;
}
}
Coin c = Coin.DIME; // c is associated with value 10Each enumerator can be initialized with specific values. The constructor is called automatically when the enum is loaded.
2. Enums can have methods:
enum Coin {
PENNY(1), NICKEL(5), DIME(10), QUARTER(25);
private final int value;
Coin(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
Coin c = Coin.DIME;
int v = c.getValue(); // Returns 103. Enum predefined methods:
All enums automatically have these methods:
values(): Returns an array of all enum constants in declaration order
enum Season { WINTER, SPRING, SUMMER, FALL }
for (Season s : Season.values()) {
System.out.println(s);
}
// Output: WINTER, SPRING, SUMMER, FALLordinal(): Returns the position of the constant (starting from 0)
for (Season s : Season.values()) {
System.out.println(s.ordinal());
}
// Output: 0, 1, 2, 34. Enum members can have bodies:
Each enum constant can override methods:
enum Operation {
PLUS { double eval(double x, double y) { return x + y; } },
MINUS { double eval(double x, double y) { return x - y; } },
TIMES { double eval(double x, double y) { return x * y; } },
DIVIDE { double eval(double x, double y) { return x / y; } };
abstract double eval(double x, double y);
}
// Usage:
for (Operation op : Operation.values()) {
System.out.println("2.0 " + op + " 4.0 = " + op.eval(2.0, 4.0));
}
// Output:
// 2.0 PLUS 4.0 = 6.0
// 2.0 MINUS 4.0 = -2.0
// 2.0 TIMES 4.0 = 8.0
// 2.0 DIVIDE 4.0 = 0.55. Enum constructor behavior:
Enum constructors are called automatically when the enum is first loaded:
enum Color {
RED, GREEN, BLUE;
private Color() {
System.out.println("Constructor called for: " + this.toString());
}
}
Color c = Color.RED;
// Output:
// Constructor called for: RED
// Constructor called for: GREEN
// Constructor called for: BLUEAll enum constants are constructed when the enum is first accessed.
1.7 Java Collections Framework
The Java Collections Framework provides a unified architecture for storing and manipulating groups of objects. It relieves programmers from implementing common data structures from scratch and provides powerful, standardized abstractions.
Collections can store any reference type (objects). They provide dynamic sizing, eliminating the need to manually manage array sizes.
1.7.1 Core Collection Interfaces
There are three main collection types:
1. List: An ordered collection where elements are accessed by index (position). Lists allow duplicate elements.
2. Set: An unordered collection of unique elements. Duplicates are automatically ignored. At most one null element is allowed.
3. Map: A collection that maps keys to values. Each key can map to at most one value, but multiple keys can map to the same value.
1.7.2 Collection Hierarchy
The Collections Framework has a clear inheritance hierarchy:
Collection(root interface)List(ordered, indexed)ArrayList(resizable array)LinkedList(doubly-linked list)Vector(synchronized array)Stack(LIFO stack)
Set(unique elements)HashSet(hash table)LinkedHashSet(hash table + insertion order)SortedSet(sorted set)TreeSet(red-black tree)
Queue(FIFO queue)Deque(double-ended queue)LinkedList(also implements Deque)
Map(separate hierarchy, not part ofCollection)HashMap(hash table)LinkedHashMap(hash table + insertion order)Hashtable(legacy synchronized hash table)SortedMap(sorted map)TreeMap(red-black tree)
1.7.3 Lists
A List is an ordered collection where elements are stored in the order they are added. Elements are accessed by integer index.
Creating and using a List:
import java.util.List;
import java.util.ArrayList;
List<String> list = new ArrayList<>();
list.add("Geeks");
list.add("Geeks");
list.add(1, "For"); // Insert at index 1
System.out.println(list); // [Geeks, For, Geeks]Common List methods:
int size(): Returns number of elementsboolean isEmpty(): Checks if list is emptyvoid clear(): Removes all elementsboolean add(E element): Appends element to endvoid add(int index, E element): Inserts element at indexE get(int index): Returns element at indexE set(int index, E element): Replaces element at indexE remove(int index): Removes element at indexboolean remove(Object o): Removes first occurrence of object
Key characteristics:
- Allows duplicate elements
- Maintains insertion order
- Provides positional access
1.7.4 Sets
A Set is an unordered collection that cannot contain duplicate elements. Only one null element is allowed.
Creating and using a Set:
import java.util.Set;
import java.util.HashSet;
Set<String> set = new HashSet<>();
set.add("Geeks");
set.add("For");
set.add("Geeks"); // Duplicate, will be ignored
set.add("Example");
set.add("Set");
System.out.println(set); // Order not guaranteed: [Geeks, Example, Set, For]Common Set methods:
int size(): Returns number of elementsboolean isEmpty(): Checks if set is emptyvoid clear(): Removes all elementsboolean add(E element): Adds element (returns false if already present)boolean remove(Object o): Removes elementboolean contains(Object o): Checks if element exists
Key characteristics:
- No duplicate elements
- No guaranteed order (unless using
LinkedHashSetorTreeSet) - Fast membership testing
Set implementations comparison:
| Implementation | Element Ordering | Complexity (find) | Data Structure |
|---|---|---|---|
HashSet |
No | \(O(1)\) | Hash table |
LinkedHashSet |
Insertion order | \(O(1)\) | Hash table + linked list |
TreeSet |
Sorted (ascending) | \(O(\log n)\) | Red-black tree |
1.7.5 Maps
A Map stores key-value pairs. Each unique key maps to exactly one value. Keys must be unique, but values can be duplicated.
Creating and using a Map:
import java.util.Map;
import java.util.HashMap;
Map<String, Integer> map = new HashMap<>();
map.put("a", 100);
map.put("b", 200);
map.put("c", 300);
map.put("a", 150); // Replaces previous value for key "a"
// Traversing the map
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}Common Map methods:
int size(): Returns number of key-value pairsboolean isEmpty(): Checks if map is emptyvoid clear(): Removes all mappingsV put(K key, V value): Associates value with key (returns previous value)V get(K key): Returns value for key (ornull)V remove(K key): Removes mapping for keyboolean containsKey(K key): Checks if key existsboolean containsValue(V value): Checks if value existsSet<K> keySet(): Returns set of all keysCollection<V> values(): Returns collection of all valuesSet<Map.Entry<K,V>> entrySet(): Returns set of key-value pairs
Key characteristics:
- Each key maps to at most one value
- Keys must be unique
- Values can be duplicated
- Fast lookup by key
Map implementations comparison:
| Implementation | Element Ordering | Null as Value | Thread Safe | Complexity (find) | Data Structure |
|---|---|---|---|---|---|
HashMap |
No | Yes | No | \(O(1)\) | Hash table |
Hashtable |
No | No | Yes | \(O(1)\) | Hash table |
TreeMap |
Sorted by key | Yes/No* | No | \(O(\log n)\) | Red-black tree |
*TreeMap allows null values but not null keys
List vs Set vs Map comparison:
| List | Set | Map |
|---|---|---|
| Index-based access | Unordered collection | Stores key-value pairs |
| Allows duplicates | No duplicates | Keys are unique |
| Maintains insertion order | Duplicate additions ignored | Look up values by key |
1.7.6 Iterator
An Iterator is an object used to loop through collections. It provides a uniform way to traverse any collection type.
Using an Iterator:
import java.util.Iterator;
import java.util.HashSet;
import java.util.Set;
Set<Integer> set = new HashSet<>();
set.add(5);
set.add(1);
set.add(3);
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
Integer value = iterator.next();
System.out.print(value + " ");
}Iterator methods:
boolean hasNext(): Returnstrueif more elements existE next(): Returns the next elementvoid remove(): Removes the last element returned bynext()
Example with ArrayList:
import java.util.ArrayList;
import java.util.Iterator;
ArrayList<String> cars = new ArrayList<>();
cars.add("Volvo");
cars.add("BMW");
cars.add("Ford");
cars.add("Mazda");
Iterator<String> it = cars.iterator();
System.out.println(it.next()); // VolvoThe iterator() method can be called on any collection implementing the Collection interface.
1.8 UML Class Diagrams
UML (Unified Modeling Language) Class Diagrams provide a visual representation of class structure and relationships in object-oriented systems. They are essential for planning and communicating system design before implementation.
1.8.1 Class Representation
A class is represented as a rectangle divided into three sections:
- Top section: Class name
- Middle section: Attributes (fields) with visibility and type
- Bottom section: Methods with visibility, parameters, and return type
Visibility indicators:
+: public-: private#: protected~: package-private
1.8.2 Relationships
1. Association: A general relationship between classes (one class uses another)
- Represented by a solid line
- Shows multiplicity:
1,*,0..1,1..*, etc.
2. Navigable Association: Indicates direction of relationship
- Solid line with an arrow
3. Inheritance (Generalization): “is-a” relationship
- Solid line with hollow triangle pointing to parent class
4. Realization/Implementation: A class implements an interface
- Dashed line with hollow triangle pointing to interface
5. Dependency: One class depends on another (weaker than association)
- Dashed line with arrow
6. Aggregation: “has-a” relationship where the part can exist independently
- Solid line with hollow diamond on the container side
7. Composition: “has-a” relationship where the part cannot exist independently
- Solid line with filled diamond on the container side
Example:
University (1) ---- (1..*) Department
Department (1) <>---- (1..*) Professor [aggregation]
Professor ---|> Person [inheritance]
This shows:
- A University has many Departments
- A Department aggregates many Professors
- Professor inherits from Person
2. Definitions
- Interface (concept): The public method signatures that a class exposes, defining what operations can be performed on objects of that class.
- Implementation: The actual code that defines how methods work, including algorithms, data structures, and internal logic.
- Abstract Method: A method declared without a body (implementation), which must be implemented by subclasses.
- Abstract Class: A class that cannot be instantiated and may contain abstract methods. Used as a base class to define common interface and partial implementation.
- Final Method: A method that cannot be overridden by subclasses, declared with the
finalkeyword in Java. - Final Class: A class that cannot be extended (subclassed), declared with the
finalkeyword. - Late Binding (Dynamic Dispatch): The process of determining which method to call at runtime based on the dynamic type of the object. Used for virtual/overridable methods.
- Early Binding (Static Dispatch): The process of determining which method to call at compile time based on the static type. Used for non-virtual methods, final methods, and static methods.
- Method Inlining: A compiler optimization where the method body is inserted directly at the call site, eliminating method call overhead. Possible with final methods.
- Interface (language construct): A special type in Java that defines a contract of method signatures without implementation. Classes can implement multiple interfaces.
- Interface Implementation: The process by which a class provides concrete implementations for all methods declared in an interface using the
implementskeyword. - Multiple Interface Implementation: The ability of a single class to implement multiple interfaces, providing a safer alternative to multiple inheritance.
- Interface Inheritance: When one interface extends another interface, inheriting all method declarations.
- Facet: A view or perspective on an object, often represented by an interface, showing only certain aspects of the object’s capabilities.
- Empty Interface (Marker Interface): An interface with no methods, used as a “tag” to mark classes with certain properties.
- Nested Interface: An interface defined inside a class or another interface.
- Ad-hoc Polymorphism (Duck Typing): The ability to treat an object as a certain type if it has the required methods, regardless of explicit type declarations. Not directly supported in Java.
- Enumeration (Enum): A special type that defines a fixed set of named constants (enumerators), providing type safety for categorical values.
- Enumerator: A named constant within an enumeration.
- Enum Constructor: A special constructor in Java enums that initializes each enum constant with specific values.
- Enum Methods: Methods defined in an enum to provide behavior associated with enum constants.
- Collection: A data structure that stores a group of objects, providing operations for adding, removing, and accessing elements.
- List: An ordered collection interface where elements are accessed by integer index. Allows duplicates and maintains insertion order.
- Set: An unordered collection interface that contains no duplicate elements. At most one null element is allowed.
- Map: An interface for storing key-value pairs where each unique key maps to exactly one value.
- ArrayList: A resizable-array implementation of the List interface, providing fast random access.
- LinkedList: A doubly-linked list implementation of List and Deque interfaces, providing efficient insertion and deletion.
- HashSet: A hash table implementation of the Set interface, providing constant-time performance for basic operations.
- HashMap: A hash table implementation of the Map interface, providing constant-time performance for basic operations.
- TreeSet: A red-black tree implementation of Set that maintains elements in sorted ascending order.
- TreeMap: A red-black tree implementation of Map that maintains keys in sorted order.
- Iterator: An object that enables traversal through a collection, providing methods to check for more elements and retrieve the next element.
- Generic Type Parameter: A placeholder for a specific type (like
<E>,<K,V>) that allows collections to be type-safe at compile time. - Entry (Map.Entry): A key-value pair stored in a Map, accessible when iterating through a Map’s entrySet.
- UML (Unified Modeling Language): A standardized visual language for modeling software systems.
- UML Class Diagram: A type of UML diagram that shows classes, their attributes, methods, and relationships between classes.
- Association (UML): A relationship where one class uses or interacts with another class.
- Aggregation (UML): A “has-a” relationship where the part can exist independently of the whole, shown with a hollow diamond.
- Composition (UML): A “has-a” relationship where the part cannot exist independently of the whole, shown with a filled diamond.
- Multiplicity (UML): Notation showing how many instances of one class relate to instances of another class (e.g.,
1,*,0..1,1..*).
3. Examples
3.1. Enum Constructor Behavior (Lab 10, Task 1)
Demonstrate when enum constructors are called.
Click to see the solution
Key Concept: Enum constructors are called automatically when the enum is first accessed, once for each constant.
enum Color {
RED,
GREEN,
BLUE;
private Color() {
System.out.println("Constructor called for: " + this.toString());
}
public void colorInfo() {
System.out.println("Universal Color");
}
}
public class Test {
public static void main(String[] args) {
System.out.println("Before accessing enum");
Color c1 = Color.RED;
System.out.println(c1);
c1.colorInfo();
System.out.println("\nAccessing another color:");
Color c2 = Color.GREEN;
System.out.println(c2);
}
}Output:
Before accessing enum
Constructor called for: RED
Constructor called for: GREEN
Constructor called for: BLUE
RED
Universal Color
Accessing another color:
GREEN
Explanation: All enum constants are constructed when the enum type is first accessed, not individually when each constant is used.
Answer: The enum constructor is called once for each constant when the enum class is loaded, which happens on first access to any enum constant or method.
3.2. Animals List Program (Lab 10, Task 3)
Write a program to create a List of animals with methods for adding, removing, updating, and displaying.
Click to see the solution
Key Concept: Encapsulate List operations in methods for better organization and reusability.
import java.util.ArrayList;
import java.util.List;
public class AnimalManager {
private List<String> animals;
public AnimalManager() {
animals = new ArrayList<>();
}
// Method to add an animal
public void addAnimal(String animal) {
animals.add(animal);
System.out.println("Added: " + animal);
}
// Method to remove an animal
public void removeAnimal(String animal) {
if (animals.remove(animal)) {
System.out.println("Removed: " + animal);
} else {
System.out.println("Animal not found: " + animal);
}
}
// Method to update an animal at a specific index
public void updateAnimal(int index, String newAnimal) {
if (index >= 0 && index < animals.size()) {
String old = animals.set(index, newAnimal);
System.out.println("Updated: " + old + " -> " + newAnimal);
} else {
System.out.println("Invalid index: " + index);
}
}
// Method to display all animals
public void displayAnimals() {
System.out.println("\nCurrent animals:");
if (animals.isEmpty()) {
System.out.println(" (no animals)");
} else {
for (int i = 0; i < animals.size(); i++) {
System.out.println(" " + i + ": " + animals.get(i));
}
}
}
public static void main(String[] args) {
AnimalManager manager = new AnimalManager();
// Adding animals
manager.addAnimal("Lion");
manager.addAnimal("Tiger");
manager.addAnimal("Bear");
manager.addAnimal("Elephant");
manager.displayAnimals();
// Updating an animal
manager.updateAnimal(1, "Leopard");
manager.displayAnimals();
// Removing an animal
manager.removeAnimal("Bear");
manager.displayAnimals();
// Try removing non-existent animal
manager.removeAnimal("Giraffe");
}
}Output:
Added: Lion
Added: Tiger
Added: Bear
Added: Elephant
Current animals:
0: Lion
1: Tiger
2: Bear
3: Elephant
Updated: Tiger -> Leopard
Current animals:
0: Lion
1: Leopard
2: Bear
3: Elephant
Removed: Bear
Current animals:
0: Lion
1: Leopard
2: Elephant
Animal not found: Giraffe
Answer: The program encapsulates four key operations on a List: add, remove, update, and display. Each method provides appropriate feedback and error handling.
3.3. Remove Odd-Length Strings from Set (Lab 10, Task 4)
Write a program that creates a Set with strings and removes all elements with odd length, leaving only even-length elements.
Click to see the solution
Key Concept: When removing elements while iterating, use an Iterator to avoid ConcurrentModificationException.
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
public class StringSetFilter {
public static void main(String[] args) {
// Create a Set with strings
Set<String> strings = new HashSet<>();
strings.add("cat"); // length 3 (odd)
strings.add("dog"); // length 3 (odd)
strings.add("bird"); // length 4 (even)
strings.add("fish"); // length 4 (even)
strings.add("elephant"); // length 8 (even)
strings.add("tiger"); // length 5 (odd)
strings.add("bear"); // length 4 (even)
System.out.println("Original set: " + strings);
// Remove odd-length strings using Iterator
Iterator<String> it = strings.iterator();
while (it.hasNext()) {
String str = it.next();
if (str.length() % 2 != 0) { // Odd length
System.out.println("Removing: " + str + " (length " + str.length() + ")");
it.remove(); // Safe removal using iterator
}
}
System.out.println("Final set (even lengths only): " + strings);
}
}Possible Output:
Original set: [cat, bear, bird, fish, dog, elephant, tiger]
Removing: cat (length 3)
Removing: dog (length 3)
Removing: tiger (length 5)
Final set (even lengths only): [bear, bird, fish, elephant]
Note: Order may vary since HashSet is unordered.
Alternative solution using removeIf (Java 8+):
import java.util.Set;
import java.util.HashSet;
public class StringSetFilterLambda {
public static void main(String[] args) {
Set<String> strings = new HashSet<>();
strings.add("cat");
strings.add("dog");
strings.add("bird");
strings.add("fish");
strings.add("elephant");
strings.add("tiger");
strings.add("bear");
System.out.println("Original set: " + strings);
// Remove odd-length strings using removeIf
strings.removeIf(str -> str.length() % 2 != 0);
System.out.println("Final set (even lengths only): " + strings);
}
}Answer: We use an Iterator to safely remove elements while iterating. Calling remove() on the iterator removes the current element without causing a ConcurrentModificationException. Alternatively, Java 8’s removeIf() method provides a more concise solution.
3.4. Map with Repetitive Value Counter (Lab 10, Task 5)
Write a program that creates a Map<String, Integer> from user input and reports if there are repetitive values and their count.
Click to see the solution
Key Concept: Use a Map to count occurrences, then analyze the values to find repetitions.
import java.util.Map;
import java.util.HashMap;
import java.util.Scanner;
public class RepetitiveValueCounter {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Map<String, Integer> wordCount = new HashMap<>();
System.out.println("Enter words (type 'done' to finish):");
// Read input and build the map
while (true) {
String word = scanner.next();
if (word.equalsIgnoreCase("done")) {
break;
}
// Count occurrences
wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
System.out.println("\nWord counts:");
for (Map.Entry<String, Integer> entry : wordCount.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// Find and report repetitive values
System.out.println("\nAnalysis:");
boolean hasRepetitions = false;
for (Map.Entry<String, Integer> entry : wordCount.entrySet()) {
if (entry.getValue() > 1) {
hasRepetitions = true;
System.out.println("'" + entry.getKey() + "' appears " +
entry.getValue() + " times (repetitive)");
}
}
if (!hasRepetitions) {
System.out.println("No repetitive values found.");
}
scanner.close();
}
}Example run:
Enter words (type 'done' to finish):
apple banana apple orange banana apple done
Word counts:
apple: 3
banana: 2
orange: 1
Analysis:
'apple' appears 3 times (repetitive)
'banana' appears 2 times (repetitive)
Alternative interpretation (checking if different keys map to same value):
import java.util.Map;
import java.util.HashMap;
import java.util.Scanner;
import java.util.List;
import java.util.ArrayList;
public class RepetitiveValueChecker {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Map<String, Integer> map = new HashMap<>();
System.out.println("Enter key-value pairs (format: key value)");
System.out.println("Type 'done' as key to finish:");
// Read key-value pairs
while (true) {
System.out.print("Key: ");
String key = scanner.next();
if (key.equalsIgnoreCase("done")) {
break;
}
System.out.print("Value: ");
int value = scanner.nextInt();
map.put(key, value);
}
System.out.println("\nMap contents: " + map);
// Check for repetitive values
Map<Integer, List<String>> valueToKeys = new HashMap<>();
for (Map.Entry<String, Integer> entry : map.entrySet()) {
int value = entry.getValue();
valueToKeys.putIfAbsent(value, new ArrayList<>());
valueToKeys.get(value).add(entry.getKey());
}
System.out.println("\nAnalysis:");
boolean hasRepetitions = false;
for (Map.Entry<Integer, List<String>> entry : valueToKeys.entrySet()) {
if (entry.getValue().size() > 1) {
hasRepetitions = true;
System.out.println("Value " + entry.getKey() +
" is mapped by " + entry.getValue().size() +
" keys: " + entry.getValue());
}
}
if (!hasRepetitions) {
System.out.println("No repetitive values found.");
}
scanner.close();
}
}Example run:
Enter key-value pairs (format: key value)
Type 'done' as key to finish:
Key: apple
Value: 100
Key: banana
Value: 200
Key: orange
Value: 100
Key: grape
Value: 200
Key: done
Map contents: {apple=100, banana=200, orange=100, grape=200}
Analysis:
Value 100 is mapped by 2 keys: [apple, orange]
Value 200 is mapped by 2 keys: [banana, grape]
Answer: The first solution counts word occurrences using a Map. The second solution checks if multiple keys map to the same value by inverting the map structure.
3.5. Online Book Reader System (Lab 10, Homework 1)
Design and implement a basic online book reader system with the following requirements:
Functionality:
- Searching the database of books and reading a book
- User membership creation and extension
- Only one active user at a time and only one active book by this user
Design Hints:
The OnlineReaderSystem class represents the main program. Instead of implementing everything in one class (which would make it too large), separate concerns into:
Library: Manages the collection of booksUserManager: Handles user managementDisplay: Manages the user interfaceBook: Represents a book entityUser: Represents a user entity
Note: This exercise was asked in interviews at Amazon, Microsoft, and many other companies.
Click to see the solution
Key Concept: Use object-oriented design principles to separate concerns. Each class should have a single, well-defined responsibility.
Step 1: Design the UML class diagram
Before coding, plan the class structure and relationships:
OnlineReaderSystemaggregatesLibrary,UserManager, andDisplayLibrarycontains a collection ofBookobjectsUserManagermanages a collection ofUserobjectsUserhas an association withBook(current book being read)
Step 2: Implementation
import java.util.*;
// Book class
class Book {
private String id;
private String title;
private String author;
private String content;
public Book(String id, String title, String author, String content) {
this.id = id;
this.title = title;
this.author = author;
this.content = content;
}
public String getId() { return id; }
public String getTitle() { return title; }
public String getAuthor() { return author; }
public String getContent() { return content; }
@Override
public String toString() {
return title + " by " + author;
}
}
// User class
class User {
private String userId;
private String name;
private String membershipType;
private Book currentBook;
public User(String userId, String name, String membershipType) {
this.userId = userId;
this.name = name;
this.membershipType = membershipType;
this.currentBook = null;
}
public String getUserId() { return userId; }
public String getName() { return name; }
public String getMembershipType() { return membershipType; }
public Book getCurrentBook() { return currentBook; }
public void setCurrentBook(Book book) {
this.currentBook = book;
}
public void extendMembership(String newType) {
this.membershipType = newType;
System.out.println("Membership extended to: " + newType);
}
@Override
public String toString() {
return name + " (ID: " + userId + ", " + membershipType + ")";
}
}
// Library class - manages books
class Library {
private Map<String, Book> books;
public Library() {
books = new HashMap<>();
}
public void addBook(Book book) {
books.put(book.getId(), book);
System.out.println("Added book: " + book);
}
public Book findBook(String id) {
return books.get(id);
}
public List<Book> searchBooks(String keyword) {
List<Book> results = new ArrayList<>();
for (Book book : books.values()) {
if (book.getTitle().toLowerCase().contains(keyword.toLowerCase()) ||
book.getAuthor().toLowerCase().contains(keyword.toLowerCase())) {
results.add(book);
}
}
return results;
}
public void displayAllBooks() {
System.out.println("\n=== Library Books ===");
for (Book book : books.values()) {
System.out.println(book);
}
}
}
// UserManager class - manages users
class UserManager {
private Map<String, User> users;
private User activeUser;
public UserManager() {
users = new HashMap<>();
activeUser = null;
}
public User createUser(String userId, String name, String membershipType) {
User user = new User(userId, name, membershipType);
users.put(userId, user);
System.out.println("User created: " + user);
return user;
}
public User findUser(String userId) {
return users.get(userId);
}
public boolean setActiveUser(String userId) {
User user = users.get(userId);
if (user != null) {
activeUser = user;
System.out.println("Active user set to: " + user);
return true;
}
System.out.println("User not found: " + userId);
return false;
}
public User getActiveUser() {
return activeUser;
}
public void logoutActiveUser() {
if (activeUser != null) {
System.out.println("User logged out: " + activeUser);
activeUser = null;
}
}
}
// Display class - manages display output
class Display {
private Book activeBook;
private User activeUser;
public void displayBook(Book book) {
if (book == null) {
System.out.println("No book to display.");
return;
}
activeBook = book;
System.out.println("\n========== READING ==========");
System.out.println("Title: " + book.getTitle());
System.out.println("Author: " + book.getAuthor());
System.out.println("-----------------------------");
System.out.println(book.getContent());
System.out.println("=============================\n");
}
public void displayUser(User user) {
if (user == null) {
System.out.println("No active user.");
return;
}
activeUser = user;
System.out.println("\n=== Active User ===");
System.out.println(user);
if (user.getCurrentBook() != null) {
System.out.println("Currently reading: " + user.getCurrentBook());
} else {
System.out.println("Not reading any book.");
}
System.out.println("===================\n");
}
public void refreshDisplay(User user, Book book) {
displayUser(user);
if (book != null) {
displayBook(book);
}
}
}
// Main OnlineReaderSystem class
class OnlineReaderSystem {
private Library library;
private UserManager userManager;
private Display display;
public OnlineReaderSystem() {
library = new Library();
userManager = new UserManager();
display = new Display();
}
public Library getLibrary() { return library; }
public UserManager getUserManager() { return userManager; }
public Display getDisplay() { return display; }
public void setActiveUserAndBook(String userId, String bookId) {
User user = userManager.findUser(userId);
if (user == null) {
System.out.println("User not found.");
return;
}
userManager.setActiveUser(userId);
Book book = library.findBook(bookId);
if (book == null) {
System.out.println("Book not found.");
return;
}
user.setCurrentBook(book);
display.refreshDisplay(user, book);
}
public void searchAndDisplay(String keyword) {
List<Book> results = library.searchBooks(keyword);
System.out.println("\n=== Search Results for: " + keyword + " ===");
if (results.isEmpty()) {
System.out.println("No books found.");
} else {
for (Book book : results) {
System.out.println(book);
}
}
}
public static void main(String[] args) {
OnlineReaderSystem system = new OnlineReaderSystem();
// Add books to library
system.getLibrary().addBook(new Book("B001", "Java Programming",
"John Smith", "This is a comprehensive guide to Java..."));
system.getLibrary().addBook(new Book("B002", "Data Structures",
"Jane Doe", "Learn about arrays, lists, trees..."));
system.getLibrary().addBook(new Book("B003", "Design Patterns",
"Bob Johnson", "Explore common software design patterns..."));
// Display all books
system.getLibrary().displayAllBooks();
// Create users
system.getUserManager().createUser("U001", "Alice", "Premium");
system.getUserManager().createUser("U002", "Bob", "Basic");
// Set active user and read a book
System.out.println("\n--- Alice starts reading ---");
system.setActiveUserAndBook("U001", "B001");
// Search for books
system.searchAndDisplay("Data");
// Extend membership
User alice = system.getUserManager().findUser("U001");
alice.extendMembership("Gold");
// Logout
system.getUserManager().logoutActiveUser();
// Another user
System.out.println("\n--- Bob starts reading ---");
system.setActiveUserAndBook("U002", "B003");
}
}Output:
Added book: Java Programming by John Smith
Added book: Data Structures by Jane Doe
Added book: Design Patterns by Bob Johnson
=== Library Books ===
Java Programming by John Smith
Data Structures by Jane Doe
Design Patterns by Bob Johnson
User created: Alice (ID: U001, Premium)
User created: Bob (ID: U002, Basic)
--- Alice starts reading ---
Active user set to: Alice (ID: U001, Premium)
=== Active User ===
Alice (ID: U001, Premium)
Currently reading: Java Programming by John Smith
===================
========== READING ==========
Title: Java Programming
Author: John Smith
-----------------------------
This is a comprehensive guide to Java...
=============================
=== Search Results for: Data ===
Data Structures by Jane Doe
Membership extended to: Gold
User logged out: Alice (ID: U001, Gold)
--- Bob starts reading ---
Active user set to: Bob (ID: U002, Basic)
=== Active User ===
Bob (ID: U002, Basic)
Currently reading: Design Patterns by Bob Johnson
===================
========== READING ==========
Title: Design Patterns
Author: Bob Johnson
-----------------------------
Explore common software design patterns...
=============================
Design Principles Applied:
- Single Responsibility Principle: Each class has one clear purpose
Library: Book managementUserManager: User managementDisplay: UI concernsOnlineReaderSystem: Coordination
- Encapsulation: Each class manages its own data with private fields and public methods
- Aggregation:
OnlineReaderSystemcontains but doesn’t ownLibrary,UserManager, andDisplay - Association:
Useris associated withBook(can exist independently)
Answer: This design separates concerns effectively, making the system maintainable and extensible. Each component can be modified independently without affecting others. The system enforces the constraint of one active user and one active book at a time through the UserManager and User classes.
3.6. Separating Interface from Implementation (Lecture 10, Example 1)
Consider an airplane hierarchy where different airplane models need different flying algorithms. Implement this correctly by separating interface from implementation.
Click to see the solution
Key Concept: Use abstract methods to force explicit implementation while providing optional default implementations through protected helper methods.
Incorrect approach (problem):
class Airplane {
public void fly() {
// Standard flying algorithm for Airbus
}
}
class AirbusA extends Airplane { } // Inherits Airbus algorithm
class AirbusB extends Airplane { } // Inherits Airbus algorithm
class Boeing extends Airplane { } // WRONG: Also inherits Airbus algorithm!Problem: Boeing automatically inherits Airbus’s flying algorithm. The compiler doesn’t detect this semantic error.
Correct approach (solution):
abstract class Airplane {
// Interface: must be implemented by all subclasses
public abstract void fly();
// Optional default implementation
protected void defaultFly() {
// Standard flying algorithm
System.out.println("Standard flying algorithm");
}
}
class AirbusA extends Airplane {
public void fly() {
defaultFly(); // Can choose to use default
}
}
class AirbusB extends Airplane {
public void fly() {
defaultFly(); // Can choose to use default
}
}
class Boeing extends Airplane {
public void fly() {
// MUST provide its own implementation
System.out.println("Boeing's own flying algorithm");
}
}Usage:
Airplane a = new AirbusA();
a.fly(); // Standard flying algorithm
Airplane b = new AirbusB();
b.fly(); // Standard flying algorithm
Airplane c = new Boeing();
c.fly(); // Boeing's own flying algorithmAnswer: By making fly() abstract, we force each subclass to explicitly implement it, preventing accidental inheritance of wrong behavior. The defaultFly() helper method provides an optional default implementation.
3.7. Using Final Methods (Lecture 10, Example 2)
Create a base class with a final method and demonstrate that it cannot be overridden.
Click to see the solution
Key Concept: The final keyword on a method prevents subclasses from overriding it, ensuring consistent behavior.
class Base {
public final void method() {
System.out.println("Base's method");
}
}
class Derived extends Base {
// This would cause a compilation error:
// public void method() {
// System.out.println("Derived's method");
// }
// Error: Cannot override the final method from Base
}Testing the code:
Base b1 = new Base();
b1.method(); // Output: Base's method
Base b2 = new Derived();
b2.method(); // Output: Base's method (same implementation)Answer: Final methods guarantee that the behavior defined in the base class cannot be changed by subclasses, enabling compiler optimizations and ensuring consistent behavior.
3.8. Creating a Final Class (Lecture 10, Example 3)
Demonstrate creating a final class and show that it cannot be extended.
Click to see the solution
final class Base {
public void method() {
System.out.println("Base's method");
}
}
// This would cause a compilation error:
// class Derived extends Base {
// }
// Error: The type Derived cannot subclass the final class BaseWhy can’t a class be both abstract and final?
// This is illegal and won't compile:
// abstract final class InvalidClass {
// abstract void method();
// }Explanation: An abstract class is incomplete by design—it requires subclasses to provide implementations. A final class forbids subclasses. These two concepts are contradictory, so using both is illegal.
Answer: Final classes prevent inheritance entirely. All methods in a final class are implicitly final. A class cannot be both abstract and final because abstract classes require inheritance to be complete.
3.9. Implementing an Interface (Lecture 10, Example 4)
Create an interface Features and implement it in a Lion class.
Click to see the solution
Key Concept: An interface defines a contract. Any class implementing it must provide implementations for all declared methods.
interface Features {
int numOfLegs();
boolean canFly();
boolean canSwim();
}
class Lion implements Features {
public int numOfLegs() {
return 4;
}
public boolean canFly() {
return false;
}
public boolean canSwim() {
return true;
}
}Usage:
// Can treat Lion as a Features interface
Features f = new Lion();
System.out.println("Number of legs: " + f.numOfLegs());
if (f.canFly()) {
System.out.println("Can fly");
} else {
System.out.println("Cannot fly");
}
if (f.canSwim()) {
System.out.println("Can swim");
}Output:
Number of legs: 4
Cannot fly
Can swim
Answer: The Lion class implements the Features interface by providing concrete implementations for all three methods. We can then treat a Lion object as a Features reference.
3.10. Multiple Interface Implementation (Lecture 10, Example 5)
Create a Person class that implements multiple interfaces representing different aspects of a person.
Click to see the solution
Key Concept: A class can implement multiple interfaces, providing a safe alternative to multiple inheritance.
interface iBodyParams {
int getHeight();
int getWeight();
}
interface iSkills {
String getProgrammingLanguage();
int getExperienceYears();
}
interface iRelations {
String getFriendName();
}
class Person implements iBodyParams, iSkills, iRelations {
private int height;
private int weight;
private String programmingLanguage;
private int experienceYears;
private String friendName;
public Person(int height, int weight, String language,
int experience, String friend) {
this.height = height;
this.weight = weight;
this.programmingLanguage = language;
this.experienceYears = experience;
this.friendName = friend;
}
// iBodyParams implementation
public int getHeight() { return height; }
public int getWeight() { return weight; }
// iSkills implementation
public String getProgrammingLanguage() { return programmingLanguage; }
public int getExperienceYears() { return experienceYears; }
// iRelations implementation
public String getFriendName() { return friendName; }
}Usage (viewing from different perspectives):
Person john = new Person(180, 75, "Java", 5, "Alice");
// View John as a set of skills
iSkills johnsSkills = john;
System.out.println("Programming language: " + johnsSkills.getProgrammingLanguage());
System.out.println("Experience: " + johnsSkills.getExperienceYears() + " years");
// View John's body parameters
iBodyParams johnsBody = john;
System.out.println("Height: " + johnsBody.getHeight() + " cm");
// View John's relationships
iRelations johnsRelations = john;
System.out.println("Friend: " + johnsRelations.getFriendName());Answer: A Person can be viewed through different interfaces depending on the context. Each interface represents a “facet” or perspective on the person.
3.11. Interface Inheritance (Lecture 10, Example 6)
Demonstrate how interfaces can extend other interfaces and how this affects implementing classes.
Click to see the solution
Key Concept: An interface can extend another interface, inheriting all its method declarations.
interface SpeedFeatures {
float maxSpeed();
float maxAcceleration();
}
interface EngineFeatures extends SpeedFeatures {
float numOfCyls();
float enginePower();
}
class Car implements EngineFeatures {
// Must implement ALL methods from both interfaces
// From SpeedFeatures
public float maxSpeed() {
return 200.0f;
}
public float maxAcceleration() {
return 5.5f;
}
// From EngineFeatures
public float numOfCyls() {
return 6.0f;
}
public float enginePower() {
return 300.0f;
}
}Usage:
Car myCar = new Car();
// Can be treated as EngineFeatures
EngineFeatures engine = myCar;
System.out.println("Engine power: " + engine.enginePower() + " HP");
System.out.println("Cylinders: " + engine.numOfCyls());
// Can also be treated as SpeedFeatures
SpeedFeatures speed = myCar;
System.out.println("Max speed: " + speed.maxSpeed() + " km/h");
System.out.println("Max acceleration: " + speed.maxAcceleration() + " m/s²");Answer: Car must implement methods from both SpeedFeatures and EngineFeatures because EngineFeatures extends SpeedFeatures. The object can be viewed as either interface type.
3.12. Type Checking with Interfaces (Lecture 10, Example 7)
Use instanceof to check if an object implements specific interfaces and perform appropriate actions.
Click to see the solution
Key Concept: The instanceof operator works with interfaces to check if an object implements a given interface.
interface Printable {
void print();
}
interface Movable {
void move();
}
interface Serializable {
void serialize();
}
abstract class Shape {
abstract void draw();
}
class Rectangle extends Shape implements Printable, Movable, Serializable {
public void draw() {
System.out.println("Drawing rectangle");
}
public void print() {
System.out.println("Printing rectangle");
}
public void move() {
System.out.println("Moving rectangle");
}
public void serialize() {
System.out.println("Serializing rectangle");
}
}Usage:
Shape a = new Rectangle();
// Check and use different interfaces
if (a instanceof Printable) {
((Printable)a).print(); // Prints: Printing rectangle
}
if (a instanceof Movable) {
((Movable)a).move(); // Prints: Moving rectangle
}
if (a instanceof Serializable) {
((Serializable)a).serialize(); // Prints: Serializing rectangle
}
// This would be false
if (a instanceof String) {
System.out.println("Never executes");
}Answer: instanceof checks if an object’s class implements a particular interface. After checking, we can safely cast the object to that interface type.
3.13. Nested Interface (Lecture 10, Example 8)
Create a nested interface inside a class and implement it.
Click to see the solution
class SomeClass {
public interface Nested {
boolean isNotNegative(int x);
}
}
class MyClass implements SomeClass.Nested {
public boolean isNotNegative(int x) {
return x >= 0;
}
}
class Demo {
public static void main(String[] args) {
SomeClass.Nested obj = new MyClass();
if (obj.isNotNegative(10)) {
System.out.println("10 is not negative");
}
if (obj.isNotNegative(-5)) {
System.out.println("Never prints");
} else {
System.out.println("-5 is negative");
}
}
}Output:
10 is not negative
-5 is negative
Answer: Nested interfaces are accessed using the enclosing class name (e.g., SomeClass.Nested). They can be implemented by any class, not just nested classes.
3.14. Basic Enumeration (Lecture 10, Example 9)
Create an enumeration for days of the week and use it in a switch statement.
Click to see the solution
Key Concept: Enumerations provide type-safe constants and work naturally with switch statements.
public enum Day {
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
}
public class DayDemo {
public void tellItLikeItIs(Day day) {
switch (day) {
case MONDAY:
System.out.println("Mondays are bad.");
break;
case FRIDAY:
System.out.println("Fridays are better.");
break;
case SATURDAY:
case SUNDAY:
System.out.println("Weekends are best.");
break;
default:
System.out.println("Midweek days are so-so.");
break;
}
}
public static void main(String[] args) {
DayDemo demo = new DayDemo();
demo.tellItLikeItIs(Day.MONDAY); // Mondays are bad.
demo.tellItLikeItIs(Day.FRIDAY); // Fridays are better.
demo.tellItLikeItIs(Day.SATURDAY); // Weekends are best.
demo.tellItLikeItIs(Day.WEDNESDAY); // Midweek days are so-so.
}
}Answer: Enumerations provide readable, type-safe constants. The compiler ensures only valid Day values can be passed to tellItLikeItIs().
3.15. Enumeration with Fields and Constructor (Lecture 10, Example 10)
Create a Coin enumeration with associated values (in cents) and methods.
Click to see the solution
Key Concept: Java enums can have fields, constructors, and methods, making them more powerful than simple constants.
enum Coin {
PENNY(1),
NICKEL(5),
DIME(10),
QUARTER(25);
private final int value;
// Constructor is private by default
Coin(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public class CoinDemo {
public static void main(String[] args) {
Coin c = Coin.DIME;
System.out.println("A dime is worth " + c.getValue() + " cents");
// Print all coins and their values
System.out.println("\nAll coins:");
for (Coin coin : Coin.values()) {
System.out.println(coin + ": " + coin.getValue() + " cents");
}
}
}Output:
A dime is worth 10 cents
All coins:
PENNY: 1 cents
NICKEL: 5 cents
DIME: 10 cents
QUARTER: 25 cents
Answer: Each enum constant is initialized with a value through the constructor. The values() method returns an array of all enum constants in declaration order.
3.16. Enum with Method Bodies (Lecture 10, Example 11)
Create an Operation enum where each constant has its own implementation of an eval() method.
Click to see the solution
Key Concept: Each enum constant can have its own method implementation, enabling operation-specific behavior.
enum Operation {
PLUS {
double eval(double x, double y) {
return x + y;
}
},
MINUS {
double eval(double x, double y) {
return x - y;
}
},
TIMES {
double eval(double x, double y) {
return x * y;
}
},
DIVIDE {
double eval(double x, double y) {
return x / y;
}
};
// Abstract method that each constant must implement
abstract double eval(double x, double y);
public static void main(String[] args) {
double x = 2.0;
double y = 4.0;
for (Operation op : Operation.values()) {
System.out.println(x + " " + op + " " + y + " = " + op.eval(x, y));
}
}
}Output:
2.0 PLUS 4.0 = 6.0
2.0 MINUS 4.0 = -2.0
2.0 TIMES 4.0 = 8.0
2.0 DIVIDE 4.0 = 0.5
Answer: Each enum constant provides its own implementation of the abstract eval() method, creating a clean alternative to switch statements.
3.17. Enum with values() and ordinal() Methods (Tutorial 10, Task 1)
Demonstrate the predefined values() and ordinal() methods available in all enums.
Click to see the solution
Key Concept: All Java enums automatically have values() (returns all constants) and ordinal() (returns position) methods.
public class Test {
enum Season {
WINTER,
SPRING,
SUMMER,
FALL
}
public static void main(String[] args) {
System.out.println("All seasons:");
for (Season s : Season.values()) {
System.out.println(s);
}
System.out.println("\nSeasons with ordinals:");
for (Season s : Season.values()) {
System.out.println(s + " has ordinal " + s.ordinal());
}
System.out.println("\nOrdinal of SUMMER: " + Season.SUMMER.ordinal());
}
}Output:
All seasons:
WINTER
SPRING
SUMMER
FALL
Seasons with ordinals:
WINTER has ordinal 0
SPRING has ordinal 1
SUMMER has ordinal 2
FALL has ordinal 3
Ordinal of SUMMER: 2
Answer: values() returns an array of all enum constants in declaration order. ordinal() returns the zero-based position of each constant.
3.18. Creating and Using an ArrayList (Tutorial 10, Task 2)
Create an ArrayList of strings and perform basic operations.
Click to see the solution
Key Concept: ArrayList is a resizable array implementation of the List interface.
import java.util.List;
import java.util.ArrayList;
public class GFG {
public static void main(String args[]) {
// Creating an ArrayList
List<String> al = new ArrayList<>();
// Adding elements
al.add("Geeks");
al.add("Geeks");
al.add(1, "For"); // Insert at index 1
// Print all elements
System.out.println(al);
// Access by index
System.out.println("Element at index 0: " + al.get(0));
System.out.println("Element at index 1: " + al.get(1));
// Size
System.out.println("Size: " + al.size());
// Remove
al.remove(2); // Remove element at index 2
System.out.println("After removal: " + al);
}
}Output:
[Geeks, For, Geeks]
Element at index 0: Geeks
Element at index 1: For
Size: 3
After removal: [Geeks, For]
Answer: ArrayList maintains insertion order and allows duplicates. Elements are accessed by zero-based index.
3.19. Creating and Using a HashSet (Tutorial 10, Task 3)
Create a HashSet and demonstrate that duplicates are automatically ignored.
Click to see the solution
Key Concept: HashSet stores unique elements only. Attempting to add a duplicate has no effect.
import java.util.Set;
import java.util.HashSet;
public class GFG {
public static void main(String[] args) {
// Create a HashSet
Set<String> hashSet = new HashSet<>();
// Adding elements
hashSet.add("Geeks");
hashSet.add("For");
hashSet.add("Geeks"); // Duplicate - will be ignored
hashSet.add("Example");
hashSet.add("Set");
// Print (order not guaranteed)
System.out.println(hashSet);
// Check if contains
System.out.println("Contains 'Geeks': " + hashSet.contains("Geeks"));
System.out.println("Contains 'Java': " + hashSet.contains("Java"));
// Size
System.out.println("Size: " + hashSet.size());
}
}Possible Output:
[Geeks, Example, Set, For]
Contains 'Geeks': true
Contains 'Java': false
Size: 4
Note: The order of elements in the output may vary since HashSet doesn’t maintain any order.
Answer: HashSet automatically filters out duplicate elements. The second “Geeks” is ignored, so the set contains only 4 elements instead of 5.
3.20. Creating and Using a HashMap (Tutorial 10, Task 4)
Create a HashMap to store key-value pairs and iterate through it.
Click to see the solution
Key Concept: HashMap stores key-value pairs where each unique key maps to one value.
import java.util.Map;
import java.util.HashMap;
public class HashMapDemo {
public static void main(String args[]) {
// Create a HashMap
Map<String, Integer> hm = new HashMap<>();
// Adding key-value pairs
hm.put("a", 100);
hm.put("b", 200);
hm.put("c", 300);
hm.put("d", 400);
// Replacing a value
hm.put("a", 150); // Replaces previous value for key "a"
System.out.println("HashMap contents:");
// Traversing through the map
for (Map.Entry<String, Integer> me : hm.entrySet()) {
System.out.println(me.getKey() + ": " + me.getValue());
}
// Access by key
System.out.println("\nValue for key 'b': " + hm.get("b"));
// Check if key exists
System.out.println("Contains key 'c': " + hm.containsKey("c"));
System.out.println("Contains key 'z': " + hm.containsKey("z"));
// Size
System.out.println("Size: " + hm.size());
}
}Possible Output:
HashMap contents:
a: 150
b: 200
c: 300
d: 400
Value for key 'b': 200
Contains key 'c': true
Contains key 'z': false
Size: 4
Note: The order of entries may vary since HashMap doesn’t maintain insertion order.
Answer: HashMap maps unique keys to values. When we put a value for an existing key, the old value is replaced. We iterate using entrySet() which returns key-value pairs.
3.21. Using an Iterator (Tutorial 10, Task 5)
Use an Iterator to traverse through an ArrayList.
Click to see the solution
Key Concept: An Iterator provides a uniform way to traverse any collection.
import java.util.ArrayList;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
// Create a collection
ArrayList<String> cars = new ArrayList<>();
cars.add("Volvo");
cars.add("BMW");
cars.add("Ford");
cars.add("Mazda");
// Get the iterator
Iterator<String> it = cars.iterator();
// Print all elements using iterator
System.out.println("All cars:");
while (it.hasNext()) {
String car = it.next();
System.out.println(car);
}
// Create a new iterator for another traversal
Iterator<String> it2 = cars.iterator();
// Print the first item
System.out.println("\nFirst car: " + it2.next());
}
}Output:
All cars:
Volvo
BMW
Ford
Mazda
First car: Volvo
Answer: iterator() returns an Iterator object. We use hasNext() to check if more elements exist and next() to retrieve them. Note that an iterator is consumed after use—we need a new iterator for a second traversal.