如何编写一个类型安全的Java方法,它返回类a或某类b的东西?例如:
public ... either(boolean b) {
if (b) {
return new Integer(1);
} else {
return new String("hi");
}
}
最干净的方法是什么?
(我唯一想到的是使用明显不好的异常,因为它滥用了一般语言功能的错误处理机制......
public String either(boolean b) throws IntException {
if (b) {
return new String("test");
} else {
throw new IntException(new Integer(1));
}
}
)
答案 0 :(得分:35)
我模拟代数数据类型的通用公式是:
instanceof
检查构造函数,然后向下转换为适当的类型以获取数据。因此对于Either a b
,它会是这样的:
abstract class Either<A, B> { }
class Left<A, B> extends Either<A, B> {
public A left_value;
public Left(A a) { left_value = a; }
}
class Right<A, B> extends Either<A, B> {
public B right_value;
public Right(B b) { right_value = b; }
}
// to construct it
Either<A, B> foo = new Left<A, B>(some_A_value);
Either<A, B> bar = new Right<A, B>(some_B_value);
// to deconstruct it
if (foo instanceof Left) {
Left<A, B> foo_left = (Left<A, B>)foo;
// do stuff with foo_left.a
} else if (foo instanceof Right) {
Right<A, B> foo_right = (Right<A, B>)foo;
// do stuff with foo_right.b
}
答案 1 :(得分:25)
这是一种静态检查的类型安全解决方案;这意味着您无法创建运行时错误。请按照其含义阅读上一句。是的,你可以用某种方式激发例外......
它非常冗长,但是,嘿,这是Java!
public class Either<A,B> {
interface Function<T> {
public void apply(T x);
}
private A left = null;
private B right = null;
private Either(A a,B b) {
left = a;
right = b;
}
public static <A,B> Either<A,B> left(A a) {
return new Either<A,B>(a,null);
}
public static <A,B> Either<A,B> right(B b) {
return new Either<A,B>(null,b);
}
/* Here's the important part: */
public void fold(Function<A> ifLeft, Function<B> ifRight) {
if(right == null)
ifLeft.apply(left);
else
ifRight.apply(right);
}
public static void main(String[] args) {
Either<String,Integer> e1 = Either.left("foo");
e1.fold(
new Function<String>() {
public void apply(String x) {
System.out.println(x);
}
},
new Function<Integer>() {
public void apply(Integer x) {
System.out.println("Integer: " + x);
}
});
}
}
您可能需要查看Functional Java和Tony Morris'blog。
Here是Functional Java中Either
实现的链接。我的示例中的fold
在那里被称为either
。它们有一个更复杂的fold
版本,它能够返回一个值(这似乎适合函数式编程风格)。
答案 2 :(得分:9)
你可以通过编写一个通用类Either
与两个类型L
和R
参与两个构造函数(一个参与L
)来与Haskell密切对应,一个接受R
)和两个方法L getLeft()
和R getRight()
,以便它们返回构造时传递的值,或者抛出异常。
答案 3 :(得分:7)
已经提供的建议虽然可行,但并不完整,因为它们依赖于一些null
引用,并有效地使“Either”伪装成值的元组。不相交的金额显然是一种或另一种。
我建议您查看FunctionalJava Either
的实施情况作为示例。
答案 4 :(得分:4)
最重要的是不要试图用另一种语言写作。通常在Java中,您希望将行为放在对象中,而不是让“脚本”在外部运行,并且get方法会破坏封装。这里没有提出这种建议的背景。
处理这个特定小片段的一种安全方法是将其写为回调。类似于一个非常简单的访客。
public interface Either {
void string(String value);
void integer(int value);
}
public void either(Either handler, boolean b) throws IntException {
if (b) {
handler.string("test");
} else {
handler.integer(new Integer(1));
}
}
您可能希望使用纯函数实现并将值返回给调用上下文。
public interface Either<R> {
R string(String value);
R integer(int value);
}
public <R> R either(Either<? extends R> handler, boolean b) throws IntException {
return b ?
handler.string("test") :
handler.integer(new Integer(1));
}
(使用Void
(大写'V')如果你想回到对返回值不感兴趣的话。)
答案 5 :(得分:4)
我已经通过以下方式以类似Scala的方式实现了它。它有点冗长(毕竟它是Java :)但它的类型安全。
public interface Choice {
public enum Type {
LEFT, RIGHT
}
public Type getType();
interface Get<T> {
T value();
}
}
public abstract class Either<A, B> implements Choice {
private static class Base<A, B> extends Either<A, B> {
@Override
public Left leftValue() {
throw new UnsupportedOperationException();
}
@Override
public Right rightValue() {
throw new UnsupportedOperationException();
}
@Override
public Type getType() {
throw new UnsupportedOperationException();
}
}
public abstract Left leftValue();
public abstract Right rightValue();
public static <A, B> Either<A, B> left(A value) {
return new Base<A, B>().new Left(value);
}
public static <A, B> Either<A, B> right(B value) {
return new Base<A, B>().new Right(value);
}
public class Left extends Either<A, B> implements Get<A> {
private A value;
public Left(A value) {
this.value = value;
}
@Override
public Type getType() {
return Type.LEFT;
}
@Override
public Left leftValue() {
return Left.this;
}
@Override
public Right rightValue() {
return null;
}
@Override
public A value() {
return value;
}
}
public class Right extends Either<A, B> implements Get<B> {
private B value;
public Right(B value) {
this.value = value;
}
@Override
public Left leftValue() {
return null;
}
@Override
public Right rightValue() {
return this;
}
@Override
public Type getType() {
return Type.RIGHT;
}
@Override
public B value() {
return value;
}
}
}
然后,您可以在代码上传递Either<A,B>
个实例。 Type
枚举主要用于switch
语句。
创建Either
值很简单:
Either<A, B> underTest;
A value = new A();
underTest = Either.left(value);
assertEquals(Choice.Type.LEFT, underTest.getType());
assertSame(underTest, underTest.leftValue());
assertNull(underTest.rightValue());
assertSame(value, underTest.leftValue().value());
或者,在使用它而不是例外的典型情况下,
public <Error, Result> Either<Error,Result> doSomething() {
// pseudo code
if (ok) {
Result value = ...
return Either.right(value);
} else {
Error errorMsg = ...
return Either.left(errorMsg);
}
}
// somewhere in the code...
Either<Err, Res> result = doSomething();
switch(result.getType()) {
case Choice.Type.LEFT:
// Handle error
Err errorValue = result.leftValue().value();
break;
case Choice.Type.RIGHT:
// Process result
Res resultValue = result.rightValue().value();
break;
}
希望它有所帮助。
答案 6 :(得分:3)
从http://blog.tmorris.net/posts/maybe-in-java/我了解到你可以将外层类的构造函数设为私有,所以只有嵌套类可以对它进行子类化。这个技巧就像上面最好的类型一样安全,但更简洁,适用于你想要的任何ADT,如Scala的案例类。
public abstract class Either<A, B> {
private Either() { } // makes this a safe ADT
public abstract boolean isRight();
public final static class Left<L, R> extends Either<L, R> {
public final L left_value;
public Left(L l) { left_value = l; }
public boolean isRight() { return false; }
}
public final static class Right<L, R> extends Either<L, R> {
public final R right_value;
public Right(R r) { right_value = r; }
public boolean isRight() { return true; }
}
}
(从最佳答案的代码和风格开始)
请注意:
子类的决赛是可选的。如果没有它们,您可以左键和右键,但仍然不能直接键入。因此,没有final
s要么宽度有限,要么无限深度。
对于像这样的ADT,我认为没有理由跳过整个反instanceof
的潮流。布尔值适用于Maybe或Either,但一般来说instanceof
是您最好的选择。
答案 7 :(得分:2)
感谢Derive4J代数数据类型现在在Java中非常容易。您所要做的就是创建以下类:
import java.util.function.Function;
@Data
public abstract class Either<A, B> {
Either(){}
/**
* The catamorphism for either. Folds over this either breaking into left or right.
*
* @param left The function to call if this is left.
* @param right The function to call if this is right.
* @return The reduced value.
*/
public abstract <X> X either(Function<A, X> left, Function<B, X> right);
}
Derive4J将负责为左侧和权限案例创建构造函数,以及模式匹配语法alla Haskell,每个方面的映射器方法等。
答案 8 :(得分:2)
在一个小型库中有一个独立的Java {8 Either
实现,&#34; ambivalence&#34;:http://github.com/poetix/ambivalence
它最接近Scala标准实现 - 例如,它为map
和hashMap
操作提供左右投影。
无法直接访问左侧或右侧值;相反,你通过提供lambdas将它们映射到单个结果类型来join
这两种类型:
Either<String, Integer> either1 = Either.ofLeft("foo");
Either<String, Integer> either2 = Either.ofRight(23);
String result1 = either1.join(String::toUpperCase, Object::toString);
String result2 = either2.join(String::toUpperCase, Object::toString);
你可以从Maven中心获得它:
<dependency>
<groupId>com.codepoetics</groupId>
<artifactId>ambivalence</artifactId>
<version>0.2</version>
</dependency>
答案 9 :(得分:2)
您无需使用instanceof
检查或冗余字段。令人惊讶的是,Java的类型系统提供了足够的功能来干净地模拟和类型。
首先,您知道任何数据类型都只能用函数编码吗?它被称为Church encoding。例如,使用Haskell签名,Either
类型可以定义如下:
type Either left right =
forall output. (left -> output) -> (right -> output) -> output
您可以将其解释为“给定左值上的函数和右值上的函数,生成其中任何一个的结果”。
扩展这个想法,在Java中我们可以定义一个名为Matcher
的接口,它包含两个函数,然后根据如何在其上进行模式匹配来定义Sum类型。这是完整的代码:
/**
* A sum class which is defined by how to pattern-match on it.
*/
public interface Sum2<case1, case2> {
<output> output match(Matcher<case1, case2, output> matcher);
/**
* A pattern-matcher for 2 cases.
*/
interface Matcher<case1, case2, output> {
output match1(case1 value);
output match2(case2 value);
}
final class Case1<case1, case2> implements Sum2<case1, case2> {
public final case1 value;
public Case1(case1 value) {
this.value = value;
}
public <output> output match(Matcher<case1, case2, output> matcher) {
return matcher.match1(value);
}
}
final class Case2<case1, case2> implements Sum2<case1, case2> {
public final case2 value;
public Case2(case2 value) {
this.value = value;
}
public <output> output match(Matcher<case1, case2, output> matcher) {
return matcher.match2(value);
}
}
}
然后你可以像这样使用它:
import junit.framework.TestCase;
public class Test extends TestCase {
public void testSum2() {
assertEquals("Case1(3)", longOrDoubleToString(new Sum2.Case1<>(3L)));
assertEquals("Case2(7.1)", longOrDoubleToString(new Sum2.Case2<>(7.1D)));
}
private String longOrDoubleToString(Sum2<Long, Double> longOrDouble) {
return longOrDouble.match(new Sum2.Matcher<Long, Double, String>() {
public String match1(Long value) {
return "Case1(" + value.toString() + ")";
}
public String match2(Double value) {
return "Case2(" + value.toString() + ")";
}
});
}
}
通过这种方法,您甚至可以在Haskell和Scala等语言中找到模式匹配的直接相似性。
此代码作为我的多个arities的复合类型库(Sums and Products,又名Unions和Tuples)的一部分进行分发。它在GitHub上:
答案 10 :(得分:1)
由于您已标记Scala,我将给出Scala答案。只需使用现有的Either
类。以下是一个示例用法:
def whatIsIt(flag: Boolean): Either[Int,String] =
if(flag) Left(123) else Right("hello")
//and then later on...
val x = whatIsIt(true)
x match {
case Left(i) => println("It was an int: " + i)
case Right(s) => println("It was a string: " + s)
}
这是完全类型安全的;你不会有擦除或类似的问题......
如果您根本无法使用Scala,至少可以将其作为如何实现自己的Either
类的示例。
答案 11 :(得分:0)
我能想到的最接近的是两个值的包装器,可以让你检查设置的值并检索它:
class Either<TLeft, TRight> {
boolean isLeft;
TLeft left;
TRight right;
Either(boolean isLeft, TLeft left1, TRight right) {
isLeft = isLeft;
left = left;
this.right = right;
}
public boolean isLeft() {
return isLeft;
}
public TLeft getLeft() {
if (isLeft()) {
return left;
} else {
throw new RuntimeException();
}
}
public TRight getRight() {
if (!isLeft()) {
return right;
} else {
throw new RuntimeException();
}
}
public static <L, R> Either<L, R> newLeft(L left, Class<R> rightType) {
return new Either<L, R>(true, left, null);
}
public static <L, R> Either<L, R> newRight(Class<L> leftType, R right) {
return new Either<L, R>(false, null, right);
}
}
class Main {
public static void main(String[] args) {
Either<String,Integer> foo;
foo = getString();
foo = getInteger();
}
private static Either<String, Integer> getInteger() {
return Either.newRight(String.class, 123);
}
private static Either<String, Integer> getString() {
return Either.newLeft("abc", Integer.class);
}
}
答案 12 :(得分:0)
根据Riccardo的answer,以下代码片段为我工作:
public class Either<L, R> {
private L left_value;
private R right_value;
private boolean right;
public L getLeft() {
if(!right) {
return left_value;
} else {
throw new IllegalArgumentException("Left is not initialized");
}
}
public R getRight() {
if(right) {
return right_value;
} else {
throw new IllegalArgumentException("Right is not initialized");
}
}
public boolean isRight() {
return right;
}
public Either(L left_v, Void right_v) {
this.left_value = left_v;
this.right = false;
}
public Either(Void left_v, R right_v) {
this.right_value = right_v;
right = true;
}
}
用法:
Either<String, Integer> onlyString = new Either<String, Integer>("string", null);
Either<String, Integer> onlyInt = new Either<String, Integer>(null, new Integer(1));
if(!onlyString.isRight()) {
String s = onlyString.getLeft();
}
答案 13 :(得分:-7)
更改您的设计,以便您不需要这个相当荒谬的功能。你对返回值做的任何事情都需要某种if / else结构。它会非常非常难看。
从一个快速的谷歌搜索,在我看来,Haskell的Either唯一常用的是错误报告,所以看起来异常实际上是纠正替换。