Java泛型,列表的列表

6

这个问题让我很困惑。我有一个如下的类:

public class SpecialList implements List<MyType> {
    // overriden methods
}

现在我需要在更高级别的类中遵守以下方法契约:

public class MyClass {
    private List<SpecialList> bigList = new ArrayList<SpecialList>();

    public void doStuff(List<MyType> list)
    {
        bigList.add((SpecialList)list); // does not compile - invalid cast
    }
}

我真的不确定我在这里做错了什么。我有一个实现List<MyType>接口的类,但我无法将该类强制转换为List<MyType>?这对我来说毫无意义。

我迷失了方向。我应该怎么做才能让它起作用?我想这可能与泛型协变有关,但此时我不知道哪里出了问题。有人能指点一下方向吗?谢谢。


你想在这里做什么?你想将list的内容添加到bigList中还是将list添加到bigList中? - Arun P Johny
可能你需要的是 addAll() bigList.addAll(list); - Arun P Johny
@ArunPJohny 我想要bigList是MyLists的列表,即MyType的列表。所以我认为addAll在这里不合适。 - Thomas
@ruakh 两个列表是一样的。我会按照你说的修改问题,因为我同意它不够清晰。 - Thomas
@Thomas 在这种情况下,将SpecialList更改为public class SpecialList implements List<List<MyType>> - Arun P Johny
2
它正在我的Eclipse中编译 :O - anshulkatta
7个回答

3

并不是每一个 List<MyType> (动物)都是 MyList (奶牛)

你正在将动物添加到奶牛列表中


那么,我如何在仍然拥有派生列表类型的情况下维护“doStuff”方法的契约呢?我真的需要让自己的列表使用通用类型,并在每个方法中将其强制转换为MyType吗? - Thomas

2
我建议一些参数:

我建议一些参数:

public class MyClass{
    private List<List<MyType>> bigList = new ArrayList<List<MyType>>();

    public <E extends List<MyType>> void doStuff(E list)
    {
        bigList.add(list);
    }
}

当您从bigList中检索元素时,由于它来自通用列表,因此无法将元素特化。 如果您绝对需要强制转换它,那么您的类架构可能不正确。 或者您可以滥用以下方法:
public class MyClass{
    private List<List<MyType>> bigList = new ArrayList<List<MyType>>();

    public <E extends List<MyType>> void doStuff(E list)
    {
        bigList.add(list);
    }

    public <E extends List<MyType>> E getStuff(Class<E> myType,int i)
    {
        List<MyType> obj = bigList.get(i);
        if(myType.isInstance(obj)) return (E) obj;
        throw new SomeErrorHere("invalid type for index");
    }
}

1

据我所记,将列表进行类型转换的正确方法是使用泛型。类似这样:

bigList.add((List<MyList>)(List<?>)list);

然而,我不确定这段代码背后的理论。

这是错误的建议;这绝对不是“正确的方法”。唯一需要经过多余的 List<?> 的时候是当你实际上进行不兼容的转换,例如你有一个 List<Object> 并且你想将其重新命名为 List<String>(或反之亦然)。你能够得以逃脱的唯一原因是这些转换在运行时无法检查。 - ruakh

1

为什么会出现这个错误

代码格式正确,几乎可以将任何对象转换为另一个对象,并且代码可以编译。如果转换是无效的,则会抛出运行时的ClassCastException

您的IDE可以在编译时检测出不确定的转换并投诉它们。可以作为警告或错误。这是一种配置问题。显然,OP的IDE被配置为使这样的代码味道成为一个编译错误。

为什么这个转换是不安全的

您可以回答您的问题,回答以下内容:

你能创建一个List<MyType>而不是一个SpecialList吗?

你不能将List<MyType>强制转换为SpecialList,因为可能有一些对象是List<MyType>,但实际上并不是SpecialList

解决方案

更改您的应用程序架构

您可以做两件事:要么在整个代码中使用类SpecialList,要么使用通用的List<MyType>

换句话说,要么将 doStuff(List<MyType> list) 改为 doStuff(SpecialList list),要么将 private List<SpecialList> bigList 改为 private List<List<MyType>> bigList。您必须决定是要使用通用接口列表还是您自己的类。请记住,您可以始终将 SpecialList 强制转换为 List<MyType>,因为所有 SpecialList 实例也都是 List<MyType> 的实例。反过来则不行。请确保强制转换始终有效。如果您确实需要使这个设计工作,请使用 instanceof 检查列表是否真的是 SpecialList。像这样:
public void doStuff(List<MyType> list)
{
    if (list instanceof SpecialList) {
        bigList.add((SpecialList)list);
    } else {
        SpecialList sl = new SpecialList(list); // I hope you have that constructor
        bigList.add(sl);
    }
}

那么,我如何在仍然拥有派生的列表类型的情况下保持doStuff方法的契约呢?我真的需要让自己的列表使用泛型并在每个方法中将对象参数转换为MyType吗? - Thomas
这两个选项都不可接受。第一个选项违反了doStuff方法的契约,而第二个选项则使得无法通过内部列表(SpecialList)控制对MyType元素的访问。我知道这是一个可怕的类设计,但我必须想办法让它工作。 - Thomas
@Thomas 添加了 instanceof 检查,请确保您正确处理了非 instanceof 的情况。 - Dariusz
@Thomas 我又编辑了一次答案。希望现在一切都清楚了。只需添加我发布的instanceof检查,你就可以了。 - Dariusz

1
你定义了一个 List<MyList>(即 MyList 的列表)。这意味着你只能在其中添加 MyList 的实例。如果你使用 addAll(),你可以添加 MyList 的列表。但是你正在尝试添加 List<MyType>MyType 明显不是 MyList

而且你显然无法将 List 强制转换为 MyList


2
回复:“你显然不能将List强制转换为MyList”:我不会说“显然”,因为这正是OP所问的。(对我来说也不显然) - ruakh

0

这在我的Eclipse中有效,这是SpecialList类,Hello是MyType类型的类

package a;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class SpecialList implements List<Hello> {

    //Overridden methods
    }

这是我的类(Myclass)

package a;

import java.util.ArrayList;
import java.util.List;

public class MyClass {
    private List<SpecialList> bigList = new ArrayList<SpecialList>();

    public void doStuff(List<Hello> list)
    {
        bigList.add((SpecialList)list);  //compiles good
    }
    public static void main(String[] args) {
        new MyClass().doStuff(null);
    }
}

没有编译时错误或运行时异常


你的Eclipse设置与OP不同。如果列表不是“SpecialList”,则会出现运行时“ClassCastException”。 - Dariusz
当然,如果它不是SpecialList,我会得到异常,但这里没有给出任何编译器错误,OP说是编译时错误!! - anshulkatta
进入您的Eclipse设置,在警告/错误部分,您可以选择如果Eclipse检测到这些情况应该采取什么样的反应;它可以被视为编译时错误,也可以被忽略。由程序员来设置这些。我喜欢它们尽可能地防御性,而您显然不喜欢。这是您的选择。 - Dariusz
你的观点没有涉及到这些,OP 是否声明了任何 Eclipse 或编译器设置更改? - anshulkatta
@Dariusz:你能详细说明一下这个选项吗?我的Eclipse版本好像没有它;当我打开我的首选项并通过“cast”过滤错误/警告部分时,唯一出现的选项是“不必要的转换或'instanceof'操作”的设置。 - ruakh

0
List <MyList> is not same as List <MyType>.

让我们简单点,看看接下来的几行:

 *List<MyList> myList = new ArrayList<MyList>(); //1
  myList.add(new MyType);//2 ......Compile ERROR*

如果您尝试将MyType实例添加到List<MyList>中,它会报错。

为什么:

  • 泛型意味着参数化类型。
  • 泛型增加了类型安全性。
  • 也就是说,使用泛型时,所有的强制转换都是自动和隐式的,
    在向列表中添加和检索对象时不需要显式地进行类型转换。

实时场景:

如果机动车管理部门提供了一个驾驶员列表。 我们认为List<Driver>List<Person>,假设Driver是Person的子类型。

如果是这种情况,我们可以将新的非驾驶员添加到列表中。

也就是说,并非所有的人都是司机。

解决上述问题的方法:

您可以在泛型中使用通配符。 请查看此链接 http://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html


这不是这种情况。他正在将一个列表添加到一个列表的列表中。 - Dariusz

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