Singleton Factory-使用Java 8实现

时间:2019-01-04 02:06:20

标签: java singleton generic-programming

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

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

下面是代码:c.cast(instance);行出现编译错误

我们不使用spring / dependency注入,而是尝试实现通用类来创建所有单例对象。

谢谢。

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

}

3 个答案:

答案 0 :(得分:5)

首先,我可以指出<T extends Object>可以仅用<T>来代替,因为Java中涉及泛型的所有事物都必须是一个对象。

您真正接近的第二部分是Class<?> 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){
        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);
        // or
        return (T) instance;
    }
}

如果您确实想要,可以将所有内容保留在原始代码中,并在方法末尾将实例转换为T,它应该可以工作。唯一的事情是您的方法调用看起来像SingletonFactory.getInstance<Foo>(Foo.class)而不是SingletonFactory.getInstance(Foo.class)。这是因为原始代码中的Class<?>而不是Class<T>

答案 1 :(得分:3)

首先,您需要更早地进行同步,您应该只同步该方法,否则,您可以在竞争条件下创建额外的实例。

第二,您应该像这样定义方法的泛型:

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

答案 2 :(得分:1)

首先,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);
    }  
}