Java 构造函数newInstance仅生成本地实例

Java 构造函数newInstance仅生成本地实例,java,reflection,Java,Reflection,看起来我在测试中遗漏了一些东西(Robolectrics | Powermockito) 我有以下关于Singleton的课程: public class Core{ private static Core instance = new Core(); public static Core getInstance() { return instance; } public static void destroy(){ instance

看起来我在测试中遗漏了一些东西(Robolectrics | Powermockito)

我有以下关于Singleton的课程:

public class Core{

   private static Core instance = new Core();

   public static Core getInstance() {
        return instance;
    }   

   public static void destroy(){
    instance = null;
   } 
}
在我的测试中,我使用
Core.destroy()
因此
Core.getInstance()
返回
null

因此,我想再次重新生成
实例的每个测试。我做了以下工作:

Constructor<Core> constructor = Core.class.getDeclaredConstructor();
                constructor.setAccessible(true);
                Core newCore = constructor.newInstance();
Constructor=Core.class.getDeclaredConstructor();
constructor.setAccessible(true);
Core newCore=constructor.newInstance();
所以现在
newCore
已经初始化,但是
Core.getInstance()
仍然返回
null


如何正确初始化核心
->
实例

这个模式怎么样

public class Core{

   private static Core instance;

   public static Core getInstance() {
        if(instance == null) instance = new Core();
        return instance;
    }   

   public static void destroy(){
    instance = null;
   } 
}

如果您只想在测试中销毁,您可以从destroy()方法中删除“public”

首先,您要使核心构造函数可访问,但默认情况下它已经是public了

其次,当您调用构造函数时,它只是创建了一个新的Core实例,它对实例没有任何作用,因为默认情况下创建的构造函数是空的,而且构造函数不是初始化Singleton的地方

若你们想刷新单例实例,你们应该有一个专门的方法

public class Core {

    private static class SingletonHolder {
        private static AtomicReference<Core> instance = new AtomicReference(new Core());
    }

    public static Core getInstance() {
        return SingletonHolder.instance.get();
    }   

    public static void destroy() {
        SingletonHolder.instance.set(null);
    } 

    public static void reset() {
        SingletonHolder.instance.compareAndSet(null, new Core());
    } 
}
因此,使用是安全的

Core.getInstance().ifPresent(core -> { ... core ... });

您应该使构造函数
私有
,这样使用singleton类的代码就不能使用它创建实例,他们应该只使用
getInstance()
方法获取实例

此外,单例对象的生存期通常与JVM相关联,因为每个JVM都应该有一个单例类的实例。因此,如果您可以销毁并重新创建实例,那么它就不是真正的Singleton IMO,因此我假设您只想重新创建用于测试的实例

要在调用
destroy()
方法后从测试类中重新创建单例,可以获取具有类实例的类的
字段。使用该
字段
可以将其设置为您创建的新实例:

public static void main(String[] args) throws Exception {
        System.out.println(Core.getInstance()); //gets instance
        Core.destroy();
        System.out.println(Core.getInstance()); // null
        reinitializeInstance(Core.class);
        System.out.println(Core.getInstance()); //gets instance
 }

public static void reinitializeInstance(Class<Core> clazz) {
    try {
        Constructor<Core> constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        Core newCore = constructor.newInstance();

        Field field = Core.class.getDeclaredField("instance"); //gets the instance field
        field.setAccessible(true);
        field.set(newCore, newCore);

    } catch (Exception e) {
        e.printStackTrace();
    }
}

在谈论单身人士时,我经常试图向人们解释一个重要的观点:

单例和只创建一个实例的东西之间有区别。而且,通常,当您认为您需要一个单例时,实际上您只需要创建一个实例

这两件事之间的区别一开始可能并不明显,但要认识到这一点很重要,特别是当您发现自己处于需要在测试之间清除单例内部状态的位置时

  • 如果您有一个单例—一个真正的单例—根据定义,JVM中可以存在一个实例。如果这个状态是可变的,这是有问题的,因为这意味着您必须关心这个状态。在测试中,您必须清除运行之间的状态,以消除由于测试执行顺序而产生的任何影响;您必须连续运行测试

  • 如果您使用依赖项注入(如中所示,而不是任何特定的框架,如Guice、Dagger、Spring等),那么使用该实例的类并不重要:您作为该类的客户机,可以控制其生命周期。因此,尽管您的生产代码在所有地方都使用相同的实例,但您的测试代码可以使用单独的实例-因此它们是解耦的-并且通常您甚至不必担心清理状态,因为您的下一个测试用例可以简单地创建类的新实例

因此,不要像这样使用
Core
类编写代码:

class MyClass {
  void foo() {
    Core core = Core.getInstance();
    // ... do stuff with the Core instance.
  }
}
你可以这样写:

class MyClass {
  private final Core core;

  MyClass(Core core) { this.core = core; }

  void foo() {
    // ... do stuff with the Core instance.
  }
}
您已经打破了
MyClass
Core
之间的静态绑定。您可以在带有
Core
单独实例的测试中实例化
MyClass

MyClass myClass = new MyClass(new Core());
// Assert something...
Core core = new Core();
MyClass myClass = new MyClass(core);
MyOtherClass myOtherClass = new MyOtherClass(core);
// Assert something...
或者,如果多个实例需要与
核心的同一实例交互:

MyClass myClass = new MyClass(new Core());
// Assert something...
Core core = new Core();
MyClass myClass = new MyClass(core);
MyOtherClass myOtherClass = new MyOtherClass(core);
// Assert something...

为什么不
publicstaticvoidreplaceanewistance(){instance=newcore();}
?@AndyTurner我不想为测试目的编写方法。那不是单身汉。不使用公共构造函数。@您也不想为了测试目的而编写脆弱、不完整的反射代码。实际上是否需要
destroy()
方法?我不明白为什么你的应用程序代码会调用它。@Kayaman是的,销毁只用于演示。我在测试中使用core=Class.forName(core.Class.getName());setStatic(core.getDeclaredField(“实例”),null)
True,但可以通过在destory()和getInstance()上进行同步来实现,或者可能无法在静态方法上设置?我的Java有点过时了。可以在静态方法上设置同步,但这可能会影响性能,还有其他解决方案。一切都取决于情况。@Holger是的,只要我不公开这些成员,就没有必要将其切换回去。@Holger,来自javadocs的复制错误,意在
compareAndSet
。更正。添加了忽略结果布尔值的注释。