ScalaにはOption型というプログラムの世界観を変えるような魅力的な型があり、それでnullを回避することができる。*1
詳しくはこちらを参照。
ScalaのOptionステキさについてアツく語ってみる - ゆろよろ日記
Javaでも、ScalaのOption型と似て非なるOption型を作れないかなーと思い、思いつきと勢いでコード書いてみました。まぁ、同じものは作れっこないので、遊びです。本気にしないでくださいw
以下のような感じ、Option
public interface Option<T> { // オプションから値を取得する。 public T get(); // オプションから値を取得するが、値がない場合はdefaultValueを返す。 public T getOrElse(T defaultValue); }
Some
public final class Some<T> implements Option<T> { private final T value; @Override public int hashCode() { return value.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } @SuppressWarnings("unchecked") Some<T> other = (Some<T>) obj; return value.equals(other.value); } public static <T> Some<T> of(T value) { return new Some<T>(value); } private Some(T value) { Validate.notNull(value); this.value = value; } @Override public T get() { return value; } @Override public T getOrElse(T defaultValue) { return value; } }
None
None
public final class None<T> implements Option<T> { private final Class<T> clazz; @Override public int hashCode() { return clazz.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } @SuppressWarnings("unchecked") None<T> other = (None<T>) obj; return clazz.equals(other.clazz); } private None(Class<T> clazz) { Validate.notNull(clazz); this.clazz = clazz; } public static <T> None<T> of(Class<T> clazz) { return new None<T>(clazz); } @Override public T get() { throw new NoSuchElementException(); } @Override public T getOrElse(T defaultValue) { return defaultValue; } }
実際の使い方は以下。値がある場合はSome.ofメソッド、ない場合はNone.ofメソッドで生成できます。このファクトリメソッドを使うと型指定が楽になって生成しやすくなります。
この例では、Noneを2個目の要素に登録しているので、getOrElseでは引数に与えたUser.of("NULL USER")が、値がない場合に利用されます。以下の例にはないですが、getを使うと”値がない”=Noneの時はNoSuchElementExceptionがスローされます。
List<Option<User>> userOptions = new ArrayList<Option<User>>(); userOptions.add(Some.of(User.of("Junichi Kato"))); // 値がある時はSome userOptions.add(None.of(User.class)); // 値がない時はNone(nullは使わない) for (Option<User> option : userOptions) { User user = option.getOrElse(User.of("NULL USER")); System.out.println(user); }
出力結果は以下。
test.User@5a9e29fb[name=Junichi Kato] test.User@7f09fd93[name=NULL USER]
追記:
Google Collectionsを使うとNoneだけを除去したコレクションをさくっと取得できる。それとNoneが除去されているはずなので、option.get()を実行しても例外は発生しません。他のコレクション系のAPIも、equalsとhashCodeをオーバーライドしているので意図通り動作すると思います。Someの場合はequalsもhashCodeも内部の値のものを返すので、透過的にコレクション操作ができるはず。便利ですね。
Collection<Option<User>> withoutNone = Collections2.filter(userOptions, new Predicate<Option<User>>() { @Override public boolean apply(Option<User> input) { return input.equals(None.of(User.class)) == false; } }); for (Option<User> option : withoutNone) { User user = option.get(); System.out.println(user); }
Scalaだとコレクションに対してflatten, flatMapを使うと、Noneを除去して、Optionの値だけを簡単に取得できる。
scala> List(None, Some(5), None, if (true) Some(10) else None) res0: List[Option[Int]] = List(None, Some(5), None, Some(10)) scala> res0.flatten res1: List[Int] = List(5, 10) scala> res0.flatMap(x => x) res2: List[Int] = List(5, 10)
あと、パターンマッチはよく使うと思います。getとか、getOrElse使っていないのが肝です。
scala> case class User(name:String) defined class User scala> def view(userOption:Option[User]) { | userOption match { | case Some(user) => println(user) // 抽出子を使ってSomeが持っている値を取得して、画面に表示。 | case None => // 値がない状態を表すNoneでは、何もしない。 | } | } view: (userOption: Option[User])Unit scala> view(Some(User("Kato"))) User(Kato) scala> view(None) // User型を意識しないで使える。 // 何も表示されない
今回つくったのは全くの似非だし、Javaだといろいろと涙ぐましい努力が必要です。本当のOption型を知りたければ、Scalaを触ってみるしかない。ということで、ScalaのOptionは一度使ってみて欲しい。そして、Javaでnullを使っている人は、そのコードでnullが本当に必要か考えよう。
nullという値は本当に必要か考えよう - じゅんいち☆かとうの技術日誌
*1:Null Objectの一種といえると思います。