将泛型类型实现为一种语言
Implementing generic types to a language
我想为一种语言添加对泛型的支持,但在我这样做之前,我需要更清楚地了解其中的泛型。
我目前的理解是实例化 class:
class ArrayList<T>
{
public int add(T object)
{
// ...
}
}
创建一个实例,其中 add
上 T
的参数类型必须与 new ArrayList<T>()
上的类型参数相同,其中 T
是实型。
实现这个看起来很简单,但仅适用于这个用例。当它需要支持内省时,它就变成了一个更复杂的概念。例如,我不会说:
true === (new ArrayList<Date>() instanceof ArrayList)
但我会说:
true === (new ArrayList<Date>() instanceof ArrayList<Date>)
我的问题是,当使用 ArrayList<Date>
作为类型引用时,它是 ArrayList<T>
的派生类型,还是本身就是 class,例如:
$list = new ArrayList<Date>();
实例:
class ArrayList
{
public int add(Date $object)
{
// ...
}
}
或者它是 ArrayList<T>
的实例,其中 T
是 Date
?
泛型只是编译时的帮手。如果我们没有泛型,我们会写这样的代码:
ArrayList list = new ArrayList();
list.add(new Date());
Date date = (Date) list.get(0);
引入了泛型来删除必要的转换,我们现在可以写:
ArrayList<Date> list = new ArrayList<>();
list.add(new Date());
Date date = list.get(0);
你可能认为 ArrayList
class 内部管理着一个 Date
的数组,但实际上
它管理一个 Object
数组。编译器会在您访问元素的位置插入缺失的强制转换。
使用泛型还有另一个好处。编译器可以检测您是否进行了无效的转换。所以不可能这样写:
ArrayList<Date> list = new ArrayList<>();
list.add(new Date());
String date = list.get(0);
您可以使用 new ArrayList<String>().getClass().equals(new ArrayList<Date>().getClass())
.
验证具有不同类型参数的 ArrayList
仍然编译为相同的 class
Java 泛型和 C++ 模板乍一看很相似,但实现方式却相反。正如 Steffen Kreutz 在评论中所说,java 对其泛型使用类型擦除。类型擦除意味着在编译时,java 控制对泛型 class 的访问,但在 运行 时,所有类型信息都消失了,并且 ArrayList<Date>
和 ArrayList<Integer>
共享完全相同的代码。
这与模板完全不同。在模板中,每个具体的 class 都是在编译时实现的,所以(在 C++ 中)vector<double>
和 vector<char>
是两个不同的 class,它们的编译代码会不同,因为一个将采用双参数,而另一个将采用字符并且它们在堆栈中的传递方式不同。
如果您需要一个泛型 class 来了解它可以接受的类型,您必须明确地使用一个属性来保留它。例如:
class MyGen<T> {
class<T> myClazz;
MyGen(class<T> clazz) {
myClass = clazz;
}
...
}
然后您可以在 myClazz 上使用反射,因为它是一个真正的 class 对象,可在 运行 时使用,而 T 只能在编译时使用,不能通过反射使用。
我想为一种语言添加对泛型的支持,但在我这样做之前,我需要更清楚地了解其中的泛型。
我目前的理解是实例化 class:
class ArrayList<T>
{
public int add(T object)
{
// ...
}
}
创建一个实例,其中 add
上 T
的参数类型必须与 new ArrayList<T>()
上的类型参数相同,其中 T
是实型。
实现这个看起来很简单,但仅适用于这个用例。当它需要支持内省时,它就变成了一个更复杂的概念。例如,我不会说:
true === (new ArrayList<Date>() instanceof ArrayList)
但我会说:
true === (new ArrayList<Date>() instanceof ArrayList<Date>)
我的问题是,当使用 ArrayList<Date>
作为类型引用时,它是 ArrayList<T>
的派生类型,还是本身就是 class,例如:
$list = new ArrayList<Date>();
实例:
class ArrayList
{
public int add(Date $object)
{
// ...
}
}
或者它是 ArrayList<T>
的实例,其中 T
是 Date
?
泛型只是编译时的帮手。如果我们没有泛型,我们会写这样的代码:
ArrayList list = new ArrayList();
list.add(new Date());
Date date = (Date) list.get(0);
引入了泛型来删除必要的转换,我们现在可以写:
ArrayList<Date> list = new ArrayList<>();
list.add(new Date());
Date date = list.get(0);
你可能认为 ArrayList
class 内部管理着一个 Date
的数组,但实际上
它管理一个 Object
数组。编译器会在您访问元素的位置插入缺失的强制转换。
使用泛型还有另一个好处。编译器可以检测您是否进行了无效的转换。所以不可能这样写:
ArrayList<Date> list = new ArrayList<>();
list.add(new Date());
String date = list.get(0);
您可以使用 new ArrayList<String>().getClass().equals(new ArrayList<Date>().getClass())
.
ArrayList
仍然编译为相同的 class
Java 泛型和 C++ 模板乍一看很相似,但实现方式却相反。正如 Steffen Kreutz 在评论中所说,java 对其泛型使用类型擦除。类型擦除意味着在编译时,java 控制对泛型 class 的访问,但在 运行 时,所有类型信息都消失了,并且 ArrayList<Date>
和 ArrayList<Integer>
共享完全相同的代码。
这与模板完全不同。在模板中,每个具体的 class 都是在编译时实现的,所以(在 C++ 中)vector<double>
和 vector<char>
是两个不同的 class,它们的编译代码会不同,因为一个将采用双参数,而另一个将采用字符并且它们在堆栈中的传递方式不同。
如果您需要一个泛型 class 来了解它可以接受的类型,您必须明确地使用一个属性来保留它。例如:
class MyGen<T> {
class<T> myClazz;
MyGen(class<T> clazz) {
myClass = clazz;
}
...
}
然后您可以在 myClazz 上使用反射,因为它是一个真正的 class 对象,可在 运行 时使用,而 T 只能在编译时使用,不能通过反射使用。