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

时间:2009-08-29 12:33:44

标签: java generics list

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

  1. 字符串
  2. 某些用户定义的类
  3. 如何创建一个类型安全的List,因为它只接受这两种类型?

    我想避免使用原始列表。

10 个答案:

答案 0 :(得分:8)

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

然而,鉴于你的情况,我看不到更好的方法。以下解决方案提供:

  • 插入没有可能共同祖先的类型的编译时类型安全性
  • 能够将当前列表替换为其他集合,而无需实施和测试任何其他代码
  • 在其他环境中使用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()
...
}

另一个选择是使用enum作为标记,而不是布尔值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”)。

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

答案 1 :(得分:7)

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

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

答案 2 :(得分:3)

我建议使用&#34;或者&#34;类型。或者可以保存一种类型或另一种类型的值,但不能同时保存两种类型。然后您的列表具有两者的类型。这是一个更明确的解决方案。

以下是用法:

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

以下是代码:

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

<强>更新

对此代码的一个很好的改进是添加&#34;匹配&#34;函数Either

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

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

答案 3 :(得分:2)

  

我想定义一个List,它只接受两种类型的对象(上面定义的)除了这两种类型之外的任何添加尝试都应该提示我“COMPILE TIME ERROR”

这就是您要求的(运行时错误):

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

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

答案 4 :(得分:1)

如果你的意思是“类型安全”,就像在编译时检查类型安全一样,那么尝试使用泛型来解决这个问题将是困难的。

主要原因是因为String类是final,因此无法从String创建子类。

如果可以继承String,则可以将String和用户定义的String子类都包含在声明为List<? extends String>的列表中:

// Not possible.
List<? extends String> list = new ArrayList<? extends String>;
list.add("A string");
list.add(new UserDefinedSubclassOfString());   // There can be no such class.

一个选项是创建一个类,该类包含与两种类型交互的方法,这两种类型实际上包含List s,参数化为需要存储的两种类型:

class MyList {

    List<String> strings;
    List<UserDefined> objects;

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

    public void add(UserDefined o) {
        objects.add(o);
    }

    // And, so on.
}

然而,这种方法的问题在于,无法使用List接口,因为它期望参数为E类型。因此,使用Object?作为参数(分别为List<Object>List<?>)将失败目的,因为无法编译时检查类型,因为Java中的所有类都以Object作为其祖先。

要考虑的一件事是如何处理从这个假设的MyList中获取对象。如果只有一个get方法,则返回类型必须是StringUserDefined类的共同祖先。这将是Object

解决这个问题的唯一方法是提供两个吸气剂,每种类型一个。例如,getStringgetUserDefined。此时,显而易见的是,无法使用List接口,这将需要在E方法中返回get类型。

正如kdgregory's answer所说,在解决方案中遇到这些问题似乎表明它可能不是解决需要解决的问题的最佳方法。

要了解泛型是什么以及哪些是可能的,不可能的,Lesson: Generics来自The Java Tutorials将是一个良好的开端。

答案 5 :(得分:0)

正如kdgregory所说,你可能想重新考虑你的方法。可能有两个列表,List<String>List<Whatever>。也许是List<SomeContainer>,包含String或用户对象。也许包含此列表的类想要成为class Blah<T>而不是class Blah,其中T可以是StringUserClass或其他。

答案 6 :(得分:0)

或者也许是一个List,它采用自定义类型MyType,将您的String和您需要的任何其他内容封装到单个抽象中。

如果您正在考虑原始数据和数据结构,那么您需要提高视野。面向对象是关于封装,抽象和信息隐藏。听起来我觉得你做得不够。

答案 7 :(得分:0)

我的问题是应该使用的“用户定义类”是什么? 没有那些很难给出好建议的知识。 用户不是创建类,程序员是。你开发了一些通用框架吗?

您的列表的商业目的是什么? 如果没有提供特定于用户的类,String是否应该用作有点默认类型? 在这种情况下,您可以设置BaseUserDefinedClass,并使用如下列表:

List<? extends BaseUserDefinedClass> = new ArrayList<DerivedFromBaseUserDefinedClass>();

答案 8 :(得分:0)

这取决于您使用的列表...

如果您打算使用它来获取字符串,您可以使用:

List<String> stringList = new ArrayList<String>();
// Add String
stringList.add("myString");
// add YourObject
YourObject obj = new YourObject(...);
stringList.add(obj.toString());
// ...
for(String s : stringList) {
    System.out.println(s);
}

如果您打算使用它来获取YourObject引用:

List<YourObject> objList = new ArrayList<YourObject>();
// Add String 
objList.add(new YourObjectAdapter("myString"));
// add YourObject
YourObject obj = new YourObject(...);
objList.add(obj)
// ...
for (YourObject y : objList) {
    System.out.println(y.toString());
    // Assuming YourObject defines the "doSomething() method"
    y.doSomething();
}
// ...
class YourObjectAdapter extends YourObject {
    private String wrappedString;
    public YourObjectAdapter(String s) {
        this.wrappedString = s;
    }
    @Override
    public void toString() {
        return wrappedString();
    }
    @Override
    public void doSomething() {
      // provide some default implementation...
    }
}

答案 9 :(得分:0)

请参阅有效Java,第29项:考虑类型安全的异构容器。您可以将该项目中的想法调整为您的特定用例。

我假设您想要List使用List<Object>,但您只想允许插入String和其他特定类型?这是你想要实现的目标吗?

如果是这样,你可以这样做:

import java.util.*;

public class HetContainer {
  Set<Class<?>> allowableClasses;

  List<Object> items;

  public HetContainer(List<Class<?>> allowableClasses) {
    this.allowableClasses = new HashSet<Class<?>>(allowableClasses);
    items = new ArrayList<Object>();
  }

  public void add(Object o) {
    if (allowableClasses.contains(o.getClass())) {
      items.add(o);
    } else {
      throw new IllegalArgumentException("Object of type " + o.getClass() + " is not allowed.");
    }
  }

  public Object get(int i) {
    return items.get(i);
  }

  public static void main(String[] args) {
    List<Class<?>> classes = new ArrayList<Class<?>>();
    classes.add(String.class);
    classes.add(Integer.class);
    HetContainer h = new HetContainer(classes);
    h.add("hello");
    h.add(new Integer(5));
    try {
      h.add(new Double(5.0));
    } catch (IllegalArgumentException e) {
      System.out.println(e);
    }
  }
}

这只是为了向您展示您可以做的事情。另外,一个警告是你不能将泛型类型放入容器中......你为什么这么问?因为无法说List<Integer>.classList<Double>.class。原因是因为“擦除”......在运行时,两者都只是List s。因此,您可以将原始List放入HetContainer,但不能放入通用List。再次阅读Effective Java,以便了解java的所有限制并根据需要调整内容。