【问题标题】:Calling a Static Singleton Class from Junit test throws StackOverflow error从 Junit 测试调用静态单例类会引发 StackOverflow 错误
【发布时间】:2017-10-26 12:21:15
【问题描述】:

当我们运行 DrawerOpenTest.java 时,它会抛出 StackOverflow 错误,而我们希望测试用例能够通过。而当 DrawerOpen 类为单例时,assertThat(actualState, is(expectedState));在测试用例中也应该是正确的。

请注意,“状态”是与 3 个基本方法的接口。

DrawerOpenTest 类

import static org.hamcrest.CoreMatchers.*;
import org.junit.*;

public class DrawerOpenTest {

@Test
public void openCloseButtonPushedPositiveTest(){
    DVDPlayer cut = DVDPlayer.getInstance(DrawerOpen.getInstance());
    State expectedState = DrawerClosedNotPlaying.getInstance();
    State actualState = cut.openCloseButtonPushed();
    assertThat(actualState, is(sameInstance(expectedState)));
}
}

DrawerOpen 类

public class DrawerOpen implements State {

private DVDPlayer player = DVDPlayer.getInstance(DrawerOpen.getInstance());

private static State state;

private DrawerOpen() {}

public static State getInstance() {
    if(state == null)
        state = new DrawerOpen();
    return state;
}


@Override
    public void openCloseButtonPushed() {
        player.close();
        player.changeState(DrawerClosedNotPlaying.getInstance());
    }

@Override
public void playButtonPushed() {
player.close();
player.play();
player.changeState(DrawerClosedPlaying.getInstance());

}

@Override
public void stopButtonPushed()
}

}

DVDPlayer 类

public class DVDPlayer {
private DVDPlayer() {}
private static DVDPlayer player = null;

private State state;

public State getState() {
    return state;
}

public static DVDPlayer getInstance(State stateParam) {
    //making it singleton
    if(player == null)
    {   
        player = new DVDPlayer();
        player.state = DrawerClosedNotPlaying.getInstance();
    }
    else 
        player.state = stateParam;
    return player;      
}

public void changeState(State newState) {
    this.state=newState;        
}
public State openCloseButtonPushed(){
    state.openCloseButtonPushed();
    return state;
}

public State playButtonPushed() {
    state.playButtonPushed();
    return state;
}

public State stopButtonPushed() {
    state.stopButtonPushed();
    return player.state;
}
public void open() {
    System.out.println("DVDPlayer is opening.....");
}
public void close() {
    System.out.println("DVDPlayer is closing.....");

}
public void play() {
    System.out.println("DVDPlayer is playing.....");
}   
public void stop() {
    System.out.println("DVDPlayer is stopping.....");
}
}

结果:它会导致 StackOverflowError,因为它会不断初始化自身。请帮助如何使该测试成功通过。

java.lang.StackOverflowError
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)
    at DrawerOpen.getInstance(DrawerOpen.java:13)
    at DrawerOpen.<init>(DrawerOpen.java:5)

【问题讨论】:

  • "使其成为单例" 您实际上并不能保证这些是单例,除非您仅在单线程代码中使用它。
  • 确实我需要一个单线程模型。因为资源 DVDPlayer 无法处理多线程模型。

标签: java junit static singleton stack-overflow


【解决方案1】:

这个:

public class DrawerOpen implements State {

  private DVDPlayer player = DVDPlayer.getInstance(DrawerOpen.getInstance());

  // ...
}

表示每次尝试创建DrawerOpen时都会调用DrawerOpen.getInstance(),因为player是成员变量。

为了在DrawerOpen.getInstance() 中创建实例,您必须创建DrawerOpen 的实例;但是再次调用DrawerOpen.getInstance(),需要你创建一个DrawerOpen,它调用DrawerOpen.getInstance()...

您可以通过将DrawerOpen.getInstance() 分配给静态字段来避免这种递归调用:

public class DrawerOpen implements State {
  private static DrawerOpen INSTANCE = DrawerOpen.getInstance();

  private DVDPlayer player = DVDPlayer.getInstance(INSTANCE);

  // ...
}

但是,还有一个问题,就是你在调用DVDPlayer.getInstance(INSTANCE)的时候并没有分配INSTANCE,所以你最终会调用DVDPlayer.getInstance(null)

解决这个问题的一种方法是调用:

  private DVDPlayer player = DVDPlayer.getInstance(this);

但是,这是所谓的不安全发布的一个示例,其中您在DrawerOpen 类完全初始化之前泄漏了this 引用。这可能会导致更多意想不到的问题。


您的代码有点混乱。您应该仔细考虑一下为什么要使用单例,因为在我看来这些不像是单例属性。

例如,你有一个依赖于参数的“单例”有点狡猾:如果你用不同的State 实例调用DVDPlayer.getInstance,你当前忽略新的状态实例,并使用前一个。这很可能会导致令人困惑或令人惊讶的行为:我给了你 this 状态实例,但 DVDPlayer 正在使用 那个 状态实例。

【讨论】:

  • 感谢以上推荐。但这并不能解决问题,第一部分仍然给出 StackOverFlow 错误,但添加第二个建议会导致 DVDPlayer 的 getInstance() 方法出现编译器错误(因为我们正在删除静态字段)
【解决方案2】:

改变这两行:

private DVDPlayer player = DVDPlayer.getInstance(DrawerOpen.getInstance());

private DrawerOpen() {}

到这里:

private DVDPlayer player;

private DrawerOpen() {
    player = DVDPlayer.getInstance(this);
}

这将摆脱递归初始化循环。

解释:构造函数中的this 指的是正在构造的对象。这是DrawerOpen.getInstance() 返回的单例 ...如果对象是在代码中的那个点构建的。

【讨论】:

  • DrawerOpen.getInstance() 是静态的,但构造函数不是。所以出现编译错误。
  • 你在说什么? 1) 在我建议的修复中没有调用DrawerOpen.getInstance()。 2)构造函数可以调用静态方法! 3)我的回答说要替换 two 特定行。不是三行(或五行)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-19
  • 2013-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多