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
30: Polymorphism in Practice
By now, you know the theory: polymorphism is just "many forms." But when you're actually staring at a blank IDE, it can feel like an academic exercise. You might be wondering, "Why go through the trouble of an inheritance hierarchy when I can just write a few if-statements?" Let's get into how this actually works in a professional codebase.
Why not just use a bunch of "if" statements to check the type?
I see this all the time with junior devs. You have a Payment class and three subclasses: CreditCard, PayPal, and Crypto. When it's time to process the payment, the instinct is to do something like this:
if (payment instanceof CreditCard) { ((CreditCard) payment).chargeCard(); } else if (payment instanceof PayPal) { ((PayPal) payment).redirecttoPayPal(); } // ... and so onThis is what I call "shotgun surgery." Every time your boss tells you to add a new payment method—say, Apple Pay—you have to hunt down every single one of these
if/elseblocks across your entire project and add a new case. If you miss one, the app crashes in production.With polymorphism, you define a
processPayment()method in the basePaymentclass. Each subclass overrides it. Now, your processing logic looks like this:public void executeTransaction(Payment payment) { payment.processPayment(); // Java figures out which version to run at runtime }Now, when you add Apple Pay, you just create the new class. You don't have to touch the
executeTransactionmethod at all. It just works. That's the real power here: your high-level logic becomes immune to changes in your low-level implementations.If I'm treating everything as the base class, how do I access subclass-specific methods?
This is the part that trips people up. If you have a
List<Payment>, you can only call methods defined in thePaymentclass. IfCreditCardhas a special method calledvalidateCVV(), you can't just call it on aPaymentobject. The compiler will stop you because it can't guarantee that every payment is a credit card.Sometimes, you actually do need that specific functionality. In those cases, you use downcasting. But do it safely. Use the
instanceofoperator (or the newer pattern matching in recent Java versions) to ensure you aren't forcing aCryptopayment to act like aCreditCard, which would throw aClassCastException.for (Payment p : payments) { p.processPayment(); // General action if (p instanceof CreditCard card) { card.validateCVV(); // Specific action for cards only } }A word of advice: if you find yourself downcasting everywhere, your abstraction is probably leaking. You should probably move that "specific" logic into the overridden
processPayment()method instead.How do I actually design my classes to make this useful?
The secret is to program to an interface (or an abstract class), not an implementation. Stop thinking about "The Credit Card Class" and start thinking about "The Payment Behavior."
I usually start by creating an
abstractclass. I make it abstract because a generic "Payment" doesn't actually exist in the real world—you never just "pay" in a vacuum; you pay via something. By making it abstract, I force every developer who joins the project to implement the required methods in their specific subclasses.public abstract class Payment { protected double amount; public Payment(double amount) { this.amount = amount; } // Every payment MUST implement this public abstract void processPayment(); // Every payment shares this logic public void printReceipt() { System.out.println("Paid: $" + amount); } }By structuring it this way, you've created a contract. You're telling the rest of your system: "I don't care how the payment happens, as long as the object provides a
processPayment()method." That's how you build software that can grow without breaking every five minutes.
📋 Practical Task
Exercise: Building a Multi-Channel Notification Dispatcher
You are building a system that sends alerts to users. Users can choose to receive alerts via different channels: Email, SMS, or Push Notification.
Your Goal: Implement a polymorphic system where a NotificationService can send a list of notifications without knowing which channel each one uses.
- Create an abstract class
Notificationwith aprotected String messageand an abstract methodvoid send(). - Create three subclasses:
EmailNotification,SMSNotification, andPushNotification. Each should overridesend()to print a message like: "Sending Email: [message]". - Create a
NotificationServiceclass with a methoddispatchAll(List<Notification> notifications). This method should loop through the list and callsend()on each object. - In your
mainmethod, create aArrayListofNotificationobjects containing at least one of each subclass, and pass that list to yourNotificationService.
Challenge: Add a specific method to EmailNotification called setSubject(String subject). In your main method, use instanceof to check if a notification is an email, and if so, set a subject before sending it.
There are no comments for now.