类继承:泛型扩展泛型

10
假设我有一个简单的类:
public class MyObject {
}

处理MyObject子类的处理程序接口

public interface MyObjectHandler<V extends MyObject>{
     List<V> handle(List<V> objects);
}

假设我有BigObjects和SmallObjects(它们都扩展了MyObject),我想为它们创建单独的处理程序。因此,我使用特定泛型创建了两个MyObjectHandler接口。
class BigObject extends MyObject {}
class SmallObject extends MyObject {}

// Handlers interfaces
interface BigObjectHandler extends MyObjectHandler<BigObject>{}
interface SmallObjectHandler extends MyObjectHandler<SmallObject>{}

// Concrete handlers
class BigHandler1 implements BigObjectHandler {....}
class BigHandler2 implements BigObjectHandler {....}

class SmallHandler1 implements SmallObjectHandler {....}
class SmallHandler2 implements SmallObjectHandler {....}

现在让我们想象一下,我们已经创建了 AbstractHandlerChain<...> 抽象类。那么,我们可以创建 BigHandlerChain 类并注入我们的 BigHandlers(SmallHandlerChain 同理)。
class BigHandlerChain extends AbstractHandlerChain<BigObjectHandler> {
    // Inject only childs of BigObjectHandler. E.g. via spring @Autowired
    public BigHandlerChain(List<BigObjectHandler> handlers) {
        this.handlers = handlers;
    }
} 

问题:是否可能为这种情况创建完美的AbstractHandlerChain?

可能的解决方案1

public abstract class HandlerChain<T extends MyObjectHandler> {

    private List<T> handlers;

    public HandlerChain(List<T> handlers) {
        this.handlers = handlers;
    }

    public <V extends MyObject> List<V> doChain(List<V> objects) {
        for (T handler : handlers) {
            objects = handler.handle(objects);
        }
        return objects;
    }
}

这个方案是可行的,但在 handler.handle(objects) 中,我得到了一个关于原始类型 'MyObjectHandler' 的 unchecked call to 'handle(List<V>)' 的警告,因此我需要添加 @SuppressWarnings("unchecked"),但这并不是很好的解决方法。

可能的解决方案2

public abstract class HandlerChain<T extends MyObjectHandler<? extends MyObject>> {

    ...

    public <V extends MyObject> List<V> doChain(List<V> objects) {
        for (T handler : handlers) {
            objects = handler.handle(objects);
        }
        return objects;
    }
}

不起作用。在handler.handle(objects)中,我得到了错误信息:handle (java.util.List<capture<? extends MyObject>>) cannot be applied to (java.util.List<V>)。为什么我不能在这种情况下将objects传递给handlers?通配符extends MyObject和V extends MyObject。难道这不足够吗?

可能的解决方案3

public abstract class HandlerChain<T extends MyObjectHandler<V>, V extends MyObject> {

    ...

    public List<V> doChain(List<V> objects) {
        for (T handler : handlers) {
            objects = handler.handle(objects);
        }
        return objects;
    }
}

这个可行,但在这种情况下我应该将BigHandlerChain定义为class BigHandlerChain extends AbstractHandlerChain<BigObjectHandler, BigObject>。但是BigObjectHandler已经包含了它可以处理的类的信息,因此这是信息重复。
public abstract class HandlerChain<T extends MyObjectHandler<V extends MyObject>> {

    ...

    public List<V> doChain(List<V> objects) {
        for (T handler : handlers) {
            objects = handler.handle(objects);
        }
        return objects;
    }
}

这里有一个解决方案,我期望用Java实现,但它不起作用!我无法像这样声明类:...class HandlerChain<T extends MyObjectHandler<V extends MyObject>。为什么我可以在MyObjectHandler之后使用通配符,但不能使用这种构造方法?

这个问题看起来与/相同:https://dev59.com/kW855IYBdhLWcg3wPB36 - Chris K
你所做的研究值得肯定。看起来你问题的关键是你期望 <T extends MyObjectHandler<V extends MyObject>> 中的 V 声明 V,但实际上 Java 编译器希望它已经被声明了。很遗憾,你并不是唯一一个希望如此的人。但事实就是这样。你的研究是正确的,你没有错过任何东西。 - Chris K
由于其他两个回答比我快几分钟,这里有一个演示,展示了两个答案使用的方法。 - Sergey Kalinichenko
3个回答

4
解决方案1:
这个方案可行,但在handler.handle(objects)中会出现“Unchecked call to 'handle(List)' as a member of raw type 'MyObjectHandler'”的警告,因为'MyObjectHandler'是一个泛型类型,但你没有在'HandlerChain'的类型参数中指定它的类型。所以你需要加上@SuppressWarnings("unchecked")来消除警告。
解决方案2:
这个方案不可行。在handler.handle(objects)中会报错:handle (java.util.List<) cannot be applied to (java.util.List)。原因是'? extends MyObject'表示的是一些扩展了'MyObject'的未指定类型,但你没有说明具体是哪种类型。如果你把SmallObject实例作为列表传递给'doChain'方法,那你就需要创建一个类public class BigHandlerChain<BigObjectHandler>。
解决方案3:
这个方案可行。但是,在这种情况下,你必须将BigHandlerChain定义为class BigHandlerChain extends AbstractHandlerChain。尽管这样会产生一些重复信息,但当组合泛型类型时,这种情况很常见。由于'doChain'方法操作某种类型,所以必须指定该类型的处理方式。换句话说,你要处理BigObject列表,并且所提供的处理程序必须能够处理BigObjects。
解决方案4:
这个方案不可行。问题在于'V'表示的是一种特定类型,而不是通配符,因此你需要指定'V'具体是什么类型。
换句话说,尽管方案3中可能存在一些重复信息,但它才是正确的方法。不幸的是,在Java中你还会看到更多这样的情况。当字段上的特定修改关键字能够达到与Getter/Setter相同的效果时(如在Ruby中),Getter/Setter被认为是不必要的样板文件。
但是请注意,如果你指定public abstract class HandlerChain<T extends MyObjectHandler<V>, V extends MyObject>,那么你只能使用一种特定类型的'MyObjectHandler'类型。因为你可能需要一个由不同的处理程序组成的链,这些处理程序都能够处理相同的对象类型,所以最好只指定对象类型:
public abstract class HandlerChain<V extends MyObject> {

    private List<MyObjectHandler<V>> handlers;

    public HandlerChain(List<MyObjectHandler<V>> handlers) {
        this.handlers = handlers;
    }

    public List<V> doChain(List<V> objects) {
        for (MyObjectHandler<V> handler : handlers) {
            objects = handler.handle(objects);
        }
        return objects;
    }

}

3
您不需要将处理程序设置为通用,仅需要设置对象即可:
public abstract class HandlerChain<V extends MyObject> {

    private List<MyObjectHandler<V>> handlers;

    public HandlerChain(List<MyObjectHandler<V>> handlers) {
        this.handlers = handlers;
    }

    public List<V> doChain(List<V> objects) {
        for (MyObjectHandler<V> handler : handlers) {
            objects = handler.handle(objects);
        }
        return objects;
    }

}

这允许:

    new HandlerChain<BigObject>(Arrays.asList(new BigHandler1(), new BigHandler2())) {
        // ...
    };

编辑:如果您比较G_H的解决方案和我的,唯一的区别是G_H使用了List<? extends MyObjectHandler<V>>。这使您可以传递一个元素类型声明更具体的列表,而不仅仅是MyObjectHandler<V>。我认为您不太可能需要这个,但您也可以利用这种额外的灵活性。


1
我已经编辑过了。你更简洁的答案正好在我完成我的答案时出现了,我意识到通配符实际上是不必要的,并且在调用doChain方法时只会妨碍它。当涉及到泛型时,我总是发现我必须在我的IDE中尝试一些东西才能得到完整的图片。 - G_H

0
修复方案4很简单:Java编译器希望通配符一起声明,然后再被使用:
public abstract class HandlerChain<V extends MyObject, T extends MyObjectHandler<V>>
{
    private List<T> handlers;

    public List<V> doChain(List<V> objects)
    {
        for (T handler : handlers)
        {
            objects=handler.handle(objects);
        }
        return objects;
    }
}

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