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
228: Building a Job Application Tracker
Alright, let's get into this. We've spent a lot of time talking about individual classes and data structures, but now I want us to actually put them together. We're building a Job Application Tracker. The goal is simple: keep track of where we've applied, the role, and the current status (Applied, Interviewing, Offered, or Rejected).
The Naive List Approach
My first instinct is usually to just throw everything into a List. It's the path of least resistance. I'll start by defining a simple JobApplication POJO and then try to manage them in a main loop.
public class JobApplication {
String company;
String role;
String status;
public JobApplication(String company, String role, String status) {
this.company = company;
this.role = role;
this.status = status;
}
// getters and setters omitted for brevity
}
// In our main logic:
List<JobApplication> apps = new ArrayList<>();
apps.add(new JobApplication("TechCorp", "Backend Dev", "Applied"));
apps.add(new JobApplication("SoftSystems", "Java Engineer", "Applied"));
This works great for adding. But here's where I hit a snag. Imagine we get an email from TechCorp saying they want to interview us. I need to update that specific application's status. With a List, I can't just "find" TechCorp. I have to iterate through the entire list, check every object, and hope I find the right one.
for (JobApplication app : apps) {
if (app.getCompany().equals("TechCorp")) {
app.setStatus("Interviewing");
break;
}
}
It's a bit clunky, right? If we have ten applications, it's fine. If we're aggressively applying to a hundred places, this O(n) lookup starts to feel like a waste of energy.
Switching Gears to a Map
I'm thinking: why am I searching for a company name manually? I should probably be using a Map. If I use the company name as the key, I can jump straight to the application I want to update.
Map<String, JobApplication> appMap = new HashMap<>();
appMap.put("TechCorp", new JobApplication("TechCorp", "Backend Dev", "Applied"));
appMap.put("SoftSystems", new JobApplication("SoftSystems", "Java Engineer", "Applied"));
// Updating is now a one-liner
appMap.get("TechCorp").setStatus("Interviewing");
Much better. The update is now nearly instantaneous. But wait—I just realized a flaw in this design. What if I apply to TechCorp for two different roles? Maybe a "Backend Dev" role and a "Systems Architect" role? My HashMap just nuked the first application because the keys must be unique. I've accidentally deleted my own data.
Solving the Collision Problem
Okay, lesson learned. Using the company name as a key is too risky. I need a unique identifier. In a real database, this would be a primary key (like a UUID or an auto-incrementing ID). Let's introduce an applicationId to our class.
public class JobApplication {
private int id;
private String company;
// ... other fields
}
Now, I'll map the Integer ID to the JobApplication object. This solves the duplicate company problem. But now I've run into a different issue: the user doesn't want to remember an ID number; they want to see all their "Interviewing" applications. A Map is great for finding a specific item by ID, but it's useless for filtering by status.
Designing a Dedicated Manager
I've realized that trying to do everything inside the main method with a single collection is making the code messy. I need a "Service" or "Manager" class that handles the logic of how we store and retrieve this data, so the rest of the app doesn't have to care if I'm using a Map, a List, or a database.
I'll keep the Map for the fast lookups by ID, but I'll use Java Streams to handle the filtering. It gives me the best of both worlds: speed for updates and flexibility for reporting.
public class ApplicationManager {
private Map<Integer, JobApplication> storage = new HashMap<>();
private int nextId = 1;
public void addApplication(String company, String role, String status) {
JobApplication app = new JobApplication(nextId++, company, role, status);
storage.put(app.getId(), app);
}
public List<JobApplication> getApplicationsByStatus(String status) {
return storage.values().stream()
.filter(app -> app.getStatus().equalsIgnoreCase(status))
.collect(Collectors.toList());
}
public boolean updateStatus(int id, String newStatus) {
if (storage.containsKey(id)) {
storage.get(id).setStatus(newStatus);
return true;
}
return false;
}
}
Now the logic is encapsulated. If I decide later that I want to save these to a file or a database, I only have to change the code inside ApplicationManager. The rest of my program just calls getApplicationsByStatus() and doesn't care how the magic happens under the hood. That's the kind of separation that keeps a project from collapsing under its own weight as it grows.
📋 Practical Task
Implement a Job Application Archive Feature
Currently, our ApplicationManager keeps everything in memory. Your task is to add a "removal" and "archiving" mechanism to the manager class.
- Modify the
ApplicationManagerclass to include a second collection (aListor anotherMap) calledarchivedApplications. - Implement a method
archiveApplication(int id). This method should remove the application from the activestoragemap and move it into thearchivedApplicationscollection. - Ensure that if an ID is provided that doesn't exist in the active storage, the method returns
falseor throws a custom exception rather than crashing. - Create a method
getArchivedCount()that returns the total number of archived applications.
Test your implementation by adding three applications, archiving two of them, and verifying that the active storage now only contains one item while the archive contains two.
There are no comments for now.