Java Generics: Type-Safe Code පහසුවෙන් ලියමු! | SC Guide

ආයුබෝවන් කට්ටියටම! කොහොමද ඉතින්, වැඩ එහෙම සාර්ථකද? අද අපි කතා කරන්න යන්නේ Java වල ගොඩක් වැදගත්, ඒ වගේම අපේ කෝඩ් එක සුපිරිම විදිහට reusable, එහෙම නැත්තම් නැවත පාවිච්චි කරන්න පුළුවන් විදිහට හදාගන්න උදව් වෙන දෙයක් ගැන – ඒ තමයි Generics. අර කලින් කාලේ ArrayList එකක් පාවිච්චි කරනකොට, "අනේ මන්දා, මේකෙන් එලියට එන්නේ මොන data type එකද?" කියලා confuse වෙලා, ඊට පස්සේ ClassCastException එකක් අරන් ඔලුවට අත තියාගෙන ඉඳලා තියෙනවද? එහෙනම් මේ ලිපිය ඔයාටම තමයි. Generics කියන්නේ compile-time එකේදීම ඔයාගේ කෝඩ් එකේ type-safety එක check කරන, ඒ වගේම පොදු algorithm එකක් එක එක data types වලට වෙන වෙනම ලියන්න ඕනේ නැතුව එක පාරක් ලියලා හැමතැනටම පාවිච්චි කරන්න පුළුවන් හැකියාව දෙන සුපිරි පහසුකමක්.
මොකක්ද මේ Generics කියන්නේ?
සරලවම කිව්වොත්, Generics කියන්නේ අපි Java කෝඩ් ලියනකොට, methods, classes, වගේ දේවල් එක්තරා data type එකකට විතරක් සීමා නොකර, ඕනෑම data type එකක් එක්ක වැඩ කරන්න පුළුවන් විදිහට හදාගන්න පුළුවන් tool එකක්. මේකෙන් වෙන්නේ මොකක්ද? අපි කෝඩ් ලියන වෙලාවේම, එහෙම නැත්තම් compile කරන වෙලාවේම type-safety එක තහවුරු වෙන එක. ඒ කියන්නේ runtime එකේදී එන්න පුළුවන් ClassCastException වගේ errors කලින්ම අඳුරගෙන ඒවා නිවැරදි කරගන්න අපිට පුළුවන් වෙනවා. උදාහරණයක් විදිහට, කලින් කාලේ ArrayList
එකකට අපිට ඕනෑම object එකක් දාන්න පුළුවන් වුණා. ඒත් ඒකෙන් ආපහු object එකක් ගන්නකොට ඒක අපිට ඕන data type එකට cast කරන්න වුණා. එතනදී වැරදි cast එකක් වුණොත් runtime error එකක් ආවා. Generic ArrayList<String>
වගේ එකක් පාවිච්චි කරනකොට ඒ ප්රශ්නය නැහැ. මොකද ඒකේ තියෙන්නේ String objects විතරයි කියලා compile-time එකේදීම දැනගන්න පුළුවන්.
මේ Generics වලදී අපි පාවිච්චි කරනවා 'Type Parameters' කියලා එකක්. මේවා '<T>', '<E>', '<K>', '<V>' වගේ අකුරු වලින් පෙන්නනවා. 'T' කියන්නේ Type එකකට, 'E' කියන්නේ Element එකකට, 'K' කියන්නේ Key එකකට, 'V' කියන්නේ Value එකකට. මේවා අපි class එකක්, method එකක් නිර්මාණය කරනකොට 'මේ class/method එකට මෙන්න මේ වගේ type එකක් එනවා' කියලා පෙන්නන්න පාවිච්චි කරනවා.
Generic Classes ලියමුද?
හරි, දැන් අපි බලමු කොහොමද Generic Class එකක් ලියන්නේ කියලා. හිතන්නකෝ අපිට ඕන Box එකක් හදන්න. මේ Box එකට අපිට ඕන එකක් දාන්න පුළුවන් වෙන්න ඕනේ – String එකක්, Integer එකක්, නැත්තම් අපේම Custom Object එකක් වුණත්. සාමාන්ය විදිහට නම් අපි Object type එකෙන් Box එක හදයි. ඒත් එතකොට type-safety එක නැති වෙනවා. මෙන්න Generics වලින් ඒක විසඳන හැටි.
// Generic Box Class
class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
දැන් බලන්න, Box<T>
කියලා අපි T
කියන Type Parameter එක දීලා තියෙනවා. මේකෙන් කියන්නේ මේ Box එකට ඕනෑම type එකක content එකක් දාන්න පුළුවන් කියන එක. අපි මේක පාවිච්චි කරන්නේ මෙහෙමයි.
public class GenericClassDemo {
public static void main(String[] args) {
// String type එකක් සහිත Box එකක්
Box<String> stringBox = new Box<>();
stringBox.setContent("Java Generics සුපිරි!");
String message = stringBox.getContent();
System.out.println("String Box Content: " + message); // Output: String Box Content: Java Generics සුපිරි!
// Integer type එකක් සහිත Box එකක්
Box<Integer> integerBox = new Box<>();
integerBox.setContent(12345);
Integer number = integerBox.getContent();
System.out.println("Integer Box Content: " + number); // Output: Integer Box Content: 12345
// compile-time error එකක් බලමු
// stringBox.setContent(100); // Compile-time Error: Incompatible types. Integer cannot be converted to String.
}
}
අන්න බලන්න! අපි stringBox
එකට Integer
එකක් දාන්න හැදුවම compile-time එකේදීම error එකක් ආවා. ඒ කියන්නේ runtime එකේදී අපිට ClassCastException
එකක් එනවා වෙනුවට, කෝඩ් ලියනකොටම වැරැද්ද අඳුරගන්න පුළුවන් වුණා. මේක තමයි Generics වල මූලිකම වාසිය.
Generic Methods ගැන දැනගමු
Classes විතරක් නෙවෙයි, Methods වලටත් අපිට Generics පාවිච්චි කරන්න පුළුවන්. Generic Methods කියන්නේ Type Parameters පාවිච්චි කරලා ඕනෑම data type එකක් එක්ක වැඩ කරන්න පුළුවන් විදිහට හදපු methods වලට. මේවා static methods වෙන්න පුළුවන්, නැත්තම් non-static methods වෙන්නත් පුළුවන්. උදාහරණයක් විදිහට, අපි ලැයිස්තුවක (array) තියෙන හැම element එකක්ම print කරන්න method එකක් හදමු.
public class GenericMethodDemo {
// Generic method එකක්, ඕනෑම array එකක් print කරන්න
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
// Generic method එකක්, elements දෙකක් swap කරන්න
public static <U> void swap(U[] array, int i, int j) {
U temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
// Integer array එකක්
Integer[] intArray = {1, 2, 3, 4, 5};
System.out.print("Original Integer Array: ");
printArray(intArray); // Output: Original Integer Array: 1 2 3 4 5
swap(intArray, 0, 4);
System.out.print("Swapped Integer Array: ");
printArray(intArray); // Output: Swapped Integer Array: 5 2 3 4 1
// String array එකක්
String[] stringArray = {"Hello", "Generics", "Java"};
System.out.print("Original String Array: ");
printArray(stringArray); // Output: Original String Array: Hello Generics Java
swap(stringArray, 0, 2);
System.out.print("Swapped String Array: ");
printArray(stringArray); // Output: Swapped String Array: Java Generics Hello
// Double array එකක්
Double[] doubleArray = {1.1, 2.2, 3.3};
System.out.print("Original Double Array: ");
printArray(doubleArray); // Output: Original Double Array: 1.1 2.2 3.3
}
}
මෙතන printArray
සහ swap
methods දෙකම static <T>
හෝ static <U>
කියලා Type Parameter එකක් පාවිච්චි කරනවා. මේකෙන් කියන්නේ මේ methods වලට එන array එකේ elements ඕනෑම data type එකක වෙන්න පුළුවන් කියන එක. අපි method එක call කරනකොට printArray(intArray)
වගේ, Java automatically ඒක Integer array එකක් කියලා හඳුනගන්නවා (Type Inference). මේක ඇත්තටම නියමයි නේද? එක method එකක් ලියලා ඕනෑම type එකකට පාවිච්චි කරන්න පුළුවන්.
Wildcards: නම්යශීලී වෙමු!
Generics වල තියෙන තව සිරා feature එකක් තමයි Wildcards. මේවා අපි පාවිච්චි කරන්නේ අපේ Generics වල flexibility එක වැඩි කරගන්න. '?' (question mark) එකෙන් තමයි wildcard එකක් පෙන්නන්නේ. ප්රධාන වශයෙන් wildcards වර්ග දෙකක් තියෙනවා:
Lower Bounded Wildcards (? super T
):මේවායින් කියන්නේ 'T' කියන Type එකේම නැත්තම් ඒ Type එකේ super-type එකක් කියන එක. උදාහරණයක් විදිහට, List<? super Integer>
කියන්නේ මේ List එකේ Integer objects, නැත්තම් Integer වල super-class එකක් (Number
, Object
වගේ) තියෙන්න පුළුවන් කියන එක. මේවා සාමාන්යයෙන් data 'add' කරන්න පාවිච්චි කරනවා (Consumer).
import java.util.ArrayList;
import java.util.List;
public class LowerBoundedWildcardDemo {
// Integer type එකක් හෝ එහි supertype එකක් වන list එකකට Integer add කරන්න
public static void addIntegers(List<? super Integer> list) {
list.add(100);
list.add(200);
// list.add(10.5); // Compile-time error: Incompatible types.
}
public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
addIntegers(numberList);
System.out.println("Number List: " + numberList); // Output: Number List: [100, 200]
List<Object> objectList = new ArrayList<>();
addIntegers(objectList);
System.out.println("Object List: " + objectList); // Output: Object List: [100, 200]
List<Integer> integerList = new ArrayList<>();
addIntegers(integerList);
System.out.println("Integer List: " + integerList); // Output: Integer List: [100, 200]
}
}
Upper Bounded Wildcards (? extends T
):මේවායින් කියන්නේ 'T' කියන Type එකේම නැත්තම් ඒ Type එකෙන් extend වෙන ඕනෑම sub-type එකක් කියන එක. උදාහරණයක් විදිහට, List<? extends Number>
කියන්නේ මේ List එකේ Number type එකට අයිති (Integer
, Double
, Float
වගේ) ඕනෑම numeric type එකක objects තියෙන්න පුළුවන් කියන එක. මේවා සාමාන්යයෙන් data 'read' කරන්න පාවිච්චි කරනවා (Producer).
import java.util.ArrayList;
import java.util.List;
public class UpperBoundedWildcardDemo {
// Number list එකක elements print කරන්න
public static void printNumbers(List<? extends Number> list) {
for (Number n : list) {
System.out.print(n + " ");
}
System.out.println();
}
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<>();
integerList.add(10);
integerList.add(20);
printNumbers(integerList); // Output: 10 20
List<Double> doubleList = new ArrayList<>();
doubleList.add(10.5);
doubleList.add(20.5);
printNumbers(doubleList); // Output: 10.5 20.5
// List<String> stringList = new ArrayList<>();
// stringList.add("Hello");
// printNumbers(stringList); // Compile-time error: Incompatible types.
}
}
මේ දෙක මතක තියාගන්න පහසුම විදිහ තමයි PECS (Producer Extends, Consumer Super) principle එක. Producer (දත්ත සපයන) කෙනෙක් නම් extends
පාවිච්චි කරන්න, Consumer (දත්ත පරිභෝජනය කරන) කෙනෙක් නම් super
පාවිච්චි කරන්න. මේවා ටිකක් සංකීර්ණ වුණත්, ලොකු project වලදී code flexibility එකයි, reusability එකයි වැඩි කරගන්න ගොඩක් උදව් වෙනවා.
ඉතින් අද අපි Java Generics ගැන පොඩි හැඳින්වීමක් කළා. Generic Classes, Generic Methods කොහොමද ලියන්නේ, වගේම Wildcards පාවිච්චි කරලා අපේ කෝඩ් එක තවත් නම්යශීලී කරගන්නේ කොහොමද කියලත් ඉගෙන ගත්තා. Generics පාවිච්චි කරන එකෙන් ඔයාගේ කෝඩ් එක වඩාත් robust, type-safe, ඒ වගේම reusable වෙනවා. Runtime errors අඩු වෙනවා, code readability එක වැඩි වෙනවා, මොකද කෝඩ් එක බලන කෙනෙකුට object එකේ type එක මොකක්ද කියලා බැලූ බැල්මටම තේරෙන නිසා.
මේක පුංචි ආරම්භයක් විතරයි. Generics කියන්නේ ගොඩක් ගැඹුරු මාතෘකාවක්. තවත් Advanced Generic concepts, Type Erasure වගේ දේවල් ගැනත් ඉස්සරහට කතා කරන්න බලාපොරොත්තු වෙනවා. ඉතින් මේ concepts ගැන ඔයාලාට මොනවා හරි ප්රශ්න තියෙනවද? නැත්තම් මේකෙන් අලුතින් මොනවා හරි ඉගෙන ගත්තද? Comment section එකේ ඔයාලගේ අදහස් දක්වන්න! අනිවාර්යයෙන්ම මේ concepts ඔයාලගේ project වලට apply කරලා බලන්න. ඊළඟ ලිපියෙන් හම්බවෙමු! සියලු දෙනාටම ජය!