如何创建一个可以接受两种不同的不相关类型的通用List?

Ash*_*wal 5 java generics list

我必须定义一个List,它有两种可能的值

  1. 一些用户定义的类

如何创建一个类型安全的List,因为它只接受这两种类型?

我想避免使用原始List.

CPe*_*ins 8

我不会声称这是一个完美的解决方案,但我会建议你选择一个"持有人"级别 - 也被一些作家称为"标记类"(包括Joshua Bloch,他说标记了类是"冗长,容易出错,效率低下").

但是,鉴于你的情况,我看不到更好的方法.以下解决方案提供:

  • 插入类型时编译时类型安全,没有可能的共同祖先
  • 能够将当前List替换为其他集合,而无需实现和测试任何其他代码
  • 在其他上下文中使用holder类的能力

你可以像这样定义你的持有者类:

class Holder {
  private String foo;
  private UserClass bar;
  boolean isString;
  boolean initialized=false;
  Holder (String str) { foo = str; isString=true; }
  Holder (UserClass bar) { this.bar = bar; isString=false; }
  String getStringVal () { 
      if (! initialized) throw new IllegalStateException ("not initialized yet");
      if (! isString) throw new IllegalStateException ("contents not string");
      return foo;
  }
  // with a similar method for getUserClassVal()
...
}

另一种方法是使用enumfor作为标记,而不是boolean isString - 这具有易于扩展到其他类型的值.

那么当然你有你的化合物清单:

   List samsList = new ArrayList()

插入很简单,并且根据您的要求,编译时类型安全:

   samsList.add (new Holder(stringVal));
   samsList.add (new Holder(userClassVal));

从列表中检索值只是稍微复杂一些:在决定使用哪个getter之前,必须检查标记(holder.isString()).例如,列表上的foreach迭代看起来像这样:

   for (Holder holder: samsList) {
      if (holder.isString())
         doYourStringProcessing (holder.getStringVal());
      else
         doYourUserClassProcessing (holder.getUserClassVal());
   }

就像我说的那样,我并不认为这是完美的,但它符合您的要求将满足您的需求并最大限度地减轻呼叫者的负担.

但是,我想指出,这对我来说感觉好像可能会考虑在某处重构/重新设计.我遵循的指导原则之一是,每当我发现自己为合理的练习辩护时,它应该比简单地"我怎么能这样做?"更值得思考.

原因如下:假设我认为异常在这种情况下是正确的,那么实际上只有两种可能性.一个是声音练习是不完整的(所以"首选X超过Y"应该改写为"除了Z以外的首选X").

但更有可能的是,潜在的条款是一个不完美的设计,我们应该努力做一些重新设计/重构.


kdg*_*ory 7

由于StringObject最终的子类,并且是最终的,因此您不会在String和除用户定义的类之间找到常见的超类型Object.所以List<Object>你必须使用它.

从设计的角度来看,在一个集合中混合不相关的类是一个坏主意.想想你想要完成的事情,你可能会想出一个更好的方法.

  • 不,你想要完成的事情是不可能的,也是有充分理由的.想象一下,你能够这样做,你会从你的清单中找到什么样的参考?除此之外,你的类和String有什么样的通用接口?为什么不尝试创建另一个存储字符串或类的类,并且只显示它们共有的行为并且与列表相关并且列表只存储该类的实例? (3认同)

sdg*_*sdh 5

我建议使用类似“任一”类型的东西。Either 可以保存一种类型或另一种类型的值,但不能同时保存两者。然后,您的列表将具有其中之一的类型。这是一个更明确的解决方案。

这是用法:

final List<Either<Integer, String>> list = new ArrayList<>();

list.add(Either.left(1234));
list.add(Either.right("hello"));

for (final Either<Integer, String> i : list) {
    if (i.isLeft()) {
        System.out.println(i.left());
    } else {
        System.out.println(i.right());
    }
}
Run Code Online (Sandbox Code Playgroud)

这是代码:

package com.stackoverflow.q1351335;

public interface Either<L, R> {

    boolean isLeft();

    L left();

    R right();

    static <L, R> Either<L, R> left(final L value) {
        return new EitherLeft<>(value);
    }

    static <L, R> Either<L, R> right(final R value) {
        return new EitherRight<>(value);
    }
}
Run Code Online (Sandbox Code Playgroud)
package com.stackoverflow.q1351335;

import java.util.Objects;

public final class EitherLeft<L, R> implements Either<L, R> {
    private final L value;
    public boolean isLeft() {
        return true;
    }
    public L left() {
        return value;
    }
    public R right() {
        throw new IllegalStateException("Cannot get right value from an EitherLeft");
    }
    public EitherLeft(final L value) {
        super();
        this.value = value;
    }
    @Override
    public boolean equals(final Object obj) {
        if (obj instanceof EitherLeft) {
            Objects.equals(this.value, ((EitherLeft) obj).value);
        }
        return false;
    }
    @Override
    public int hashCode() {
        return Objects.hashCode(value);
    }
    @Override
    public String toString() {
        return "EitherLeft[" + Objects.toString(value) + "]";
    }
}
Run Code Online (Sandbox Code Playgroud)
package com.stackoverflow.q1351335;

import java.util.Objects;

public final class EitherRight<L, R> implements Either<L, R> {
    private final R value;
    public boolean isLeft() {
        return false;
    }
    public L left() {
        throw new IllegalStateException("Cannot get left value from an EitherRight");
    }
    public R right() { return value; }
    public EitherRight(final R value) {
        super();
        this.value = value;
    }
    @Override
    public boolean equals(final Object obj) {
        if (obj instanceof EitherRight) {
            Objects.equals(this.value, ((EitherRight) obj).value);
        }
        return false;
    }
    @Override
    public int hashCode() {
        return Objects.hashCode(value);
    }
    @Override
    public String toString() {
        return "EitherRight[" + Objects.toString(value) + "]";
    }
}
Run Code Online (Sandbox Code Playgroud)

更新

对此代码的一个很好的改进是添加一个“match”函数Either

// Illustration of usage: 
myEither.match(
    leftValue -> {
        // Do stuff with left
    }, 
    rightValue -> {
        // Do stuff with right
    });
Run Code Online (Sandbox Code Playgroud)

这种方法的优点是不可能意外访问 an 的左侧EitherRight或 an 的右侧EitherLeft。这是对更多函数式(如 FP)语言所使用方法的模仿。


Ste*_*n C 3

我想定义一个列表,它只能接受两种类型的对象(上面定义的),除了这两种类型之外的任何添加尝试都会提示我“编译时间错误”

这符合您的要求(有运行时错误):

public class MyList extends ArrayList<Object> {

    public MyList() {
        super();
    }

    public MyList(int initialSize) {
        super(initialSize);
    }

    @Override
    public void add(Object obj) {
        if ((obj instanceof String) || (obj instanceof SomeType)) {
            add(obj);
        } else {
            throw new IllegalArgumentException("not a String or SomeType");
        }
    }

    public void add(String s) {
        super.add(s);
    }

    public void add(SomeType s) {
        super.add(s);
    }
}
Run Code Online (Sandbox Code Playgroud)

在 Java 中没有办法实现这一点,这会给您带来编译时错误,即您将错误类型(在您的意义上)的元素添加到List. 但是,如果这不是List,您可以将类定义为具有重载add方法等。创建新的“adder”方法不会对您有帮助,因为现有add(T)方法仍然存在于界面中。无论你做什么(在Java中),调用它都不会出现编译时错误。