Liigu peamise sisu juurde

Optional ja null safety

Õppevideo antud teemal:

Sissejuhatus

Probleemid null-väärtusega

null lisati programmeerimiskeeltesse viisina, kuidas esindada väärtuse või tulemuse puudumist. Selle looja, Tony Hoare, nimetas seda hiljem oma "miljardi dollari veaks" kuna see on aastakümnete jooksul põhjustanud tohutul hulgal vigu programmides.

null-väärtuse põhiline viga seisneb selles, et see on keele tüübisüsteemile nähtamatu. Iga viitetüüp Javas võib endas hoida null-väärtust, kuid antud muutuja signatuurist pole võimalik kuidagi välja lugeda, kas seda võib juhtuda või mitte:

String name = user.getNickname();   // can this be null? who knows

Seetõttu on arendajad sunnitud koodi kirjutades sellele ka tähelepanu pöörama (mida enamus juhtudes lihtsalt unustatakse teha). See võib viia olukorrani, mille tulemus on NullPointerException programmi töö ajal:

String upper = user.getNickname().toUpperCase();  // NullPointerException if nickname is null

Kerge lahendus sellele on kontrollida igal pool, et muutuja väärtus poleks null:

String nickname = user.getNickname();
if (nickname != null) {
System.out.println(nickname.toUpperCase());
}

Sellisel lähenemisel on mitu probleemi:

  • Väga kerge unustada selle tegemist ning kompilaator ei sunni ka seda kontrolli peale
  • Teeb koodi raskemini loetavamaks
  • Igal pool, kus seda meetodit vaja kasutada, peaks sellist kontrolli tegema

Selle kõige juurpõhjus on see, et null-väärtusel endal pole mingit tähendust. See on lihtsalt viis, kuidas märku anda, et väärtust ei eksisteeri, mis parasjagu sulandub hästi kokku iga viitetüübiga.

Optional klass

Kuna meetodid peavad tagastama väärtusi, mis parasjagu võivad ka null olla (näiteks otsingupäring ei tagastanud midagi), siis traditsiooniliselt on seda tehtud järgnevalt:

public Product findByName(String name) {
// ...
return null; // not found
}

Alates Java 8st on võimalik kasutada Optional klassi. Optional<T> on konteiner, mis hoiab endas väärtust, mis võib juhuslikult ka null olla. Selle peamine eesmärk on arendajale selgelt märku anda, et antud väärtusel on võimalus puudulik olla ning suunata sellega tegelema:

public Optional<Product> findByName(String name) {
// ...
return Optional.empty(); // not found
}

Antud juhul kompilaator nüüd sunnib meetodi kasutajat tühja tulemusega tegelema.

Optional väärtuse loomine

Optional objekti on võimalik luua järneval kolmel viisil:

// Contains a value — throws NullPointerException if value is null
Optional<String> a = Optional.of("hello");

// Contains a value if non-null, empty if null
Optional<String> b = Optional.ofNullable(someValue);

// Always empty
Optional<String> c = Optional.empty();

Praktikas kasutatakse kõige rohkem Optional.ofNullable() meetodit, kui on teada, et tulemus võib null olla. Optional.of() kasutatakse juhul, kui ollakse kindlad, et väärtus ei saa null olla, kuna vastasel juhul tekiks NullPointerException erind.

Väärtuste kontrollimine ja kasutamine

isPresent() ja isEmpty() meetodid

Optional<String> opt = Optional.of("hello");

opt.isPresent(); // true
opt.isEmpty(); // false

Optional<String> empty = Optional.empty();

empty.isPresent(); // false
empty.isEmpty(); // true

get() meetod

get() meetod tagastab väärtuse, kui see on olemas, kuid viskab NoSuchElementException kui Optional on tühi. get() meetodi kasutusele peaks alati eelnema isPresent(), et viimast vältida.

if (opt.isPresent()) {
System.out.println(opt.get());
}

orElse() ja orElseGet() meetod

orElse() meetod tagastab väärtuse, kui see on olemas, või tagastab mingi vaikeväärtuse.

String result = opt.orElse("default");

orElseGet() meetod töötab sarnaselt eelnevale, kuid võtab sisendiks lambda-funktsioooni ning seda kutsutakse välja ainult siis, kui vaikeväärtuse välja arvutamine on ressurssimahukas:

// Always get heavy resource
String result = opt.orElse(getHeavyResource());

// Get heavy resource when required.
String result = opt.orElseGet(() -> getHeavyResource())

Antud StackOverflow vastus seletab nende kahe meetodit kõige paremini.

orElseThrow() meetod

orElseThrow() meetod võimaldab arendajal visata erindit, kui väärtust ei eksiteeri:

String result = opt.orElseThrow(() -> new IllegalStateException("Value not found"));

Optional tagastustüübina

Optional on mõeldud kasutuseks meetodi tagastustüübina. Sellega antakse meetodi kasutajale märku, et tagastusväärtus võib puudulik olla:

public Optional<Product> findByName(String name) {
for (Product p : products) {
if (p.getName().equals(name)) {
return Optional.of(p);
}
}
return Optional.empty();
}

See sunnib meetodi kasutajat nüüd tegelema olukorraga, kui väärtus puudub:

Optional<Product> result = stock.findByName("Widget");

if (result.isPresent()) {
System.out.println(result.get().getPrice());
} else {
System.out.println("Product not found");
}

// Or more concisely
double price = stock.findByName("Widget")
.orElseThrow(() -> new IllegalArgumentException("Product not found"))
.getPrice();

Mille jaoks Optional pole mõeldud

Optional on spetsiifiliselt mõeldud tagastustüübina kasutuseks ehk see ei ole sobilik järgmisteks kasutusjuhtudeks:

  • Väljana - väljade puhul kasutatakse kas null või mõnda vaikeväärtust
  • Meetodi parameetrina - seljuhul tuleks kasutada meetodite ülelaadimist
  • Kollektsioonides - List<Optional<String>> on veider ja tekitab segadust. Kollektsioonides võib null väärtust hoida.
// Wrong — Optional as a field
class User {
private Optional<String> nickname; // avoid this
}

// Wrong — Optional as a parameter
public void process(Optional<String> input) { ... } // avoid this

// Correct — Optional as a return type
public Optional<String> getNickname() { ... }

info

Javal on hetkel aktiivne ettepanek JEP 8303099, mis pakub välja keeletasemel null-ohutust. Ettepaneku kohaselt saaks kasutada ? märki nullable tüüpide ja ! märki null-restricted tüüpide tähistamiseks:

// ? — Value might be null
// ! — Value can never be null
public String? getNickname() { ... }
public String! getName() { ... }

String? nickname = user.getNickname(); // Compiler knows it might be null
String! name = user.getName(); // Compiler guarantees that this value can never be null

Ettepanek on veel mustandfaasis antud peatüki kirjutamise hetkel (veebruar 2026) ning lõplik süntaks võib muutuda.