【问题标题】:Overriding Java generic methods覆盖 Java 泛型方法
【发布时间】:2009-03-10 20:01:56
【问题描述】:

我想创建一个用于将对象复制到同一类的目标对象的接口。简单的方法是使用强制转换:

import org.junit.Test;
import org.junit.internal.runners.JUnit4ClassRunner;
import org.junit.runner.RunWith;

@RunWith(JUnit4ClassRunner.class)
public class TestGenerics {
public static interface Copyable {
    public void copy(Copyable c);
}

public static class A implements Copyable {
    private String aField = "--A--";
    protected void innerCopy(Copyable c) {
        A a = (A)c;
        System.out.println(a.aField);
    }
    public void copy(Copyable c) {
        innerCopy(c);
    }
}

public static class B extends A {
    private String bField = "--B--";
    protected void innerCopy(Copyable c) {
        B b = (B)c;
        super.innerCopy(b);
        System.out.println(b.bField);
    }
}

@Test
public void testCopy() {
    Copyable b1 = new B();
    Copyable b2 = new B();
    b1.copy(b2);
}
}

但我也找到了一种可以使用泛型完成的方法:

import org.junit.Test;
import org.junit.internal.runners.JUnit4ClassRunner;
import org.junit.runner.RunWith;

@RunWith(JUnit4ClassRunner.class)
public class TestGenerics {
    public static interface Copyable<T> {
        public void copy(T t);
    }

    public static class A<T extends A<?>> implements Copyable<T> {
        private String a = "--A--";
        public void copy(T t) {
            System.out.println(t.a);
        }
    }

    public static class B<T extends B<?>> extends A<T> {
        private String b = "--B--";
        public void copy(T t) {
            super.copy(t);
            System.out.println(t.b);
        }
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testCopy() {
        Copyable b1 = new B();
        Copyable b2 = new B();
        b1.copy(b2);
    }
}

虽然我发现摆脱警告的唯一方法是注释。而且感觉好像出了点问题。 那么有什么问题呢?我可以接受问题的根源有问题。因此,欢迎任何形式的澄清。

【问题讨论】:

  • 你在哪一行得到警告,它说什么?
  • testCopy() 中的所有 3 行都给出了关于泛型类型的引用应该参数化的警告

标签: java generics contravariance


【解决方案1】:

你的接口定义:

public interface Copyable<T extends Copyable<T>> {
    void copy(T copyFrom);
}

你的实现:

public class Example implements Copyable<Example> {
    private Object data;
    void copy(Example copyFrom) {
        data = copyFrom.data;
    }
    //nontrivial stuff
}

这应该会处理您的警告。

【讨论】:

  • 是的,这就是我的起点,但现在如何扩展 Example 类以保持 copy(...) 以相同的方式工作?
【解决方案2】:

假设您不想进一步子类化,您只需要:

public static /*final*/ class AClass implements Copyable<AClass> {

对于抽象类,你要做“枚举”的事情:

public static abstract class AClass<T extends AClass<T>> implements Copyable<T> {

【讨论】:

  • 您的代码与我发布的类似。但我仍然不明白两件事。第一:我应该怎么打电话才能让它在没有警告的情况下进行?第二:我使用泛型的方式是否适合这种情况,或者它看起来就像碰巧按预期工作的糟糕代码?
  • 还有,["enum" thing] 是什么意思?
  • 您可以使用new AClass 调用构造函数的第一个示例。对于第二个示例,您将使用与第一个类似的形式进行子类化。枚举的东西——看看 java.lang.Enum (Enum>) 的定义。
  • 谢谢!您指向 java.lang.Enum (Enum> 的指针帮助我找到了关于这个主题的更多有用信息。
【解决方案3】:

在 testCopy 中,警告之一是因为您正在实例化 Copyable 的“原始类型”,而不是一些具体的 Copyable。实例化 Copyable 后,它只能应用于 Ts(包括 T 的子类型)。为了使用正式类型进行实例化,需要稍微更改类定义:

public static class A<T extends A> implements Copyable<T>
public static class B<T extends B> extends A<T>

下一个问题是 Copyable 只能传递 B 的编译时类型(基于 Copyable 的定义)。上面的 testCopy() 传递了一个可复制的编译时类型。以下是一些可行的示例,并附有简要说明:

public void testExamples()
{
    // implementation of A that applies to A and subtypes
    Copyable<A> aCopier = new A<A>();

    // implementation of B that applies to B and subtypes
    Copyable<B> bCopier = new B<B>();

    // implementation of A that applies to B and subtypes
    Copyable<B> bCopier2 = new A<B>();
}
【解决方案4】:

我一直在尝试找出一种方法来消除您第一种方法中的警告,但我想不出任何可行的方法。即便如此,我认为第一种方法是两害相权取其轻。不安全的强制转换比需要给你的类提供如此复杂的 api 更好。

一种完全独立的方法是覆盖 Object.clone() 并实现 Cloneable。

【讨论】:

  • 是的,我也在想“两害相权取其轻”。但是,找出这种使用泛型的方式是否接近它们的意图还是它只是一个有效的糟糕代码仍然很有趣。 Cloneable 无法解决我的问题,因为我需要复制到现有对象。
【解决方案5】:

这是第二种方法的最佳代码。它编译时没有任何警告。

import static org.junit.Assert.fail;

import org.junit.Test;
import org.junit.internal.runners.JUnit4ClassRunner;
import org.junit.runner.RunWith;

@RunWith(JUnit4ClassRunner.class)
public class TestGenerics {
    public static interface Copyable<T> {
        public void copy(T t);
    }

    public static class A<T extends A<T>> implements Copyable<T> {
        private String a = "--A--";
        public void copy(T t) {
            System.out.println(t.a);
        }
        @SuppressWarnings("unchecked")
        public static Copyable<Object> getInstance() {
            return new A();
        }

    }

    public static class B<T extends B<T>> extends A<T> {
        private String b = "--B--";
        public void copy(T t) {
            super.copy(t);
            System.out.println(t.b);
        }
        @SuppressWarnings("unchecked")
        public static Copyable<Object> getInstance() {
            return new B();
        }
    }


    @Test
    public void testCopy() {
        Copyable<Object> b1 = B.getInstance();
        Copyable<Object> b2 = B.getInstance();
        Copyable<Object> a = A.getInstance();
        b1.copy(b2); // this works as intended
        try {
            b1.copy(a); // this throws ClassCastException
            fail();
        } catch (ClassCastException cce) {
        }
    }
}

我还借助反思弄清楚了这个程序中发生的所有事情:

       for (Method method : A.class.getMethods()) {
               if (method.getName().equals("copy")) {
                       System.out.println(method.toString());
               }

       }
       for (Method method : B.class.getMethods()) {
               if (method.getName().equals("copy")) {
                       System.out.println(method.toString());
               }

       }

这是输出:

public void com.sbp.core.TestGenerics$A.copy(com.sbp.core.TestGenerics$A)
public void com.sbp.core.TestGenerics$A.copy(java.lang.Object)

public void com.sbp.core.TestGenerics$B.copy(com.sbp.core.TestGenerics$B)
public void com.sbp.core.TestGenerics$B.copy(com.sbp.core.TestGenerics$A)
public void com.sbp.core.TestGenerics$A.copy(java.lang.Object)

意思是:

  1. A 和 B 中的 copy(...) 方法使编译器生成“桥” - 每种方法有 2 种不同的方法,其中一种方法的参数类型来自 祖先(Reified T from Copyable 变成 Object,reified "T extends A" 从 A 变成 A) 这就是为什么它是覆盖而不是重载, 另一个具有用于定义类的具体参数类型。第一的 方法(带有自动生成的主体)向下转换它的参数来调用 第二(他们称之为桥梁)。由于这种沮丧,我们得到 如果我们调用 b1.copy(a),运行时会出现 ClassCastException。

  2. 看起来直接类型转换对我来说是更清洁和更好的工具 问题和泛型更好地用于其直接目的 - 强制编译时类型检查。

【讨论】:

    【解决方案6】:

    我学习了 Scala,现在我知道我在 2 年前想要的东西可以通过 逆变类型参数 和 Scala 的类型系统来实现:

    trait CopyableTo[-T] {
      def copyTo(t: T)
    }
    
    class A(private var a: Int) extends CopyableTo[A] {
      override def copyTo(t: A) {
        println("A:copy")
        t.a = this.a
      }
    }
    
    class B(private var a: Int, private var b: Int) extends A(a) with CopyableTo[B] {
      def copyTo(t: B) {
        println("B:copy")
        super.copyTo(t)
        t.b = this.b
      }
    }
    
    @Test
    def zzz {
      val b1 = new B(1, 2)
      val a1 = new A(3)
      val b2 = new B(4, 5)
      b1.copyTo(a1)
      a1.copyTo(b1)
      b1.copyTo(b2)
    }
    

    Java 类型系统对此太弱了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-01
      • 2014-05-23
      相关资源
      最近更新 更多