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
62: Bounded Type Parameters
Imagine you're hiring for a position at your company. If you put out a job ad saying, "I'm looking for a Person," anyone can apply. You'll get poets, chefs, and accountants. But the problem is, once they're hired, you can't actually ask them to do anything specific to the job—like "perform surgery" or "write a legal brief"—because a generic Person doesn't necessarily have those skills. To get someone who can actually do the work, you have to change the requirement: "I'm looking for a Person who is a Doctor."
That's exactly what's happening with Bounded Type Parameters in Java. When we use a plain generic like <T>, Java treats T as an Object. And while every class inherits from Object, Object is pretty boring; it doesn't have the specialized methods you actually need to get work done.
Getting more out of T
Let's look at a scenario I've run into a dozen times. Say you want to write a method that finds the larger of two numbers. If you try to do it with a standard generic, you'll hit a wall immediately:
public <T> T findMax(T a, T b) {
// Error: The operator > is undefined for the type T
return (a > b) ? a : b;
}
Java is complaining because T could be anything—a String, a Scanner, or a UserAccount. You can't use the > operator on a Scanner. To fix this, we need to put a "bound" on the type. We need to tell Java, "I don't care exactly what T is, as long as it implements the Comparable interface."
Here is how we map that "Doctor" requirement to the code:
public <T extends Comparable<T>> T findMax(T a, T b) {
return (a.compareTo(b) > 0) ? a : b;
}
By adding extends Comparable<T>, we've told the compiler that T is guaranteed to have the compareTo method. Now, the code compiles, and we can pass in Integers, Doubles, or even custom Employee objects, provided they implement Comparable.
The Ceiling and the Floor
Most of the time, you'll use Upper Bounds (the extends keyword). I think of this as setting a "ceiling." You're saying the type must be the specified class or any subclass of it. If you use <T extends Number>, you've capped it at Number. You can use Integer or Double, but you can't use String.
Then there's the weird sibling: Lower Bounds. This uses the super keyword. You'll rarely see this in a class definition (in fact, you can't use it there), but you'll see it in method arguments. It’s used when you want to write to a collection. If you have a List<? super Integer>, you're saying "this list can hold Integers or any superclass of Integer (like Number or Object)."
I'll be honest: lower bounds can feel backward at first. Just remember this rule of thumb I use: Use extends when you want to get something out of a structure and use its methods; use super when you want to put something into a structure.
Mixing and Matching Bounds
Sometimes, one bound isn't enough. What if you need a type that is both a Number and Comparable? Java lets you use an ampersand (&) for this. The only catch is that the class must come first, followed by the interfaces.
public <T extends Number & Comparable<T>> void processNumericValue(T value) {
// Now I can call .doubleValue() AND .compareTo()
}
It's a bit wordy, sure, but it gives you total control over the capabilities of your generic types without sacrificing type safety.
📋 Practical Task
Build a Weight-Based Shipping Calculator
You need to create a shipping system that calculates the total weight of a shipment. However, not every object in your system has a weight (e.g., a DigitalDownload doesn't, but a PhysicalBook does).
Your task:
- Create an interface called
Weightablewith a methoddouble getWeight(). - Create two classes,
PhysicalBookandLaptop, that both implementWeightable. - Create a class called
ShippingContainerwith a generic method calledcalculateTotalWeight. - Crucially: Use a bounded type parameter in
calculateTotalWeightto ensure the method only accepts aListof items that areWeightable. If the method is called with a list of objects that aren'tWeightable, it should fail at compile time. - In your
mainmethod, instantiate a list ofPhysicalBookobjects and use yourShippingContainerto find their total weight.
There are no comments for now.