Pregunta

Quería crear una interfaz para copiar un objeto a un objeto de destino de la misma clase. La forma más sencilla es usar el casting:

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

Pero también he encontrado una forma de hacerlo usando genéricos:

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

Aunque la única forma que he encontrado para deshacerme de las advertencias es la anotación. Y se siente como si algo estuviera mal. ¿Así que qué hay de malo? Puedo aceptar que algo anda mal en la raíz del problema. Así que cualquier tipo de aclaración es bienvenida.

¿Fue útil?

Solución 6

Aprendí Scala y ahora sé que lo que quería hace 2 años podría haberse logrado con el parámetro de tipo contravariante y el sistema de tipos de 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)
}

El sistema de tipos Java es demasiado débil para esto.

Otros consejos

Definición de su interfaz:

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

Su implementación:

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

Eso debería ocuparse de sus advertencias.

Suponiendo que no desea subclasificar más, solo necesita:

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

Para una clase abstracta, haces " enum " cosa:

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

En testCopy, una de las advertencias es porque estás instanciando un " tipo sin formato " de Copiable en lugar de algo concreto Copiable < T > ;. Una vez que crea una instancia de Copiable, solo se puede aplicar a Ts (que incluyen subtipos de T). Para crear instancias con un tipo formal, las definiciones de clase deberán cambiarse ligeramente:

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

El siguiente problema es que un Copiable < B > solo se puede pasar un tipo de B en tiempo de compilación (basado en la definición de Copiable). Y testCopy () anterior le está pasando un tipo de Copiable en tiempo de compilación. A continuación hay algunos ejemplos de lo que funcionará, con breves descripciones:

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

Sigo tratando de encontrar una manera de deshacerme de las advertencias en su primer enfoque y no puedo encontrar nada que funcione. Aun así, creo que el primer enfoque es el menor de dos males. Un reparto inseguro es mejor que la necesidad de dar a sus clases una API tan complicada.

Un enfoque completamente separado sería anular Object.clone () e implementar Cloneable.

Este es el mejor código posible de segundo enfoque. Se compila sin advertencias.

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

Y también descubrí todo lo que sucede en este programa con ayuda de la reflexión:

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

       }

Aquí está el resultado:

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)

Significa que:

  1. Los métodos de copia (...) en A y B hacen que el compilador genere " bridges " - 2 métodos diferentes para cada uno, uno con tipo de argumento refinado de ancestro (la T reificada de Copiable se convierte en Objeto, reified " T se extiende A & Quot; de A se convierte en A) y es por eso que se anula y no se sobrecarga, y el otro con el tipo de argumento reificado para definir la clase. primero método (con cuerpo autogenerado) downcasts su argumento para llamar al segundo (lo llaman puente). Debido a este abatimiento obtenemos ClassCastException en tiempo de ejecución si llamamos a b1.copy (a).

  2. Parece que la conversión directa es una herramienta más limpia y mejor para mi problema y genéricos se utilizan mejor en su propósito directo: hacer cumplir la verificación del tipo de tiempo de compilación.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top