Java 8 Optional: NullPointerException වලට බයි බයි! Safe Code ලියන්න - SC Guide

Java 8 Optional: NullPointerException වලට බයි බයි! Safe Code ලියන්න - SC Guide

ආයුබෝවන්! කොහොමද ඉතින් අපේ කට්ටියට? අද අපි කතා කරන්න යන්නේ Java development වලදී ඕනම කෙනෙක්ට නිතරම වගේ මුහුණ දෙන්න වෙන, හරිම හිසරදයක් වගේ වැඩක් ගැන. ඒ තමයි NullPointerException (NPE)! මම දන්නවා, මේක ඇහෙනකොටත් සමහරුන්ට සීතල වතුර ටිකක් ඔලුවට වක්කරගත්තා වගේ වෙයි. මොකද මේක කොච්චර පොඩි වෙලාවකට ආවත්, අපේ Application එකේ වැඩේ අවුල් කරන හැටි හොඳටම දන්නවනේ.

හිතන්නකෝ, අපේ කෝච්චිය නියම වෙලාවට එනවා කියලා ටිකට් අරන් ගියාම, පොඩි වැස්සකටත් පරක්කු වෙනකොට එන කේන්තිය වගේ දෙයක් තමයි මේ NullPointerException කියන්නෙත්. එක පාරටම Application එක බිඳ දාලා දානවා. ඒත් බය වෙන්න එපා! Java 8 එක්ක ආපු හරිම සුපිරි feature එකක් තියෙනවා මේ වගේ හිසරද වලින් මිදෙන්න. ඒ තමයි Optional කියන Class එක.

අද අපි මේ Guide එකෙන් බලාපොරොත්තු වෙන්නේ, Optional කියන්නේ මොකක්ද? ඒක කොහොමද අපිට මේ NullPointerException වලින් බේරෙන්න උදව් කරන්නේ කියන එක පැහැදිලිව කියලා දෙන්න. කම්මැලි කතා පැත්තකට දාලා, අපි යමු බලන්න මේ Optional magic එක කොහොමද වැඩ කරන්නේ කියලා!

NullPointerException (NPE) කියන්නේ මොකක්ද? ☠️

මුලින්ම බලමු මේ NPE කියන්නේ මොකක්ද කියලා. සරලව කිව්වොත්, අපි යම්කිසි object එකක method එකක් call කරන්න හරි, ඒකේ field එකකට access කරන්න හරි හදනකොට, ඒ object එක null නම්, ඒ කියන්නේ ඒක කිසිම value එකක් reference කරන්නේ නැත්නම්, Java Virtual Machine (JVM) එක මේ NullPointerException එක විසි කරනවා (throws an exception). මේක Java development වලදී නිතරම වගේ එන runtime error එකක්. ගොඩක් වෙලාවට මේක සිදුවෙන්නේ අපිට නොදැනීමයි, ඒ නිසා debug කරන්නත් අමාරුයි.

උදාහරණයක් විදිහට බලන්නකෝ මේ simple code snippet එක.

public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class Main {
    public static void main(String[] args) {
        User user = null; // මෙතනදී අපි user object එක null විදිහට assign කරනවා
        String userName = user.getName(); // මෙතනදී NullPointerException එකක් එනවා
        System.out.println("User Name: " + userName);
    }
}

උඩ තියෙන code එක run කරොත්, user.getName() කියන line එකේදී අනිවාර්යයෙන්ම NPE එකක් එනවා. මොකද user කියන object එක null නිසා. මේ වගේ දේවල් නිසා තමයි අපේ Application එක crash වෙන්නේ.

Optional කියන්නේ මොකක්ද? 📦

Java 8 එක්ක හඳුන්වා දුන්න java.util.Optional කියන්නේ null values හැසිරවීමට තියෙන සුපිරිම විසඳුමක්. සරලව කිව්වොත්, Optional කියන්නේ container object එකක්. මේ container එක ඇතුලේ non-null value එකක් තියෙන්නත් පුළුවන්, එහෙම නැත්නම් කිසිම value එකක් නැතුව empty වෙන්නත් පුළුවන්.

මේකෙන් වෙන්නේ, අපි null check කරන්න අමතක වුණාම එන NPE එක වෙනුවට, Optional එකේ value එකක් තියෙනවද නැද්ද කියලා explicitly කියන්න අපිට බල කරන එක. මේක හරියට, අපි කඩේට ගිහින් බඩු ගන්නකොට, බඩු නැත්නම් 'බඩු නෑ' කියලා කියනවා වෙනුවට, 'මේ බෑග් එක හිස්' කියලා බෑග් එක පෙන්නනවා වගේ දෙයක්. ඒක අපිට තේරෙනවා බෑග් එක හිස් කියලා, එතකොට අපිට ඊළඟට මොකද කරන්නේ කියලා තීරණය කරන්න පුළුවන්.

Optional භාවිතා කරන්නේ කොහොමද? 🤔

හරි, දැන් අපි බලමු මේ Optional එක practical විදිහට කොහොමද අපේ code එකට ගේන්නේ කියලා.

1. Optional Instances හදාගන්න විදිහ (Creating Optional Instances)

  • Optional.empty(): හිස් Optional object එකක් හදන්න මේක පාවිච්චි කරනවා.
  • Optional.of(value): non-null value එකකින් Optional එකක් හදන්න මේක පාවිච්චි කරනවා. හැබැයි මතක තියාගන්න, value එක null වුණොත් මේක NPE එකක් throw කරනවා!
  • Optional.ofNullable(value): මේක තමයි ගොඩක් වෙලාවට පාවිච්චි වෙන්නේ. value එක null ද නැද්ද කියලා නොදන්නකොට මේක පාවිච්චි කරන්න පුළුවන්. Value එක null නම්, Optional.empty() එකක් return කරනවා. null නැත්නම්, Optional.of(value) වගේම වැඩ කරනවා.
String maybeName = "Sunil";
Optional<String> optionalMaybeName = Optional.ofNullable(maybeName);

String anotherMaybeName = null;
Optional<String> optionalAnotherMaybeName = Optional.ofNullable(anotherMaybeName); // Returns Optional.empty()
String name = "Kamal";
Optional<String> optionalName = Optional.of(name);

// String nullName = null;
// Optional<String> optionalNullName = Optional.of(nullName); // NullPointerException here!
Optional<String> emptyOptional = Optional.empty();

2. Value එකක් තියෙනවද කියලා බලන විදිහ (Checking for Presence)

  • isPresent(): Optional එක ඇතුලේ value එකක් තියෙනවද කියලා boolean එකකින් කියනවා.
  • isEmpty() (Java 11+): Optional එක හිස්ද කියලා boolean එකකින් කියනවා. isPresent() එකේ ප්‍රතිලෝමය වගේ.
if (optionalAnotherMaybeName.isEmpty()) {
    System.out.println("Another name is empty (Java 11+).");
}
if (optionalName.isPresent()) {
    System.out.println("Name is present: " + optionalName.get());
} else {
    System.out.println("Name is not present.");
}

3. Value එක ගන්න විදිහ (Getting the Value)

  • get(): Optional එක ඇතුලේ තියෙන actual value එක return කරනවා. හැබැයි! Optional එක හිස් නම් මේක NoSuchElementException එකක් throw කරනවා. ඒ නිසා මේක isPresent() එක්ක විතරක් පාවිච්චි කරන්න!
Optional<String> name = Optional.of("Chamara");
String value = name.get(); // "Chamara"

// Optional<String> emptyName = Optional.empty();
// String emptyValue = emptyName.get(); // NoSuchElementException!

4. Value එකක් තියෙනවා නම් මොකක් හරි කරන්න (Conditional Actions)

  • ifPresent(Consumer action): Optional එක ඇතුලේ value එකක් තියෙනවා නම්, දීපු Consumer එක run කරනවා.
  • ifPresentOrElse(Consumer action, Runnable emptyAction) (Java 9+): value එකක් තියෙනවා නම් action එක run කරනවා, නැත්නම් emptyAction එක run කරනවා.
Optional<String> phone = Optional.ofNullable("0771234567");
phone.ifPresentOrElse(
    p -> System.out.println("Phone: " + p),
    () -> System.out.println("No phone number provided.")
);
Optional<String> optionalEmail = Optional.ofNullable("[email protected]");
optionalEmail.ifPresent(email -> System.out.println("Email found: " + email));

Optional<String> noEmail = Optional.empty();
noEmail.ifPresent(email -> System.out.println("This won't print."));

5. Default Values දෙන්න (Providing Default Values)

මේවා තමයි ගොඩක්ම ප්‍රයෝජනවත් methods. Value එකක් නැත්නම් default value එකක් දෙන්න මේවා පාවිච්චි කරන්න පුළුවන්.

  • orElse(T other): Optional එකේ value එකක් තියෙනවා නම් ඒක return කරනවා. නැත්නම් දීපු other default value එක return කරනවා. සටහන: other value එක Optional එක empty වුණත් නැතත් හැමවෙලේම evaluate වෙනවා.
  • orElseGet(Supplier<? extends T> supplier): Optional එකේ value එකක් තියෙනවා නම් ඒක return කරනවා. නැත්නම් දීපු Supplier එක call කරලා ඒකෙන් එන value එක return කරනවා. වැදගත්: Supplier එක call වෙන්නේ Optional එක empty නම් විතරයි. ඒ කියන්නේ lazy evaluation.
  • orElseThrow(Supplier<? extends X> exceptionSupplier): Optional එක හිස් නම් දීපු Supplier එකෙන් එන exception එක throw කරනවා. Value එකක් තියෙනවා නම් ඒක return කරනවා.
try {
    String importantValue = Optional.ofNullable(null)
        .orElseThrow(() -> new IllegalArgumentException("Value must be present!"));
} catch (IllegalArgumentException e) {
    System.out.println("Caught exception: " + e.getMessage());
}

String validValue = Optional.of("Valid Data")
    .orElseThrow(() -> new IllegalArgumentException("Should not happen"));
System.out.println("Valid Data: " + validValue);
// Imagine a heavy operation to get default value
String expensiveDefault = Optional.ofNullable(null)
    .orElseGet(() -> {
        System.out.println("Getting expensive default...");
        return "Default User";
    });
System.out.println("Expensive Default User: " + expensiveDefault);

// This will NOT print "Getting expensive default..." because value is present
String anotherUser = Optional.of("Normal User")
    .orElseGet(() -> {
        System.out.println("Getting expensive default...");
        return "Default User";
    });
System.out.println("Another User: " + anotherUser);
String username = Optional.ofNullable(null).orElse("Guest");
System.out.println("Username: " + username); // Output: Guest

String activeUsername = Optional.of("Admin").orElse("Guest");
System.out.println("Active Username: " + activeUsername); // Output: Admin

6. Values Transform කරන්න (Transforming Values)

  • map(Function<? super T, ? extends U> mapper): Optional එක ඇතුලේ value එකක් තියෙනවා නම්, ඒක දීපු Function එකට pass කරලා, ඒකෙන් එන result එක අලුත් Optional එකක් විදිහට return කරනවා.
  • flatMap(Function<? super T, ? extends Optional<U>> mapper): map වගේමයි. හැබැයි, mapper function එක return කරන්නේ Optional එකක් නම්, මේක nested Optional එකක් හැදෙන්නේ නැතුව, straight up Optional එකක් return කරනවා. (එකම මට්ටමේ තියාගන්න).
// Imagine a method that returns Optional<Integer>
public static Optional<Integer> parseNumber(String s) {
    try {
        return Optional.of(Integer.parseInt(s));
    } catch (NumberFormatException e) {
        return Optional.empty();
    }
}

Optional<String> ageString = Optional.of("30");
Optional<Integer> age = ageString.flatMap(Main::parseNumber);
System.out.println("Parsed Age: " + age.orElse(0)); // 30

Optional<String> invalidAgeString = Optional.of("abc");
Optional<Integer> invalidAge = invalidAgeString.flatMap(Main::parseNumber);
System.out.println("Invalid Age: " + invalidAge.orElse(0)); // 0
Optional<String> companyName = Optional.of("softlogic");
Optional<String> upperCaseName = companyName.map(String::toUpperCase);
System.out.println("Upper Case Name: " + upperCaseName.orElse("N/A")); // SOFTLOGIC

Optional<String> emptyCompany = Optional.empty();
Optional<String> upperCaseEmpty = emptyCompany.map(String::toUpperCase);
System.out.println("Upper Case Empty: " + upperCaseEmpty.orElse("N/A")); // N/A

7. Values Filter කරන්න (Filtering Values)

  • filter(Predicate<? super T> predicate): Optional එක ඇතුලේ value එකක් තියෙනවා නම්, දීපු Predicate එකෙන් ඒක check කරනවා. true නම් Optional එකේ value එක ඒ විදිහටම return කරනවා. false නම් Optional.empty() එකක් return කරනවා.
Optional<Integer> age = Optional.of(25);
Optional<Integer> adultAge = age.filter(a -> a >= 18);
System.out.println("Adult Age: " + adultAge.orElse(0)); // 25

Optional<Integer> childAge = Optional.of(15);
Optional<Integer> filteredChildAge = childAge.filter(a -> a >= 18);
System.out.println("Filtered Child Age: " + filteredChildAge.orElse(0)); // 0 (empty optional)

Optional වල වාසි මොනවද? 👍

දැන් ඔයාලට තේරෙනවා ඇති Optional එක කොච්චර වටිනවද කියලා. මෙන්න ඒකෙන් අපිට ලැබෙන ප්‍රධාන වාසි ටිකක්:

  1. NullPointerException වලින් මිදීම: මේක තමයි ප්‍රධානම වාසිය. Optional භාවිතා කිරීමෙන්, null විය හැකි තැන් explicit විදිහට handle කරන්න අපිට බල කරනවා. ඒ නිසා runtime එකේදී එන NPE අවදානම අඩු වෙනවා.
  2. Code එකේ Readability එක වැඩිවීම: if (value != null) කියලා හැම තැනම check කරනවා වෙනුවට, Optional methods (ifPresent, orElse, map වගේ) පාවිච්චි කරන එකෙන් code එක වඩාත් පැහැදිලි, කියවන්න පහසු, functional style එකකට ලියන්න පුළුවන්.
  3. API Design එක දියුණු වීම: method එකක් Optional එකක් return කරනවා නම්, ඒ method එකෙන් value එකක් නැති වෙන්න පුළුවන් කියලා caller ට පැහැදිලිවම කියනවා. ඒ නිසා caller ට ඒක handle කරන්න පුළුවන්.
  4. Functional Programming වලට උදව් වීම: Java 8 Stream API එකත් එක්ක වගේම, Optional එකත් functional programming style එකට හොඳට ගැලපෙනවා.

Optional භාවිතා නොකළ යුතු තැන් ⚠️

ඕනම දෙයක් වගේ, Optional වලටත් ඒක පාවිච්චි කරන්න හොඳ නැති තැන් තියෙනවා. හැම තැනම Optional දාන එක හොඳ පුරුද්දක් නෙවෙයි. මතක තියාගන්න, Optional එක designed කරලා තියෙන්නේ method return types සඳහා මිසක්, field වලට හෝ parameters වලට නෙවෙයි.

  1. Class Field එකක් විදිහට: Optional එක class එකක field එකක් විදිහට පාවිච්චි කරන එක එච්චර හොඳ පුරුද්දක් නෙවෙයි. ඒක serialization, persistence වගේ දේවල් වලට ගැටලු ඇති කරන්න පුළුවන්. ඒ වගේ තැන් වලට null value එකක් පාවිච්චි කරන එක වඩාත් සුදුසුයි.
  2. Method Parameter එකක් විදිහට: method එකක parameter එකක් විදිහට Optional එකක් පාවිච්චි කරනවා නම්, ඒක code එකේ clarity එක අඩු කරන්න පුළුවන්. Parameter එකක් null වෙන්න පුළුවන් නම්, null එක pass කරලා, method එක ඇතුලේ check කරන එක සාමාන්‍යයෙන් වඩා හොඳයි, නැත්නම් method overloading කරන්න පුළුවන්.
  3. Collections ඇතුලේ: List<Optional<String>> වගේ collections ඇතුලේ Optional objects දාන එක එච්චර සුදුසු නෑ. ඒ වෙනුවට, null values remove කරලා clean collection එකක් පාවිච්චි කරන්න පුළුවන්.
  4. Exceptions වෙනුවට: යම්කිසි value එකක් නැතිවීම exceptional case එකක් නම් (එනම්, ඒක සාමාන්‍ය තත්ත්වයක් නෙවෙයි නම්), Optional.empty() එකක් return කරනවා වෙනුවට, අදාල Exception එක throw කරන එක වඩාත් සුදුසුයි.

අවසාන වශයෙන් 🚀

ඉතින්, අද අපි Java 8 වල Optional කියන Class එක ගැන විස්තරාත්මකව කතා කළා. NullPointerException කියන හිසරදයෙන් මිදිලා, වඩාත් safe සහ කියවන්න පහසු Java applications develop කරන්න Optional කොච්චර උදව් වෙනවද කියලා ඔයාලට දැන් පැහැදිලි ඇති.

මතක තියාගන්න, Optional කියන්නේ මැජික් විසඳුමක් නෙවෙයි, හැබැයි හරි විදිහට පාවිච්චි කරොත්, ඒක ඔයාලගේ code base එක ගොඩක් දියුණු කරන්න පුළුවන්. අද ඉඳන්ම ඔයාලගේ project වල Optional භාවිතා කරලා බලන්න. ඒක ඔයාලගේ developer ජීවිතේට ලොකු සහනයක් වෙයි!

මේ ගැන ඔයාලගේ අදහස්, ප්‍රශ්න, හෝ ඔයාලගේ අත්දැකීම් පහළින් comment කරන්න. අපි ඊළඟ ලිපියෙන් හම්බවෙමු! තව සුපිරිම Java concept එකක් අරගෙන එන්නම්.

ජය වේවා!