Reputation: 3568
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
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
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