JavaでOption
ScalaのOptionが羨ましかったので、自分で使うために作ってみた。
使ってみる
// Java public Option<String> castString(Object obj) { if( obj instanceof String) return Option.Some((String)obj); return Option.None(); }
// Scala def castString(obj: java.lang.Object): Option[String] = { obj match { case str: String => Some(str) case _ => None } }
大本と比べると、作るときが冗長。しょうがないけれど。
あと、パターンマッチが無いからget()を使う機会が多い。
それでも、nullチェックをしなくていいって素敵。
以下、ソース貼り付けで長いので注意。
ソース
とりあえずこんな感じの簡易実装。
(いるとは思わないけど)使いたい人は自由にどうぞ。責任は持ちませんが。
// Option.java package util; public abstract class Option<T> implements Iterable<T>{ public abstract boolean isDefined(); public static <T> None<T> None() { return new None<T>(); } public static <T> Some<T> Some(T v) { return new Some<T>(v); } public abstract T get(); public abstract T getOrElse(T elseValue); }
// Some.java package util; import java.util.ArrayList; import java.util.Iterator; public class Some<T> extends Option<T> { private final T value; Some(T value) { this.value = value; } @Override public boolean isDefined() { return true; } @Override public Iterator<T> iterator() { ArrayList<T> list = new ArrayList<T>(1); list.add(value); return list.iterator(); } @Override public T get() { return value; } @Override public T getOrElse(T elseValue) { return get(); } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { // nullを弾く if( obj == null ) return false; if( !(obj instanceof Some) ) return false; Some s1 = (Some)obj; if( value == null ) { // NullPointerException回避 return s1.value == null; } return value.equals(s1.value); } @Override public int hashCode() { return value.hashCode(); } @Override public String toString() { return new StringBuilder(). append("Some(").append(value).append(")"). toString(); } }
// None.java package util; import java.util.ArrayList; import java.util.Iterator; public class None<T> extends Option<T> { None() {} @Override public boolean isDefined() { return false; } @Override public Iterator<T> iterator() { ArrayList<T> list = new ArrayList<T>(1); return list.iterator(); } @Override public T get() { throw new UnsupportedOperationException("None has Nothing!"); } @Override public T getOrElse(T elseValue) { return elseValue; } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if( obj == null ) return false; return ( obj instanceof None ); } @Override public int hashCode() { return super.hashCode(); } @Override public String toString() { return "None"; } }
使い方 兼 テスト(JUnit4)は次の通り。
package util; import java.util.Iterator; import org.junit.*; import static org.junit.Assert.*; import static org.hamcrest.core.Is.*; public class OptionTest { @Test public void Option状態で値の有無を確認() { Option<Integer> res0 = Option.Some(10); Option<Integer> res1 = Option.None(); assertThat(res0.isDefined(), is(true)); assertThat(res1.isDefined(), is(false)); } @Test public void 値をイテレータで取得できる() { Option<Integer> res0 = Option.Some(10); Option<Integer> res1 = Option.None(); Iterator<Integer> it0 = res0.iterator(); assertThat(it0.hasNext(), is(true)); assertThat(it0.next(), is(10)); assertThat(it0.hasNext(), is(false)); Iterator<Integer> it1 = res1.iterator(); assertThat(it1.hasNext(), is(false)); } @Test public void for文で使用できる() { // 要素がある場合は要素1としてループする。 // つまり、ifと同様に(型安全に)使える。 Option<Integer> res0 = Option.Some(10); for(int num : res0) { assertThat(num, is(10)); } // 要素が無い場合は要素0としてループする // Someの時のみ実行される機構になる。 Option<Integer> res1 = Option.None(); for(@SuppressWarnings("unused") int num : res1) { fail(); } } @Test public void 値があれば取得できる() { Option<String> res0 = Option.Some("abc"); Option<String> res1 = Option.None(); assertThat(res0.get(), is("abc")); assertThat(res0.getOrElse("xxx"), is("abc")); assertThat(res1.getOrElse("xxx"), is("xxx")); } @Test(expected=UnsupportedOperationException.class) public void Noneから値を取得すると例外() { Option<String> res0 = Option.None(); res0.get(); } @Test public void 比較() { // Some vs None assertThat(Option.Some(10).equals(Option.Some(10)), is(true)); assertThat(Option.Some(10).equals(Option.Some("abc")), is(false)); assertThat(Option.Some(10).equals(Option.None()), is(false)); assertThat(Option.None().equals(Option.Some(10)), is(false)); assertThat(Option.None().equals(Option.None()), is(true)); // Some(null)のチェック assertThat(Option.Some(null).equals(Option.Some(null)), is(true)); assertThat(Option.Some(null).equals(Option.Some("abc")), is(false)); assertThat(Option.Some("abc").equals(Option.Some(null)), is(false)); // 型が違ってもNoneは等しい Option<String> res0 = Option.None(); Option<Integer> res1 = Option.None(); assertThat(res0.equals(res1), is(true)); } @Test public void toStringのテスト() { assertThat(Option.Some(10).toString(), is("Some(10)")); assertThat(Option.Some("abc").toString(), is("Some(abc)")); assertThat(Option.None().toString(), is("None")); } }