Java8 - "effectively final"

4

我是使用RxVertx(一种类似于RxJava的技术)和Java8进行开发,但出现了编译错误。

这是我的代码:

public rx.Observable<Game> findGame(long templateId, GameModelType game_model, GameStateType state) {

return context.findGame(templateId, state)
    .flatMap(new Func1<RxMessage<byte[]>, rx.Observable<Game>>() {

        @Override
        public Observable<Game> call(RxMessage<byte[]> gameRawReply) {

            Game game = null;

            switch(game_model) {

                case SINGLE: {

                    ebs.subscribe(new Action1<RxMessage<byte[]>>() {

                        @Override
                        public void call(RxMessage<byte[]> t1) {

                            if(!singleGame.contains(0) {
                                game = new Game();       // ERROR is at this line
                                singleGames.put(0, game);
                            } else {
                              game = singleGames.get(0); // ERROR is at this line
                            }
                        }
                    });
                }
            }

            return rx.Observable.from(game);
        }
    });
}

编译错误为:“定义在封闭作用域中的局部变量game必须是final或有效final”。由于我在函数末尾进行了分配、设置并返回'game',所以我无法将其定义为final。如何使这段代码编译通过?谢谢。

1
public void call会立即执行吗?我不确定这段代码是否按照你的预期运行。 - Sebas
可能是重复的问题:最终局部变量无法分配 - user11153
Sebas:你可能是对的,我刚接触RxJava,发现它很难理解。但我主要是一次解决一个问题 :-) - Shvalb
你确定 game = singleGames.get(0)rx.Observable.from(game) 之前执行吗?看起来似乎不是这样。 - talex
你可以使用AtomicReference,但是其他人已经指出,在这种情况下使用它是可疑的。 - Stuart Marks
显示剩余2条评论
6个回答

7
我有一个“Holder”类,我在这种情况下使用它。
/**
 * Make a final one of these to hold non-final things in.
 *
 * @param <T>
 */
public class Holder<T> {
  private T held = null;

  public Holder() {
  }

  public Holder(T it) {
    held = it;
  }

  public void hold(T it) {
    held = it;
  }

  public T held() {
    return held;
  }

  public boolean isEmpty() {
    return held == null;
  }

  @Override
  public String toString() {
    return String.valueOf(held);
  }

}

然后你可以执行以下操作:

final Holder<Game> theGame = new Holder<>();
...

theGame.hold(myGame);
...
{
  // Access the game through the `final Holder`
  theGame.held() ....

不错的干净简单封装。 - Shvalb
好的,在仔细研究了您的解决方案后 - 我选择了它,因为它更加简洁和健壮。谢谢! - Shvalb
2
对于 toString 方法,你实际上可以使用 String.valueOf(held),如果字符串为 null,则返回 "null",否则调用 held.toString() - insumity
4
你可以使用AtomicReference。 - Stuart Marks
1
如果操作在调用者的不同线程上运行,则需要内存屏障。 - Stuart Marks
显示剩余2条评论

2

由于您不需要修改对象的引用,因此可以将Game包装在其他内容中。

最快(但丑陋)的解决方法是使用大小为1的数组,然后稍后设置数组的内容。这种方法有效是因为数组实际上是final的,数组中包含的内容不必是final的。

@Override
    public Observable<Game> call(RxMessage<byte[]> gameRawReply) {

        Game[] game = new Game[1];

        switch(game_model) {

            case SINGLE: {

                ebs.subscribe(new Action1<RxMessage<byte[]>>() {

                    @Override
                    public void call(RxMessage<byte[]> t1) {

                        if(!singleGame.contains(0) {
                            game[0] = new Game();       
                            singleGames.put(0, game[0]);
                        } else {
                          game[0] = singleGames.get(0);
                        }
                    }
                });
            }
        }

        return rx.Observable.from(game[0]);
    }

另一个类似的选项是创建一个新类,该类具有一个 Game 字段,之后您可以设置该字段。


就像你所说的:“这很丑” - 但在我找到更好的解决方案之前,我会继续使用它!谢谢 :-) - Shvalb

1

CyclopsMutableLazyImmutable对象来处理这种情况。 Mutable是完全可变的,而LazyImmutable只能被设置一次。

 Mutable<Game> game = Mutable.of(null);

 public void call(RxMessage<byte[]> t1) {

                        if(!singleGame.contains(0) {
                           game.mutate(g -> new Game());       
                            singleGames.put(0, game.get());
                        } else {
                          game[0] = game.mutate(g->singleGames.get(0));
                        }
               }

LazyImmutable可以被用来懒惰地设置一个值,只有一次:
LazyImmutable<Game> game = LazyImmutable.def();

public void call(RxMessage<byte[]> t1) {

     //new Game() is only ever called once
     Game g = game.computeIfAbsent(()->new Game());
}

0

虽然其他方法看起来可行,但我想提醒您,订阅ebs不能保证同步,您可能会在内部函数中始终返回null。由于您依赖另一个Observable,因此可以通过以下方式简单地组合它:

public rx.Observable<Game> findGame(
        long templateId, 
        GameModelType game_model, 
        GameStateType state) {

    return context.findGame(templateId, state)
    .flatMap(gameRawReply -> {
        switch(game_model) {
        case SINGLE: {
            return ebs.map(t1 -> {
                Game game;
                if (!singleGame.contains(0) {
                    game = new Game();
                    singleGames.put(0, game);
                } else {
                    game = singleGames.get(0);
                }
                return game;
            });
        }
        }

        return rx.Observable.just(null);
    });
}

0
你不能直接这样做。但是你可以使用一个包装类:只需定义一个名为“GameContainer”的类,其中game作为其属性,并将最终引用转发到该容器即可。

我会按照dkatzel的建议去做。 - Shvalb

0
@dkatzel的建议很好,但还有另一种选择:将检索/创建游戏的所有内容提取到帮助方法中,然后声明final Game game = getOrCreateGame();。我认为这比最终数组方法更清晰,尽管最终数组方法肯定也可以工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接