Доступ к переменной осуществляется внутри внутреннего класса. Должен быть объявлен окончательным

116

Так что название говорит само за себя. Я получаю ошибку компиляции в моем onClick.

Вот код.

public class fieldsActivity extends Activity {

Button addSiteButton;
Button cancelButton;
Button signInButton;


/**
 * Called when the activity is first created.
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // to create a custom title bar for activity window
    requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);

    setContentView(R.layout.fields);
    // use custom layout title bar
    getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.topbar);

    Pager adapter = new Pager();
    ViewPager mPager = (ViewPager) findViewById(R.id.fieldspager);
    mPager.setAdapter(adapter);
    mPager.setCurrentItem(1);



    addSiteButton = (Button) findViewById(R.id.addSiteButton);
    addSiteButton.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
           mPager.setCurrentItem(2, true); //Compilation error happens here.
        }


    });


    cancelButton = (Button) findViewById(R.id.cancel_button);
    signInButton = (Button) findViewById(R.id.sign_in_button);

}
PhDeOliveira
источник
1
Если вы используете Eclipse, вы можете нажать Ctrl-1 (Cmd-1 в OS X) с выбранной ошибкой, чтобы увидеть быстрое исправление, которое покажет вам, что нужно изменить. Подробнее здесь: depth-first.com/articles/2008/01/11/...
Intrications

Ответы:

130

Если вы не хотите делать его окончательным, вы всегда можете просто сделать его глобальной переменной.

Кевин Чжао
источник
1
@KevinZhao Являются ли глобальные переменные окончательными после инициализации?
the_prole
@the_prole Я думаю, что вы можете использовать final в Java, но я не уверен, что вы можете использовать его при создании приложения для Android, так что поиск в Google может быть хорошей идеей :-)
Кевин Чжао
15
Оглядываясь назад, можно сказать, что использование глобальных переменных - плохая идея, если их можно избежать, если только вы не новичок, и в этом случае чрезмерное усложнение вашей программы глобальными переменными - хороший опыт обучения. Вот хорошая статья, объясняющая, почему глобальные переменные - плохая идея.
the_prole
65

Вы можете объявить переменную final или сделать ее переменной экземпляра (или глобальной). Если вы объявите его окончательным, вы не сможете изменить его позже.

Любая переменная, определенная в методе и доступная анонимному внутреннему классу, должна быть окончательной. В противном случае вы могли бы использовать эту переменную во внутреннем классе, не зная, что если переменная изменяется во внутреннем классе, а затем используется позже во включающей области видимости, изменения, сделанные во внутреннем классе, не сохраняются в охватывающей области. По сути, то, что происходит во внутреннем классе, остается во внутреннем классе.

Я написал здесь более подробное объяснение . Это также объясняет, почему переменные экземпляра и глобальные переменные не нужно объявлять окончательными.

Брендан Л
источник
44

Ошибка говорит сама за себя, измените:

ViewPager mPager = (ViewPager) findViewById(R.id.fieldspager);

в

final ViewPager mPager = (ViewPager) findViewById(R.id.fieldspager);
Veger
источник
87
Причина: если два метода видят одну и ту же локальную переменную, Java хочет, чтобы вы поклялись, что вы не измените ее - finalна языке Java. Вместе с отсутствием параметров по ссылке это правило гарантирует, что локальные переменные назначаются только в методе, которому они принадлежат. Таким образом, код становится более читабельным.
ignis
@ignis Я получаю ошибку NullPointerException addSiteButton.setOnClickListener(new View.OnClickListener() {, вы знаете, почему это может произойти?
PhDeOliveira
1
@PhDeOliveira NPE обычно вызывается, когда вы вызываете метод для переменной, содержащей null. Наверное, findViewById возвращается null. Я не могу сказать больше, поскольку я не программист Android; Советую открыть отдельный вопрос. Конечно, это не имеет ничего общего с внутренними классами, финал, и др Similia .
ignis
25

Вот забавный ответ.

Вы можете объявить окончательный одноэлементный массив и, по-видимому, изменить элементы массива, как хотите. Я уверен, что это нарушает саму причину, по которой это правило компилятора было реализовано в первую очередь, но это удобно, когда вы находитесь в привязке ко времени, как я сегодня.

На самом деле я не могу претендовать на это. Это была рекомендация IntelliJ! Чувствует себя немного взломанным. Но это не так плохо, как глобальная переменная, поэтому я подумал, что стоит упомянуть здесь. Это всего лишь одно решение проблемы. Не обязательно лучший.

final int[] tapCount = {0};

addSiteButton.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View v) {
       tapCount[0]++;
    }

});
the_new_mr
источник
В приведенном выше случае вы не меняете объект, на который указывает ссылка, а изменяете содержимое внутри массива. ссылка имеет хорошее объяснение.
Abilash
Да, я знаю. Похоже на хакерство для решения проблемы. Спасибо за ваш комментарий, разъясняющий его другим.
the_new_mr
4

Как сказал @Veger, вы можете сделать finalтак, чтобы переменная могла использоваться во внутреннем классе.

final ViewPager pager = (ViewPager) findViewById(R.id.fieldspager);

Я назвал его, pagerа не mPagerпотому, что вы используете его как локальную переменную в onCreateметоде. mПрефикс cusomarily зарезервирован для переменных - членов класса (то есть переменные, которые объявлены в начале класса и доступны для всех методов класса).

Если вам действительно нужна переменная-член класса, сделать ее окончательной не получится, потому что вы не можете использовать ее findViewByIdдля установки до onCreate. Решение состоит в том, чтобы не использовать анонимный внутренний класс. Таким образом, mPagerпеременную не нужно объявлять окончательной и ее можно использовать во всем классе.

public class MainActivity extends AppCompatActivity {

    private ViewPager mPager;
    private Button mButton;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // ...

        mPager = (ViewPager) findViewById(R.id.fieldspager);

        // ...

        mButton.setOnClickListener(myButtonClickHandler);
    }


    View.OnClickListener myButtonClickHandler = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mPager.setCurrentItem(2, true);
        }
    };
}
Suragch
источник
0
    public class ConfigureActivity extends Activity {

        EditText etOne;
        EditText etTwo;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_configure);

            Button btnConfigure = findViewById(R.id.btnConfigure1);   
            btnConfigure.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            configure();
                        }
                    });
    }

    public  void configure(){
            String one = etOne.getText().toString();
            String two = etTwo.getText().toString();
    }
}
Шив Буйя
источник