Question

Je voulais créer une interface pour copier un objet dans un objet de destination de la même classe. La méthode la plus simple consiste à utiliser le 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);
}
}

Mais j'ai aussi trouvé un moyen de le faire avec des génériques:

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

Bien que le seul moyen que j’ai trouvé pour me débarrasser des avertissements est l’annotation. Et on a l'impression que quelque chose ne va pas. Alors, qu'est-ce qui ne va pas? Je peux accepter que quelque chose ne va pas à la racine du problème. Toute clarification est donc la bienvenue.

Était-ce utile?

La solution 6

J'ai appris Scala et je sais maintenant que ce que je voulais il y a deux ans aurait pu être réalisé avec le paramètre de type contravariant et le système de types 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)
}

Le système de types Java est trop faible pour cela.

Autres conseils

Votre définition d'interface:

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

Votre implémentation:

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

Cela devrait prendre soin de vos avertissements.

En supposant que vous ne vouliez pas passer plus loin dans la sous-classe, il vous suffit de:

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

Pour une classe abstraite, vous faites la " enum " chose:

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

Dans testCopy, l'un des avertissements vient du fait que vous instanciez un & "type brut &"; Copiable plutôt que du concret Copiable < T > ;. Une fois que vous instanciez un objet à copier, il ne peut être appliqué qu’à Ts (qui comprend les sous-types de T). Pour instancier avec un type formel, les définitions de classe devront être légèrement modifiées:

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

Le problème suivant est qu’un < B > pouvant être copié; ne peut être passé qu’un type de compilation B (basé sur la définition de Copyable). Et testCopy () ci-dessus lui transmet un type copiable au moment de la compilation. Vous trouverez ci-dessous quelques exemples de ce qui fonctionnera, avec une brève description:

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

Je continue d'essayer de trouver un moyen de supprimer les avertissements lors de votre première approche et je ne peux rien proposer qui fonctionne. Malgré tout, je pense que la première approche est le moindre de deux maux. Mieux vaut donner un casting peu sûr que de devoir donner à vos cours une interface aussi compliquée.

Une approche complètement séparée consisterait à remplacer Object.clone () et à implémenter Cloneable.

C’est le meilleur code possible de deuxième approche. Il compile sans aucun avertissement.

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

Et aussi j'ai compris tout ce qui se passe dans ce programme avec l'aide de la réflexion:

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

       }

Voici la sortie:

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)

Cela signifie que:

  1. Les méthodes copy (...) en A et B permettent au compilateur de générer " ponts " - 2 méthodes différentes pour chacune, une avec le type d'argument reifed de ancêtre (T réifié de Copyable devient Object, réifié & "T s'étend Un & Quot; de A devient A) et c’est pourquoi il est prioritaire et non surchargé, et l'autre avec un type d'argument réifié pour définir la classe. Premier method (avec un corps auto-généré) décline son argument pour appeler le deuxième (ils appellent cela un pont). À cause de cette décadence nous obtenons ClassCastException au runtime si nous appelons b1.copy (a).

  2. On dirait que le transtypage direct est plus propre et constitue un meilleur outil pour mon problème et les génériques sont mieux utilisés dans leur but direct - à appliquer la vérification du type de temps de compilation.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top