5. ledna 2010

Lekce ze Scaly: Jak se vyvarovat NullPointerException

Jak se v jazyce Scala brání NullPointerException? V hojné míře se používá třída Option, která zajistí, že nemusí metoda vracet null, ale vrací existující instanci. Jak to celé funguje si ukážeme v Javě:

public abstract class Option<T> {
  public abstract T get();
  public abstract boolean isNone();
  public abstract boolean isSome();
}

Pokud potřebujeme aby nějaká metoda vracela něco a nebo null, použijeme třídu Option. Díky ní metoda vrátí existující instanci, ze které jednoduše zjistíme, zda návratová hodnota má být null (isNone() vrací true) a nebo je to nějaká smyslu plná hodnota (isSome() vrací true) a pak se k této hodnotě dostaneme pomocí metody get().

Teď už to chce jenom nějaký rozumný příklad. Vezněme si např. Map a její metodu get:

V get(T key);
Ta v případě, že klíč v mapě neexistuje vrací null. Takže typicky píšeme něco jako:

String value = map.get(key);
if (value != null) {
  System.out.println(value);
} else {
  System.out.println("Nic");
}

A jak by tedy měla metoda get objektu Map vypadat kdybychom byli ve Scale:

Option<V> get(T key);
A nyní jak by vypadal náš kód pro čtení z mapy:

Option<String> value = map.get(key);
if (value.isSome()) {
  System.out.println(value.get());
} else {
  System.out.println("Nic");
}

A zbavili jsme se ošklivého porovnání na null, což jsme chtěli. Navíc pokud metoda vrací Option a ne přímo hodnotu je podstatně větší pravděpodobnost, že nezapomeneme otestovat zda Option něco obsahuje. Pokud metoda vrací přímo hodnotu nebo null, pak hrozí, že hodnotu rovnu použijeme a neotestujeme ji na null.

A nyní přistupme k implementaci:

public class None<T> extends Option<T> {

  public T get() {
    throw new UnsupportedOperationException("The None had no value");
  }

  public boolean isNone() {
    return true;
  }

  public boolean isSome() {
    return false;
  }

  public boolean equals(Object obj) {
    return ((obj == this) || (obj instanceof None));
  }

 public int hashCode() {
   return 1;
  }

}

public class Some<T> extends Option<T> {
  protected T value;
 
  public Some(T value) {
    super();
    this.value = value;
  }

  public T get() {
    return value;
  }

  public boolean isNone() {
    return false;
  }

  public boolean isSome() {
    return true;
  }

  public boolean equals(Object obj) {
    if (obj == this) return true;
    if (obj instanceof Some) {
      Some other = (Some) obj;
      return value.equals(other.value);
    }
    return false;
  }

  public int hashCode() {
    return value.hashCode();
  }
 
}

A jak to celé funguje ve Scale? Díky pattern matchingu a type inference ještě elegantněji:

val value = map.get(key) 
value match {
  case None => print "Nic"
  case Some(x) => print x
}

11 komentářů:

Anonymní řekl(a)...

Nevypadly ti tam <T> resp. nezměnily se v neznámé html značky?

Jira řekl(a)...

Máš pravdu, opraveno ...

Anonymní řekl(a)...
Tento komentář byl odstraněn administrátorem blogu.
s45ad446512gd řekl(a)...
Tento komentář byl odstraněn administrátorem blogu.
英文發音真難 řekl(a)...
Tento komentář byl odstraněn administrátorem blogu.
非常冷 řekl(a)...
Tento komentář byl odstraněn administrátorem blogu.
Anonymní řekl(a)...
Tento komentář byl odstraněn administrátorem blogu.
Anonymní řekl(a)...
Tento komentář byl odstraněn administrátorem blogu.
Anonymní řekl(a)...
Tento komentář byl odstraněn administrátorem blogu.
Anonymní řekl(a)...
Tento komentář byl odstraněn administrátorem blogu.
Anonymní řekl(a)...
Tento komentář byl odstraněn administrátorem blogu.