Переопределение универсальных методов Java
-
08-07-2019 - |
Вопрос
Я хотел создать интерфейс для копирования объекта в объект назначения того же класса. Самый простой способ - использовать приведение:
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
Я изучил 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 слишком слаба для этого.
Другие советы
Определение вашего интерфейса:
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
}
Это должно позаботиться о ваших предупреждениях.
Предполагая, что вы не хотите создавать подклассы дальше, вам просто нужно:
public static /*final*/ class AClass implements Copyable<AClass> {
Для абстрактного класса вы делаете " enum " вещь:
public static abstract class AClass<T extends AClass<T>> implements Copyable<T> {
В testCopy одно из предупреждений заключается в том, что вы создаете экземпляр " raw type " Копируемый, а не какой-то конкретный Копируемый < 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>();
}
Я продолжаю пытаться найти способ избавиться от предупреждений при первом подходе, и я не могу придумать ничего, что работает. Тем не менее, я думаю, что первый подход - меньшее из двух зол. Небезопасное приведение лучше, чем необходимость давать вашим классам такой сложный API.
Совершенно отдельный подход заключается в переопределении Object.clone () и реализации Cloneable.
Это наилучший код второго подхода. Он компилируется без каких-либо предупреждений.
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)
Это означает, что:
<Ол>Методы copy (...) в A и B заставляют компилятор генерировать " bridges " - 2 разных метода для каждого, один с повторным типом аргумента от предок (reified T из Copyable становится Object, reified " T расширяется Quot &; из А становится А), и именно поэтому он переопределен, а не перегружен, а другой - с уточненным типом аргумента для определения класса. Первый метод (с автоматически сгенерированным телом) понижает свой аргумент для вызова второе (они называют это мостом). Из-за этого уныния мы получаем ClassCastException во время выполнения, если мы вызываем b1.copy (a).
Похоже, что прямое литье по типу чище и лучше проблема и дженерики лучше использовать по прямому назначению - для принудительно проверять тип времени компиляции.