Объявление класса внутри метода - ключевое слово Final

Учитывая следующий внутренний класс (IsSomething) внутри метода:

public class InnerMethod {

private int x;


public class Something {
    private int y;

    public void printMyNumber(double x)
    {
        class IsSomething extends Something {

            public void print() {
                System.out.println(x);
            }

        }
    }
}

}

Почему переменная X должна быть FINAL, чтобы она работала? (Я говорю о параметре X функции "printMyNumber".)

3 ответа

Решение

Разница между локальными переменными и переменными членами класса. Переменная-член существует во время жизни включающего объекта, поэтому на нее может ссылаться экземпляр внутреннего класса. Однако локальная переменная существует только во время вызова метода и обрабатывается компилятором по-разному, поскольку ее неявная копия создается как член внутреннего класса. Не объявляя локальную переменную final, можно изменить ее, что приведет к незначительным ошибкам из-за того, что внутренний класс все еще ссылается на исходное значение этой переменной.

Конечные локальные переменные

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

Вторая причина возникает, когда мы хотим получить доступ к локальной переменной или параметру из внутреннего класса. Насколько мне известно, именно по этой причине окончательные локальные переменные и параметры были введены в язык Java в JDK 1.1.

public class Access1 {
  public void f() {
    final int i = 3;
    Runnable runnable = new Runnable() {
    public void run() {
      System.out.println(i);
    }
    };
  }
}

Внутри метода run() мы можем получить доступ к i, только если мы сделаем его final во внешнем классе. Чтобы понять аргументацию, мы должны

посмотрите, что делает компилятор. Он производит два файла, Access1.class и Access1$1.class. Когда мы декомпилируем их с помощью JAD, мы получаем:

public class Access1 {
  public Access1() {}
  public void f() {
    Access1$1 access1$1 = new Access1$1(this);
  }
}

а также

class Access1$1 implements Runnable {
  Access1$1(Access1 access1) {
    this$0 = access1;
  }
  public void run() {
    System.out.println(3);
  }
  private final Access1 this$0;
}

Поскольку значение i является окончательным, компилятор может "встроить" его во внутренний

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

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

Рассмотрим следующий класс:

public class Access2 {
  public void f() {
    for (int i=0; i<10; i++) {
    final int value = i;
    Runnable runnable = new Runnable() {
      public void run() {
        System.out.println(value);
      }
    };
    }
  }
} 

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

во время кодирования должен был изменить вышеуказанный код на следующее:

public class Access3 {
  public void f() {
    Runnable[] runners = new Runnable[10];
    for (final int[] i={0}; i[0]<runners.length; i[0]++) {
    runners[i[0]] = new Runnable() {
      private int counter = i[0];
      public void run() {
        System.out.println(counter);
      }
    };
    }
    for (int i=0; i<runners.length; i++)
    runners[i].run();
  }
  public static void main(String[] args) {
    new Access3().f();
  }
}

Теперь нам не нужно объявлять дополнительную окончательную локальную переменную. На самом деле, не правда ли, что

int[] я похож на обычный указатель C на int? Мне понадобилось 4 года, чтобы увидеть это, но я хотел бы услышать от вас, если вы слышали эту идею где-то еще.

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

Из JLS:

Any local variable, formal method parameter or exception handler parameter used but not declared in an inner class must be declared final. Any local variable, used but not declared in an inner class must be definitely assigned (§16) before the body of the inner class.

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

Другие вопросы по тегам