自作の Android アプリで、画面上のボタンを押したら〇〇変数をOFFにする、というコードを作っていたら Variable 〇〇 is accessed from within inner class, needs to be final or effectively final というエラーが出た。
ONにしたい変数をグローバルにする、という簡単な対策でしたが、Android Studio で確認しました。
参考
試した環境
Android Studio Bumblebee | 2021.1.1 Patch 3
エラーの出る例
onClickListenerの実装とその後の処理などを参考に、画面上のボタンを押したらboolean型変数 onActive
をOFFにする処理を考えました。
変数の宣言場所としては、Button buttonStart
や Button buttonStop
と同列だったらいいかと思って以下のような位置にしました。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("debug", "onCreate");
boolean onActive = true; // ここではNG
Button buttonStart = findViewById(R.id.button_start);
buttonStart.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Do something in response to button click
Log.d("debug", "button_start");
synchronized (this) {
for (int i = 0; i < 30; i++) {
Log.d("debug", " i = " + i);
try {
wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(!onActive) break;
}
}
}
});
Button buttonStop = findViewById(R.id.button_stop);
buttonStop.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Log.d("debug", "button_stop");
onActive = false;
}
});
}
残念ながら、この位置では、
Variable 'onActive' is accessed from within inner class, needs to be final or effectively final
が出ます。インナークラスから呼ばれるので、finalな変数にするか、事実上finalになるようにする必要あり、ってことですね。が、ボタンクリックで再代入したいのでfinalでは困ります。
解決策1
解決策の1つ目は、onCreate の外で変数を定義する(グローバル変数として宣言する)、です。
public class MainActivity extends AppCompatActivity {
boolean onActive = true; // この位置で宣言
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("debug", "onCreate");
onActive = true;
// boolean onActive = true; // ここではNG
Button buttonStart = findViewById(R.id.button_start);
buttonStart.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Do something in response to button click
Log.d("debug", "button_start");
synchronized (this) {
for (int i = 0; i < 30; i++) {
Log.d("debug", " i = " + i);
try {
wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(!onActive) break;
}
}
}
});
Button buttonStop = findViewById(R.id.button_stop);
buttonStop.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Log.d("debug", "button_stop");
onActive = false;
}
});
}
解決策2
Android studioの提案する解決策は
Transform 'onActive' into final one element array
です。final宣言はするけど、配列にすれば要素の値を変えられる、ということのようですね。Android studio任せで修正してもらうと、こうなりました。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("debug", "onCreate");
final boolean[] onActive = {true}; // Android studio の quick fix
// boolean onActive = true; // ここではNG
Button buttonStart = findViewById(R.id.button_start);
buttonStart.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Do something in response to button click
Log.d("debug", "button_start");
synchronized (this) {
for (int i = 0; i < 30; i++) {
Log.d("debug", " i = " + i);
try {
wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(!onActive[0]) break; // Android studio の quick fix、[0]付きに替わる
}
}
}
});
Button buttonStop = findViewById(R.id.button_stop);
buttonStop.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Log.d("debug", "button_stop");
onActive[0] = false; // Android studio の quick fix、[0]付きに替わる
}
});
}
Stack Overflow にも、時間のないときに採用できる、必ずしも最高ではない、変わった(? 原文はfunny answer)対策として紹介されています。
おまけ
残念ながらこのサンプルは、Startボタンを押したら forループが終了するまで Stop ボタンの処理に移行しないので、Stopボタンを押しても breakでの中断はされません...
あくまでエラー回避のサンプルということでお願いします。