Java泛型歧义,问题是什么?

7bsow1i6  于 2023-04-10  发布在  Java
关注(0)|答案(2)|浏览(108)

问题如下:
有一个ErrorMessageBuilder,实现这个接口的类的示例可以根据传递给它们的异常生成消息,此外,还有一个选项来检查类是否可以处理传递的异常。

interface ErrorMessageBuilder<T extends Exception> {
    String buildMessage(T exception);
    boolean canHandle(Class<? extends Exception> clazz);
}

接下来,有一个类ExceptionHandlerPair,用于创建对(Exception+Handler)

class ExceptionHandlerPair<T extends Exception>{
    private final Class<T> clazz;
    private final ErrorMessageBuilder<? extends Exception> builder;

    public ExceptionHandlerPair(Class<T> clazz, ErrorMessageBuilder<? extends Exception> builder) {
        if(!builder.canHandle(clazz)){
            throw new IllegalArgumentException("Handler can't handle this exception");
        }

        this.clazz = clazz;
        this.builder = builder;
    }

    public boolean canHandle(Class<? extends Exception> clazz) {
        return this.clazz.isAssignableFrom(clazz);
    }

    public ErrorMessageBuilder<? extends Exception> getBuilder() {
        return builder;
    }
}

之后,我尝试创建一个Bilder并创建对。例如,我创建了一个RuntimeExceptionErrorMessageBuilder,它可以处理任何RuntimeException:

class RuntimeExceptionErrorMessageBuilder implements ErrorMessageBuilder<RuntimeException> {
    public String buildMessage(RuntimeException exception) {
        return "Some runtime exception";
    }

    @Override
    public boolean canHandle(Class<? extends Exception> clazz) {
        return RuntimeException.class.isAssignableFrom(clazz);
    }
}
public class SomeClass {
    public static void main(String[] args) {
        var builder = new RuntimeExceptionErrorMessageBuilder();

//        builder.canHandle(NullPointerException.class); // ? -> true
//        builder.canHandle(IllegalArgumentException.class); // ? -> true
//        builder.canHandle(Exception.class); // ? -> false

        var pair = new ExceptionHandlerPair<>(NullPointerException.class, builder);

        if(pair.canHandle(NullPointerException.class)){ // ? -> true
            var message = pair.getBuilder().buildMessage(new NullPointerException()); // <- error Required type: capture of ? extends Exception,  Provided: IllegalArgumentException
            System.out.println(message);
        }

        if(pair.canHandle(IllegalArgumentException.class)){ // ? -> true
            var message = pair.getBuilder().buildMessage(new IllegalArgumentException()); // <- error Required type: capture of ? extends Exception,  Provided: IllegalArgumentException
            System.out.println(message);
        }
    }
}

我们得到一个错误,buildMessage(x)参数给出了一个错误,x参数应该是Exception的后代,但IllegalArgumentException是后代,这是什么问题?
好吧,我想如果这个限制适用于参数,那么让我们添加一个ExceptionWrapper,如下所示:

class ErrorWrapper<T extends Exception>{
    private final T exception;

    public ErrorWrapper(T exception) {
        this.exception = exception;
    }

    public T getException() {
        return exception;
    }
}

之后,使用ErrorWrapper修改ErrorMessageBuilder和RuntimeExceptionErrorMessageBuilder:

interface ErrorMessageBuilder<T extends Exception> {
    String buildMessage(ErrorWrapper<T> exception);
    boolean canHandle(Class<? extends Exception> clazz);
}

class RuntimeExceptionErrorMessageBuilder implements ErrorMessageBuilder<RuntimeException> {
    public String buildMessage(ErrorWrapper<RuntimeException> exception) {
        return "Some runtime exception";
    }

    @Override
    public boolean canHandle(Class<? extends Exception> clazz) {
        return RuntimeException.class.isAssignableFrom(clazz);
    }
}

然后更改main并尝试创建具有更改的消息,但即使我尝试欺骗java也无法工作:错误所需类型:ErrorWrapper〈捕获?扩展异常〉,提供:ErrorWrapper〈capture of?extends Exception〉

public class SomeClassWrapper {
    public static void main(String[] args) {
        var builder = new RuntimeExceptionErrorMessageBuilder();

        ExceptionHandlerPair<NullPointerException> pair = new ExceptionHandlerPair<>(NullPointerException.class, builder);

        if(pair.canHandle(NullPointerException.class)){ // ? -> true
            ErrorWrapper<NullPointerException> npeWrapper = new ErrorWrapper<>(new NullPointerException());

            var message = pair.getBuilder().buildMessage(npeWrapper); // <- error Required type: ErrorWrapper <capture of ? extends Exception>,  Provided: ErrorWrapper<NullPointerException
            System.out.println(message);
        }

        // 
        if(pair.canHandle(NullPointerException.class)){ // ? -> true
            // and here we'll try to swoon over Java!!! :)
            ErrorWrapper<? extends Exception> npeWrapper = new ErrorWrapper<>(new NullPointerException());

            var message = pair.getBuilder().buildMessage(npeWrapper); // <- error Required type: ErrorWrapper <capture of ? extends Exception>,  Provided: ErrorWrapper <capture of ? extends Exception>
            System.out.println(message);
        }
    }
}

如果有人能解释这种行为,我将不胜感激!!!提前感谢!

bfrts1fy

bfrts1fy1#

在我看来,修复方法是在ExceptionHandlerPair类中引入另一个泛型类型变量。在下面的代码中,类型变量TErrorMessageBuilder处理的异常类型,UcanHandle方法将返回true的异常类型:

class ExceptionHandlerPair<T extends Exception, U extends T>{
    private final Class<U> clazz;
    private final ErrorMessageBuilder<T> builder;

    public ExceptionHandlerPair(Class<U> clazz, ErrorMessageBuilder<T> builder) {
        if(!builder.canHandle(clazz)){
            throw new IllegalArgumentException("Handler can't handle this exception");
        }

        this.clazz = clazz;
        this.builder = builder;
    }

    public boolean canHandle(Class<? extends Exception> clazz) {
        return this.clazz.isAssignableFrom(clazz);
    }

    public ErrorMessageBuilder<T> getBuilder() {
        return builder;
    }
}

我对你的代码做了这个修改,你的SomeClass类被编译了。
使用通配符的问题(?)是你扔掉了关于构建器处理什么类型的异常的信息。在你的代码中,pair.getBuilder()总是返回ErrorMessageBuilder<? extends Exception>,不管你的pair是如何构建的。这个构建器处理的异常类型将是Exception的任意子类,但是你不知道是什么。它可能是一个ErrorMessageBuilder<Exception>,或者一个ErrorMessageBuilder<RuntimeException>,如果是这种情况,你可以将一个NullPointerException传递给.getBuilder()方法。但是,无限通配符也允许getBuilder()方法返回一个ErrorMessageBuilder<NegativeArraySizeException>,例如,你不能把NullPointerException传递给buildMessage()方法。

91zkwejq

91zkwejq2#

导致此错误的原因是您在以下方法ExceptionHandlerPair.getBuilder()中使用通配符作为返回类型,不建议这样做,因为它会导致编译器产生歧义您可以查看下面的讨论,了解有关不建议这样做的原因的更多详细信息Generic wildcard types should not be used in return parameters
https://rules.sonarsource.com/java/RSPEC-1452
强烈建议不要使用通配符类型作为返回类型。因为类型推断规则相当复杂,API的用户不太可能知道如何正确使用它。让我们以返回“List〈?extends Animal〉"的方法为例。是否有可能在这个列表中添加一个狗,猫......我们根本不知道。编译器也不知道。这就是为什么它不允许这样直接使用。通配符类型的使用应该限于方法参数。当方法返回通配符类型时,此规则会引起问题。
相反,您只需将getBuilder()的返回类型更改为ErrorMessageBuilder<T>

public class ExceptionHandlerPair<T extends Exception> {

//~ ----------------------------------------------------------------------------------------------------------------
//~ Instance fields 
//~ ----------------------------------------------------------------------------------------------------------------

public final Class<T> clazz;
private final ErrorMessageBuilder<T> builder;

//~ ----------------------------------------------------------------------------------------------------------------
//~ Constructors 
//~ ----------------------------------------------------------------------------------------------------------------

public ExceptionHandlerPair(Class<T> clazz, ErrorMessageBuilder<T> builder) {
    if (!builder.canHandle(clazz)) {
        throw new IllegalArgumentException("Handler can't handle this exception");
    }

    this.clazz = clazz;
    this.builder = builder;
}

//~ ----------------------------------------------------------------------------------------------------------------
//~ Methods 
//~ ----------------------------------------------------------------------------------------------------------------

public boolean canHandle(Class<? extends Exception> clazz) {
    return this.clazz.isAssignableFrom(clazz);
}

public ErrorMessageBuilder<T> getBuilder() {
    return builder;
}

}

相关问题