Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
284: Practice Exercise: Implementing a Simple Event Bus
I've always found that the hardest part of growing a Java project isn't writing the initial features—it's managing how those features talk to each other. If every class has a direct reference to every other class, you end up with a "big ball of mud" where changing one line in the User class breaks the EmailService, the AnalyticsEngine, and the DatabaseLogger. This is why we use an Event Bus.
The Coupling Problem
Let's imagine we're building a simple RPG. When an enemy dies, a few things need to happen: the player gets XP, the quest tracker checks if we've killed enough goblins, and the UI needs to show a "Victory!" toast. My first instinct might be to just put all that logic inside the Enemy class:
public class Enemy {
private QuestTracker questTracker;
private ExperienceManager xpManager;
private UIManager uiManager;
public void die() {
xpManager.addXp(100);
questTracker.notifyKill("Goblin");
uiManager.showToast("Enemy Defeated!");
}
}
This looks fine for a weekend project, but it's a nightmare for a real system. The Enemy class now needs to know about the UI, the XP system, and the quest system. If I decide to add a SoundManager to play a death sound, I have to modify the Enemy class again. I don't like that. The enemy should just know it died; it shouldn't care who is listening.
Moving to a Central Hub
I want a middleman. I'll create an EventBus where the Enemy can just shout, "I died!" into the void, and whoever cares can listen for that specific shout. I'll start simple. I'll create a basic event class and a bus that holds a list of listeners.
public class EnemyDeathEvent {
public final String enemyType;
public EnemyDeathEvent(String type) { this.enemyType = type; }
}
public class EventBus {
private final List<Consumer<Object>> listeners = new ArrayList<>();
public void subscribe(Consumer<Object> listener) {
listeners.add(listener);
}
public void publish(Object event) {
listeners.forEach(l -> l.accept(event));
}
}
I tried running this, and while it "works," I hit a wall immediately. Since the listeners are Consumer<Object>, every single listener has to manually check if (event instanceof EnemyDeathEvent) and cast it. That's tedious and feels very "Java 1.4." We can do better with generics.
Fighting the Type System
I want to be able to subscribe to a specific type of event. I'll try using a Map where the key is the event class and the value is a list of listeners for that specific class. This is where Java's type system usually starts fighting back.
public class EventBus {
private final Map<Class<?>, List<Consumer<?>>> subscribers = new HashMap<>();
public <T> void subscribe(Class<T> eventType, Consumer<T> listener) {
subscribers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(listener);
}
public void publish(Object event) {
Class<?> eventType = event.getClass();
List<Consumer<?>> eventListeners = subscribers.get(eventType);
if (eventListeners != null) {
for (Consumer<?> listener : eventListeners) {
// Here is the tricky part...
((Consumer<Object>) listener).accept(event);
}
}
}
}
You'll notice that ugly cast to (Consumer<Object>). I'll be honest: it feels a bit dirty, but because we've controlled the input in the subscribe method (ensuring the listener matches the eventType key), this is safe. The JVM will complain about "unchecked" casts, but we've logically guaranteed the types match.
Putting it into Action
Now let's see if this actually solves our coupling problem. I can now set up my systems independently. The Enemy class doesn't need to know about the QuestTracker anymore; it only needs a reference to the EventBus.
// Setup
EventBus bus = new EventBus();
// The QuestTracker just registers itself
bus.subscribe(EnemyDeathEvent.class, event -> {
System.out.println("Quest updated: " + event.enemyType + " slain!");
});
// The Enemy just publishes
public class Enemy {
private EventBus bus;
public Enemy(EventBus bus) { this.bus = bus; }
public void die() {
bus.publish(new EnemyDeathEvent("Goblin"));
}
}
This is much cleaner. If I want to add a SoundManager, I just create a new subscriber in my main initialization code. I never have to touch the Enemy class again. We've successfully decoupled the cause (enemy death) from the effect (XP, Quests, UI).
📋 Practical Task
Exercise: Implementing a Player Inventory Event System
Build a decoupled inventory notification system using the Event Bus pattern explored in this lesson.
- The Event: Create an
ItemPickedUpEventclass that contains theitemName(String) and theitemWeight(double). - The Bus: Implement an
EventBusclass with asubscribemethod (using a Map of classes to listeners) and apublishmethod. - The Listeners:
- Create a
WeightTrackerthat subscribes toItemPickedUpEventand maintains a running total of the player's total carried weight. - Create a
InventoryLoggerthat subscribes toItemPickedUpEventand prints a message: "Log: Player picked up [itemName]".
- Create a
- The Test: In a
mainmethod, instantiate the bus and the listeners. Then, simulate picking up three different items (e.g., "Iron Sword", "Healing Potion", "Heavy Shield") by publishing events to the bus. Verify that both the logger prints the message and the weight tracker calculates the correct total weight.
There are no comments for now.