Java 9 或更高版本中的预计泛型专业化,vs List<int>:.remove() 将如何工作?
Projected generic specialization in Java 9 or later, vs List<int>: how will .remove() work?
泛型专业化和值类型是未来 JVM 的预期功能; link 到 Valhalla 项目页面 here。
现在,据我了解,可以声明:
final List<int> myList = new ArrayList<>(); // for instance
但是 List
除了在 Collection
接口中定义的方法之外还定义了另一个 .remove()
方法,该方法将 int
作为参数作为索引在要删除的列表中;这就是为什么目前,list
的内容在下面的示例中:
final List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.remove(2);
将是 [1, 2]
而不是 [1, 3]
(选择了最具体的重载)。
但是,如果将来我们能够声明一个 List<int>
,我们会遇到一个问题:将选择 remove
方法的哪个重载?
此答案基于 Brian Goetz 的 this paper,日期为 2014 年 12 月。这是我能找到的有关该主题的最新信息;但是请注意,这篇论文是 "informal sketch",因此关于您的问题还没有确定的内容。
首先,List<int>
不会是 List<Integer>
(Subtyping) 的子类型:
Initially, it might also seem sensible that Box<int>
could be a subtype of raw Box
. But, given our translation strategy, the Box
class cannot be a superclass of whatever class represents Box<int>
, as then Box<int>
would have a field t
of type Object
, whereas t
should be of type int
. So Box<int>
cannot be a subtype of raw Box
. (And for the same reason, Box<int>
cannot be a subtype of Box<Integer>
.)
...
Since generics are invariant, it is not surprising that List<int>
is not a subtype of List<Integer>
. The slightly surprising thing here is that a specialized type cannot interoperate with its raw counterpart. However, this is not an unreasonable restriction; not only are raw types discouraged (having been introduced solely for the purpose of supporting the gradual migration from non-generic code to generic code), but it is still possible to write fully generic code using generic methods -- see "Generic Methods".
这篇论文也列出了"Migration challenges",reference-primitive overloadings(问题是你的问题)就是其中之一:
Some overloadings that are valid today would become problematic under specialization. For example, these methods would have a problem if specialized with T=int
:
public void remove(int position);
public void remove(T element);
Such overloads would be problematic both on the specialization side (what methods to generate) and on the overload selection side (which method to invoke.)
建议的解决方案被称为 the "peeling" technique:
Consider the overload pair in a List-like class:
interface ListLike<T> {
public void remove(int position);
public void remove(T element);
}
Existing uses of ListLike
will all involve reference instantiations, since those are the only instantiations currently allowed in a pre-specialization world. Note that while compatibility requires that reference instantiations have both of these methods, it requires nothing of non-reference instantiations (since none currently exist.)
The intuition behind peeling is to observe that, while we're used to thinking of the existing ListLike
as the generic type, that it might really be the union of a type that is generic across all instantiations, and a type that is generic across only reference instantations.
If we were writing this class from scratch in a post-specialization world, we might have written it as:
interface ListLike<any T> {
void removeByIndex(int position);
void removeByValue(T element);
}
But, such a change now would not be either source-compatible or binary-compatible. However, peeling allows us to add these methods to the generic layer, and implement them in a reference-specific layer, without requiring them in the specializations, restoring compatibility:
interface ListLike<any T> {
// New methods added to the generic layer
void removeByValue(T element);
void removeByIndex(int pos);
layer<ref T> {
// Abstract methods that exist only in the ref layer
void remove(int pos);
void remove(T element);
// Default implementations of the new generic methods
default void removeByIndex(int pos) { remove(pos); }
default void removeByValue(T t) { remove(t); }
}
}
Now, reference instantiations have remove(T)
, and remove(int)
(as well as the new methods removeByIndex
and removeByValue
), ensuring compatibility, and specializations have the nonproblematic overloads of removeByValue(T)
and removeByIndex(int)
. Existing implementations of ListLike
would continue to compile since the new methods have a default implementation for reference specializations (which simply bridges to the existing remove methods). For value instantiations, removeByIndex
and removeByValue
are seen as abstract and must be provided, but remove does not exist at all.
This technique also enables the "implementation by parts" technique; it is possible to declare a method abstract in the generic layer and provide concrete implementations in both the value and reference layers. If we allowed layers for T=int
, it would also enable the "specializing the specializations" technique.
使用此技术,将保持向后兼容性并使用新方法 removeByValue
和 removeByIndex
。
泛型专业化和值类型是未来 JVM 的预期功能; link 到 Valhalla 项目页面 here。
现在,据我了解,可以声明:
final List<int> myList = new ArrayList<>(); // for instance
但是 List
除了在 Collection
接口中定义的方法之外还定义了另一个 .remove()
方法,该方法将 int
作为参数作为索引在要删除的列表中;这就是为什么目前,list
的内容在下面的示例中:
final List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.remove(2);
将是 [1, 2]
而不是 [1, 3]
(选择了最具体的重载)。
但是,如果将来我们能够声明一个 List<int>
,我们会遇到一个问题:将选择 remove
方法的哪个重载?
此答案基于 Brian Goetz 的 this paper,日期为 2014 年 12 月。这是我能找到的有关该主题的最新信息;但是请注意,这篇论文是 "informal sketch",因此关于您的问题还没有确定的内容。
首先,List<int>
不会是 List<Integer>
(Subtyping) 的子类型:
Initially, it might also seem sensible that
Box<int>
could be a subtype of rawBox
. But, given our translation strategy, theBox
class cannot be a superclass of whatever class representsBox<int>
, as thenBox<int>
would have a fieldt
of typeObject
, whereast
should be of typeint
. SoBox<int>
cannot be a subtype of rawBox
. (And for the same reason,Box<int>
cannot be a subtype ofBox<Integer>
.)...
Since generics are invariant, it is not surprising that
List<int>
is not a subtype ofList<Integer>
. The slightly surprising thing here is that a specialized type cannot interoperate with its raw counterpart. However, this is not an unreasonable restriction; not only are raw types discouraged (having been introduced solely for the purpose of supporting the gradual migration from non-generic code to generic code), but it is still possible to write fully generic code using generic methods -- see "Generic Methods".
这篇论文也列出了"Migration challenges",reference-primitive overloadings(问题是你的问题)就是其中之一:
Some overloadings that are valid today would become problematic under specialization. For example, these methods would have a problem if specialized with
T=int
:public void remove(int position); public void remove(T element);
Such overloads would be problematic both on the specialization side (what methods to generate) and on the overload selection side (which method to invoke.)
建议的解决方案被称为 the "peeling" technique:
Consider the overload pair in a List-like class:
interface ListLike<T> { public void remove(int position); public void remove(T element); }
Existing uses of
ListLike
will all involve reference instantiations, since those are the only instantiations currently allowed in a pre-specialization world. Note that while compatibility requires that reference instantiations have both of these methods, it requires nothing of non-reference instantiations (since none currently exist.)The intuition behind peeling is to observe that, while we're used to thinking of the existing
ListLike
as the generic type, that it might really be the union of a type that is generic across all instantiations, and a type that is generic across only reference instantations.If we were writing this class from scratch in a post-specialization world, we might have written it as:
interface ListLike<any T> { void removeByIndex(int position); void removeByValue(T element); }
But, such a change now would not be either source-compatible or binary-compatible. However, peeling allows us to add these methods to the generic layer, and implement them in a reference-specific layer, without requiring them in the specializations, restoring compatibility:
interface ListLike<any T> { // New methods added to the generic layer void removeByValue(T element); void removeByIndex(int pos); layer<ref T> { // Abstract methods that exist only in the ref layer void remove(int pos); void remove(T element); // Default implementations of the new generic methods default void removeByIndex(int pos) { remove(pos); } default void removeByValue(T t) { remove(t); } } }
Now, reference instantiations have
remove(T)
, andremove(int)
(as well as the new methodsremoveByIndex
andremoveByValue
), ensuring compatibility, and specializations have the nonproblematic overloads ofremoveByValue(T)
andremoveByIndex(int)
. Existing implementations ofListLike
would continue to compile since the new methods have a default implementation for reference specializations (which simply bridges to the existing remove methods). For value instantiations,removeByIndex
andremoveByValue
are seen as abstract and must be provided, but remove does not exist at all.This technique also enables the "implementation by parts" technique; it is possible to declare a method abstract in the generic layer and provide concrete implementations in both the value and reference layers. If we allowed layers for
T=int
, it would also enable the "specializing the specializations" technique.
使用此技术,将保持向后兼容性并使用新方法 removeByValue
和 removeByIndex
。