John McClane
John McClane

Reputation: 3568

How to pass a generic interface to a method where it is reified?

I have a generic interface

public interface MyInterface<T> {
    T method(T input);
}

and a couple of implementations of it, via ordinary classes like

public class MyClass<T> implements MyInterface<T> {
    @Override
    public T method(T input) {
        T output = input; // whatever
        return output;
    }
}

and anonymous classes (see below). Now I want to test these implementations:

class TestClass1 {
    // ...
}

class TestClass2 {
    final int n;
    final String s;

    TestClass2(int n, String s) {
        this.n = n;
        this.s = s;
    }
    // ...
}

public class TestGenericImplementation {
    private static <T> void makeTest(T testObject, MyInterface<T> impl) {
        T output = impl.method(testObject);
        if (output == null)
            throw new NullPointerException();
        // verify output further
    }

    // Question 1. How to specify the parameter here properly?
    public static void testImplementation(MyInterface impl) {
        // Question 2. How to avoid compiler warning about unchecked cast below?

        // Doesn't work if impl is of type MyInterface<?> above
        makeTest(new TestClass1(), impl);
        makeTest(new TestClass2(1, "ABC"), impl); 

        // Ugly typecasts. Compiler complains.
        makeTest(new TestClass1(), (MyInterface<TestClass1>) impl);  
        makeTest(new TestClass2(1, "ABC"), (MyInterface<TestClass2>) impl); 
    }

    public static void main(String[] args) {
        // Question 3. How to pass the interface argument here?

        // Works but issues compiler warning about raw type
        testImplementation(new MyClass());
        testImplementation(new MyInterface() {
            @Override
            public Object method(Object input) {
                return null; // whatever
            }
        });

        // Looks ugly
        testImplementation(new MyClass<Object>()); 
        testImplementation(new MyInterface<Object>() {
            @Override
            public Object method(Object input) {
                return null;
            }
        });

        /* Diamond operator appeared only in Java 7,
         * while generics were introduced in Java 5.
         * What was the recommended way to solve this problem between 2004 and 2011?
         * Besides that, this doesn't work for anonymous classes.
         */
        testImplementation(new MyClass<>()); 
        testImplementation(new MyInterface<>() { // Doesn't work
            @Override
            public Object method(Object input) {
                return null;
            }
        });

        testImplementation(x -> x); // Lambda exprssions are for simple cases only
    }
}

The problem is that compiler is issuing a series of errors and warnings due to transition from a generic interface to its reified versions (those I need to use with the concrete classes TestClass1 and TestClass2 in place of the generic type variable T). Is it possible to avoid these warnings completely? If not (i.e. if they can only be suppressed), are there any pitfalls arising from this?

Upvotes: -1

Views: 189

Answers (2)

John McClane
John McClane

Reputation: 3568

This is how I've solved it.

/* This utility method bears the brunt.
 * It shows that the cast is safe for this particular interface.
 * It is recommended to explain why. Example is given below. */
@SuppressWarnings("unchecked")
public static <T> MyInterface<T> reify(MyInterface<?> impl) {
    /* Safe because instances of MyInterface<T> doesn't suppose to hold
     * objects of type T (directly or indirectly) between invocations
     * of its methods. */
    return (MyInterface<T>) impl;
}

// Use a wildcard type in the signature
public static void testImplementation(MyInterface<?> impl) {
    // No warnings now
    makeTest(new TestClass1(), reify(impl));
    makeTest(new TestClass2(1, "ABC"), reify(impl));
}

public static void main(String[] args) {
    // Use the diamond operator for ordinary classes
    testImplementation(new MyClass<>());
    // Use lambda expressions when possible
    testImplementation(x -> x);
    /* Anonymous classes still require explicit type
    (Object in this case, Bound when the type variable is bounded
    at the interface definition: MyInterface<T extends Bound> */
    testImplementation(new MyInterface<Object>() {
        @Override
        public Object method(Object input) {
            return input;
        }
    });
}

Still requires one @SuppressWarnings but concentrates all unsafe operations in one place where it is to be explained why the suppression is safe.

If someone has got a better solution please let me know.

Upvotes: 0

Michael
Michael

Reputation: 44250

<T> void makeTest(T testObject, Test.MyInterface<T> impl)

expects T to be the same for both arguments.

public static void testImplementation(Test.MyInterface impl) {
    makeTest(new Test.TestClass1(), impl);
    makeTest(new Test.TestClass2(1, "ABC"), impl);
    //...

This presents a contradiction. In the first case T will be TestClass1, in the second it will be TestClass2. If you were to use the generic version of Test.MyInterface, there is no type that can possibly satisfy it. You're only getting away with this because you're using raw types. It can't be Test.MyInterface<TestClass1> and Test.MyInterface<TestClass2> simultaneously.

You need to get rid of the testImplementation method and stop using raw types. The first part of your main method might look like:

public static void main(String[] args) {

    makeTest(new Test.TestClass1(), new Test.MyClass<>());
    makeTest(new Test.TestClass2(1, "ABC"), new Test.MyClass<>());

Upvotes: 0

Related Questions