我有以下代码抛出IndexOutOfBoundsException异常:
List<Character> list = new ArrayList<>();
char c = 'a';
list.add(c);
list.remove(c); // gets fixed by passing list.remove((Character)c);
我知道发生这种情况是因为在移除时不会发生AutoBoxing,而在添加元素时会发生AutoBoxing。我的问题是为什么?添加从char到Character的AutoBoxing是可行的,而在remove方法中它不是吗?
答案 0 :(得分:6)
这不是真正的自动拆箱问题,而是一个重载问题:存在List::remove(int)
(通过列表中的索引删除)方法,该方法比{{1}更具体(通过使用List::remove(E)
搜索对象来删除。)
在您的情况下,Object::equals
已投放到char
。
对于int
,使用索引删除的等效版本为add
(请参阅javadoc for details)。 List::add(int, E)
相当于List::add(E)
。
答案 1 :(得分:4)
Java会考虑将原始类型装入包装类型,但前提是它还没有考虑过不需要打包参数的重载方法。
MDN Reference,涵盖了如下选择的重载:
- 第一阶段(§15.12.2.2)执行重载解析而不允许装箱或拆箱转换,或使用变量arity方法调用。如果在此阶段没有找到适用的方法,则处理继续到第二阶段。
醇>这保证了在Java SE 5.0之前在Java编程语言中有效的任何调用都不会因为引入变量arity方法而被认为是不明确的,隐式装箱和/或取消装箱。但是,变量arity方法(第8.4.1节)的声明可以更改为给定方法方法调用表达式选择的方法,因为变量arity方法在第一阶段被视为固定arity方法。例如,在已声明m(Object)的类中声明m(Object ...)会导致不再为某些调用表达式(例如m(null))选择m(Object),因为m(Object [] )更具体。
(大胆强调我的)
- 第二阶段(§15.12.2.3)执行重载解析,同时允许装箱和拆箱,但仍然排除使用变量arity方法调用。如果在此阶段没有找到适用的方法,则处理继续到第三阶段。
醇>这确保了如果通过固定的arity方法调用适用,则永远不会通过变量arity方法调用选择方法。
- 第三阶段(§15.12.2.4)允许重载与变量arity方法,装箱和拆箱相结合。
醇>
编译器在没有装箱的情况下看不到add
可能匹配add(Character)
的其他重载,因此它会在第二阶段考虑装箱并找到匹配。
但是,编译器确实看到remove
的重载匹配而没有装箱,JLS, Section 15.12.2,因此char
被扩展为int
。编译器在第一阶段找到了它的方法,因此从未考虑过第二阶段。
正如您所想的那样,这会迫使您明确地将char
强制转换为Character
以获得匹配的正确方法。
这恰好是在Java 5中引入装箱和拆箱导致在该版本之前不存在的模糊性的情况。如果这些方法名称的设计考虑到这一点,那么设计者很可能会使用不同的方法名称,避免过载和这种模糊性。