Sostituzione dei metodi generici Java
-
08-07-2019 - |
Domanda
Volevo creare un'interfaccia per copiare un oggetto in un oggetto di destinazione della stessa classe. Il modo semplice è usare il 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);
}
}
Ma ho anche trovato un modo per farlo usando i generici:
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);
}
}
Anche se l'unico modo che ho trovato per sbarazzarmi degli avvertimenti è l'annotazione. E sembra che qualcosa non vada. Quindi cosa c'è che non va? Posso accettare che qualcosa non va alla radice del problema. Quindi qualsiasi tipo di chiarimento è il benvenuto.
Soluzione 6
Ho imparato Scala e ora so che la cosa che volevo 2 anni fa avrebbe potuto essere raggiunta con il parametro di tipo contraddittorio e il sistema di tipi di 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)
}
Il sistema di tipi Java è troppo debole per questo.
Altri suggerimenti
Definizione dell'interfaccia:
public interface Copyable<T extends Copyable<T>> {
void copy(T copyFrom);
}
L'implementazione:
public class Example implements Copyable<Example> {
private Object data;
void copy(Example copyFrom) {
data = copyFrom.data;
}
//nontrivial stuff
}
Questo dovrebbe occuparsi dei tuoi avvertimenti.
Supponendo che non desideri sottoclassare ulteriormente, devi solo:
public static /*final*/ class AClass implements Copyable<AClass> {
Per una classe astratta, fai il " enum " cosa:
public static abstract class AClass<T extends AClass<T>> implements Copyable<T> {
In testCopy, uno degli avvertimenti è perché stai istanziando un " raw type " di Copyable anziché di alcuni Copyable concreti < T > ;. Dopo aver creato un'istanza di un oggetto Copiabile, può essere applicato solo a Ts (che include i sottotipi di T). Per creare un'istanza con un tipo formale, le definizioni delle classi dovranno essere leggermente modificate:
public static class A<T extends A> implements Copyable<T>
public static class B<T extends B> extends A<T>
Il prossimo numero è che un < B > può essere passato solo un tipo di compilazione in tempo B (basato sulla definizione di Copiabile). E testCopy () sopra sta passando un tipo di tempo di compilazione di Copia. Di seguito sono riportati alcuni esempi di ciò che funzionerà, con brevi descrizioni:
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>();
}
Continuo a cercare un modo per sbarazzarmi degli avvertimenti nel tuo primo approccio e non riesco a trovare nulla che funzioni. Anche così, penso che il primo approccio sia il minore dei due mali. Un cast non sicuro è meglio che dover dare alle tue classi un api così complicato.
Un approccio completamente separato sarebbe quello di sovrascrivere Object.clone () e implementare Cloneable.
Questo è il miglior codice possibile del secondo approccio. Si compila senza alcun avviso.
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) {
}
}
}
E ho anche capito tutto ciò che accade in questo programma con l'aiuto della riflessione:
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());
}
}
Ecco l'output:
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 che:
-
I metodi di copia (...) in A e B fanno sì che il compilatore generi " bridges " - 2 metodi diversi per ciascuno, uno con tipo di argomento reifed da antenato (T reificato da Copiabile diventa Oggetto, reificato " T si estende Un quot &; da A diventa A) ed è per questo che ha la precedenza e non il sovraccarico, e l'altro con tipo di argomento reificato per la definizione della classe. Primo metodo (con corpo autogenerato) downcasts il suo argomento per chiamare il secondo (lo chiamano un ponte). A causa di questo downcasting otteniamo ClassCastException in runtime se chiamiamo b1.copy (a).
-
Sembra che il casting di tipo diretto sia uno strumento più pulito e migliore per il mio problema e generici sono meglio usati nel loro scopo diretto - a applicare il controllo del tipo di tempo di compilazione.