问题 用抽象类方法解决Java泛型类型错误


我想在java中实现这样的东西,但是得到编译时错误:

方法onMessage(TextMessage,Class)中的类型   AbstractTestLoader不适用于参数   (TextMessage,Class)

我理解这个错误的原因,但我也觉得应该有一些方法来实现这一点,或者可能是其他方式。

public abstract class AbstractTestLoader<T extends AbstractEntity<T>> {

    public void onMessage(TextMessage message) throws Exception {
        onMessage(message, this.getClass()); // I want to correct this line in such a way so that I can call below method with actual Class of {T extends AbstractEntity<T>}
    }

    public void onMessage(TextMessage message, Class<T> clazz) throws Exception {
        //here my original logic will go
    }
}

12308
2018-03-24 04:13


起源

你为什么 感觉 所以?也许您想要添加对底层问题的理解,以确保每个人都在同一页面上。因为我们必须在探索解决方案空间时就根本原因达成一致。 - GhostCat
嗨@GhostCat,我已经尝试用注释行来探索我的潜在问题 I want to correct this line in such a way so that I can call below method with actual Class of {T extends AbstractEntity<T>} 此外,如果您想回答,您可以参考我发布的解决方案以获得更好的解决方案。 - Vishal Zanzrukia
将 AbstractTestLoader 是超级直接? - glee8e
@GhostCat:Vishal的直觉很好。还有像C#这样的其他语言可以实现 具体化的仿制药 所以你可以这样说 T.class,和 有一些话题 关于改变Java来做同样的事情 在某个未来版本中 - StriplingWarrior
我大部分都知道。我想确定OP知道他们。除此之外,搜索重复的问题以便关闭是对手机的麻烦。这就是为什么首先评论确保OP真正了解问题的原因。 - GhostCat


答案:


应该注意的是,虽然Java泛型在运行时被擦除,但是如果它们存在于类文件中,则有限的反射apis来检索它们。

以下是这些假设的快速解决方案:

  1. AbstractTestLoader 是超级直接的。
  2. 在声明超类时,子类不使用类型通配符,例如像这样的子类 class GenericLoader<T extends SubclassAbstractEntity<T>> extends AbstractTestLoader<T> 不存在。

这是代码:

public class AbstractTestLoader<T extends AbstractEntity<T>> {

    private static final ClassValue<Class<?>> TYPE_PARAMETER_CACHE = new ClassValue<Class<?>>() {
        @Override
        protected Class<?> computeValue(Class<?> type) {
            assert AbstractTestLoader.class.isAssignableFrom(type);
            assert type.getSuperclass() == AbstractTestLoader.class;
            Type genericSuperclass = type.getGenericSuperclass();
            assert genericSuperclass instanceof  ParameterizedType;
            Type entityType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
            assert entityType instanceof  Class<?>;
            return (Class<?>) entityType;
        }
    };

    @SuppressWarnings("unchecked")
    protected Class<T> getEntityType() {
        return (Class<T>) TYPE_PARAMETER_CACHE.get(this.getClass());
    }

    public void onMessage(Object message) throws Exception {
        onMessage(message, getEntityType()); // getting compile time error here
    }

    public void onMessage(Object message, Class<T> clazz) throws Exception {
        //here my original logic will go
    }
}

getEntityType 可以在这两个假设失败的子类中重写。


5
2018-03-24 04:43





最后,经过几次尝试,我得到了简单而有效的解决方案,但如果可能,我仍然愿意听取其他最佳答案。谢谢

public abstract class AbstractTestLoader<T extends AbstractEntity<T>> {

    abstract Class<T> getEntityType();

    public void onMessage(TextMessage message) throws Exception {
        onMessage(message, getEntityType());
    }

    public void onMessage(TextMessage message, Class<T> clazz) throws Exception {
        //here my original logic will go
    }
}

4
2018-03-24 04:33





Java通过实现泛型 类型擦除,这意味着它只是一个编译时的概念。程序运行时,a之间没有区别 AbstractTestLoader<Foo> 和a AbstractTestLoader<Bar>

有一些解决方法,比如你发现的那个,其中一个类知道它的类型 T 在编译时可以提供实际的类类型。但是没有办法使用纯泛型来发现泛型类型在运行时的含义。


2
2018-03-24 04:37



实际上,有一种方法。请参阅@ glee8e的回答 - Federico Peralta Schaffner
@FedericoPeraltaSchaffner感谢您提及我,但@ mark不会触发系统通知给我,因为尾随 's 使系统认为你正在呼唤一个名叫的人 glee8e's。只是一个提醒:) - glee8e
@FedericoPeraltaSchaffner:glee8e的答案很好,很可能在很多情况下都能很好用。我不认为这会使我的答案无效:在我看来,它有资格作为我提到的解决方法之一“哪个班级知道我的类型 T在编译时可以提供一个实际的类类型。“在Vishal的答案中,它通过抽象方法来实现;在glee8e的情况下,它通过要求以特定方式声明消费类来实现。 - StriplingWarrior
@StriplingWarrior足够公平。确实,在运行时没有办法发现类的泛型类型,但是(虽然很奇怪)可以发现超类的泛型类型(如果它是通用的)。 - Federico Peralta Schaffner