从泛型返回异常

Returning exceptions from generics

我目前有多个 类,即 FacadeA...FacadeZ,它们扩展了 FacadeBaseFacadeBase 应该有(某种通用方法?),即 checkFacade,它会抛出异常 FacadeAException...FacadeZException 扩展 FacadeException.

这是我目前所了解的。我坚持使用通用部分(或任何可以解决我问题的方法)。

是的,我知道我无法像示例中那样实例化泛型。

public abstract class FacadeBase {
  public void checkFacade(final String facadeId) throws Facade<A...Z>Exception {
    if (!isFacadeAvailable(facadeId)) {
      throw new Facade<A...Z>Exception(facadeId);
    }
  }
  ...
}

public class FacadeA extends FacadeBase {
  public void doSomethingWithFacadeA(final String facadeId) throws FacadeAException {
    checkFacade(facadeId));
    ...
  }
}

public class FacadeAException extends FacadeException {
  private FacadeAException(final String message) {
    super(message);
  }
}

public abstract class FacadeException extends Exception {
  private FacadeException(final String message) {
    super(message);
  }
}

您可以使用泛型,但这还不够,因为您需要在运行时实例化特定类型。
一般来说,你有两种方式:

  • 反射方式
  • 声明方式

1)反射方式不简单。 Java 没有提供您需要做的所有事情。您可以从 Spring 库中获得启发或使用库来做到这一点。
org.springframework.core.GenericTypeResolver 应该对你特别有帮助。
您可以使用 static Class<?> resolveTypeArgument(Class<?> clazz, Class<?> genericIfc) 方法:

Resolve the single type argument of the given generic interface against the given target class which is assumed to implement the generic interface and possibly declare a concrete type for its type variable.

如:

public abstract class FacadeBase<T extends FacadeException> {

    private final Class<T> genericClazz;
    public FacadeBase () {
        this.genericClazz = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), FacadeBase.class);    
    }
}

现在您可以使用 genericClazz.newInstance() 或更好的 Constructor.newInstance() 来实例化泛型的 class。

2) 声明方式更简单,但需要一些样板代码。

使您的摘要 class 成为通用的 class,指定异常类型并提供一个构造函数来存储要抛出的异常。
例如:

public abstract class FacadeBase<T extends FacadeException> {

    private Supplier<T> suppException;

    public FacadeBase(Supplier<T> suppException) {
        this.suppException = suppException;
    }

    public void checkFacadeAvailability(final String facadeId) throws T {
        if (!isFacadeAvailable(facadeId)) {
            throw suppException.get();
        }
    }
}

Subclasses 应该与他们的供应商一起调用超级构造函数:

public class FacadeA extends FacadeBase<FacadeExceptionA>{

    public FacadeA(Supplier<FacadeExceptionA> suppException) {
        super(suppException);
    }   
}

作为替代方案,您可以将 Supplier 替换为 Class 参数,但总体思路是相同的。

停在 throws FacadeException 有两个很好的理由:

  • FacadeBase.checkFacade 的合同无论如何都会迫使客户赶上 FacadeException(假设编程接口的良好实践)
  • 异常classes 不能是通用的。尽管您可以在 throws 子句中使用 Exception 类型参数,但这只是过度设计(除了违反最佳实践之外)

所以 subclasses 应该声明 throws Facade<A...Z>Exception 或者在适用的地方完全省略这个子句。但这对 client/caller 没有影响(除非他们采取不鼓励的方式声明 subclass 类型)

如果你需要检查调用方引发了哪个异常,那么无论如何你都必须知道具体的异常class,因为无论如何你都无法检查 instanceof T

据我了解,问题的核心是如何按照此伪代码示意性表示的方式声明和实现 checkFacade() 方法:

public abstract class FacadeBase {
  public void checkFacade(final String facadeId) throws Facade<A...Z>Exception {
    if (!isFacadeAvailable(facadeId)) {
      throw new Facade<A...Z>Exception(facadeId);
    }
  }
  ...
}

我首先注意到,将 特征 异常与具体 FacadeBase subclasses 相关联会破坏抽象。 subclasses 肯定会抛出它们自己的特定异常,但是一旦其他 classes 知道或关心那些特别的,特别是为了能够识别那些 subclasses , 抽象分崩离析。

特别是,如果您的 isFacadeAvailable(facadeId) 方法与 FacadeBase 的子 class 有任何关系,在 Java class 路径中可用,那么这似乎可能与外观实现的特征异常可用相关。在那种情况下,当 FacadeQ 不可用时,您不能期望能够实例化 FacadeQException,并且当任何异常时,您可能会 运行 进入 class 加载失败class路径中不存在。

其次,我观察到因为所有 Facade[A-Z]Exception 扩展 FacadeExceptionFacadeBase.checkFacade() 可以简单地声明 FacadeException 而不是声明所有单独的异常。这不会阻止其他代码捕获特定的异常,如果这些异常仍然被此方法抛出的话。

为了真正抛出个别异常,您需要先构建它们,这需要一个大的 switch 块,一个大的 if/then/ else 语句、工厂方法或适当异常的反射实例化 class,或它们的某种组合。请注意,异常是对象;它们可以分配给变量并从方法返回。一个 throw 语句可以抛出 any Throwable;它不需要是新实例化的。因此,您可以考虑以下方面:

public void checkFacade(final String facadeId) throws FacadeException {
    if (!isFacadeAvailable(facadeId)) {
        throw createFacadeException(facadeId);
    }
}

private FacadeException createFacadeException(String facadeId) {
    if ("A".equals(facadeId)) {
        return new FacadeAException();
    } else // ...
}

HOWEVER,我强烈建议您改为考虑给 FacadeException 一个成员,通过它来传达不可用的 ID facade,而不是通过抛出特定于 facade 的异常来做到这一点。或者如果你不想把它放在 FacadeException 本身,然后定义一个 FacadeUnavailableException subclass 携带它:

public class FacadeUnavailableException extends FacadeException {
    private final String facadeId;

    private FacadeAException(String message, String facadeId) {
        super(message);
        this.facadeId = facadeId;
    }

    public String getFacadeId() {
        return facadeId;
    }
}

有了这个,你的问题就变得简单多了:

public void checkFacade(final String facadeId) throws FacadeUnavailableException {
    if (!isFacadeAvailable(facadeId)) {
        throw new FacadeUnavailableException("unavailable", facadeId);
    }
}