ජාවා ජෙනරික්ස් වයිල්ඩ්කාඩ්ස් | Java Generics Wildcards (? extends, ? super, ?) | SC Guide

ජාවා ජෙනරික්ස් වයිල්ඩ්කාඩ්ස් | Java Generics Wildcards (? extends, ? super, ?) | SC Guide

ආයුබෝවන් කට්ටිය! කොහොමද ඉතින්? Java වලින් code කරන අපි හැමෝටම නිතරම වගේ මුහුණ දෙන්න වෙන පොඩි "අයියෝ, මේක මොකක්ද?" වගේ ප්‍රශ්නයක් තමයි Generics කියන්නේ. විශේෂයෙන්ම, ලොකු Applications හදනකොට, මේක නැතුව බෑ. නමුත්, සමහර වෙලාවට Generics එක්ක වැඩ කරනකොට, "Type Safety" තියාගන්න ඕනෙ නිසා, Code එක ටිකක් Rigidity එකට යනවා නේද? අන්න ඒ වගේ තැනකට අපිට එන "Super Hero" කෙනෙක් තමයි Wildcards කියන්නේ. මේ Article එකෙන් අපි Java වල තියෙන මේ Wildcards කියන නියම Concept එක ගැන සරලව, පහසුවෙන් තේරුම් ගනිමු.

Generics කියන්නේ මොකක්ද?

මුලින්ම අපි බලමු Generics කියන්නේ මොකක්ද කියලා. සරලවම කිව්වොත්, Generics කියන්නේ Java වලට ආපු නියම feature එකක්. මේකෙන් වෙන්නේ Class, Interface, Methods වලට "Type Parameters" දාන්න පුළුවන් වෙන එක. හිතන්නකෝ, අපිට List එකක් ඕනේ String විතරක් තියාගන්න. එතකොට අපි List<String> කියලා දානවා. මේකෙන් වෙන්නේ Compile Time එකේදී ම Type Safety එක හදන එක. ඒ කියන්නේ, ඔයා අත්වැරදීමකින් List<String> එකකට Integer එකක් දාන්න හැදුවොත්, Compiler එකෙන් Error එකක් දෙනවා. මේකෙන් Run Time එකේදී ClassCastException වගේ ඒවයින් ගැලවෙන්න පුළුවන්. ඒ වගේම, Code Reuseability එකත් වැඩි වෙනවා. එකම Code එකකින් විවිධ Data Types එක්ක වැඩ කරන්න පුළුවන්. වැඩේ පහසුයි නේද?

Wildcards මොකටද?

හරි, එහෙනම් Generics හොඳයි. හැබැයි මේකේ පොඩි ගැටලුවක් තියෙනවා. හිතන්නකෝ, ඔයාට Method එකක් තියෙනවා List<Number> එකක් Parameter එකක් විදියට ගන්න. ඔයා හිතයි, List<Integer> එකක් මේ Method එකට දෙන්න පුළුවන් කියලා. මොකද Integer කියන්නේ Number එකක්නේ. නමුත් Java වලදී, List<Integer> කියන්නේ List<Number> එකක Subtype එකක් නෙවෙයි! මේක ගොඩක් දෙනෙක්ට පැටලෙන තැනක්.

Java මේක කරන්නේ Type Safety එක වෙනුවෙන්. හිතන්න, List<Number> එකකට Integer, Double, Float වගේ ඕනෑම Number Type එකක් දාන්න පුළුවන්. දැන් ඔයා List<Integer> එකක් List<Number> එකක් විදියට දීලා, ඒ List එකට Double එකක් දැම්මොත්, Original List<Integer> එකට Double එකක් Add වෙනවා. ඒක බරපතල Type Safety Issue එකක්. අන්න මේ ගැටලුවට විසඳුම තමයි Wildcards.

Unbounded Wildcards (?) - බඳින්නේ නැති ඒවා

මුලින්ම අපි බලමු Unbounded Wildcard එක ගැන. මේක අපි ? කියලා දානවා. මේකෙන් කියවෙන්නේ "ඕනෑම වර්ගයක" (any type) කියන එක. මේක ගොඩක් වෙලාවට පාවිච්චි කරන්නේ Generic Type එක ගැන වැඩිය ගණන් නොගෙන Elements කියවන්න (read only operations) විතරක් ඕනෙ වුණාම.

හිතන්නකෝ, ඔයාට Method එකක් ඕනේ ඕනෑම වර්ගයක List එකක Elements Print කරන්න:

import java.util.Arrays;
import java.util.List;

public class UnboundedWildcardExample {
    public static void printList(List<?> list) {
        for (Object elem : list) {
            System.out.println(elem);
        }
    }

    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3);
        List<String> strings = Arrays.asList("Hello", "World");
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);

        System.out.println("\n--- Printing Integers ---");
        printList(integers);

        System.out.println("\n--- Printing Strings ---");
        printList(strings);

        System.out.println("\n--- Printing Doubles ---");
        printList(doubles);
    }
}

මේ Code එකේදී printList Method එක List<?> එකක් Parameter එකට ගන්නවා. මේ නිසා අපිට List<Integer> එකක් වුණත්, List<String> එකක් වුණත්, List<Double> එකක් වුණත් මේ Method එකට Pass කරන්න පුළුවන්. වැඩේ පහසුයි නේද?

හැබැයි, මේකේ පොඩි Limitations එකකුත් තියෙනවා. List<?> එකකට අපිට null හැර වෙන කිසිම Element එකක් Add කරන්න බෑ. මොකද Compile Time එකේදී Type එක ගැන කිසිම දෙයක් දන්නේ නැති නිසා, Java Compiler එක බයයි වැරදි Type එකක් Add වෙයි කියලා. ඒ නිසා, Unbounded Wildcard එක ගොඩක් වෙලාවට පාවිච්චි කරන්නේ Data කියවන්න විතරයි.

import java.util.ArrayList;
import java.util.List;

public class UnboundedWildcardLimitations {
    public static void main(String[] args) {
        List<?> unknownList = new ArrayList<Integer>(); // Can be any type
        // unknownList.add(new Object()); // Compile-time error! (Cannot add elements other than null)
        // unknownList.add("Hello");     // Compile-time error!
        unknownList.add(null);         // This is allowed

        // You can read elements, but they are treated as Object
        Object obj = unknownList.isEmpty() ? null : unknownList.get(0);
        System.out.println("First element (if any): " + obj);
    }
}

Upper-Bounded Wildcards (? extends T) - උඩින් සීමා වෙච්ච ඒවා

ඊළඟට අපි කතා කරමු Upper-Bounded Wildcards ගැන. මේක අපි ? extends T කියලා දානවා. මේකෙන් කියවෙන්නේ "T කියන Type එකට හෝ T එකෙන් Extend වෙන ඕනෑම Subtype එකකට" කියන එක. මේක ගොඩක් වෙලාවට පාවිච්චි කරන්නේ Data Collection එකකින් Data කියවන්න (read) විතරක් ඕනෙ වුණාම.

හිතන්නකෝ, ඔයාට Method එකක් ඕනේ Number වර්ගයේ (Integer, Double, Float වගේ) List එකක එකතුව හොයන්න:

import java.util.Arrays;
import java.util.List;

public class UpperBoundedWildcardExample {
    // This method can sum elements from any List whose type extends Number
    public static double sumOfList(List<? extends Number> list) {
        double sum = 0.0;
        for (Number n : list) { // We can safely read Numbers or their subtypes
            sum += n.doubleValue();
        }
        return sum;
    }

    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
        List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
        List<Number> numbers = Arrays.asList(10, 20.5, 30L); // Contains Integer, Double, Long

        System.out.println("Sum of integers: " + sumOfList(integers)); // Works! Output: 15.0
        System.out.println("Sum of doubles: " + sumOfList(doubles));   // Works! Output: 6.6
        System.out.println("Sum of numbers: " + sumOfList(numbers));   // Works! Output: 60.5

        // Trying to add elements to a List declared with ? extends Number is NOT allowed (except null).
        // List<? extends Number> numList = new ArrayList<Integer>();
        // numList.add(new Integer(10)); // Compile-time error!
    }
}

මේ Code එකේදී sumOfList Method එක List<? extends Number> එකක් Parameter එකට ගන්නවා. මේ නිසා අපිට List<Integer>, List<Double> වගේ Number එකෙන් extend වෙන ඕනෑම Type එකක List එකක් මේ Method එකට Pass කරන්න පුළුවන්. ඒක තමයි මේකේ නියම වාසිය!

හැබැයි, ? extends T එක්ක අපිට Collection එකට Element Add කරන්න බෑ (null හැර). ඒකට හේතුව, List<? extends Number> කියන List එක ඇත්තටම List<Integer> එකක්ද, List<Double> එකක්ද කියලා Compile Time එකේදී හරියටම දන්නේ නැති නිසා. Type Safety එකට හානි වෙන්න පුළුවන් නිසා Java මේක Block කරනවා.

Lower-Bounded Wildcards (? super T) - යටින් සීමා වෙච්ච ඒවා

අවසාන වශයෙන් අපි කතා කරමු Lower-Bounded Wildcards ගැන. මේක අපි ? super T කියලා දානවා. මේකෙන් කියවෙන්නේ "T කියන Type එකට හෝ T එකේ Supertype එකකට" කියන එක. මේක ගොඩක් වෙලාවට පාවිච්චි කරන්නේ Data Collection එකකට Data Add කරන්න (write) ඕනෙ වුණාම.

හිතන්නකෝ, ඔයාට Method එකක් ඕනේ List එකකට Integer Value Add කරන්න. හැබැයි ඒ List එක Integer හරි Integer වල Supertype එකක් (Number, Object) හරි වෙන්න පුළුවන්:

import java.util.ArrayList;
import java.util.List;

public class LowerBoundedWildcardExample {
    // This method can add Integers to a List whose type is Integer or a supertype of Integer
    public static void addIntegers(List<? super Integer> list) {
        list.add(10); // Works! We are guaranteed that Integer is acceptable
        list.add(20); // Works!
        list.add(Integer.valueOf(30)); // Works!
        // list.add(new Double(10.5)); // Compile-time error! Cannot add anything that is not an Integer or its subtype (or null)
    }

    // Helper method to print any List
    public static void printList(List<?> list) {
        for (Object o : list) {
            System.out.print(o + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        List<Integer> integers = new ArrayList<>();
        addIntegers(integers);
        System.out.print("Integers list after adding: ");
        printList(integers); // Output: 10 20 30

        List<Number> numbers = new ArrayList<>();
        addIntegers(numbers);
        numbers.add(40.5); // Can add other Numbers if it's a List<Number>
        System.out.print("Numbers list after adding: ");
        printList(numbers); // Output: 10 20 30 40.5

        List<Object> objects = new ArrayList<>();
        addIntegers(objects);
        objects.add("Hello"); // Can add other Objects if it's a List<Object>
        System.out.print("Objects list after adding: ");
        printList(objects); // Output: 10 20 30 Hello
    }
}

මේ Code එකේදී addIntegers Method එක List<? super Integer> එකක් Parameter එකට ගන්නවා. මේ නිසා අපිට List<Integer>, List<Number>, List<Object> වගේ Integer වල Supertype එකක List එකක් මේ Method එකට Pass කරන්න පුළුවන්.

? super T එක්ක අපිට T Type එකේ හරි T Type එකේ Subtype එකක හරි Element Add කරන්න පුළුවන්. ඒත් Read කරන්න පුළුවන් Object විදියට විතරයි. මොකද List එකේ තියෙන්න පුළුවන් Integer වල Supertype එකක් නිසා, Compiler එකට දන්නේ නෑ ඒක හරියටම මොන Type එකක්ද කියලා. ඒ නිසා කියවනකොට Object විදියට කියවන්න වෙනවා.

PECS (Producer Extends, Consumer Super) Principle

දැන් ඔයාට පැහැදිලියි නේද මේ Wildcards තුන කොහොමද වැඩ කරන්නේ කියලා. මේක මතක තියාගන්න පහසු විදියක් තමයි "PECS" (Producer Extends, Consumer Super) කියන රීතිය.

  • Producer Extends: ඔයා Data Collection එකකින් දේවල් කියවනවා නම් (produce කරනවා නම්), ? extends T පාවිච්චි කරන්න.
  • Consumer Super: ඔයා Data Collection එකකට දේවල් දානවා නම් (consume කරනවා නම්), ? super T පාවිච්චි කරන්න.
  • ඔයා කියවනවාත් නැත්නම්, ලියනවාත් නැත්නම්, ? (Unbounded) පාවිච්චි කරන්න.

අවසන් වශයෙන්

ඉතින්, Wildcards ගැන මේ පොඩි පැහැදිලි කිරීමෙන් ඔයාගේ Java Generics දැනුම ටිකක් දියුණු වෙන්න ඇති කියලා හිතනවා. මේවා මුලින් ටිකක් පැටලෙනවා වගේ දැනුනත්, නිතරම Practice කරනකොට ලේසි වෙනවා. ඔයාගේ Code කරනකොට මේ Wildcards පාවිච්චි කරලා බලන්න. විශේෂයෙන්ම, Java Collections Framework එකේ තියෙන Copy, AddAll වගේ Methods වල මේවා ගොඩක් පාවිච්චි කරනවා.

ඔයාට මේ ගැන තව මොනවා හරි ප්‍රශ්න තියෙනවා නම්, පහලින් Comment Section එකේ දාන්න. අපි ඒ ගැන කතා කරමු. ඒ වගේම, මේ Article එකට කැමති නම්, යාළුවෝ එක්ක Share කරන්නත් අමතක කරන්න එපා. තව මේ වගේ Technical Blog Posts අරගෙන එන්න අපි බලාගෙන ඉන්නවා! එහෙනම්, ආයෙත් දවසක හමුවෙමු! Happy Coding!