2012-01-20 3 views
3

Мы все знаем, вы не можете сделать что-то вроде этого:Зачем требовать, чтобы локальные переменные были окончательными при доступе от анонимных внутренних классов?

int a = 7; 
new Runnable() { 
    public void run() { 
     System.out.println(a); 
    } 
}.run(); 
... 

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

Что я борюсь, чтобы увидеть, однако, почему компилятор не имеет хак реализации, так что, когда он видит вышеуказанную ситуацию, компилирует вниз к чему-то вроде:

int[] a = {7}; 
new Runnable() { 
    public void run() { 
     System.out.println(a[0]); 
    } 
}.run(); 
... 

Тогда мы в где безопасно получить доступ к анонимному внутреннему классу и действительно изменить его, если мы пожелаем. Конечно, это может сделать только этот взлом, когда мы фактически меняем a. Насколько я мог видеть, это было бы относительно простой вещью, чтобы работать, будет работать для всех типов и позволит a быть измененным из любого контекста. Конечно, вышеупомянутое предложение может быть изменено на использование синтетических классов-оболочек для нескольких значений или другого подхода, который немного эффективнее, но идея такая же. Я думаю, что есть небольшой удар производительности, но я сомневаюсь, что это было бы чрезмерно, особенно с возможностью большего оптимизма под капотом. Помимо, возможно, определенных рефлексивных вызовов, которые полагаются на синтетические поля, которые могут быть нарушены, я не вижу многих недостатков, но я никогда не слышал, чтобы это серьезно предлагалось! Есть ли причина, почему?

+0

«конечные» переменные являются фактически экземплярами внутреннего класса (нет внутренних классов pe SE, только компиляция генерирует что-то похожее на них). Зачем нужно использовать массив - он не будет совместим с сериализацией и многими другими вещами. – bestsss

+0

Еще одна проблема, которая у вас есть (и может иметь в groovy, которая не имеет этого ограничения) заключается в том, что не конечные переменные могут меняться (в противном случае у вас не было бы проблем с окончанием) int a = 1; // использовать a в другом потоке. a = 2; Если нить всегда видит a = 1 или может иногда видеть a = 2. –

+2

Не ненавидите ли вы, когда кто-то закрывает вопрос, находящийся посредине ответа. :( –

ответ

2

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

int a = 7; 
Runnable r = new Runnable() { 
    public void run() { 
     a = 5; 
    } 
}; 
r.run(); 
System.out.println(a); 

Вы могли бы ожидать это напечатать 5 (который на самом деле это было бы в C#). Но поскольку была сделана только копия , она фактически напечатала бы 7 ... если бы она была разрешена, без больших изменений.

Конечно, Java может был изменен, чтобы действительно захватить переменной вместо его значения (как C# были для анонимных функций). Для этого требуется автоматически создать дополнительный класс для хранения «локальных» переменных и сделать так, чтобы и метод, и анонимный внутренний класс совместно использовали экземпляр этого дополнительного класса. Это сделало бы анонимные внутренние классы более мощными, но, возможно, их было труднее понять. C# решила пойти по маршруту мощности, но сложнее; Java пошел на ограничительный, но простой подход.

(Использование массива вместо пользовательского класса допустимо для одной переменной, но становится более расточительным, если задействовано несколько переменных - вам действительно не нужно создавать объект-оболочку для для каждой переменной, если вы можете помочь.)

Обратите внимание, что в подходе с захватом-переменной существуют значительные сложности, по крайней мере, с использованием правил C#. Например:

List<Runnable> runnables = new ArrayList<Runnable>(); 
int outer = 0; 
for (int i = 0; i < 10; i++) { 
    int inner = 0; 
    runnables.add(new Runnable() { 
     public void run() { 
      outer++; 
      inner++; 
     } 
    }); 
} 

Сколько «внутренних» переменных создано? Один для каждого экземпляра цикла или один общий? В принципе, масштабы делают жизнь сложной для такого рода вещей. Возможный, но сложный.

+2

@Downvoter: Заметь прокомментировать? –

+2

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

+0

Почему downvote? Это правильный ответ на вопрос с большим количеством неточностей в нем. @ ErnestFriedman-Hill - OP, похоже, не понимает, почему ссылки на локальные переменные *** действительно *** должны быть окончательными. – Perception

0

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

+2

Массивы - или любые объекты - являются (по крайней мере концептуально) всегда выделенными на куче, и благодаря GC проблема «проблема» здесь не проблематична. – Voo

+0

@Voo - правильно, но было бы грубо взломать внутреннее представление всех примитивных значений метода как массивов, чтобы они могли быть закрыты в анонимных классах. Прочитав, как это делает C#, это гораздо более элегантное решение, но гораздо более сложное, как указывали другие. – Perception

1

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

int a = 1; 
// use a in another thread, or potentially run it later 
a = 2; 
РЕКОМЕНДУЕМЫМ

нить всегда видит a = 1 или может иногда видеть a = 2.

Единственный способ писать предсказуемые анонимные классы - это если локальные переменные не изменяются. Компилятор может быть более умным и обнаруживать, когда переменная больше не может измениться, и это упростит некоторые нечетные случаи IMHO. Но самое простое решение - потребовать, чтобы локальная переменная была окончательной.

BTW: Альтернатива, в которой у моей IDE есть автоматическое исправление, заключается в использовании массива из одного.

final int[] a = { 1 }; 
// use a[0] in an anonymous class. 
a[0] = 2; 

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