我正在尝试创建一个只能容纳两个对象中的一个的类,我想用泛型来做这个。这是一个想法:
public class Union<A, B> {
private final A a;
private final B b;
public Union(A a) {
this.a = a;
b = null;
}
public Union(B b) {
a = null;
this.b = b;
}
// isA, isB, getA, getB...
}
当然这不起作用,因为由于类型擦除,构造函数具有相同的类型签名。我意识到一个解决方案是让一个构造函数同时使用两个,但我希望其中一个值为空,因此使用单个参数构造函数似乎更优雅。
// Ugly solution
public Union(A a, B b) {
if (!(a == null ^ b == null)) {
throw new IllegalArgumentException("One must exist, one must be null!");
}
this.a = a;
this.b = b;
}
对此有优雅的解决方案吗?
编辑1:我正在使用Java 6.
编辑2:我想要这样做的原因是因为我有一个可以返回两种类型之一的方法。我做了一个没有泛型的具体版本,但想知道我是否可以使它通用。 是的,我意识到拥有一种具有两种不同返回类型的方法是手头的真正问题,但如果有一个很好的方法,我仍然很好奇。
我认为durron597's answer是最好的,因为它指出
Union<Foo, Bar>
和Union<Bar, Foo>
的行为应该相同,但它们不相同(这是我决定停止追求此行为的主要原因)。 这是一个比“丑陋”的构造函数更加丑陋的问题。我觉得最好的选择可能是这个摘要(因为接口不能决定可见性)并制作
isA
和getA
内容protected
,然后实现类有更好的命名方法来避免<A, B>
!=<B, A>
问题。我将添加我自己的答案以及更多细节。
最终修改:对于它的价值,我决定使用静态方法作为伪构造函数(
public static Union<A, B> fromA(A a)
和public static Union<A, B> fromB(B b)
)是最好的方法(同时使真实构造函数私有)。当Union<A, B>
和Union<B, A>
被用作返回值时,它们永远不会真实地相互比较。
另一个编辑,6个月了:当我问这个时,我真的不敢相信我是多么天真,静态工厂方法显然是绝对正确的选择,显然是一个明智的选择。 / p>
除此之外,我发现Functional Java非常有趣。我还没有使用过它,但是在搜索'java disjunct union'时我确实发现了这个
Either
,这正是我想要的。虽然功能Java仅适用于Java 7和8,但是幸运的是我现在正在使用的Java 8项目。
答案 0 :(得分:5)
这样做真的没有任何意义。什么时候才有意义?例如(假设,目前,您的初始代码有效):
var encodeUrl = function(url) {
return url.split('//')[0] + '//'
+ url.split('//')[1]
.replace(/[a-z ]/gi, function(c) {
return '%' + c.charCodeAt(0).toString(16);
});
}
url = 'http://www.helloworld.com/';
encodedUrl = encodeUrl(url)
document.body.innerHTML = 'Raw url: <pre>' + url + '</pre>EncodedUrl: <pre>' + encodedUrl + '</pre>Result: <a href="' + encodedUrl + '">TEST</a>';
但如果你这样做了,那么:
Union<String, Integer> union = new Union("Hello");
// stuff
if (union.isA()) { ...
即使类和数据相同,也会有不同的行为。您对Union<Integer, String> union = new Union("Hello");
// stuff
if (union.isA()) { ...
和isA
的概念基本上是&#34;左对比&#34; - 更重要的是哪一个是左对比哪个是哪一个是String而哪一个是整数。换句话说,isB
与Union<String, Integer>
非常不同,这可能不是您想要的。
如果我们举例说明会发生什么:
Union<Integer, String>
某事List<Union<?, ?>> myList;
for(Union<?, ?> element : myList) {
if(element.isA()) {
// What does this even mean?
的事实并不重要,除非你关心它是左派还是右派,在这种情况下你应该称之为。
如果这个讨论不是左派与右派,那么唯一重要的是在创建类时使用你的特定类型。简单地拥有一个接口会更有意义;
A
你甚至可以做&#34;是&#34;抽象类中的方法:
public interface Union<A, B> {
boolean isA();
boolean isB();
A getA();
B getB();
}
然后,当您实际实例化该类时,无论如何都将使用特定类型......
public abstract class AbstractUnion<A, B> {
public boolean isA() { return getB() == null; }
public boolean isB() { return getA() == null; }
}
然后,当您实际选择了实施类型时,您实际上就会知道自己得到了什么。
除此之外:如果在阅读完上述所有内容之后,您仍然希望按照初始问题中描述的方式执行此操作,那么正确的方法是使用静态工厂方法和私有构造函数,如{{3}所述}。但是,我希望你能进一步思考你真正需要你的课程在真实用例中做什么。
答案 1 :(得分:2)
我会使用私有构造函数和2个静态创建者
public class Union<A, B> {
private final A a;
private final B b;
// private constructor to force use or creation methods
private Union(A a, B b) {
if ((a == null) && (b == null)) { // ensure both cannot be null
throw new IllegalArgumentException();
}
this.a = a;
this.b = b;
}
public static <A, B> Union<A, B> unionFromA(A a) {
Union<A,B> u = new Union(a, null);
return u;
}
public static <A, B> Union<A, B> unionFromB(B b) {
Union<A,B> u = new Union(null, b);
return u;
}
...
}
答案 2 :(得分:2)
如果你必须使用构造函数,那么很可能没有一个优雅的解决方案。
使用工厂方法,您可以拥有一个优雅的解决方案,保留a和b的最终结果 工厂方法将使用“丑陋”构造函数,但这是好的,因为它是实现的一部分。公共接口保留了所有需求,以便从构造函数转换为工厂方法。
这样做的目的是让Union<A,B>
与Union<B,A>
按durron597's answer互换。
这不是完全可能的,因为我稍后会举例说明,但我们可以非常接近。
public class Union<A, B> {
private final A a;
private final B b;
private Union(A a, B b) {
assert a == null ^ b == null;
this.a = a;
this.b = b;
}
public static <A, B> Union<A, B> valueOfA(A a) {
if (a == null) {
throw new IllegalArgumentException();
}
Union<A, B> res = new Union<>(a, null);
return res;
}
public static <A, B> Union<A, B> valueOfB(B b) {
if (b == null) {
throw new IllegalArgumentException();
}
Union<A, B> res = new Union<>(null, b);
return res;
}
public boolean isClass(Class<?> clazz) {
return a != null ? clazz.isInstance(a) : clazz.isInstance(b);
}
// The casts are always type safe.
@SuppressWarnings("unchecked")
public <C> C get(Class<C> clazz) {
if (a != null && clazz.isInstance(a)) {
return (C)a;
}
if (b != null && clazz.isInstance(b)) {
return (C)b;
}
throw new IllegalStateException("This Union does not contain an object of class " + clazz);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Union)) {
return false;
}
Union union = (Union) o;
Object parm = union.a != null ? union.a : union.b;
return a != null ? a.equals(parm) : b.equals(parm);
}
@Override
public int hashCode() {
int hash = 3;
hash = 71 * hash + Objects.hashCode(this.a);
hash = 71 * hash + Objects.hashCode(this.b);
return hash;
}
}
以下是如何使用和如何使用它的示例
useUnionAsParm2
显示了此解决方案的局限性。编译器无法检测用于接受任何包含String的Union的方法的错误参数。我们必须求助于运行时类型检查。
public class Test {
public static void main(String[] args) {
Union<String, Integer> alfa = Union.valueOfA("Hello");
Union<Integer, String> beta = Union.valueOfB("Hello");
Union<HashMap, String> gamma = Union.valueOfB("Hello");
Union<HashMap, Integer> delta = Union.valueOfB( 13 );
// Union<A,B> compared do Union<B,A>.
// Prints true because both unions contain equal objects
System.out.println(alfa.equals(beta));
// Prints false since "Hello" is not an Union.
System.out.println(alfa.equals("Hello"));
// Union<A,B> compared do Union<C,A>.
// Prints true because both unions contain equal objects
System.out.println(alfa.equals(gamma));
// Union<A,B> compared to Union<C,D>
// Could print true if a type of one union inherited or implement a
//type of the other union. In this case contained objects are not equal, so false.
System.out.println(alfa.equals(delta));
useUnionAsParm(alfa);
// Next two lines produce compiler error
//useUnionAsParm(beta);
//useUnionAsParm(gamma);
useUnionAsParm2(alfa);
useUnionAsParm2(beta);
useUnionAsParm2(gamma);
// Will throw IllegalStateException
// Would be nice if it was possible to declare useUnionAsParm2 in a way
//that caused the compiler to generate an error for this line.
useUnionAsParm2(delta);
}
/**
* Prints a string contained in an Union.
*
* This is an example of how not to do it.
*
* @param parm Union containing a String
*/
public static void useUnionAsParm(Union<String, Integer> parm) {
System.out.println(parm.get(String.class));
}
/**
* Prints a string contained in an Union. Correct example.
*
* @param parm Union containing a String
*/
public static void useUnionAsParm2(Union<? extends Object, ? extends Object> parm) {
System.out.println( parm.get(String.class) );
}
}
答案 3 :(得分:1)
&#34;联盟&#34;这里说错了。我们不是在讨论两种类型的并集,它们将包括任何类型的所有对象,可能具有重叠。
这个数据结构更像是一个元组,其中一个额外的索引指向一个重要的元素。一个更好的词可能是&#34;选项&#34;。事实上,java.util.Optional
是一个特例。
所以,我可能会像这样设计它
interface Opt2<T0,T1>
int ordinal(); // 0 or 1
Object value();
default boolean is0(){ return ordinal()==0; }
default T0 get0(){ if(is0()) return (T0)value(); else throw ... }
static <T0,T1> Opt2<T0,T1> of0(T0 value){ ... }
答案 4 :(得分:0)
正如durron597's answer指出的那样,从概念上讲,Union<Foo, Bar>
和Union<Bar, Foo>
应该表现相同但行为却截然不同。哪个是A
哪个是B
无关紧要,哪个类型更重要。
这是我认为最好的,
// I include this because if it's not off in its own package away from where its
// used these protected methods can still be called. Also I specifically use an
// abstract class so I can make the methods protected so no one can call them.
package com.company.utils;
public abstract class Union<A, B> {
private A a;
private B b;
protected Union(A a, B b) {
assert a == null ^ b == null: "Exactly one param must be null";
this.a = a;
this.b = b;
}
// final methods to prevent over riding in the child and calling them there
protected final boolean isA() { return a != null; }
protected final boolean isB() { return b != null; }
protected final A getA() {
if (!isA()) { throw new IllegalStateException(); }
return a;
}
protected final B getB() {
if (!isB()) { throw new IllegalStateException(); }
return b;
}
}
并实施。在使用它的地方(com.company.utils
除外),只能找到具有明确名称的方法。
package com.company.domain;
import com.company.utils.Union;
public class FooOrBar extends Union<Foo, Bar> {
public FooOrBar(Foo foo) { super(foo, null); }
public FooOrBar(Bar bar) { super(null, bar); }
public boolean isFoo() { return isA(); }
public boolean isBar() { return isB(); }
public Foo getFoo() { return getA(); }
public Bar getBar() { return getB(); }
}
另一个想法可能是Map<Class<?>, ?>
或其他东西,或者至少要保留这些值。我不知道。所有这些代码都很糟糕。它是由设计不佳的方法产生的,需要多种返回类型。