tags:

views:

1217

answers:

5

I wanted to create an interface for copying an object to a destination object of the same class. The simple way is to use 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);
}
}

But also i've found a way it can be done using generics:

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

Though the only way i've found to get rid of warnings is the annotation. And it feels like something is wrong. So what's wrong? I can accept that something is wrong in the root of the problem. So any sort of clarification is welcome.

+2  A: 

Assuming you don't want to subclass further you just need:

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

For an abstract class, you do the "enum" thing:

public static abstract class AClass<T extends AClass<T>> implements Copyable<T> {
Tom Hawtin - tackline
Your code looks similar to what i've posted. But i still don't understand 2 things. First: what and how should i call to make it go without warnings? Second: is my way of using generics suitable for this case or does it look just like bad code that happens to work as intended?
Oleg Galako
And also, what does ["enum" thing] mean?
Oleg Galako
You would call constructr the first example with `new AClass`. For the second example you would subclass using a form similar to the first. The enum thing - look at the definition of java.lang.Enum (Enum<E extends Enum<E>>).
Tom Hawtin - tackline
Thanks! Your pointer to java.lang.Enum (Enum<E extends Enum<E>> helped me to find more useful info on this subject.
Oleg Galako
A: 

I keep trying to figure out a way to get rid of the warnings in your first approach and I can't come up with anything that works. Even so, I think the first approach is the lesser of two evils. An unsafe cast is better than needing to give your classes such a complicated api.

A completely separate approach would be to override Object.clone() and implement Cloneable.

Alex Rockwell
Yes, i'm thinking the same about 'lesser of two evils'. But still its interesting to find out if this way of using generics is close to what they are intented for or is it just a bad code that works.Cloneable wont solve my problem because i need to copy to an existing object.
Oleg Galako
+1  A: 

In testCopy, one of the warnings is because you're instantiating a "raw type" of Copyable rather than some concrete Copyable<T>. Once you instantiate a Copyable, it can only be applied to Ts (which include subtypes of T). In order to instantiate with a formal type, the class definitions will need to be changed slightly:

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

The next issue is that a Copyable<B> can only be passed a compile-time type of B (based on the definition of Copyable). And testCopy() above is passing it a compile-time type of Copyable. Below are some examples of what will work, with brief descriptions:

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>();
}
Justin C
A<A>, A<B> etc give the same warning, because types in brackets are still generics
Oleg Galako
A: 

Your interface definition:

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

Your implementation:

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

That should take care of your warnings.

wowest
Yes, this is what i've started from, but now how do you extend the Example class to keep copy(...) working the same way?
Oleg Galako
A: 

This is the best possible code of second approach. It compiles without any warnings.

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

And also i figured out all that happens in this program with help of reflection:

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

       }

Here is the 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)

It means that:

  1. The copy(...) methods in A and B make compiler generate "bridges" - 2 different methods for each, one with reifed argument type from ancestor (reified T from Copyable becomes Object, reified "T extends A" from A becomes A) and that is why it's override and not overload, and the other one with reified argument type for defining class. First method (with autogenerated body) downcasts its argument to call the second (they call it a bridge). Because of this downcasting we get ClassCastException in runtime if we call b1.copy(a).

  2. It looks like direct type casting is cleaner and better tool for my problem and generics are better used in their direct purpose - to enforce compile time type checking.

Oleg Galako