Окончательная локальная переменная, возможно, не была инициализирована в анонимном внутреннем классе

Вот мой код:

final Foo myFoo = new Foo(new Inner() {
    @Override
    callback(){
         myFoo.bar();
    }
});

(с реальными именами функций)

final MyArrayAdapter aa = new MyArrayAdapter(new View.OnClickListener() {
    @Override
    onClick(){
         aa.notifyDataSetChanged();
    }
});

Java выдает мне ошибку о том, что myFoo, возможно, не был инициализирован. Есть ли способ исправить это? Я потенциально могу установить для обратного вызова значение null, когда я создаю объект, а затем изменяю его впоследствии, но я надеюсь, что будет более чистый способ. Любые идеи? (Кроме того, это не сработало бы, если бы Foo не был написан мной и не предоставлял интерфейс для изменения обратного вызова позже)

Если кому-то интересно, в моем конкретном сценарии Foo — это ArrayAdapter, а панель — notifyDataSetChanged(). То, что отображается адаптером, зависит от значений элементов в массиве, и эти значения изменяются при щелчке по ним. Обратный вызов — это clickListener.


person Drew    schedule 20.04.2012    source источник
comment
Нельзя ли просто сослаться на this.bar() или bar()?   -  person Tony Ennis    schedule 20.04.2012
comment
нет, мой фактический код сложнее, чем этот пример, и обратный вызов находится в другом анонимном внутреннем классе   -  person Drew    schedule 20.04.2012
comment
Зачем вам переопределять notifyDataSetChanged? Из документов API кажется, что вы должны вызывать myFoo.registerDataSetObserver(new DataSetObserver(){}).   -  person sharakan    schedule 20.04.2012
comment
Когда один из элементов в данных ArrayAdapter изменяется (из-за нажатия на него), остальные элементы в ArrayAdapter необходимо перерисовывать. Есть ли лучший способ сделать это, чем вызов notifyDataSetChanged()?   -  person Drew    schedule 20.04.2012
comment
Вам нужно передать myFoo конструктору Inner. Исправьте это.   -  person Thorbjørn Ravn Andersen    schedule 20.04.2012
comment
Почему? Это не ошибка. Это лучший стиль, чтобы сделать это таким образом?   -  person Drew    schedule 20.04.2012


Ответы (3)


Короткий ответ: вы определенно не можете сделать это в Java, но компилятор уже сказал вам об этом. Вы пытаетесь создать два объекта одновременно со ссылками друг на друга, это проблема курицы и яйца. Суть в том, что вы должны сначала создать ОДНУ из них.

Предложение представляет собой двухэтапное создание:

....
final MyArrayAdapter aa = new MyArrayAdapter();
aa.initializeButtonClickListener();
....

а затем добавьте в адаптер метод инициализации.

public void initializeButtonClickListener(){
    this.button.setOnClickListener(new View.OnClickListener() {
        @Override
        onClick(){
             notifyDataSetChanged();
        }
    });
}

Поскольку эта конструкция несколько сложна (т. е. сложнее, чем простой вызов конструктора), я бы рекомендовал затем вытащить эти две строки в фабричный метод MyArrayAdapter и сделать конструктор закрытым.

person sharakan    schedule 20.04.2012
comment
Хорошо, я понимаю, почему это невозможно. Я переписал его, чтобы установить обратный вызов после создания первого объекта. Я не уверен, почему я не должен вызывать notifyDataSetChanged(), хотя я хочу перерисовать все, когда изменяется один элемент массива. - person Drew; 20.04.2012
comment
Хм... Кажется, я запутался в именах в вашем примере. Я думал, что Inner был DataSetObserver, вызываемым адаптером, а не вызывающим его. Где находится объект, который прослушивает ClickListener (т.е. Inner)? Я бы подумал, что будет три строки: создайте ArrayAdapter, создайте ClickListener, который вызывает .notifyDataSetChanged, затем создайте объект «Clickable» (я полагаю, View), который использует ArrayAdapter в качестве своей «модели», а затем вызовите view.setOnClickListener (слушатель), чтобы завершить круг. - person sharakan; 20.04.2012
comment
Хорошо, я добавил версию с моим реальным сценарием. Кнопка, к которой прикреплен кликлистенер, находится внутри адаптера. - person Drew; 20.04.2012
comment
@Drew В этом случае у меня был бы метод делегата в MyArrayAdapter с именем setOnClickListenerToButton, который вызывает this.button.setOnClickListener(). Я изменю код соответствующим образом. Я бы предложил, чтобы следующим шагом было, возможно, вытащить это сложное творение в фабричный метод, но это зависит от вас. - person sharakan; 20.04.2012
comment
Я пошел с двухэтапным созданием. Фабрика звучит как хорошая идея, но я не уверен, как она решит проблему необходимости двух шагов, я все еще не могу построить оба объекта со ссылками друг на друга. - person Drew; 20.04.2012
comment
Это не так, просто объединяет шаги, чтобы убедиться, что все сделано правильно для всех, кому нужен MyArrayAdapter. - person sharakan; 20.04.2012

Это будет работать в целом и без доступа к определению Inner, но вам нужно создать вспомогательный класс:

class CallbackHelper {
    private Foo foo;
    public void setFoo( Foo f ){ foo = f; }
    public Foo getFoo(){ return foo; }
}

А затем используйте его следующим образом:

final CallbackHelper cbh = new CallbackHelper();
Foo myFoo = new Foo(new Inner() {
    @Override
    callback(){
         cbh.getFoo().bar();
    }
});
cbh.setFoo( myFoo );
person trutheality    schedule 20.04.2012

Вы можете объявить метод callBarOnMyFoo в содержащем классе и вызвать его из обратного вызова:

void callBarOnMyFoo() {
    myFoo.bar();
}

Тогда ваш обратный вызов должен будет вызвать callBarOnMyFoo, который является методом экземпляра внешнего класса, на который код Inner должен иметь ссылку (я забыл синтаксис для него навскидку).

person Keith Randall    schedule 20.04.2012
comment
Однако myFoo является локальной переменной, и callBarOnMyFoo не будет иметь к ней доступа. Я думаю, я мог бы превратить его в член, но это кажется таким же уродливым, как и мое решение установить прослушиватель после создания - person Drew; 20.04.2012