覆盖方法需要从 List<ChildClass> 转换为 List<ParentClass>

Overriding method needs to convert from List<ChildClass> to List<ParentClass>

我正在模拟一些测试代码。在一个模拟中,我试图模拟 ServletFileUpload::parseRequest,其中 return 是一个 FileItems 列表。

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

    private class MockServletFileUpload extends ServletFileUpload {

        List<MockDiskFileItem> fileItems;

        @Override
        public List<FileItem> parseRequest(HttpServletRequest request) {
            return fileItems;
        }
    }

但 Eclipse 在方法的 return 上发出错误信号:Type mismatch: cannot convert from List<MockDiskFileItem> to List<FileItem>。但是MockDiskFileItem implements FileItem!为什么这不起作用?

另一方面,此版本的方法将 List 的各个元素转换为父级 class,但不会出现错误:

    @Override
    public List<FileItem> parseRequest(HttpServletRequest request) {
        return fileItems.stream()
                .map( dfi -> (FileItem) dfi )
                .collect(Collectors.toList());
    }

为什么元素必须单独铸造?有没有更短的写法?

更新

澄清一下,我的问题主要是一个实际问题:如何最有效地从 List<MockDiskFileItem> convert/cast 到 List<FileItem>。我还想知道为什么 Java 不能自动执行整个列表的 conversion/cast ,而花时间阅读该方法第二个版本的任何人都应该清楚,Java 自动 convert/cast 任何 MockDiskFileItemFileItem.

更新 2

实际上我对自己的问题有一个答案,它说明了为什么这个问题是不同的而不是重复的。您可以从下面的一个答案中看出这是一个不同的问题。为什么关闭?你们版主的手太重了。这让你获得了多少声望点?这个网站的完整性付出了什么代价?

这样写,不会出错:

public List<? extends FileItem> parseRequest(final HttpServletRequest request) {
    return fileItems;
}

正如上面的评论所说,你遇到了逆变的问题,泛型不是协变的。

更新

哦,是的,我完全忘记了他正在覆盖一个方法。对不起。 这是 IMO 最简单、最有效的转换,行数最少:

class MockServletFileUpload extends ServletFileUpload {

    List<MockDiskFileItem> fileItems;

    @Override public List<FileItem> parseRequest(final HttpServletRequest request) {
        return new ArrayList<>(fileItems);
    }



    public static void main(final String[] args) {
        final MockServletFileUpload msfu = new MockServletFileUpload();
        msfu.fileItems = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            msfu.fileItems.add(new MockDiskFileItemImpl());
        }


        final List<FileItem> result = msfu.parseRequest(null);
        System.out.println("Results:");
        System.out.println(result.getClass());
        for (final FileItem fileItem : result) {
            System.out.println("\t" + fileItem.getClass());
        }
        System.out.println("Done.");
    }
}

直到相当多的项目(我猜 20k+)这也将比流或并行流快得多。

我添加了一些简单的测试代码来证明这些项目仍然是 MockDiskFileItems(或我的实现)。

说明

This SO question有助于看清问题,这里稍微总结一下。将 List<ChildClass> foo 之类的泛型转换为 List<ParentClass> bar

List<ChildClass> foo = new ArrayList<>();
List<ParentClass> bar = (List<ParentClass>) foo; // Doesn't work

只会为内存中的同一个对象(同一个指针)创建一个别名,但将其视为由不同类型的元素组成的列表。危险在于 sibling/other 子类型的对象随后可以添加到该列表,其元素实际上是第一个子类型。给出的经典例子:

List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs; // doesn't work because ...
animals.add(new Cat()); // ... BIG problem

希望您能看到最后一行中的问题!

为了避免将 Cat 添加到 Dog 列表中的危险,您真正需要的是一个新列表,可以这么说,转换为父元素类型。所以你会看到上面的解决方案对原始列表进行了 copy 转换(隐式)到父类型。