我希望了解为什么做出这个决定。泛型异常有什么问题吗?
(据我所知,泛型只是编译时的语法糖,在
.class
文件中它们会被转换成Object
,因此声明泛型类就像其中的所有内容都是Object
一样。如果我错了,请纠正我。)我希望了解为什么做出这个决定。泛型异常有什么问题吗?
.class
文件中它们会被转换成Object
,因此声明泛型类就像其中的所有内容都是Object
一样。如果我错了,请纠正我。)正如Mark所说,这些类型是不可具体化的,在以下情况下会产生问题:
try {
doSomeStuff();
} catch (SomeException<Integer> e) {
// ignore that
} catch (SomeException<String> e) {
crashAndBurn()
}
SomeException<Integer>
和 SomeException<String>
均被擦除为相同类型,JVM无法区分异常实例,因此无法确定应该执行哪个 catch
块。
这主要是因为它的设计存在问题。
这个问题会阻碍简洁的抽象设计,例如:
public interface Repository<ID, E extends Entity<ID>> {
E getById(ID id) throws EntityNotFoundException<E, ID>;
}
泛型类型无法具体化可能导致catch子句失败,但这并不是借口。编译器可以简单地禁止扩展Throwable的具体通用类型或禁止在catch子句中使用泛型。
EntityNotFoundException
。但这将使泛型失去意义。 - Frans下面是一个使用异常处理的简单示例:
class IntegerExceptionTest {
public static void main(String[] args) {
try {
throw new IntegerException(42);
} catch (IntegerException e) {
assert e.getValue() == 42;
}
}
}
语句的主体使用给定值抛出异常,该异常被子句捕获。
相反,下面的定义是被禁止的,因为它创建了一个带参数的类型的新异常:
class ParametricException<T> extends Exception { // compile-time error
private final T value;
public ParametricException(T value) { this.value = value; }
public T getValue() { return value; }
}
% javac ParametricException.java
ParametricException.java:1: a generic class may not extend
java.lang.Throwable
class ParametricException<T> extends Exception { // compile-time error
^
1 error
这种限制是合理的,因为几乎任何捕获这样异常的尝试都必定失败,因为该类型不是可重现的。人们可能期望异常的典型使用方式如下:
class ParametricExceptionTest {
public static void main(String[] args) {
try {
throw new ParametricException<Integer>(42);
} catch (ParametricException<Integer> e) { // compile-time error
assert e.getValue()==42;
}
}
}
这是不允许的,因为catch子句中的类型不可具体化。在撰写本文时,Sun编译器会报告此类情况下的语法错误级联:
% javac ParametricExceptionTest.java
ParametricExceptionTest.java:5: <identifier> expected
} catch (ParametricException<Integer> e) {
^
ParametricExceptionTest.java:8: ')' expected
}
^
ParametricExceptionTest.java:9: '}' expected
}
^
3 errors
由于异常不能是参数化的,因此语法受到限制,类型必须写成标识符,没有后续参数。
泛型在编译时进行类型正确性检查。然后通过一种叫做“类型擦除”的过程来删除泛型类型信息。例如,List<Integer>
将转换为非泛型类型List
。
由于存在“类型擦除”,所以无法在运行时确定类型参数。
假设您允许像这样扩展Throwable
:
public class GenericException<T> extends Throwable
现在我们来考虑下面的代码:
try {
throw new GenericException<Integer>();
}
catch(GenericException<Integer> e) {
System.err.println("Integer");
}
catch(GenericException<String> e) {
System.err.println("String");
}
由于类型擦除,运行时将不知道执行哪个捕获块。
因此,如果通用类是Throwable的直接或间接子类,则会出现编译时错误。
来源:类型擦除问题
try
{
doSomethingThatCanThrow();
}
catch (MyException<Foo> e)
{
// handle it
}
正如您所指出的,参数化只是一种语法糖。然而,编译器试图确保在编译范围内所有引用对象的参数化保持一致。在异常情况下,编译器无法保证MyException仅从它正在处理的范围中抛出。
虽然与问题不太相关,但如果您真的想要一个扩展了Throwable
的inner class
,您可以将其声明为static
。当Throwable
在逻辑上与封闭类有关,但与该封闭类的特定泛型类型无关时,这是适用的。通过声明它为static
,它不会绑定到封闭类的实例,因此问题消失了。
以下(虽然不是非常好的)示例说明了这一点:
/** A map for <String, V> pairs where the Vs must be strictly increasing */
public class IncreasingPairs<V extends Comparable<V>> {
private final Map<String, V> map;
public IncreasingPairs() {
map = new HashMap<>();
}
public void insertPair(String newKey, V value) {
// ensure new value is bigger than every value already in the map
for (String oldKey : map.keySet())
if (!(value.compareTo(map.get(oldKey)) > 0))
throw new InvalidPairException(newKey, oldKey);
map.put(newKey, value);
}
/** Thrown when an invalid Pair is inserted */
public static class InvalidPairException extends RuntimeException {
/** Constructs the Exception, independent of V! */
public InvalidPairException(String newKey, String oldKey) {
super(String.format("Value with key %s is not bigger than the value associated with existing key %s",
newKey, oldKey));
}
}
}
myList.get(i)
,显然get
仍然返回一个Object
。编译器是否插入了一个转换为A
的强制类型转换以在运行时捕获一些约束条件?如果没有,那么OP是正确的,在最后它归结为运行时的Object
。(类文件肯定包含有关A
的元数据,但据我所知,这只是元数据。) - Mihai Danila