覆盖Java泛型方法

时间:2009-03-10 20:01:57

标签: java generics contravariance

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

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);
    }
}

虽然我发现摆脱警告的唯一方法是注释。而且感觉有些不对劲。 那有什么不对?我可以接受问题的根源是错误的。所以欢迎任何形式的澄清。

6 个答案:

答案 0 :(得分:4)

您的界面定义:

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
}

这应该照顾你的警告。

答案 1 :(得分:3)

假设你不想进一步继承,你只需要:

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

对于抽象类,您执行“枚举”操作:

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

答案 2 :(得分:2)

在testCopy中,其中一个警告是因为你实例化了一个可复制的“原始类型”而不是一些具体的可复制&lt; T&gt;。一旦实例化了Copyable,它就只能应用于Ts(包括T的子类型)。为了使用正式类型进行实例化,需要稍微更改类定义:

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

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

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>();
}

答案 3 :(得分:0)

我一直试图找出摆脱第一种方法警告的方法,但我无法想出任何有效的方法。即便如此,我认为第一种方法是两种罪恶中较小的一种。不安全的演员阵容比需要为你的课程提供如此复杂的api更好。

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

答案 4 :(得分:0)

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

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中的复制(...)方法使编译器生成“桥” - 每种方法有2种不同的方法,一种方法有reifed参数类型 祖先(来自Copyable的具体T变为对象,具体化为“T延伸” “来自A成为A”,这就是为什么它超越而不是过载, 另一个用于定义类的reified参数类型。第一 方法(使用自动生成的主体) downcasts 其参数调用 第二(他们称之为桥梁)。由于这种向下倾斜,我们得到了 运行时的ClassCastException如果我们调用b1.copy(a)。

  2. 看起来直接式铸造更清洁,更适合我的工具 问题和泛型更好地用于他们的直接目的 - 强制执行编译时类型检查。

答案 5 :(得分:0)

我已经学会了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类型系统太弱了。