【问题标题】:Java Generics methods - using for the first timeJava 泛型方法 - 第一次使用
【发布时间】:2010-12-30 17:34:53
【问题描述】:

我第一次尝试使用泛型方法并且有点困惑。我创建了一个简单的例子来证明我可能会以错误的方式处理这个问题并且需要理顺。我正在使用 Eclipse 3.6.1。我的印象是编译器通过推理确定参数类型,但不确定为什么它迫使我在泛型方法中使用强制转换。这是一个简单的例子。

class Test1 
{
    Test1 () {};
    public String getX () {return "Test1"};
};

class Test2 
{
    Test2 () {};
    public String getX () {return "Test2"};
};

我的主要方法:

public static void main(String args[]) 
{ 
    Test1 tst1 = new Test1();
    Test2 tst2 = new Test2();

    System.out.println("result: " + displayTest(tst1, tst2));
}

static <T,S> boolean displayTest(T x, S y)
{
    System.out.println("X: " + ((Test1) x).getX());
    System.out.println("Y: " + ((Test2) y).getX());

    if (((Test1) x).getX().equals(((Test2) y).getX()))
        return true;
    else
        return false;
}

我认为编译器会知道在这种情况下TTest1 的一个实例,而STest2,但在Eclipse 中,getX 不是一个有效的方法。为了让它编译,它迫使我将对象转换为正确的类型,在我看来这违反了泛型方法的一般原则。

显然,我没有得到这个并且做错了什么。那么编译器如何知道泛型方法中的类型是什么?这样的事情应该怎么做?在我试图实现这一点的大型系统中,我有几种方法可以对不同类型的对象进行操作,并试图使它们具有通用性。即方法 1 调用方法 2(它使用泛型类型),后者又调用方法 3(再次传递泛型类型)。我希望只有函数调用的开始(在这种情况下调用方法 1)需要知道对象是什么类型,并且所有后续方法都只是泛型方法。

非常感谢。

【问题讨论】:

    标签: java generics


    【解决方案1】:

    编译器不知道方法中的类型。它们可以是任何东西。

    如果您使用&lt;T extends Y&gt;,其中Y 是定义getY() 的接口,它会起作用。

    泛型的目的是确保编译时安全。它们主要是编译时的概念。例如:

    public static <T> T instantiate(Class<T> clazz) throws Exception {
        return clazz.newInstance();
    }
    

    这个方法可以用,不用任何情况,像这样:

    Foo foo = instantiate(Foo.class);
    Bar bar = instantiate(Bar.class);
    

    另一个例子,来自 Collections 框架。有Collections.enumeration(collection),这是通用的。所以:

    Enumeration<String> enumeration1 = 
          Collections.enumeration(new ArrayList<String>(..));
    Enumeration<Integer> enumeration2 = 
          Collections.enumeration(new ArrayList<Integer>(..));
    

    没有强制转换,但您确定这些将是类型。然后nextElement() 方法将返回StringInteger,无需强制转换:

    String s = enumeration1.nextElement();
    Integer i = enumeration2.nextElement();
    

    如果没有泛型,您将需要在这些示例中使用强制类型转换,并且如果您传递了错误的参数,您会得到一个运行时异常(很可能是 ClassCastException)。使用泛型,您会在编译时遇到异常。

    编译器实际上代表您添加这些转换,但只有在确定转换不会出错之后。

    【讨论】:

      【解决方案2】:

      问题是在编译时编译器不知道TS 的具体类型。您的 main 方法调用 displayTest 并带有 Test1Test2 的具体实例,但没有什么能阻止您使用 StringInteger 来调用它。

      这意味着你不能在x 上调用getX,因为你不能保证TTest1 的子类。

      可以使用捕获来限制 TS 的类型:

      boolean <T extends Test1, S extends Test2> displayTest(T x, S y)
      

      这告诉编译器T 必须是Test1(或Test1 的子类),这意味着您不需要强制转换。

      【讨论】:

      • 啊,好的。我现在明白了。就像我说的,第一次使用泛型。感谢所有快速回复......
      【解决方案3】:

      为什么编译器会知道 T 和 S 是 Test1 和 Test2?您可以从代码中的任何位置调用 displayTest,使用任意数量的不同对象类型进行任意次数。它应该在那里做什么?

      另外,eclipse != 编译器。 Eclipse 的代码完成不会“编译”任何东西。编译器确切地知道其中放入了哪些类型,但仅限于对该函数的每个 CALL。这就是为什么它被称为泛型。它可以采用任何泛型类型。

      【讨论】:

        【解决方案4】:

        当您说编译器通过推理确定参数类型时,它的含义与您的想法不同。

        由于 displayMethod 是一个泛型方法,调用该方法的“正确”方式是通过调用它来告诉编译器 T 和 S 在这个特定方法调用中代表什么

        ClassName.&lt;Test1,Test2&gt;displayMethod(tst1,tst2)

        但是由于 java 很聪明,编译器可以推断出你不告诉它确切的 T 和 S,如果你这样做,T 和 S 将是 Test1 和 Test2,

        ClassName.displayMethod(tst1, tst2)

        另外,就像 cameron skinner 所说,您可以通过使 T 扩展 Test1 和 S 扩展 Test2 来修复您的代码。您可以做的另一个修复是保留 &lt;T, S&gt; 原样,并将您的 getX() 方法重命名为覆盖 toString() 方法。由于toString()Object 类的成员,所以泛型类T and S 可以访问它,您不需要将方法限制为只接受Test1 and Test2 类型的参数

        【讨论】:

        • 它不起作用,因为 T 和 S 无权访问 getX()。那完全是一个完全不同的问题。 INFERENCE 允许编译器在方法调用中推断类的类型。但是泛型类型 T 和 S 必须有权访问您在 T 和 S 上调用的所有方法。由于 T 和 S 是泛型类型,因此属于 Object 类型,它们无权访问 getX()
        • 是的,当然没有想到这一点。然而,就我而言,我需要访问对象中的多个方法,因此重载 toString() 不适用于这种情况。但是,我刚刚意识到我确实需要同时约束 T 和 S,但在这种情况下,我需要它们中的任何一个能够成为 Test1 或 Test2。我该怎么办?
        • 因此,修复代码的两种方法是更改​​为 或重命名 getX() 以覆盖 toString() 方法
        • 与其说Test1和Test2的术语,你能具体告诉我你正在解决的真正问题吗?因为我实现 Test1 和 Test2 的方式与你的非常不同
        • 我有 T extends Test1 工作,但正如我刚刚发布的那样,需要它们中的任何一个能够成为其中一个。谢谢。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-08-17
        • 2016-03-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-23
        相关资源
        最近更新 更多