【问题标题】:Singleton Factory - Implementation using Java 8单例工厂 - 使用 Java 8 实现
【发布时间】:2019-01-04 02:06:20
【问题描述】:

我想实现一个通用的单例工厂模式,其中我将所需对象的类作为参数传递,工厂类应该检查映射是否已经为它创建了一个对象,如果是,则从映射返回对象.如果没有,则创建一个新实例,将其放入地图并返回该实例。

我可以将通用返回类型作为 Object,但我不想在调用 get 实例方法的每个地方都强制转换返回的对象。

以下是代码:我在c.cast(instance); 行收到编译错误

我们不使用弹簧/依赖注入,而是尝试实现公共类来处理创建所有单例对象。

public class SingletonFactory {
    public static Map<String,Object> objectFactory = new HashMap<String, Object>();

    public static <T extends Object> T getInstance(Class<?> c){
        String key = c.toString();
        Object instance= objectFactory.get(key);
        if (instance == null) {
            synchronized (c) {
                try {
                    instance = c.newInstance();
                    objectFactory.put(key, instance);
                } catch(IllegalAccessException | InstantiationException e) {
                    throw new RuntimeException("Exception while creating singleton instance for class : "+key+" - Exception Message : "+e);
                }
            }
        }
        return c.cast(instance);
    }
}

【问题讨论】:

  • 你希望从函数中返回泛型,但实例是一个非泛型的对象
  • 是的,所以只是想弄清楚如何将我的地图声明为具有泛型类型。是否至少可以实现这样的工厂。我想我很接近,但不知道如何编写该代码。
  • 你应该把Class&lt;?&gt;改成Class extends T> 所以当你进行强制转换时,它会将实例强制转换为 T

标签: java singleton generic-programming


【解决方案1】:

首先,我可以指出&lt;T extends Object&gt; 可以只替换为&lt;T&gt;,因为Java 中涉及泛型的所有内容都必须是一个对象。

您真正了解的第二部分是Class&lt;?&gt; c。这就是说你可以传入任何类,它会返回T 的任何类型。 c.cast(instance) 可以替换为 (T) instance,如果您认为这样看起来更好,但实际上有一个区别,这里有更详细的说明:Java Class.cast() vs. cast operator

最终代码如下所示:

public class SingletonFactory {
    public static Map<String,Object> objectFactory = new HashMap<String, Object>();

    public static <T> T getInstance(Class<T> c){
        synchronized (c) {
            String key = c.toString();
            Object instance= objectFactory.get(key);
            if (instance == null) {
                try {
                    instance = c.newInstance();
                    objectFactory.put(key, instance);
                } catch(IllegalAccessException | InstantiationException e){
                    throw new RuntimeException("Exception while creating singleton instance for class : "+key+" - Exception Message : "+e);
                }
            }
            return c.cast(instance);
            // or
            return (T) instance;
        }
    }
}

此外,如果您真的愿意,您可以将所有内容保留在原始代码中,并在方法结束时将实例强制转换为 T,它应该可以工作。唯一的问题是您的方法调用看起来像SingletonFactory.getInstance&lt;Foo&gt;(Foo.class) 而不是SingletonFactory.getInstance(Foo.class)。这是因为原始代码中的 Class&lt;?&gt; 而不是 Class&lt;T&gt;

编辑:我还更改了代码以提前同步感谢@Khan9797

【讨论】:

  • 由于您使用延迟初始化,我建议您使用双重检查锁定。两个线程有​​可能检查实例值是否为空。如果实例变量为空,他们都尝试创建。所以你最终会为给定的键覆盖哈希图。
  • @Khan9797 你是对的。我编辑了我的答案以更早地同步。我可以在类对象上同步,还是应该只同步方法本身?还是有关系?如果我保持原样,通过在类上同步,在编辑 HashMap 时我是否也必须有一些锁定?
【解决方案2】:

首先,你需要提前同步,你应该简单地同步方法,否则,你可以在竞争条件下创建额外的实例。

其次,你应该像这样定义方法的泛型:

public static <T> T getInstance(Class<? extends T> c)

【讨论】:

  • 我不必将同步提升到方法级别 - 我只想在找不到该类的实例时同步该类。我认为没有必要锁定整个方法。
  • 两个线程可以查看 if(instance == null) 并假设实例为 null 并创建两个对象
  • 为什么需要Class&lt;? extends T&gt; c?在 HashMap 中,它们都存储为 Object,而不是 T
【解决方案3】:

首先,getInstance() 在创建新实例方面不是线程安全的。当多个线程同时运行并且variable == nulltrue 时,您有可能创建给定类的多个实例。

public class SingletonFactory {
    private static Map<Class, Object> objectHolder = new HashMap<>();

    public <T> T getInstance(Class<T> clazz) {
        Object instance = objectHolder.get(clazz);
        if(instance == null) {
            synchronized (clazz) {
                if(instance == null) {
                    try{
                        instance = clazz.newInstance();
                        objectHolder.put(clazz, instance);
                    } catch (Exception e) {
                        // do some logging and maybe exit the program. Since the it would affect how whole system works.
                    }                            
                }
            }
        }

        return clazz.cast(instance);
    }  
}

但更好的方法是使用急切初始化而不是延迟初始化。我们需要同步临界区的原因是我们在需要时创建这些实例。所以它变成了readers-writers 问题。但是如果我们只做读取过程,那么我们不需要同步,因为我们不会修改它的值。如果您知道将要创建并需要访问的所有类,我们可以首先初始化它们。这样我们就可以摆脱 synchronized 的性能缺陷

public class SingletonFactory {
    private static Map<Class, Object> objectHolder = new HashMap<>();

    private Map<Class, Object> initialize() {
        Map<Class, Object> objectHolder = new HashMap<>();
        // create some objects and put it into Map
        return objectHolder;        

    }           

    public <T> T getInstance(Class<T> clazz) {
        Object instance = objectHolder.get(clazz);            
        return clazz.cast(instance);
    }  
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-30
    相关资源
    最近更新 更多