【问题标题】:Implementing generic types to a language将泛型类型实现为一种语言
【发布时间】:2015-09-28 08:03:02
【问题描述】:

我想为一种语言添加对泛型的支持,但在此之前,我需要更清楚地了解泛型在其中的含义。

我目前的理解是实例化类:

class ArrayList<T>
{
    public int add(T object)
    {
        // ...
    }
}

创建一个实例,其中add 上的T 的参数类型必须与new ArrayList&lt;T&gt;() 上的类型参数相同,其中T 是一个真实类型。

实现这一点似乎很简单,但仅适用于这个用例。当它需要支持自省时,它就变成了一个更复杂的概念。例如,我不会这样说:

true === (new ArrayList<Date>() instanceof ArrayList)

但我想说:

true === (new ArrayList<Date>() instanceof ArrayList<Date>)

我的问题是,当使用ArrayList&lt;Date&gt; 作为类型引用时,它是从ArrayList&lt;T&gt; 派生的类型,还是本身就是一个类,例如:

$list = new ArrayList<Date>();

一个实例:

class ArrayList
{
    public int add(Date $object)
    {
        // ...
    }
}

或者它是ArrayList&lt;T&gt; 的一个实例,其中TDate

【问题讨论】:

  • ArrayList&lt;T&gt; 仅在类声明中使用,如果这是您的意思。使用ArrayList&lt;T&gt; 作为类型没有意义(如果T 未知)。
  • @Haketo 我知道,我说的是更抽象的意义。我试图弄清楚实例化的对象是否仍然包含它是泛型类型的实例的信息,或者是否使用扩展的类型参数创建了一种新的虚拟类,该对象现在是一个实例。这个问题对我来说非常重要,因为它将决定类型参数在整个语言中的传播位置。如果它们从实例化传播,则只需要引用类型,如果从类名中,则需要从声明中复制(形象地)类。
  • 我的评论是关于 new ArrayList&lt;Date&gt;() instanceof ArrayList&lt;T&gt; 的声明,当 T 未知时,如果 TDate,那么你的结果与 new ArrayList&lt;Date&gt;() instanceof ArrayList&lt;Date&gt; 相同。 `T` 仅用于泛型类声明,例如保存类型的变量。
  • @Haketo 哦,我明白了,我将删除&lt;T&gt; 然后,它在问题中的含义相同。本质上,这意味着ArrayList&lt;Date&gt; 不应该等于ArrayList,因为ArrayList 作为一个没有参数化类型的类是不存在的。
  • Java 使用type erasure,因此$listArrayList 的一个实例。

标签: oop generics types instantiation


【解决方案1】:

Java 泛型和 C++ 模板乍一看很相似,但实现方式相反。正如 Steffen Kreutz 在评论中所说,java 对其泛型使用类型擦除。类型擦除意味着在编译时,java 控制对泛型类的访问,但在运行时,所有类型信息都消失了,ArrayList&lt;Date&gt;ArrayList&lt;Integer&gt; 共享完全相同相同的代码。 p>

这与模板完全不同。在模板中,每个具体类都是在编译时实现的,因此(在 C++ 中)vector&lt;double&gt;vector&lt;char&gt; 是两个不同的类,它们的编译代码会有所不同,因为一个会带双参数,而另一个会带 chars 和它们在堆栈中的传递方式不同。

如果你需要一个泛型类来知道它可以接受的类型,你必须明确地使用一个属性来保持它。例如:

class MyGen<T> {
    class<T> myClazz;

    MyGen(class<T> clazz) {
        myClass = clazz;
    }
    ...
}

然后您可以在 myClazz 上使用反射,因为它是可在运行时使用的真正类对象,而 T 只能在编译时使用,不能通过反射使用。

【讨论】:

  • 我并不想将这个问题与 Java 联系起来,我计划实现的泛型类型更接近 .NET。我正在试验的语言是解释的 PHP。因此,每个对象都需要携带已声明类的“变体”。类型擦除是不可能的,因为 PHP 中不存在转换对象,因为它是动态的。但是,可以通过存储变体(类似于您的属性示例)或通过扁平化类(如在 C++ 中)来进行自省。问题是,哪个更好?我应该提到这种语言是动态的。
  • 对不起,如果我有点困惑,您的回答确实解决了我的两个选择。我只是想考虑创建每个“具体类”对性能的影响,以应对跨语言维护“属性”的困难。
  • @Flosculus:我认为如果它是您正在试验的语言,您应该将其标记为 PHP。不幸的是,我对 PHP 真的不够了解。但是对于同样是动态语言的 Python,我会尝试使用自定义注解来传递类型信息,也许还可以使用抽象基类来存储它。
  • 真的可以是任何语言,我会尝试第一个选项,它看起来会很好地级联,因为每个类引用都指向一个特定的类,而不是具有特定状态的基类(凌乱)。
【解决方案2】:

泛型只是编译时的助手。如果我们没有泛型,我们会编写如下代码:

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 类在内部管理了一个Dates 数组,但实际上 它管理Objects 的数组。编译器会在您访问元素的位置插入缺失的类型转换。

使用泛型还有另一个好处。编译器可以检测您是否进行了无效的强制转换。所以不可能这样写:

ArrayList<Date> list = new ArrayList<>();
list.add(new Date());
String date = list.get(0);

您可以使用new ArrayList&lt;String&gt;().getClass().equals(new ArrayList&lt;Date&gt;().getClass()) 验证具有不同类型参数的ArrayLists 仍然编译为同一个类。

看看Oracle Java Documentation about type erasure

【讨论】:

    猜你喜欢
    • 2019-12-06
    • 1970-01-01
    • 2020-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多