Окончательный параметр int, используемый в анонимном Runnable

Мне интересно, использую ли я правильный метод для вызова чего-то в другом потоке. Я делаю это на Android, но думаю, что это общий вопрос Java.

У меня есть метод с некоторыми параметрами. Допустим, они внутр.

class Main1 {
  public static void mainOnlyWork(int x, int y) {
     // not important here.
  }
}

class Test {
  Handler mHandler;

  // called from main thread, stored as reference.
  public Test() {
    mHandler = new Handler();
  }

  public static void callInMainThread(final int x, final int y) {
    mHandler.post(new Runnable() {
      public void run() {
        Main1.mainOnlyWork(x, y);
      }
    });
  }

Теперь мой вопрос: безопасно ли использовать окончательные целые числа для создания анонимного запуска без каких-либо членов класса? Если я пропущу ключевые слова final для параметров x и y, Eclipse будет жаловаться. Мне кажется, что в таких случаях предполагается использовать только постоянные числа. Если я передам не константу, это нормально? Java «делает» его постоянным, переходя к этой функции?

Но я хочу вызвать Test.callInMainThread из собственного кода с помощью JNI. На мой взгляд, Java никак не может определить, являются ли эти числа константами или нет. Могу ли я доверять Java, чтобы творить чудеса? Всегда ли так будет работать?

Я думаю, может быть, мне нужно создать прокси-класс, например:

private abstract RunnableXY implements Runnable {
  public RunnableXY(int x, int y) {
    this.x = x;
    this.y = y;
  }

  public int x;
  public int y;

  public abstract void run();
}

И метод вызова будет использовать:

  public static void callInMainThread(final int x, final int y) {
    mHandler.post(new RunnableXY(x,y) {
      public void run() {
        Main1.mainOnlyWork(this.x, this.y);
      }
    });
  }

Таким образом, я защищаю значения от сборки мусора до тех пор, пока runnable не будет использован и удален. Должен ли я создавать обертки или маркировка final x в параметрах метода безопасна? Когда я пытаюсь это сделать, ключевое слово final работает просто отлично. Однако в потоках я не думаю, что если это работает сейчас, это будет работать всегда. Всегда ли это работает с финалом? Как это делается, если это так? Будет ли разница, если параметр будет не примитивного типа, а Object?

Обновление: я уже понимаю, что означает final в Java. Это не вопрос. Вопрос в том, где находится область переменных, используемых при создании Runnable. Они являются локальными, это означает, что после завершения функции на их значение нельзя ссылаться. Где хранятся эти значения, когда объект Runnable передается другому потоку и ожидает выполнения?


person Pihhan    schedule 23.07.2013    source источник
comment
Я думаю, что нашел ответ сам. Эти параметры должны быть окончательными, потому что они затем используются при генерации метода run() анонимного Runnable. Если я запускаю callInMainThread(x=3, y=5), public void run() сгенерирует тело как Main1.mainOnlyWork(3, 5), не ссылаясь на какую-либо переменную, локальную или глобальную. Это то, что я искал. Он хранится в коде Runnable.run(), который создается в этот момент. И код не изменится при переходе между потоками.   -  person Pihhan    schedule 24.07.2013


Ответы (3)


Начнем с основ: Java создает копии при передаче параметров. X и y, которые получает ваш метод, не являются исходными переменными, которые передаются, они являются копиями исходных значений. Когда вы объявляете такой метод, значения, которые вы передаете, не обязательно должны быть константами, но копии, которые получает метод, равны 1:

callInMainThread(final int x, final int y) { ..... }

Второй: Нет, вам не нужно делать обертки. Когда вы обращаетесь к переменным, которые являются локальными для внешней области видимости, компилятор Java автоматически создает поля для их хранения, точно так же, как оболочки, которые вы создали вручную. Это для вас прозрачно.

Одна из причин, по которой нельзя опустить final, заключается в том, что в Java не реализован какой-либо механизм для передачи изменений значения переменной между локальной переменной метода и сгенерированным полем в анонимном классе. Кроме того, анонимный класс может жить дольше, чем вызов метода. Что произойдет, если анонимный класс прочитает или запишет переменную после возврата метода? Если переменная не является окончательной, вы больше не можете читать ее значение или записывать в нее.


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

person Joni    schedule 23.07.2013
comment
Ах, спасибо! Я не понимал, что именно имеется в виду под третьим абзацем, пока сам не сообразил его другими словами. - person Pihhan; 24.07.2013

Внутренне копия параметров и локальных переменных (находящихся в стеке вызова метода) берется в потоке. Это связано с тем, что после завершения вызова метода поток продолжает существовать.

И переменные должны быть окончательными, чтобы запретить перезапись исходных переменных в методе, что приведет к тому, что в потоке будет другая версия. Что просто вводит в заблуждение. Таким образом, нужно позволить обеим версиям одного и того же имени означать одно и то же.

Тщательный языковой дизайн.

(Несколько упрощенно, не называя симметрично обращенные случаи, когда то же самое верно.)

person Joop Eggen    schedule 23.07.2013

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

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

public void test(String a) {
    a = "Hello";
}

а также

public void test(final String a) {
    a = "Hello";
}

за исключением того, что компилятор выдаст ошибку во втором случае. В первом случае, когда метод test() закончит работу, a будет восстановлено исходное значение (это не будет "Hello"). Таким образом, окончательные параметры делают параметр «постоянным» (обратите внимание на объекты: вы все равно можете изменить состояние объекта, но не ссылку на объект).


Ключевое слово final требуется в анонимных классах, потому что вы ссылаетесь на переменную в другой области класса (в вашем случае ваш анонимный экземпляр Runnable ссылается на переменные экземпляра Main1). Поэтому, если это выполняется в другом потоке, и они не являются окончательными, вы можете перезаписать исходную ссылку на время действия метода. Это заставит каждый поток ссылаться на разные объекты с одним и тем же именем переменной, что сбивает с толку, и поэтому это было заблокировано в дизайне языка.


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

person m0skit0    schedule 23.07.2013
comment
Меня беспокоит именно тот факт, что они работают локально. Если я вызываю его три раза с разными параметрами, быстро подряд. Прежде чем основной поток начнет выполнять первую функцию, параметры метода были изменены 3 раза. Где хранятся эти локальные значения? Если в стеке локального потока, возможно, он будет перезаписан. Основной поток не должен обращаться к стеку рабочих потоков, верно? Являются ли эти целые числа в памяти кучи общими для потоков? - person Pihhan; 24.07.2013
comment
Локальные переменные хранятся в стеке, потому что они являются временным хранилищем. Каждый поток имеет свой собственный стек, он не является общим. Это локальный контекст потока. Они не могут быть перезаписаны в стеке локального потока, за исключением текущего контекста потока, независимо от того, насколько быстро они вызываются или откуда - на самом деле я не могу понять, что именно вы подразумеваете под этим. Кроме того, опять же, эти значения копируются, поэтому независимо от того, изменяются ли они в локальной области, вы изменяете исключительно эту локальную копию, а не другую копию и не исходное значение. - person m0skit0; 24.07.2013
comment
Тогда мой вопрос в том, как они копируются в новый Runnable() {}. Они сделаны как поля этого анонимного объекта? Я хочу знать, как эти переменные отправляются в другой поток. Я понимаю, что означает локальная переменная. Но эти переменные не используются при вызове этого метода. Они передаются методу, который будет вызываться в будущем. Где они в промежутке времени? - person Pihhan; 24.07.2013
comment
Runnable также создается при вызове этого метода. Таким образом, значения, которые использует Runnable, будут значениями, переданными методу, потому что анонимный класс находится внутри этой области. То, как они копируются в анонимный интерфейс, на самом деле зависит от реализации Java анонимных классов/интерфейсов. ИМХО это не имеет значения: копируются как поля или как там. Вы можете проверить исходный код JRE, если действительно хотите узнать подробности. - person m0skit0; 24.07.2013
comment
Это то, о чем я прошу. Обеспечивает ли Java/Dalvik неизменность переменных и как это делается? Неважно, может быть, если я уверен, что VM гарантирует, что они никогда не будут перезаписаны или недействительны. Я недостаточно опытен, чтобы понять исходный код JRE и понять, безопасен он или нет. Я надеюсь, что кто-то более мудрый знает. - person Pihhan; 24.07.2013
comment
Я до сих пор не понимаю, что вы подразумеваете под убедиться, что переменные всегда не повреждены. Переменная final не может быть изменена, это проверяется во время компиляции. Опять же: параметры являются копией, локальный контекст может изменять их по своему усмотрению, это не повлияет на ту же переменную других потоков. как это делается Прочтите исходный код. Если у вас нет знаний, чтобы понять исходный код JRE, вы не поймете, как он это делает. В любом случае, как на самом деле не имеет значения. Кроме того, это может быть реализовано по-разному в зависимости от разработчика JRE. Вы можете предположить, что JRE делает это правильно. - person m0skit0; 24.07.2013