覆盖Java的泛型方法

3

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

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

虽然我发现唯一摆脱警告的方法是注释掉相关代码,但这让人感觉不对劲。

那么到底出了什么问题呢?我可以接受在问题的根源存在某些问题。所以任何澄清都是受欢迎的。


你在哪一行收到警告?警告内容是什么? - Michael Myers
testCopy()函数内的所有三行都会警告到泛型类型应该被参数化。 - Oleg Galako
6个回答

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
}

那应该可以解决你的警告问题了。

是的,这就是我开始的地方,但现在你如何扩展Example类以使copy(...)以相同的方式工作? - Oleg Galako

3
假设您不想进一步进行子类化,您只需要这样做:
public static /*final*/ class AClass implements Copyable<AClass> {

对于一个抽象类,您可以进行"枚举"操作:
public static abstract class AClass<T extends AClass<T>> implements Copyable<T> {

你的代码看起来和我发布的很相似。但是我仍然不明白两件事。第一:我应该如何调用以避免警告?第二:我使用泛型的方式是否适合这种情况,还是它只是看起来像是能正常工作的糟糕代码? - Oleg Galako
还有,["enum"这个东西]是什么意思? - Oleg Galako
你可以使用 new AClass 来调用第一个示例中的构造函数。对于第二个示例,你可以使用类似于第一个示例的形式进行子类化。关于枚举,请查看 java.lang.Enum 的定义(Enum<E extends Enum<E>>)。 - Tom Hawtin - tackline
谢谢!你提供的 java.lang.Enum(Enum <E extends Enum <E>>)指针帮助我找到了更多有用的关于这个主题的信息。 - Oleg Galako

2

在testCopy中,其中一个警告是因为您实例化了“原始类型”Copyable,而不是某个具体的Copyable<T>。一旦您实例化了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> 只能传递 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>();
}

A<A>、A<B>等都会给出相同的警告,因为括号中的类型仍然是泛型。 - Oleg Galako

0

我学会了Scala,现在我知道两年前我想要的东西可以通过逆变类型参数和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的类型系统对此太弱了。


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中的copy(...)方法使编译器生成“桥接”-每个方法都有两种不同的形式,一种是从祖先(Copyable的reified T变成Object,A的reified "T extends A"变成A)继承的具体化参数类型,因此它是覆盖而不是重载,另一种是为定义类具体化参数类型。第一个方法(带有自动生成的主体)将其参数向下转换以调用第二个方法(他们称之为桥)。由于这种向下转换,如果我们调用b1.copy(a),就会在运行时出现ClassCastException。

  2. 直接类型转换似乎是解决我的问题的更清晰、更好的工具,泛型最好用于它们的直接目的-强制执行编译时类型检查。


0

我一直在努力想出一种方法来消除你的第一种方法中出现的警告,但是我无法想出任何有效的解决方案。即便如此,我认为第一种方法比两害相权取其轻更加明智。不安全的转换总比需要为你的类提供如此复杂的API要好。

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


是的,我对“两害相权取其轻”也有同样的想法。但是,找出使用泛型的这种方式是否接近其预期用途还是只是一个能够工作的糟糕代码,这仍然很有趣。 Cloneable无法解决我的问题,因为我需要复制到现有对象。 - Oleg Galako

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接