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"));
	}

}