Java 通用单例工厂模式

标签 java generics singleton

我有一段时间无法理解下面的难题。这是编译但抛出异常的代码片段

    Exception in thread "main" java.lang.ClassCastException: 
    TestGenericSingleton$$Lambda$1/303563356 cannot be cast to 
    TestGenericSingleton$IntegerConsumer
        at TestGenericSingleton.main(TestGenericSingleton.java:23)
import java.util.function.Consumer;

public class TestGenericSingleton 
{
    static final Consumer<Object> NOOP_SINGLETON = t -> {System.out.println("NOOP Consumer accepting " + t);};
    
    @SuppressWarnings("unchecked")
    static <R extends Consumer<T>, T> R noopConsumer() 
    {
        return (R)NOOP_SINGLETON;
    }
    
    static interface IntegerConsumer extends Consumer<Integer> {};
    
    public static void main(String[] argv) 
    {
        Consumer<Boolean> cb = noopConsumer();
        cb.accept(true);
        
        IntegerConsumer ic = t -> {System.out.println("NOOP Consumer2 accepting " + t);} ;
        ic.accept(3);
        
        ic = noopConsumer();
        ic.accept(3);
        System.out.println("Done");
    }
}

让我难过的是,Java 编译器可以从第 20 行的 lambda 中生成一个适当的 IntegerConsumer 兼容对象,但不能使用先前构造为第 8 行的单例的非泛型 lambda。这是因为第 20 行的 lambda 有一个 Consumer 的可具体化子类型,它立即适合 IntegerConsumer 引用的类型,而第 10 行的 lambda 不能在运行时转换为真正的 Consumer 子类型吗?但是第 8 行的通用有界类型声明不应该处理这个问题吗?非常感谢任何帮助!

最佳答案

the previously constructed non-generic lambda constructed as the singleton on line 8 can not be used

我们将删除 lambda 并了解异常的根本原因。让我们考虑以下更简单的示例。

public class TestGenericObject {

    static final Object NOOP_SINGLETON = new Object();

    static <R extends Object> R noopConsumer() {
        return (R) NOOP_SINGLETON;
    }

    public static void main(String[] argv) {
        Object cb = noopConsumer();
        Integer ic = noopConsumer(); // Throws java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.Integer
    }
}

异常(exception)是有道理的。 NOOP_SINGLETON 的实际类型是 Object,但我们试图将其转换为 Integer。这与尝试相同,Integer ic = (Integer) new Object() .在你的情况下,出于同样的原因你不能投 Consumer<Object>输入 IntegerConsumer .


一个有趣的观察是 noopConsumer() 中没有抛出异常。而是被扔在 main() 中.
以下是 javap -v -c 的输出对于方法 noopConsumer

... // Removed lines for clarity
 static <R extends java.lang.Object> R noopConsumer();
    ...
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #2                  // Field NOOP_SINGLETON:Ljava/lang/Object;
         3: areturn

您可以看到没有用于转换的操作代码。但是对于 main()

  public static void main(java.lang.String[]);
    Code:
      stack=1, locals=3, args_size=1
         0: invokestatic  #3                  // Method noopConsumer:()Ljava/lang/Object;
         3: astore_1
         4: invokestatic  #3                  // Method noopConsumer:()Ljava/lang/Object;
         7: checkcast     #4                  // class java/lang/Integer
        10: astore_2
        11: return

在这一行 7:checkcast #4 ,它检查返回的类型是否为整数。此行为是由于 2 个原因

  1. noopConsumer() ,R 的更严格的界限是 Object 并且 NOOP_SINGLETON 也是 Object 类型。因此,在类型删除后,强制转换是多余的并已删除。
  2. 原因main()由于 Type Erasure 再次进行类型转换检查.如链接中所述,如果需要,将在类型删除期间插入类型转换。

回到 Lambdas。 Lambda 使用 invokedynamic opcode 在运行时生成代码。 Thisthis是很好的资源,可以更好地了解运行时的 lambda 处理。 对于下面的代码,

public static void main(String[] argv) {
        Consumer<Object> NOOP_SINGLETON = t -> {System.out.println("NOOP Consumer accepting " + t);};
        TestGenericSingleton.IntegerConsumer ic = t -> {System.out.println("NOOP Consumer2 accepting " + t);} ;
    }

让我们分析字节码。

public static void main(java.lang.String[]);
   ...
    Code:
      stack=1, locals=3, args_size=1
         0: invokedynamic #2,  0              // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
         5: astore_1
         6: invokedynamic #3,  0              // InvokeDynamic #1:accept:()Lcom/TestGenericSingleton$IntegerConsumer;
        11: astore_2
        12: return

invokedynamic通过 2 种不同的预期类型 Ljava/util/function/Consumer()Lcom/TestGenericSingleton$IntegerConsumerLambdaMetafactory.metafactory() .
所以尽管代码 t -> {System.out.println("NOOP Consumer accepting " + t);}在 lambda 中是相同的,它们是 2 种不同的类型。


总结,lambda 表达式是在运行时构建的,返回的实例将具有声明中指定的类型。因此,NOOP_SINGLETON 的类型是 Consumer ic 的类型是 IntegerConsumer .从类型类型转换 ConsumerIntegerConsumer会因为同样的原因而失败,Integer ic = (Integer)new Object()失败。

关于Java 通用单例工厂模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64762477/

相关文章:

Typescript - 实现泛型类型的类实例

ios - 单例属性根据调用返回不同的值

java - 单例客户端应该如何使用单例?

ios - 播放声音后 childViewController 不释放

java - log4j2 在 Hadoop 二进制文件中给出 : java. io.IOException : Could not locate executable null\bin\winutils. exe

Java 泛型 ?扩展字符串

java - 无法摆脱循环

java - Hamcrest 泛型 hell #2 : iterableWithSize gives errror "is not applicable for the arguments"

java - Java将数据写入外部文件

java - 在 Tomcat/Coyote 上更改反斜杠行为