☕ Java (Java 21+ LTS) 🟢 Lesson 23 of 25 📂 Part 05: Collections & Capstone (Page 3 of 5) ⏱️ 22 min read

Generics & Type Safety — Classes, Methods & Wildcards (జెనరిక్స్ & టైప్ సేఫ్టీ ఆర్కిటెక్చర్)

📌 Covered in this lesson:
Custom Label Storage Crates & Medical Pill Dispenser Analogy · Compile-Time Type Safety vs Runtime ClassCastException · Generic Classes & Interfaces (<T>, <K, V>) · Generic Methods & Type Inference · Bounded Type Parameters (<T extends Comparable<T>>) · Wildcards (?, ? extends T, ? super T) · The PECS Principle (Producer Extends, Consumer Super) · Type Erasure & Bytecode Mechanics · Enterprise Generic CRUD Repository Engine · 5 Production Pitfalls

సాఫ్ట్‌వేర్ ఇంజనీరింగ్‌లో అత్యంత ప్రమాదకరమైన బగ్ ఏదైనా ఉందంటే అది — "ప్రొడక్షన్‌లో రన్‌టైమ్ వద్ద అకస్మాత్తుగా పేలిపోయే టైప్ కాస్టింగ్ ఎర్రర్" (Runtime ClassCastException). జావా 5 కు పూర్వం కలెక్షన్లలో డేటాను నిల్వ చేయడానికి కేవలం సాధారణ Object రిఫరెన్సులను మాత్రమే ఉపయోగించేవారు. దీనివల్ల డెవలపర్లు ప్రతి వస్తువును బయటకు తీసినప్పుడు బలవంతంగా టైప్ క్యాస్ట్ చేయాల్సి వచ్చేది. ఈ భయంకరమైన బలహీనతను రూపుమాపి, ఆధునిక కంపైల్-టైమ్ టైప్ సేఫ్టీని అందించడానికి Java 5 లో ప్రవేశపెట్టబడిన విప్లవాత్మక ఫీచరే Generics. ఒకే కోడ్‌ను వివిధ డేటా టైపులకు అనుగుణంగా రీయూజ్ చేయడం, బౌండెడ్ టైపులు, వైల్డ్‌కార్డులు (? extends, ? super), ప్రఖ్యాత PECS ప్రిన్సిపల్ మరియు JVM లోపల Type Erasure ఎలా పనిచేస్తుందో ఈ సమగ్ర పాఠంలో సవివరంగా నేర్చుకుందాం.

కస్టమ్ లేబుల్ స్టోరేజ్ క్రేట్లు & మెడికల్ పిల్ డిస్పెన్సర్ అనాలజీ (The Custom Label Storage Crates & Medical Pill Dispenser Analogy):

  1. 1. Generics లేని పాత కాలం (స్పష్టమైన లేబుల్ లేని గందరగోళపు పెట్టె - Raw Types Chaos): జావా 5 కి ముందు కలెక్షన్స్ కేవలం సాధారణ Object లను మాత్రమే స్టోర్ చేసేవి. ఒక గదిలో ఏ లేబుల్ లేని ఒక పెద్ద పెట్టె ఉందనుకుందాం. అందులో యాపిల్స్, సుత్తులు, మందులు అన్నీ కలిసిపోయాయి. చీకట్లో చేతులు పెట్టి బయటకు తీసిన ప్రతిసారీ "ఇది కచ్చితంగా యాపిలేనా?" అని ధృవీకరించుకోవాలి (Explicit Casting). పొరపాటున సుత్తి వస్తే నోట్లో పెట్టుకోగానే పన్ను విరిగిపోతుంది (ClassCastException)!
  2. 2. Generics (కచ్చితమైన కస్టమ్ లేబుల్ కలిగిన స్టోరేజ్ క్రేట్ - Type Safe Containers): జావా 5 లో ప్రవేశపెట్టిన Generics <T> ద్వారా, మనం క్రేట్ పైన Box<Apple> అని కచ్చితమైన లేబుల్ వేస్తాము. ఇప్పుడు ఆ పెట్టెలోకి యాపిల్స్ తప్ప మరే ఇతర వస్తువునూ పెట్టడానికి వీల్లేదు. ఒకవేళ ఎవరైనా పొరపాటున సుత్తి పెట్టాలని చూస్తే, జావా కంపైలర్ అక్కడికక్కడే రెడ్ ఎర్రర్ ఇచ్చి అడ్డుకుంటుంది (Compile-Time Safety). పెట్టె నుండి బయటకు తీసేటప్పుడు కూడా ఎలాంటి టైప్ క్యాస్టింగ్ అవసరం లేకుండా నేరుగా Apple లభిస్తుంది.
  3. 3. PECS వైల్డ్‌కార్డ్స్ (మందుల డిస్పెన్సర్ సూత్రం - Producer Extends, Consumer Super): ఆసుపత్రిలోని ఒక మందుల డిస్పెన్సర్ నుండి రోగులకు మందులను పంపిణీ చేయడమే లక్ష్యమైతే (Read-Only Producer), మనం ఏదైనా నిర్దిష్ట యాంటీబయాటిక్ లేదా దాని సబ్‌క్లాస్‌లన్నింటినీ స్వీకరించవచ్చు (? extends Antibiotic). ఒకవేళ ఆ డిస్పెన్సర్‌లోకి ఫార్మసిస్ట్ కొత్త మందులను లోడ్ చేయడమే పని అయితే (Write-Only Consumer), మనం ఆ మందును లేదా దాని సూపర్ క్లాస్ కేటగిరీలను అనుమతించాలి (? super Amoxicillin). ఇదే ప్రసిద్ధ PECS ప్రిన్సిపల్: Producer Extends, Consumer Super!

💡 ఈ పాఠంలో మీరు జెనరిక్ క్లాస్‌లు, మెథడ్స్, బౌండెడ్ టైప్ పారామీటర్లు, PECS రూల్స్ మరియు రియల్-వరల్డ్ స్ప్రింగ్ డేటా లాంటి ఎంటర్‌ప్రైజ్ టైప్-సేఫ్ రిపోజిటరీ ఆర్కిటెక్చర్‌ను స్వయంగా నిర్మిస్తారు!

1. Generics ఆవశ్యకత & Type Safety (Why Java Generics Matter)

జావా ప్రారంభమైనప్పుడు (Java 1.0 నుండి 1.4 వరకు), కలెక్షన్లు అన్నీ కేవలం java.lang.Object టైపును మాత్రమే పారామీటర్‌గా తీసుకునేవి. ఎందుకంటే జావాలో ప్రతి క్లాస్ కూడా Object యొక్క సబ్‌క్లాసే. అయితే దీనివల్ల రెండు తీవ్రమైన సమస్యలు ఉత్పన్నమయ్యాయి:

⚠️ పాత జావా కోడ్ యొక్క రెండు పెను ప్రమాదాలు:
1. రుచించని బలవంతపు కాస్టింగ్ (Clunky Explicit Casting): String name = (String) list.get(0); అని ప్రతి లైనులో క్యాస్టింగ్ కోడ్ రాయాల్సి వచ్చేది.
2. రన్‌టైమ్ క్రాష్‌లు (Runtime ClassCastException): జావా కంపైలర్ లిస్ట్‌లోకి ఏ వస్తువు వెళుతుందో చెక్ చేయలేకపోయేది. ఒక డెవలపర్ లిస్ట్‌లో స్ట్రింగ్స్ ఉంటాయనుకుని లూప్ రాస్తే, అందులో ఎవరైనా పొరపాటున Integer పెడితే రన్‌టైమ్‌లో మొత్తం అప్లికేషన్ షట్‌డౌన్ అయ్యేది.

Java 5 లో Generics ప్రవేశపెట్టడం వలన, ఈ సమస్యలన్నీ కంపైల్-టైమ్ (Compile-Time) లోనే పరిష్కరించబడ్డాయి. టైప్ తప్పులు జరిగితే ప్రోగ్రామ్ కంపైల్ కూడా కాదు. దీనివలన రన్‌టైమ్ వద్ద బగ్‌లు వచ్చే అవకాశమే ఉండదు.

2. Generic Classes & Interfaces (<T>, <K, V>)

మనం ఒక క్లాస్ లేదా ఇంటర్‌ఫేస్‌ను నిర్వచించేటప్పుడు నిర్దిష్ట డేటా టైపుకు పరిమితం కాకుండా, ఒక పారామీటరైజ్డ్ టైప్ (Type Parameter) ను ఉంచితే దానిని Generic Class అంటారు. జావాలో ప్రామాణికంగా ఉపయోగించే టైప్ పారామీటర్ పేర్లు:

  • T: Type (ఏదైనా సాధారణ డేటా రకం)
  • E: Element (కలెక్షన్లలోని మూలకం)
  • K, V: Key & Value (మ్యాప్‌లలో కీ మరియు విలువ జంటలు)
  • N: Number (సంఖ్యా రకాలు)
  • R: Return Type (ఫంక్షనల్ ఇంటర్‌ఫేస్‌లలో ఫలితం)
// Generic Class Definition:
public class ResultHolder<T> {
    private final T data;
    private final boolean success;

    public ResultHolder(T data, boolean success) {
        this.data = data;
        this.success = success;
    }
    public T getData() { return data; }
}

3. Generic Methods & Type Inference (మెథడ్ లెవల్ జెనరిక్స్)

మొత్తం క్లాస్‌ను జెనరిక్‌గా మార్చకుండా, కేవలం ఒకే ఒక్క మెథడ్‌ను మాత్రమే జెనరిక్‌గా రూపొందించవచ్చు. మెథడ్ యొక్క రిటర్న్ టైప్‌కు ముందు <T> లేదా <K, V> ను ప్రకటించడం ద్వారా ఇది సాధ్యమవుతుంది.

అంతేకాకుండా, ఆధునిక జావా కంపైలర్ Type Inference (టైప్ ఇన్‌ఫరెన్స్) ను ఉపయోగిస్తుంది. మనం పంపిన ఆర్గ్యుమెంట్లను బట్టి ఏ టైప్ పాస్ చేయబడిందో కంపైలర్ స్వయంగా ఊహించగలదు. మనం మెథడ్‌ను కాల్ చేసేటప్పుడు ప్రత్యేకంగా Class.<String>method() అని చెప్పనవసరం లేదు; కేవలం Class.method("Hello") అంటే చాలు.

4. ప్రాక్టికల్ కోడ్: Generic Class & Method Showcase

క్రింది జావా ప్రోగ్రామ్‌లో రీయూజబుల్ API రెస్పాన్స్ రాపర్ క్లాస్ (ApiResponse<T>) మరియు రెండు వేర్వేరు రకాల ఎరేలను లేదా లిస్ట్‌లను స్వాప్/ప్రింట్ చేసే జెనరిక్ మెథడ్స్ ఎలా పనిచేస్తాయో పరిశీలించండి.

☕ Java — GenericClassAndMethodShowcase.java
import java.util.*;

public class GenericClassAndMethodShowcase {

    // 1. Generic API Response Wrapper Class (ఏ డేటా పేలోడ్‌నైనా మోయగలదు)
    public static class ApiResponse<T> {
        private final int statusCode;
        private final String message;
        private final T payload;

        public ApiResponse(int statusCode, String message, T payload) {
            this.statusCode = statusCode;
            this.message = message;
            this.payload = payload;
        }

        public int getStatusCode() { return statusCode; }
        public String getMessage() { return message; }
        public T getPayload() { return payload; }

        @Override
        public String toString() {
            return "ApiResponse [code=" + statusCode + ", msg=" + message + ", data=" + payload + "]";
        }
    }

    // 2. Generic Method: ఏ రకమైన లిస్ట్‌నైనా ఒకే విధంగా ప్రింట్ చేసే యుటిలిటీ
    public static <E> void printListDetails(String label, List<E> list) {
        System.out.println("🔹 " + label + " (Size: " + list.size() + "):");
        for (E item : list) {
            System.out.println("   -> Item: " + item + " [Type: " + item.getClass().getSimpleName() + "]");
        }
    }

    // 3. Generic Method: రెండు ఆబ్జెక్టులలో ఏది పెద్దదో పోల్చే మెథడ్ (Bounded to Comparable)
    public static <T extends Comparable<T>> T findMax(T a, T b) {
        return a.compareTo(b) >= 0 ? a : b;
    }

    public static void main(String[] args) {
        System.out.println("=== 1. GENERIC API RESPONSE TEST ===");
        
        // String పేలోడ్‌తో ApiResponse
        ApiResponse<String> authResponse = new ApiResponse<>(200, "Success", "JWT_TOKEN_ABC123");
        System.out.println(authResponse);
        String token = authResponse.getPayload(); // ఎటువంటి టైప్ క్యాస్టింగ్ అవసరం లేదు!
        System.out.println("Extracted Token: " + token);

        // List పేలోడ్‌తో ApiResponse
        List<Integer> portNumbers = List.of(8080, 8443, 9092);
        ApiResponse<List<Integer>> portsResponse = new ApiResponse<>(200, "Port List", portNumbers);
        System.out.println("Configured Ports: " + portsResponse.getPayload());

        System.out.println("
=== 2. GENERIC METHOD UTILITY ===");
        List<String> devTeams = List.of("Backend", "Frontend", "DevOps");
        List<Double> latencies = List.of(12.4, 45.1, 8.9);
        printListDetails("Engineering Teams", devTeams);
        printListDetails("Server Latencies (ms)", latencies);

        System.out.println("
=== 3. BOUNDED GENERIC METHOD (MAX FINDER) ===");
        System.out.println("Max of (150, 420): " + findMax(150, 420));
        System.out.println("Max of ('Zebra', 'Apple'): " + findMax("Zebra", "Apple"));
    }
}
💻 Program Terminal Output:
=== 1. GENERIC API RESPONSE TEST ===
ApiResponse [code=200, msg=Success, data=JWT_TOKEN_ABC123]
Extracted Token: JWT_TOKEN_ABC123
Configured Ports: [8080, 8443, 9092]

=== 2. GENERIC METHOD UTILITY ===
🔹 Engineering Teams (Size: 3):
   -> Item: Backend [Type: String]
   -> Item: Frontend [Type: String]
   -> Item: DevOps [Type: String]
🔹 Server Latencies (ms) (Size: 3):
   -> Item: 12.4 [Type: Double]
   -> Item: 45.1 [Type: Double]
   -> Item: 8.9 [Type: Double]

=== 3. BOUNDED GENERIC METHOD (MAX FINDER) ===
Max of (150, 420): 420
Max of ('Zebra', 'Apple'): Zebra

5. Bounded Type Parameters (<T extends Number> & Multiple Bounds)

సాధారణంగా <T> అంటే ఏ ఆబ్జెక్ట్ అయినా రావచ్చు. కానీ మనకు కేవలం సంఖ్యలు మాత్రమే కావాలన్నా (లేదా ఒక నిర్దిష్ట ఇంటర్‌ఫేస్‌ను అమలు చేసిన ఆబ్జెక్టులు మాత్రమే రావాలన్నా), మనం Bounded Type Parameters ను ఉపయోగిస్తాము.

1. Upper Bound (<T extends SuperClass/Interface>):
ఉదాహరణకు public class NumericStats<T extends Number> అని రాస్తే, ఈ క్లాస్ కేవలం Integer, Double, Float, Long వంటి Number సబ్‌క్లాస్‌లను మాత్రమే అంగీకరిస్తుంది. పొరపాటున ఎవరైనా NumericStats<String> అని రాస్తే కంపైలర్ అక్కడే తిరస్కరిస్తుంది.

2. Multiple Bounds (<T extends Class & Interface1 & Interface2>):
ఒక టైప్ ఒక నిర్దిష్ట క్లాస్‌ను ఎక్స్‌టెండ్ చేసి, అదే సమయంలో ఒకటి లేదా అంతకంటే ఎక్కువ ఇంటర్‌ఫేస్‌లను అమలు చేయాలని కోరవచ్చు: <T extends Number & Comparable<T>>. (గుర్తుంచుకోండి: క్లాస్ పేరు ఎల్లప్పుడూ మొదట రావాలి, ఇంటర్‌ఫేస్‌లు & గుర్తుతో తర్వాత రావాలి).

6. Wildcards: ?, ? extends T, ? super T (వైల్డ్‌కార్డ్స్ పరిచయం)

జావాలో ఒక అత్యంత కీలకమైన అంశాన్ని గమనించాలి: Integer అనేది Number యొక్క సబ్‌క్లాస్. కానీ List<Integer> అనేది List<Number> యొక్క సబ్‌టైప్ కాదు! (Generics are Invariant). దీనివల్ల మనం ఒక మెథడ్‌కు List<Number> ను పారామీటర్‌గా పెడితే, దానికి List<Integer> ను పాస్ చేయలేము (కంపైలర్ ఎర్రర్ వస్తుంది).

ఈ పరిమితిని అధిగమించడానికి జావా వైల్డ్‌కార్డ్ గుర్తు ? (Unknown Type) ను అందిస్తుంది:

  • Unbounded Wildcard (List<?>): ఏ రకమైన లిస్ట్‌నైనా అంగీకరిస్తుంది (Read-Only).
  • Upper Bounded Wildcard (List<? extends Number>): Number లేదా దాని సబ్‌క్లాస్ లిస్ట్‌లను అంగీకరిస్తుంది (Covariance).
  • Lower Bounded Wildcard (List<? super Integer>): Integer లేదా దాని సూపర్ క్లాస్ (Number, Object) లిస్ట్‌లను అంగీకరిస్తుంది (Contravariance).

7. The PECS Principle (Producer Extends, Consumer Super)

ప్రపంచ ప్రఖ్యాత జావా పుస్తకం "Effective Java" గ్రంథకర్త జాషువా బ్లాచ్ (Joshua Bloch) రూపొందించిన అమూల్యమైన సూత్రమే PECS. ఇంటర్వ్యూలలో ఇది తరచుగా అడిగే గోల్డెన్ క్వశ్చన్:

🌟 PECS సూత్రం: "Producer Extends, Consumer Super"

1. Producer Extends (? extends T): మీ కలెక్షన్ కేవలం డేటాను బయటకు ఉత్పత్తి చేయడానికే (Read-Only) వాడుతుంటే extends వాడాలి. ఎందుకంటే బయటకు వచ్చే ప్రతి వస్తువు కనీసం T అని గ్యారెంటీ ఉంటుంది. అయితే దీనిలోకి మనం కొత్త ఎలిమెంట్లను add() చేయలేము (కేవలం null తప్ప).

2. Consumer Super (? super T): మీ కలెక్షన్ కొత్త డేటాను లోపలికి వినియోగించుకోవడానికే (Write-Only) వాడుతుంటే super వాడాలి. ఎందుకంటే మనం T లేదా దాని సబ్‌క్లాస్ డేటాను సురక్షితంగా లోపలికి యాడ్ చేయవచ్చు.

8. Type Erasure & Bytecode ఇంటర్నల్స్ (JVM Internals)

జావా 5 లో జెనరిక్స్‌ను ప్రవేశపెట్టినప్పుడు సన్ మైక్రోసిస్టమ్స్ ఇంజనీర్ల ముందు ఒక అతిపెద్ద సవాలు ఉంది: "పాత జావా 1.4 క్లాసులతో బ్యాక్‌వర్డ్ కంపాటబిలిటీ (Backward Compatibility) దెబ్బతినకూడదు". దీని కోసం వారు Type Erasure (టైప్ ఎరేజర్) అనే టెక్నిక్‌ను ఎంచుకున్నారు.

⚙️ కంపైలేషన్ సమయంలో Type Erasure ఏం చేస్తుంది?

  1. కంపైలర్ అన్ని జెనరిక్ టైప్ పారామీటర్లను తొలగించి, వాటి స్థానంలో వాటి బౌండ్ (లేదా Object) ను బైట్‌కోడ్‌లో ప్రతిష్ఠిస్తుంది. ఉదాహరణకు List<String> అనేది బైట్‌కోడ్‌లో కేవలం సాధారణ List గా మారిపోతుంది!
  2. టైప్ సేఫ్టీ కోసం అవసరమైన చోట్ల కంపైలర్ స్వయంగా సింథటిక్ కాస్ట్‌లను (Synthetic Type Casts) మరియు బ్రిడ్జ్ మెథడ్స్ (Bridge Methods) ను చొప్పిస్తుంది.
  3. రన్‌టైమ్‌లో JVM కు జెనరిక్ టైప్ సమాచారం తెలియదు! అందుకే list instanceof List<String> అని రాయడం చట్టవిరుద్ధం (కంపైలర్ ఒప్పుకోదు).

9. ప్రాక్టికల్ కోడ్: PECS Wildcard Inventory Processor

క్రింది జావా ప్రోగ్రామ్‌లో ఎలక్ట్రానిక్స్ మరియు మొబైల్ ఫోన్ల ఇన్వెంటరీ ప్రాసెసింగ్ ద్వారా ? extends (Producer - రీడ్ మాత్రమే) మరియు ? super (Consumer - రైట్ మాత్రమే) వాస్తవ కోడ్‌లో ఎలా ప్రవర్తిస్తాయో స్పష్టంగా చూడండి.

☕ Java — PecsWildcardInventoryProcessor.java
import java.util.*;

public class PecsWildcardInventoryProcessor {

    // డొమైన్ మోడల్స్: హైరార్కీ
    static class Device {
        final String model;
        final double price;
        public Device(String model, double price) { this.model = model; this.price = price; }
        public double getPrice() { return price; }
        @Override public String toString() { return model + " (₹" + price + ")"; }
    }

    static class Smartphone extends Device {
        final String os;
        public Smartphone(String model, double price, String os) {
            super(model, price);
            this.os = os;
        }
    }

    static class Laptop extends Device {
        final int ramGb;
        public Laptop(String model, double price, int ramGb) {
            super(model, price);
            this.ramGb = ramGb;
        }
    }

    // 1. PRODUCER EXTENDS (? extends Device): కేవలం రీడ్ చేయడానికి మాత్రమే (Calculates total value)
    public static double calculateTotalInventoryValue(List<? extends Device> inventory) {
        double total = 0.0;
        for (Device d : inventory) {
            total += d.getPrice(); // సురక్షితంగా Device గా రీడ్ చేయవచ్చు!
        }
        // inventory.add(new Device("Watch", 5000)); // కంపైలర్ ఒప్పుకోదు! (Producer cannot add)
        return total;
    }

    // 2. CONSUMER SUPER (? super Smartphone): కొత్త స్మార్ట్‌ఫోన్లను లిస్ట్‌లోకి జోడించడానికి
    public static void restockSmartphones(List<? super Smartphone> stockList, List<Smartphone> newArrivals) {
        for (Smartphone phone : newArrivals) {
            stockList.add(phone); // సురక్షితంగా Smartphone ని జోడించవచ్చు!
            System.out.println("  + ఇన్వెంటరీలోకి చేర్చబడింది: " + phone);
        }
    }

    public static void main(String[] args) {
        System.out.println("=== 1. PRODUCER EXTENDS TEST (? extends Device) ===");
        List<Smartphone> phones = List.of(
            new Smartphone("Pixel 8 Pro", 85000, "Android"),
            new Smartphone("iPhone 15", 79000, "iOS")
        );
        List<Laptop> laptops = List.of(
            new Laptop("MacBook Pro M3", 195000, 36),
            new Laptop("Dell XPS 15", 145000, 32)
        );

        // List మరియు List రెండింటినీ ఒకే మెథడ్‌లోకి పంపగలిగాం!
        double phoneTotal = calculateTotalInventoryValue(phones);
        double laptopTotal = calculateTotalInventoryValue(laptops);
        System.out.println("స్మార్ట్‌ఫోన్ల మొత్తం విలువ: ₹" + phoneTotal);
        System.out.println("ల్యాప్‌టాప్‌ల మొత్తం విలువ: ₹" + laptopTotal);

        System.out.println("
=== 2. CONSUMER SUPER TEST (? super Smartphone) ===");
        // సూపర్ టైప్ అయిన List లోకి Smartphone లను రెస్పాన్సిబుల్‌గా యాడ్ చేస్తున్నాం
        List<Device> generalWarehouse = new ArrayList<>();
        generalWarehouse.add(new Laptop("ThinkPad T14", 95000, 16));

        System.out.println("ప్రారంభ వేర్‌హౌస్ నిల్వ: " + generalWarehouse);
        System.out.println("కొత్త స్మార్ట్‌ఫోన్ల స్టాక్ లోడింగ్...");
        restockSmartphones(generalWarehouse, phones);

        System.out.println("అప్‌డేట్ చేయబడిన వేర్‌హౌస్ మొత్తం నిల్వలు (Size: " + generalWarehouse.size() + "):");
        for (Device d : generalWarehouse) {
            System.out.println("  - " + d);
        }
    }
}
💻 Program Terminal Output:
=== 1. PRODUCER EXTENDS TEST (? extends Device) ===
స్మార్ట్‌ఫోన్ల మొత్తం విలువ: ₹164000.0
ల్యాప్‌టాప్‌ల మొత్తం విలువ: ₹340000.0

=== 2. CONSUMER SUPER TEST (? super Smartphone) ===
ప్రారంభ వేర్‌హౌస్ నిల్వ: [ThinkPad T14 (₹95000.0)]
కొత్త స్మార్ట్‌ఫోన్ల స్టాక్ లోడింగ్...
  + ఇన్వెంటరీలోకి చేర్చబడింది: Pixel 8 Pro (₹85000.0)
  + ఇన్వెంటరీలోకి చేర్చబడింది: iPhone 15 (₹79000.0)
అప్‌డేట్ చేయబడిన వేర్‌హౌస్ మొత్తం నిల్వలు (Size: 3):
  - ThinkPad T14 (₹95000.0)
  - Pixel 8 Pro (₹85000.0)
  - iPhone 15 (₹79000.0)

10. క్యాప్‌స్టోన్ ప్రాజెక్ట్: Enterprise Generic Repository Engine

Spring Data JPA లేదా Hibernate లాంటి ఆధునిక ఎంటర్‌ప్రైజ్ ఫ్రేమ్‌వర్క్‌లు ఎలా పనిచేస్తాయో మీరు ఎప్పుడైనా ఆలోచించారా? అవి JpaRepository<Entity, ID> అనే జెనరిక్ ఇంటర్‌ఫేస్ ఆధారంగా పనిచేస్తాయి. ఇప్పుడు మనం స్వయంగా ఒక యూనివర్సల్, టైప్-సేఫ్ ఇన్-మెమరీ డేటాబేస్ రిపోజిటరీ ఇంజిన్‌ను నిర్మిద్దాం:

☕ Java — EnterpriseGenericRepositoryEngine.java
import java.util.*;
import java.util.function.Predicate;

public class EnterpriseGenericRepositoryEngine {

    // ప్రతి ఎంటిటీ తప్పనిసరిగా ఐడీని కలిగి ఉండాలని నిర్దేశించే ఇంటర్‌ఫేస్
    public interface Identifiable<ID> {
        ID getId();
    }

    // జెనరిక్ CRUD రిపోజిటరీ ఇంటర్‌ఫేస్
    public interface CrudRepository<T extends Identifiable<ID>, ID> {
        T save(T entity);
        Optional<T> findById(ID id);
        List<T> findAll();
        List<T> findByCriteria(Predicate<T> predicate);
        boolean deleteById(ID id);
        long count();
    }

    // జెనరిక్ ఇన్-మెమరీ ఇంప్లిమెంటేషన్ (ఏ ఎంటిటీకైనా సమానంగా పనిచేస్తుంది)
    public static class InMemoryRepository<T extends Identifiable<ID>, ID> implements CrudRepository<T, ID> {
        private final Map<ID, T> storage = new HashMap<>();

        @Override
        public T save(T entity) {
            Objects.requireNonNull(entity, "సేవ్ చేయాల్సిన ఎంటిటీ null కాకూడదు");
            storage.put(entity.getId(), entity);
            return entity;
        }

        @Override
        public Optional<T> findById(ID id) {
            return Optional.ofNullable(storage.get(id));
        }

        @Override
        public List<T> findAll() {
            return new ArrayList<>(storage.values());
        }

        @Override
        public List<T> findByCriteria(Predicate<T> predicate) {
            List<T> results = new ArrayList<>();
            for (T item : storage.values()) {
                if (predicate.test(item)) {
                    results.add(item);
                }
            }
            return results;
        }

        @Override
        public boolean deleteById(ID id) {
            return storage.remove(id) != null;
        }

        @Override
        public long count() {
            return storage.size();
        }
    }

    // డొమైన్ మోడల్ 1: Customer Record (Java 21)
    public record Customer(Long id, String name, String tier, double balance) implements Identifiable<Long> {
        @Override public Long getId() { return id; }
    }

    // డొమైన్ మోడల్ 2: Order Record (Java 21)
    public record Order(String orderNumber, double amount, String status) implements Identifiable<String> {
        @Override public String getId() { return orderNumber; }
    }

    public static void main(String[] args) {
        System.out.println("=== ENTERPRISE GENERIC REPOSITORY ENGINE ===");

        // 1. కస్టమర్ రిపోజిటరీ (ID టైప్: Long)
        CrudRepository<Customer, Long> customerRepo = new InMemoryRepository<>();
        customerRepo.save(new Customer(101L, "Balaji Tech", "PLATINUM", 85000.0));
        customerRepo.save(new Customer(102L, "Anand Kumar", "GOLD", 32000.0));
        customerRepo.save(new Customer(103L, "Kavitha Reddy", "PLATINUM", 120000.0));

        System.out.println("మొత్తం కస్టమర్ల సంఖ్య: " + customerRepo.count());
        
        // Predicate ఫిల్టర్ ద్వారా ప్లాటినమ్ కస్టమర్ల శోధన
        List<Customer> platinumMembers = customerRepo.findByCriteria(c -> "PLATINUM".equals(c.tier()));
        System.out.println("ప్లాటినమ్ కస్టమర్లు: " + platinumMembers);

        // 2. ఆర్డర్ రిపోజిటరీ (ID టైప్: String) - అదే ఇంజిన్ వేరొక టైప్‌కు క్లీన్‌గా పనిచేస్తుంది!
        CrudRepository<Order, String> orderRepo = new InMemoryRepository<>();
        orderRepo.save(new Order("ORD-2026-X1", 15400.0, "PAID"));
        orderRepo.save(new Order("ORD-2026-X2", 4500.0, "PENDING"));

        System.out.println("
ఆర్డర్ శోధన (ORD-2026-X1):");
        orderRepo.findById("ORD-2026-X1").ifPresent(o -> 
            System.out.println("  లభించిన ఆర్డర్ వివరాలు: " + o + " | మొత్తం: ₹" + o.amount())
        );

        // ఆర్డర్ డిలీషన్
        orderRepo.deleteById("ORD-2026-X2");
        System.out.println("ఆర్డర్ తొలగింపు తర్వాత మిగిలిన ఆర్డర్లు: " + orderRepo.count());
    }
}
💻 Program Terminal Output:
=== ENTERPRISE GENERIC REPOSITORY ENGINE ===
మొత్తం కస్టమర్ల సంఖ్య: 3
ప్లాటినమ్ కస్టమర్లు: [Customer[id=101, name=Balaji Tech, tier=PLATINUM, balance=85000.0], Customer[id=103, name=Kavitha Reddy, tier=PLATINUM, balance=120000.0]]

ఆర్డర్ శోధన (ORD-2026-X1):
  లభించిన ఆర్డర్ వివరాలు: Order[orderNumber=ORD-2026-X1, amount=15400.0, status=PAID] | మొత్తం: ₹15400.0
ఆర్డర్ తొలగింపు తర్వాత మిగిలిన ఆర్డర్లు: 1

11. ప్రొడక్షన్ పిట్‌ఫాల్స్ & ఆంటీ-పాటర్న్స్ (5 Critical Production Traps)

1. Using Raw Types (రాతపూర్వక టైప్ లేని పాత పద్ధతి)

List list = new ArrayList(); అని రా టైపులను వాడటం అత్యంత ప్రమాదకరం. ఇది కంపైలర్ టైప్ చెకింగ్‌ను పూర్తిగా నిలిపివేస్తుంది. ఆధునిక జావాలో ఎల్లప్పుడూ డైమండ్ ఆపరేటర్‌తో కూడిన స్పెసిఫిక్ టైపులనే వాడాలి: List<String> list = new ArrayList<>();.

2. Generic Array Creation Trap (జెనరిక్ ఎరేల నిషేధం)

జావాలో new T[10] లేదా new List<String>[10] అని జెనరిక్ ఎరేలను నేరుగా క్రియేట్ చేయడానికి వీల్లేదు! ఎందుకంటే ఎరేలు రన్‌టైమ్‌లో తమ టైపును గుర్తుపెట్టుకుంటాయి (Reifiable), కానీ జెనరిక్స్ రన్‌టైమ్‌లో ఎరేజ్ అయిపోతాయి (Type Erasure). దీని పరిష్కారానికి List<List<String>> లేదా Array.newInstance(clazz, size) ని వాడాలి.

3. Primitive Types as Type Arguments (ప్రిమిటివ్ టైపుల పరిమితి)

జెనరిక్స్ టైప్ ఆర్గ్యుమెంట్‌గా ప్రిమిటివ్ టైపులను అనుమతించవు: List<int> అని రాయలేము; తప్పనిసరిగా బాక్సింగ్ రాపర్ క్లాస్ List<Integer> నే వాడాలి. (ప్రాజెక్ట్ వల్హల్లా - Project Valhalla ద్వారా భవిష్యత్ జావాలో ఇది మారనుంది).

4. Overloading Methods that Differ Only by Generic Type

ఒకే క్లాస్‌లో void process(List<String> l) మరియు void process(List<Integer> l) అని ఓవర్‌లోడ్ చేయలేము. ఎందుకంటే టైప్ ఎరేజర్ తర్వాత రెంటి సిగ్నేచర్ void process(List l) గా మారి డూప్లికేట్ మెథడ్ ఎర్రర్ వస్తుంది.

5. Violating PECS Rule (Adding into ? extends Collection)

List<? extends Number> list అని రాసిన తర్వాత అందులోకి list.add(10) అని నంబర్‌ను యాడ్ చేయడానికి ప్రయత్నించడం బిగినర్స్ చేసే అతిపెద్ద తప్పు. లిస్ట్ అసలు రకం List<Double> అయి ఉండవచ్చు కాబట్టి కంపైలర్ ఏ వస్తువునూ యాడ్ చేయనివ్వదు.

12. ఇంటర్వ్యూ క్విజ్ & సెల్ఫ్-టెస్ట్ (5 Deep Interview Practice Quizzes)

జావా జెనరిక్స్, వైల్డ్‌కార్డ్స్ మరియు టైప్ ఎరేజర్‌పై మీ పట్టును ఈ 5 లోతైన ప్రాక్టికల్ ప్రశ్నలతో నిరూపించుకోండి. సమాధానం కోసం ప్రశ్నపై క్లిక్ చేయండి:

ప్రశ్న 1: జావాలో Type Erasure అంటే ఏమిటి? ఇది ఎందుకు ప్రవేశపెట్టబడింది?

సమాధానం: జావా కంపైలర్ కోడ్‌ను బైట్‌కోడ్‌గా మార్చేటప్పుడు అన్ని జెనరిక్ టైప్ పారామీటర్లను తొలగించి వాటి స్థానంలో వాటి బౌండరీ (లేదా Object) ను రీప్లేస్ చేస్తుంది. దీనినే Type Erasure అంటారు. జావా 5 కంటే ముందు రాసిన లెగసీ కోడ్‌తో పూర్తి బ్యాక్‌వర్డ్ కంపాటబిలిటీని కాపాడటం కోసమే ఈ విధానాన్ని ఎంచుకున్నారు.

ప్రశ్న 2: PECS ప్రిన్సిపల్ అంటే ఏమిటి? ఎప్పుడు ఏ వైల్డ్‌కార్డ్ ఉపయోగించాలి?

సమాధానం: PECS అంటే Producer Extends, Consumer Super. కలెక్షన్ నుండి డేటాను కేవలం బయటకు రీడ్ చేయడమే మీ లక్ష్యమైతే ? extends T వాడాలి (ఇది ప్రొడ్యూసర్). ఒకవేళ కలెక్షన్‌లోకి కొత్త డేటాను లోపలికి రైట్ చేయడమే మీ లక్ష్యమైతే ? super T వాడాలి (ఇది కన్స్యూమర్).

ప్రశ్న 3: List<Integer> అనేది List<Number> కి సబ్‌టైప్ అవుతుందా? కారణం ఏమిటి?

సమాధానం: కాదు! Integer అనేది Number కి సబ్‌క్లాస్ అయినప్పటికీ, List<Integer> అనేది List<Number> కి సబ్‌టైప్ కాదు. ఎందుకంటే జెనరిక్స్ Invariable గా ఉంటాయి. ఒకవేళ అలా అనుమతిస్తే, List<Number> రిఫరెన్స్ ద్వారా అందులోకి ఎవరైనా Double ను ఇన్సర్ట్ చేసి, Integer లిస్ట్‌ను నాశనం చేసే ప్రమాదం ఉండేది.

ప్రశ్న 4: Java లో జెనరిక్ ఎరేలను (Generic Arrays - new T[10]) ఎందుకు సృష్టించలేము?

సమాధానం: ఎరేలు రన్‌టైమ్‌లో తమ ఖచ్చితమైన ఎలిమెంట్ రకాన్ని గుర్తుపెట్టుకుంటాయి (Arrays are Reifiable). కానీ జెనరిక్స్ రన్‌టైమ్‌లో ఎరేజ్ అయిపోతాయి (Type Erasure వలన). కనుక రన్‌టైమ్‌లో new T[10] ని ఏ టైప్ ఎరేగా అలోకేట్ చేయాలో JVM కి తెలియదు కాబట్టి కంపైలర్ దీనిని నిరోధిస్తుంది.

ప్రశ్న 5: Bounded Type Parameter లో Multiple Bounds సింటాక్స్ మరియు నిబంధనలు ఏమిటి?

సమాధానం: సింటాక్స్: <T extends ClassName & Interface1 & Interface2>. నిబంధన: జావాలో మల్టిపుల్ ఇన్హెరిటెన్స్ క్లాసులకు వర్తించదు కాబట్టి, గరిష్టంగా కేవలం ఒకే ఒక క్లాస్ మాత్రమే ఉండవచ్చు మరియు ఆ క్లాస్ పేరు తప్పనిసరిగా మొదటి స్థానంలోనే ఉండాలి. ఆ తర్వాత & ఆపరేటర్‌తో ఎన్ని ఇంటర్‌ఫేస్‌లనైనా చేర్చవచ్చు.

← మునుపటి పాఠం: 22. Set & Map Deep Dive తర్వాతి పాఠం: 24. Lambdas & Stream API →