Java中的简单通用列表

7
为什么Java泛型如此棘手?我以为我终于理解了,但是在下面的somOtherMethod中使用以下任一getOuterList方法时,Eclipse会在那一行给出一个错误提示。
protected List<?> getOuterList() {
  // blah blah
}

protected List<? extends Object> getOuterList() {
  // blah blah
}

protected void someOtherMethod() {
  ...
  getOuterList().add((MyObject)myObject);  //compile error
  ...
}
更新:好的,我现在理解这个错误了。这是因为我没有理解List<?>List<? extends SomeObject>的真正含义。在前一种情况下,我认为它意味着可以包含任何内容的列表。在后一种情况下,我假设它是一系列扩展SomeObject的对象列表。正确表示我的理解应该只是List<Object>List<SomeObject>(不带extends)。我原以为extends可以帮助我解决问题,但实际上并没有。所以这就是我的真正问题所在:
public interface DogKennel {
  public List<Dog> getDogs();
}

public class GreyHoundKennel implements DogKennel {

  protected List<GreyHound> greyHounds;

  public List<GreyHound> getGreyHounds() {
    return this.greyHounds;
  }

  public List<Dog> getDogs() {
    // Is there no way to handle this with generics
    // w/out creating a new List?
    return getGreyHounds(); //compiler error
  }

}
5个回答

3
你所说的方法返回一个“某种未知类型的List”(你不能添加元素,因为你不能保证添加的元素是该类型的子类型)。实际上,你想要的是一个“任何你想要的类型的List”,所以你需要将该方法变成泛型方法:
protected <T> List<T> getOuterList() {
  // blah blah
}

好的,我刚刚看了你的更新:

这完全取决于您想要使用 getDogs() 的结果来做什么。如果您不打算向列表中添加任何项目,则 getDogs() 应该返回类型 List<? extends Dog>,那么问题就解决了。

如果您希望能够向其中添加内容,并且通过类型 List<Dog> 意味着您可以向其中添加任何类型的 Dog,那么逻辑上这个列表不能是 greyHounds,因为 greyHounds 的类型是 List<GreyHound>,所以 Dog 对象不应该放在其中。

这意味着您必须创建一个新的列表。当然要记住,对新列表的任何更改都不会反映在原始列表 greyHouds 中。


除非它返回一个空列表(或一个由“null”组成的列表),否则这并不是很有用。 - Tom Hawtin - tackline
谢谢,泛型正是我在寻找的。 - Joshua Pinter

3
这个声明:
List<?> getOuterList() { }

告诉编译器“我不知道会得到什么样的列表”。然后你基本上执行。
list<dunno-what-this-is>.add((MyObject)myObject)

它无法将MyObject添加到不知道其类型的某些内容的列表中。

这个声明:

protected List<? extends Object> getOuterList() { ... }

告诉编译器,“这是一系列Object的子类型”。因此,当然不能将“MyObject”强制转换为对象列表。因为编译器只知道该列表可以包含对象。但是,您可以执行以下操作:
List<? super MyObject>.getOuterList() { ... }

然后成功添加了一个MyObject。这是因为现在编译器知道List是MyObject的列表,或者任何MyObject的父类型,所以它肯定可以接受MyObject。

编辑:至于你的DogKennel示例,我认为这段代码片段可以实现你想要的功能:

protected List<GreyHound> greyHounds;

// We only want a List of GreyHounds here:
public List<GreyHound> getGreyHounds() {
    return this.greyHounds;
}

// The list returned can be a List of any type of Dog:
public List<? extends Dog> getDogs() {
    return getGreyHounds();
}

1

你被Java泛型不支持类型参数多态性的事实绊倒了。

让我们一起分析你的代码片段:

protected List<GreyHound> greyHounds; // List<GreyHound> is fine

/** This method returns a lovely List of GreyHounds */
public List<GreyHound> getGreyHounds() { 
  return this.greyHounds;
}

/** Here is the problem.  A List<GreyHound> is not a List<Dog> */
public List<Dog> getDogs() {
  return getGreyHounds(); //compiler error
}

所以您原来的评论是正确的。这两个列表之间确实没有继承关系。因此,我建议您研究以下两个选项:

  1. 尝试按照您在评论中建议的方式返回一个新列表。例如:return new ArrayList<Dog>(this.greyHounds);

  2. 您真的需要保留特定品种的狗的列表吗?也许您应该将数据成员定义为 List<Dog>,然后向其中添加特定的 GreyHounds。即,protected List<Dog> greyHoundsOnly; ,通过对象的外部接口来管理允许进入犬舍的狗的类型。

除非您有充分的理由保留特定类型的列表,否则我会认真考虑选项2。

编辑:补充上面建议的选项:

选项1:返回一个新列表。优点:简单直接,可以获得带类型的列表,并且消除了线程安全问题(不向外界公开内部引用)。缺点:似乎会降低性能。

// Original code starts here.
public interface DogKennel {
  public List<Dog> getDogs();
}

public class GreyHoundKennel implements DogKennel {

  protected List<GreyHound> greyHounds;

  public List<GreyHound> getGreyHounds() {
    return this.greyHounds;
  }
// Original code ends here

  public List<Dog> getDogs() {
    // This line eliminates the thread safety issue in returning 
    // an internal reference.  It does use additional memory + cost
    // CPU time required to copy the elements.  Unless this list is
    // very large, it will be hard to notice this cost.
    return new ArrayList<Dog>(this.greyHounds);
  }

}

选项2:使用不同的数据表示。优点:与多态性更加协调,返回原始目标的通用列表。缺点:这是一种略微不同的架构,可能不适合原始任务。
public abstract class DogKennel {
  protected List<Dog> dogs = new ArrayList<Dog>();
}

public class GreyHoundKennel extends DogKennel {

  // Force an interface that only allows what I want to allow
  public void addDog(GreyHound greyHound) { dogs.add(greyHound); }

  public List<Dog> getDogs() {
    // Greatly reduces risk of side-effecting and thread safety issues
    // Plus, you get the generic list that you were hoping for
    return Collections.unmodifiableList(this.dogs);
  }

}

选项#1:这会影响性能。最好不要使用泛型。选项#2:“我真的需要保留特定品种的狗列表吗?” 不需要,但我认为这就是类型化列表的好处?如果我不能这样做,那么泛型有什么意义呢?我的结论:泛型是浪费时间。 - andersonbd1
@andersonbd1,你真的认为选项1会影响性能吗?你一次追踪多少只狗?除非超过一千只,否则你很难测量成本,特别是当你将其与返回的漂亮类型列表的编码便利性进行比较时。如果你真的想要一个类型化的列表,那么它会给你(这通常是我用于相当多的数据的选项)。如果你认为泛型是浪费时间,那你就大错特错了。 - Bob Cross
@BobCross - 选项#1-当然,对于90%的列表来说,这不会影响性能,但我们在谈论通用编程语言。作为应用程序开发人员,我必须像这样解决语言问题吗?是的,我认为泛型是浪费时间的(除非你实际上正在编写模板类型类),但这会打开整个静态动态的辩论 :-) 至于我,我会回到使用未经过类型标记的集合和“@SuppressWarnings("unchecked")”。 - andersonbd1
@andersonbd1,嗯,我没有所有的上下文,所以你必须做出最适合你工作的最佳决策。我认为我们现在互相干扰的原因是因为我们在抽象地交谈。在我的绩效评估之后,我会尝试向答案中添加一些更具体的代码。 - Bob Cross
@BobCross - 另一个让选项#1(创建新列表)变得糟糕的事情是,它会破坏使用该列表代理的应用程序(例如ORM应用程序)。ORM需要知道何时向列表添加和删除内容,因此您不能随意创建新列表。 - andersonbd1
@andersonbd1,就像我说的一样,我没有所有的上下文。我的工作更多地是声明性的。当我想要代理类似的行为时,我通常使用CopyOnWriteArrayList或类似的东西。这对我来说很有效,但(根据您的评论)可能不适用于您。 - Bob Cross

0

已经有一个被接受的答案了,不过请考虑以下代码修改。

public interface DogKernel {
    public List<? extends Dog> getDogs();
}

public class GreyHoundKennel implements DogKernel {
    protected List<GreyHound> greyHounds;

    public List<GreyHound> getGreyHounds() {
        return this.greyHounds;
    }

    public List<? extends Dog> getDogs() {
        return getGreyHounds(); // no compilation error
    }

    public static void main(String[] args) {
    GreyHoundKennel inst = new GreyHoundKennel();
    List<? extends Dog> dogs = inst.getDogs();
    }
}

Java的泛型确实有些问题,但并不是那么糟糕。 顺便说一句,Scala通过提供变异处理的方式非常优雅地解决了这个问题。
更新---------- 请考虑以下更新后的代码片段。
public interface DogKennel<T extends Dog> {
    public List<T> getDogs();
}

public class GreyHoundKennel implements DogKennel<GreyHound> {
    private List<GreyHound> greyHounds;

    public List<GreyHound> getDogs() {
        return greyHounds; // no compilation error
    }

    public static void main(String[] args) {
        GreyHoundKennel inst = new GreyHoundKennel();
        inst.getDogs().add(new GreyHound()); // no compilation error
    }
}

但是您不能向从getDogs返回的列表中添加内容 - 这就是引发此讨论的原因。 - andersonbd1
抱歉,我更多地关注了您帖子的第二部分。请在“更新”部分考虑另一种替代方法。另外,请注意,通常最好不要直接改变集合属性。相反,建议提供单独的变异器方法,如addDog、removeDog,并使getter返回不可修改的集合,就像被接受的答案中所提出的那样。 - 01es

0

? 的一般类型意味着“某些特定类型,但我不知道是哪种”。任何使用 ? 的东西基本上都是只读的,因为你不知道实际类型,所以不能写入。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接