Created
April 18, 2012 06:21
-
-
Save msfroh/2411438 to your computer and use it in GitHub Desktop.
Option
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public abstract class Option<T> { | |
public abstract T get(); | |
public abstract boolean isDefined(); | |
// Factory method to return the singleton None instance | |
@SuppressWarnings({"unchecked"}) | |
public static <T> Option<T> none() { | |
return NONE; | |
} | |
// Factory method to return a non-empty Some instance | |
public static <T> Option<T> some(final T value) { | |
return new Some<T>(value); | |
} | |
private static None NONE = new None(); | |
private static class None extends Option { | |
// None has no element to return from get() | |
@Override | |
public Object get() { | |
throw new NoSuchElementException("get() called on None"); | |
} | |
// None is never defined | |
@Override | |
public boolean isDefined() { | |
return false; | |
} | |
// We'll override toString() to make tests/debugging clearer | |
@Override | |
public String toString() { | |
return "None"; | |
} | |
} | |
private static class Some<T> extends Option<T> { | |
private final T value; | |
// Some wraps an object value. It is up to the caller | |
// of the some() factory method above to ensure that | |
// value is not null. | |
public Some(final T value) { | |
this.value = value; | |
} | |
// Return the wrapped value | |
@Override | |
public T get() { | |
return value; | |
} | |
// Some is always defined | |
@Override | |
public boolean isDefined() { | |
return true; | |
} | |
// We'll override toString() to make tests/debugging clearer | |
@Override | |
public String toString() { | |
return "Some(" + value + ")"; | |
} | |
// We didn't need to override equals() for None, since it is a | |
// singleton and referential equality is fine. Some needs to | |
// define equals() in terms of the contained value. | |
public boolean equals(Object other) { | |
return other instanceof Some && ((Some) other).value.equals(value); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public abstract class Option<T> implements Iterable<T> { | |
/* ... previous method and subclass definitions ... */ | |
public final T getOrElse(T defaultVal) { | |
return isDefined() ? get() : defaultVal; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public abstract class Option<T> implements Iterable<T> { | |
/* ... previous method and subclass definitions ... */ | |
@Override | |
public Iterator<T> iterator() { | |
return isDefined() ? Collections.singleton(get()).iterator() : | |
Collections.<T>emptySet().iterator(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public abstract class Option<T> implements Iterable<T> { | |
/* ... previous method and subclass definitions ... */ | |
public static <T> Option<T> option(T value) { | |
if (value == null) { | |
return none(); | |
} | |
return some(value); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class OptionTest { | |
/* ... previous tests ... */ | |
@Test | |
public void testGet() throws Exception { | |
Option<Integer> a = some(5); | |
Option<Integer> b = none(); | |
assertEquals(5, a.get().intValue()); | |
try { | |
b.get(); | |
fail("Should have thrown exception"); | |
} catch (NoSuchElementException e) { | |
// Exception should have been thrown | |
} | |
// Some should return its own value | |
assertEquals(5, a.getOrElse(1).intValue()); | |
// None returns the given default value | |
assertEquals(1, b.getOrElse(1).intValue()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import static collections.Option.none; | |
import static collections.Option.some; | |
import static org.junit.Assert.*; | |
public class OptionTest { | |
@Test | |
public void testIterable() throws Exception { | |
// For Some, the body of the for loop will execute | |
Option<Integer> a = some(5); | |
boolean didRun = false; | |
for (Integer i : a) { | |
didRun = true; | |
assertEquals(Integer.valueOf(5), i); | |
} | |
assertTrue(didRun); | |
// For None, it does not execute | |
Option<Integer> b = none(); | |
for (Integer i : b) { | |
fail("This should not execute"); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* ... previous imports ... */ | |
import static collections.Option.option; | |
public class OptionTest { | |
/* ... previous tests ... */ | |
@Test | |
public void testWrapNull() throws Exception { | |
Map<String, Integer> scoreMap = new HashMap<String, Integer>(); | |
scoreMap.put("Michael", 42); | |
Option<Integer> a = option(scoreMap.get("Michael")); | |
assertTrue(a.isDefined()); | |
Option<Integer> b = option(scoreMap.get("Bob")); | |
assertFalse(b.isDefined()); | |
// Here is the ugly "traditional" Java way of dealing with Maps | |
Integer score = scoreMap.get("Michael"); | |
if (score != null) { | |
System.out.println("Michael's score is " + score); | |
} | |
// This feels more elegant to me | |
for (Integer myScore : option(scoreMap.get("Michael"))) { | |
System.out.println("Michael's score is " + score); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment