从Junit测试调用静态Singleton类会引发StackOverflow错误

时间:2017-10-26 12:21:16

标签: java junit static singleton stack-overflow

当我们运行DrawerOpenTest.java时,它会抛出StackOverflow错误,而我们希望传递测试用例。当DrawerOpen类是singleton时,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 Class

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)

2 个答案:

答案 0 :(得分:0)

此:

public class DrawerOpen implements State {

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

  // ...
}

表示每次尝试创建DrawerOpen.getInstance()时都会调用DrawerOpen,因为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);

  // ...
}

然而,还有另一个问题,即您在致电INSTANCE时未能指定DVDPlayer.getInstance(INSTANCE),因此您最终会调用DVDPlayer.getInstance(null)

解决此问题的一种方法是致电:

  private DVDPlayer player = DVDPlayer.getInstance(this);

但是,这是所谓的 unsafe publication 的示例,其中您在this类完全初始化之前泄漏了DrawerOpen引用。这可能会导致进一步的意外问题。

你的代码有点混乱。你应该仔细考虑一下你为什么要使用单身人士,因为这些对我来说不像单身​​人士属性。

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

答案 1 :(得分:0)

更改这两行:

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

private DrawerOpen() {}

到此:

private DVDPlayer player;

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

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

说明:构造函数中的this引用正在构造的对象。这是DrawerOpen.getInstance()返回的 的单例...如果对象是在代码中的那个点构造的。