我应该始终使用通配符作为函数参数的泛型类型,还是只传递一个泛型类型的实例



考虑以下无效Java代码:

class Example {
    private static class Base {}
    private static class Child extends Base{
        public void do(){}
    }
    public void foo(List<Base>) {}
    public static void main(String[] args) {
        List<Child> list = new ArrayList<>();
        fillListInPlaceWithChildren(list);
        foo(list); //compile error, List<Child> does not extend List<Base>
        list.stream().forEach(Child::do);
    }
}

这不会编译,因为List<Child>不是List<Base>的子类,因此不能传递给foo函数。为了解决这类问题,据我所知,我有两个选择:

  1. 让foo接受List<? extends Base>:

    class Example {
        private static class Base {}
        private static class Child extends Base{
            public void do(){}
        }
        public void foo(List<? extends Base>) {}
        public static void main(String[] args) {
            List<Child> list = new ArrayList<>();
            fillListInPlaceWithChildren(list);
            foo(list); 
            list.stream().forEach(Child::do);
        }
    }
    
  2. listList<Base>,并在需要时将元素强制转换为Child的实例

    class Example {
        private static class Base {}
        private static class Child extends Base{
            public void do(){}
        }
        public void foo(List<Base>) {}
        public static void main(String[] args) {
            List<Base> list = new ArrayList<>();
            fillListInPlaceWithChildren(list);
            foo(list); 
            list.stream().forEach((item) -> ((Child)item).do());
        }
    }
    

哪种选择被认为是最佳实践,为什么?或者它们都有不同的用例?

不能将List<Child>传递给需要List<Base>的方法,因为这可能会导致一些问题。如果你通过List<Child>,在这种情况下会发生什么?

void foo(List<Base> list) {
    list.add(new Base());
}

如果您不依赖仅包含Base实例的列表。使用List<? extends Base>比在代码中添加类型转换更安全,而且它不会给阅读代码的人带来太多惊喜。

我总是建议使用更高级别的抽象。如果您可以使用List<Base> list而不是List<Child>列表,请使用它!。

但在这种情况下,是foo制定了合同。foo应该接受List<? extend Base>中包含的所有列表,还是只接受List<Child>才有意义?

问题就是答案。

最新更新