undefined

Что происходит на самом деле (Во что превращаются классы компилятором)

Java Multithreading
4 уровень , 7 лекция
Открыта
Что происходит на самом деле (Во что превращаются классы компилятором) - 1

— Привет, Амиго! Вот тебе еще немного информации.

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

— Ага. Я даже помню, что у них имена – это числа: 1, 2, 3 и т.д.

— Именно так. Но вот еще какой есть нюанс.

Если класс был объявлен внутри метода и использовал какие-то переменные, то ссылки на них будут добавлены в сгенерированный класс. Смотри сам:

Было:

Исходный пример:
class Car
{
 public ArrayList<Car> createPoliceCars(int count)
 {
  ArrayList<Car> result = new ArrayList<Car>();

  for(int i=0; i<count; i++)
  {
   final int number = i;
   result.add(new Car()
    {
     public String toString()
     {
      return ""+number;
     }
    });
  }
  return result;
 }
}

Результат компиляции:

Что сгенерировал компилятор:
class Car
{
 public ArrayList<Car> createPoliceCars(int count)
 {
  ArrayList<Car> result = new ArrayList<Car>();

  for(int i=0; i<count; i++)
  {
   final int number = i;
   result.add(new Anonymous2(number));
  }
   return result;
  }

 class Anonymous2
 {
  final int number;
  Anonymous2(int number)
 {
  this.number = number;
 }

  public String toString()
  {
   return ""+number;
  }
 }
}

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

Теперь второй момент. Метод toString() использует переданную переменную. Для этого пришлось:

А) сохранить ее внутри сгенерированного класса

Б) добавить ее в конструктор.

— Понял. Классы, объявленные внутри метода, всегда работают с копией переменных.

— Именно!

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

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

— Согласен. Все-таки это крутая штука – анонимные локальные классы.

А если я объявлю внутри метода свой локальный класс и буду в нем использовать переменные метода, компилятор их тоже добавит этому классу?

— Да, добавит в класс и его конструктор.

— Я так и думал.

Комментарии (101)
Чтобы просмотреть все комментарии или оставить свой,
перейдите в полную версию
Е К 33 уровень, Краснодар
21 марта 2021
доп материал чтоб хоть немного по полочкам)
ilya 30 уровень
11 марта 2021
зачем все это нужно? это только путает.
Даниил Александрович 31 уровень, Тамбов
5 марта 2021
тяжело както эти внутренние классы заходят, вроде все понятно, но нюансы вызывают отторжение.
Иван 31 уровень, Рязань
16 февраля 2021
Разве не должно быть class Anonymous2 extends Car?
Alena 26 уровень, Halifax
12 февраля 2021
Всё сложна😑
Иван 31 уровень, Москва
19 января 2021
Хотел почитать комментарии с реакциями на пикчу, а они тут классы обсуждают :с
Карина Морозова 35 уровень, Калининград
11 января 2021
Посоветуйте книжки какие-то что ли. Про эти классы и их великие множества
halfear 33 уровень, Москва
25 декабря 2020
По-человечески объяснено почему локальные переменные внутренних классов сделаны final: https://www.youtube.com/watch?v=bLWQM6fUQiE&list=PLoij6udfBnciXh1_Itg-Hiw9EdGGzwYPE&index=3 от [28:20] и с переходом на следующую лекцию.
Pig Man 41 уровень
5 декабря 2020
Короче, если вы объявляете локальный класс и в нем используете локальные переменные, то они должны быть final или effectively final, для вашего же блага. Потому что написав вот так:

    private interface Action {
        void doSameThink();
    }

    public static void main(String[] args) {
        String str = "hello";
        Action action = new Action() {
            @Override
            public void doSameThink() {
                System.out.println(str);
            }
        };
        str = "goodbye";
        action.doSameThink();
    }
Вы бы могли ожидать, что в консоль выведется "goodbye", но вывелось бы "hello". Все потому что str в методе main() и str в методе doSameThink() - это разные переменные, хотя мы записываем их так, будто это одна и та же переменная. Сделано это для упрощения жизни, но компилятор все же преобразует наш код в правильный, а именно:

    class Anonymous1 {
        private final String str2 = "hello";

        public void doSameThink() {
            System.out.println(str2);
        }
    }
Добавит под наш метод объявление анонимного класса и в нем создаст приватные поля для всех локальных переменных, к которым мы обратились. Эти поля нужны, потому что мы не можем обратиться к str напрямую, так как это локальная переменная метода main() и вне его о ней никто не знает. Поэтому мы работаем с дубликатом (назвал str2, чтобы было видно, что это другая переменная). И чтобы мы не думали, что можем поменять значение str в main и увидеть это изменение в локальном классе - все переменные должны быть final или effectively final.
Илья 41 уровень, Санкт-Петербург
2 декабря 2020
если анонимным классам присваивается компилятором имена цифрами (1, 2, 3 и т.д.) тогда почему в прошлой задаче с джинсами, где мы получали имена классов с помощью рефлексии, имя анонимного класса, который реализовывал абстрактный класс, не вывелось на экран?