【问题标题】:How to pass a generic interface to a method where it is reified?如何将通用接口传递给它被具体化的方法?
【发布时间】:2018-12-18 15:09:05
【问题描述】:

我有一个通用接口

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

以及它的几个实现,通过像这样的普通类

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

和匿名类(见下文)。现在我想测试这些实现:

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

问题是编译器由于从通用接口转换到其具体版本而发出一系列错误和警告(我需要使用具体类 TestClass1TestClass2 来代替泛型类型变量T)。是否可以完全避免这些警告?如果不是(即只能被压制),是否会因此产生任何陷阱?

【问题讨论】:

  • Java 中的泛型永远不会被具体化。什么意思?
  • @Michael 我的意思是在testImplementation() 中从T 转换到TestClass1TestClass2。如果这里 reify 是一个错误的术语,请纠正我。

标签: java generics reification


【解决方案1】:
<T> void makeTest(T testObject, Test.MyInterface<T> impl)

期望两个参数的 T 相同。

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

这提出了一个矛盾。在第一种情况下,T 将是TestClass1,在第二种情况下,它将是TestClass2。如果您要使用Test.MyInterface 的通用版本,则没有任何类型可以满足它。你只是逃脱了这个,因为你使用的是原始类型。不能同时是Test.MyInterface&lt;TestClass1&gt;Test.MyInterface&lt;TestClass2&gt;

您需要摆脱 testImplementation 方法并停止使用原始类型。您的 main 方法的第一部分可能如下所示:

public static void main(String[] args) {

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

【讨论】:

  • testImplementation() 方法是不可避免的,因为它结合了针对特定MyInterface 实现的一堆测试,并在最后输出最终结果。如果我需要对 10 种不同的实现进行 10 次测试怎么办?将 100 个 makeTest() 调用插入到 main()?
  • 我在问题中提到使用&lt;?&gt; 通配符。也许testImplementation(MyInterface&lt;?&gt; impl) 会是更好的方法签名?
  • 这并不能解决逻辑矛盾的事实。
【解决方案2】:

我就是这样解决的。

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

仍然需要一个@SuppressWarnings,但将所有不安全的操作集中在一个地方,以便解释为什么压制是安全的。

如果有人有更好的解决方案,请告诉我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-26
    • 2011-02-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多