Доступ к переменным из внешних методов - PullRequest
2 голосов
/ 06 августа 2020

Я изучаю внутренний класс в Java, и у меня есть проблема, связанная со ссылкой на переменные во внешних методах. Например, у меня есть исходный код, чтобы подсчитать, сколько раз compareTo() методы вызываются во время сортировки:

        int counter = 0;
        Date[] dates = new Date[100];
        for(int i = 0; i < dates.length; i++)
        {
            dates[i] = new Date()
            {
                public int compareTo(Date other)
                {
                    counter++;
                    return super.compareTo(other);
                }
            };
        }
        Arrays.sort(dates);
        System.out.println(counter + " comparisons");

При выполнении исходного кода вы можете видеть, что существует ошибка при использовании counter++. Чтобы решить эту проблему, некоторые люди сказали мне, что мне нужно изменить вот так:

        int[] counter = new int[1];
        Date[] dates = new Date[100];
        for(int i = 0; i < dates.length; i++)
        {
            dates[i] = new Date()
            {
                public int compareTo(Date other)
                {
                    counter[0]++;
                    return super.compareTo(other);
                }
            };
        }
        Arrays.sort(dates);
        System.out.println(counter[0] + " comparisons");

Я не понимаю, в чем разница между этими двумя кодами и в чем причина этой ошибки и ее решения?

1 Ответ

2 голосов
/ 06 августа 2020

Вы создаете фрагмент кода, который может «путешествовать». Код в {}, принадлежащий вашему объявлению new Date(), запускается не там, где вы его написали; он прикреплен к этому объекту даты, который вы сделали, и идет вместе с ним. Этот объект даты может путешествовать: он может храниться в поле. Может быть, он будет запущен через 18 дней в совершенно другом потоке. Виртуальная машина понятия не имеет, поэтому ее нужно подготовить к тому, что это произойдет.

Допустим, она знает: что должно произойти с вашей переменной «счетчик»?

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

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

Хорошо. Что, если compareTo вызывается в другом потоке? Можно ли теперь пометить локальную переменную как «изменчивую»? Можно ли утверждать, что в java даже локальные переменные могут отображать состояние гонки?

Это критический вызов; что-то, что должны решить разработчики языка.

Java Разработчики языка решили: против тихого обновления в кучу и против , позволяя местным жителям потенциально подвергаться множественному доступ к потоку.

Следовательно, любая локальная переменная, к которой вы обращаетесь в любом кодовом блоке, который может «перемещаться» *, должна быть объявлена ​​либо [A] final, либо [B] действовать так, как если бы это могло быть, в котором case java сделает его окончательным для вас.

Изменение состоит в том, что counter, сама переменная, не меняет во втором фрагменте: это ссылка на array, и эта ссылка никогда не меняется. Фактически, вы сами добавили уровень косвенного обращения и доступ к куче: в куче существуют массивы.

Как бы то ни было, я считаю использование AtomicX более читаемым. Поэтому, если вам нужен int, который можно изменять в коде перемещения, не используйте new int[1]; сделать new AtomicInteger. Если вам нужна изменяемая строка, используйте new AtomicReference<String>(), а не new String[1].

NB: Да, в this Speci c код, переменная счетчика используется только, даже операция сортировки в этом методе и счетчик var могут go исчезнуть после завершения этого метода, но компилятор не выполняет такого чрезвычайно глубокого анализа, чтобы выяснить это, он использует гораздо более простое правило: хочу получить доступ локальная переменная из внешнего скрипта в коде "путешествия"? Не разрешено - если он (фактически) не является окончательным.

*) Код перемещения - это что-либо внутри определения локального или анонимного класса метода и что-либо в лямбда-выражении. Итак:


void method() {
    class MethodLocalClassDef {
        // anything here is considered 'travelling'
    }

    Object o = new Object() {
        // this is an anonymous class def,
        // and anything in here is 'travelling'
    };

    Runnable r = () -> {
        // this is a lambda, and considered 'travelling'
    };
}
...