Javaのmethod referenceは初回しか評価されない
はじめに
言いたいことは掲題の通り。method referenceを渡しちゃうと実行時に毎度評価されるわけではない。 毎度評価されたければlambdaを渡した方がいい。
Reference
公式のドキュメントには以下のように記述がある。
The timing of method reference expression evaluation is more complex than that of lambda expressions (§15.27.4). When a method reference expression has an expression (rather than a type) preceding the :: separator, that subexpression is evaluated immediately. The result of evaluation is stored until the method of the corresponding functional interface type is invoked; at that point, the result is used as the target reference for the invocation. This means the expression preceding the :: separator is evaluated only when the program encounters the method reference expression, and is not re-evaluated on subsequent invocations on the functional interface type.
上記の様に最後の一文に「merhod referenceのプログラムに出会った時だけ評価されて、呼び出しのタイミングでは再評価されない」と明言されている。
具体的に
どう言う時に困るかと言うと、例えばクラス生成時に何かをsubscribeしていてviewを更新したいんだけど、viewだけが作り変えられてしまう時とか困る。viewが作り変えられるのでviewのinstanceは新しくなってるんだけど、method referenceでviewを更新しようとしてもこちらは再評価されず古いinstanceを更新しようとしてUIは何も変わらないということが起こりうる。
実際にコード例を示す。
例えばFragmentでBetterKnifeを使っていて、onCreateView()
でviewをbindし、
@BindView(R.id.button) Button button; @BindView(R.id.image) MyImageView imageView; @Nullable @Override public View onCreateView( @NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState ) { View view = inflater.inflate(R.layout.fragment_main, container, false); ButterKnife.bind(this, view); return view; }
onViewCreated()
で本当に初回だけ以下のようなsubscribeを開始するとする。
以下は何かbuttonがあって、押されるたびに別のimage viewのvisibilityがVISIBLE <-> GONEでtoggleになる処理だ。
(何かしらの事情があってviewが作り変えられるたびにsubscribeしたくないものと仮定して読んでほしい :pray: )
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); if (!isInitialized) { disposable.add( RxView.clicks(button) .observeOn(AndroidSchedulers.mainThread()) .subscribe(imageView::toggleVisibility) ); } isInitialized = true; }
一見良さそうに見えるがfragmentが一度detachされ、再度attachされた後、buttonを押してもimage viewのvisibilityは変わらない。この時点でsubscribe()
の引数に渡されたmethod referenceは古いview imageのinstanceに対する参照を持っているからだ。
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); if (!isInitialized) { disposable.add( RxView.clicks(button) .observeOn(AndroidSchedulers.mainThread()) .subscribe((signal) -> { imageView.toggleVisibility(signal); }) ); } isInitialized = true; }
こんな感じでlambdaを渡してやることで回避できる。