compiler-construction - Почему этот код Java компилируется?





(13)


x не инициализируется в x = x + 1 ;.

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

См. Примитивные типы данных

В области метода или класса строка ниже компилируется (с предупреждением):

int x = x = 1;

В области класса, где переменные получают свои значения по умолчанию , следующее дает ошибку «неопределенной ссылки»:

int x = x + 1;

Разве это не первый x = x = 1 должен закончиться той же ошибкой «неопределенной ссылки»? Или, может быть, вторая строка int x = x + 1 должна компилироваться? Или что-то мне не хватает?




Complier читает заявления справа налево, и мы решили сделать обратное. Вот почему это сначала раздражало. Сделайте это привычкой читать инструкции (код) справа налево, у вас не будет такой проблемы.




int x = x + 1;

успешно компилируется в Visual Studio 2008 с предупреждением

warning C4700: uninitialized local variable 'x' used`



Второй int x=x=1 компилируется, потому что вы присваиваете значение x, но в другом случае int x=x+1 здесь переменная x не инициализируется, помните, что в java локальная переменная не инициализируется значением по умолчанию. Примечание. Если это значение ( int x=x+1 ) в области видимости класса также затем также даст ошибку компиляции, так как переменная не будет создана.




В int x = x + 1; вы добавляете 1 в x, так что такое значение x , оно еще не создано.

Но в int x=x=1; будет компилироваться без ошибок, потому что вы назначаете 1 в x .




Ваша первая часть кода содержит вторую = вместо плюса. Это скомпилируется в любом месте, в то время как вторая часть кода не будет компилироваться в любом месте.




int x = x = 1;

эквивалентно

int x = 1;
x = x; //warning here

пока в

int x = x + 1; 

сначала нам нужно вычислить x+1 но значение x неизвестно, поэтому вы получаете ошибку (компилятор знает, что значение x неизвестно)




В java или на любом современном языке назначение происходит справа.

Предположим, что если у вас есть две переменные x и y,

int z = x = y = 5;

Этот оператор действителен, и именно так компилятор разбивает их.

y = 5;
x = y;
z = x; // which will be 5

Но в вашем случае

int x = x + 1;

Компилятор дал исключение, потому что он так разбивается.

x = 1; // oops, it isn't declared because assignment comes from the right.



Во втором фрагменте кода x используется перед его объявлением, тогда как в первом фрагменте кода он просто назначается дважды, что не имеет смысла, но является допустимым.




int x = x = 1; не равно:

int x;
x = 1;
x = x;

javap помогает нам снова, это инструкция JVM, сгенерированная для этого кода:

0: iconst_1    //load constant to stack
1: dup         //duplicate it
2: istore_1    //set x to constant
3: istore_1    //set x to constant

скорее:

int x = 1;
x = 1;

Здесь нет причин бросать неопределенную опорную ошибку. В настоящее время используется переменная до ее инициализации, поэтому этот код полностью соответствует спецификации. Фактически, использование переменных вообще не является просто назначением. И JIT-компилятор будет идти еще дальше, он устранит такие конструкции. Говоря честно, я не понимаю, как этот код связан с спецификацией JLS для инициализации и использования переменных. Нет проблем без проблем. ;)

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

Если мы напишем:

int b, c, d, e, f;
int a = b = c = d = e = f = 5;

равно:

f = 5
e = 5
d = 5
c = 5
b = 5
a = 5

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

a = b = c = f = e = d = a = a = a = a = a = e = f = 5;



ТЛ; др

Для полей int b = b + 1 является незаконным, поскольку b является незаконной прямой ссылкой на b . Вы можете исправить это, написав int b = this.b + 1 , который компилируется без жалоб.

Для локальных переменных int d = d + 1 является незаконным, поскольку d не инициализируется перед использованием. Это не относится к полям, которые всегда инициализируются по умолчанию.

Вы можете видеть разницу, пытаясь скомпилировать

int x = (x = 1) + x;

как объявление поля и как объявление локальной переменной. Первое не удастся, но последнее преуспеет из-за разницы в семантике.

Введение

Во-первых, правила для инициализаторов полей и локальных переменных очень разные. Таким образом, этот ответ будет решать правила в двух частях.

Мы будем использовать эту тестовую программу:

public class test {
    int a = a = 1;
    int b = b + 1;
    public static void Main(String[] args) {
        int c = c = 1;
        int d = d + 1;
    }
}

Объявление b недействительно и не выполняется с illegal forward reference ошибкой.
Объявление d недействительно и не выполняется с variable d might not have been initialized ошибка.

Тот факт, что эти ошибки различны, должен указывать на то, что причины ошибок также различны.

поля

Инициализаторы полей в Java управляются JLS §8.3.2 , Инициализация полей.

Объем поля определен в JLS §6.3 , «Объем декларации».

Соответствующие правила:

  • Объем объявления члена m объявленного или унаследованного классом C (§8.1.6), является всем телом C, включая любые объявления вложенного типа.
  • Инициализационные выражения для переменных экземпляра могут использовать простое имя любой статической переменной, объявленной или унаследованной классом, даже той, чья декларация появляется позже.
  • Использование переменных экземпляра, объявления которых появляются по тексту после использования, иногда ограничены, хотя эти переменные экземпляра находятся в области видимости. См. Раздел 8.3.3.3 для точных правил, регулирующих прямую ссылку на переменные экземпляра.

В §8.3.2.3 говорится:

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

  • Использование происходит в экземпляре (соответственно статическом) инициализаторе переменной C или в экземпляре (соответственно статическом) инициализаторе C.
  • Использование не находится в левой части задания.
  • Использование осуществляется по простому имени.
  • C - самый внутренний класс или интерфейс, охватывающий использование.

Фактически вы можете ссылаться на поля до их объявления, за исключением некоторых случаев. Эти ограничения предназначены для предотвращения

int j = i;
int i = j;

от компиляции. Спецификация Java говорит: «вышеприведенные ограничения предназначены для улавливания, во время компиляции, циклических или иначе искаженных инициализаций».

К чему эти правила действительно сводятся?

Короче говоря, правила в основном говорят, что вы должны объявить поле перед ссылкой на это поле, если (a) ссылка находится в инициализаторе, (b) ссылка не назначается, (c) ссылка является простое имя (нет таких классификаторов), и (d) он не доступен из внутреннего класса. Таким образом, прямая ссылка, которая удовлетворяет всем четырем условиям, является незаконной, но обратная ссылка, которая не работает, по крайней мере, при одном условии, является ОК.

int a = a = 1; компилируется, потому что это нарушает (b): присваивается ссылка a , поэтому законно ссылаться на a до полной декларации.

int b = this.b + 1 также компилируется, потому что он нарушает (c): ссылка this.b не является простым именем (оно соответствует this. ). Эта нечетная конструкция все еще отлично определена, потому что this.b имеет значение 0.

Таким образом, в основном ограничения на ссылки на поля в инициализаторах предотвращают успешную компиляцию int a = a + 1 .

Обратите внимание, что объявление поля int b = (b = 1) + b не будет скомпилировано, поскольку окончательный b все еще является незаконной прямой ссылкой.

Местные переменные

Локальные объявления переменных управляются JLS §14.4 , Локальные заявления о декларации переменных.

Область локальной переменной определена в JLS §6.3 , Область применения Декларации:

  • Объем объявления локальной переменной в блоке (§14.4) - это остальная часть блока, в котором появляется декларация, начиная с ее собственного инициализатора и включая любые другие деклараторы справа в заявлении о объявлении локальной переменной.

Обратите внимание, что инициализаторы входят в область объявляемой переменной. Итак, почему нет int d = d + 1; компилировать?

Причина связана с правилом Java над определенным назначением ( JLS §16 ). Определенное назначение в основном говорит о том, что каждый доступ к локальной переменной должен иметь предыдущее присвоение этой переменной, а компилятор Java проверяет циклы и ветви, чтобы гарантировать, что назначение всегда происходит до использования (поэтому определенное назначение имеет весь раздел спецификации к нему). Основное правило:

  • Для каждого доступа к локальному переменному или пустому окончательному полю x необходимо обязательно назначить до доступа или возникнуть ошибка времени компиляции.

В int d = d + 1; , доступ к d разрешен для локальной переменной, но, поскольку d не был назначен до того, как d был достигнут, компилятор выдает ошибку. В int c = c = 1 сначала выполняется c = 1 , которое присваивает c , а затем c инициализируется результатом этого присвоения (что равно 1).

Обратите внимание, что из-за определенных правил присваивания объявление локальной переменной int d = (d = 1) + d; будет успешно скомпилироваться (в отличие от объявления поля int b = (b = 1) + b ), поскольку d определенно назначается к моменту достижения окончательного d .




Давайте разбить его шаг за шагом, правая ассоциативная

int x = x = 1

x = 1 , присвойте 1 переменной x

int x = x , присваиваем то, что x является для себя, как int. Поскольку x ранее был назначен как 1, он сохраняет 1, хотя и избыточным образом.

Это хорошо компилируется.

int x = x + 1

x + 1 , добавьте один к переменной x. Однако x не определено, это приведет к ошибке компиляции.

int x = x + 1 , таким образом, эта строка компилирует ошибки, так как правая часть равенств не будет компилировать, добавляя одну к неназначенной переменной




  1. Строки неизменны в Java, если вы храните пароль как обычный текст, он будет доступен в памяти до тех пор, пока сборщик мусора не очистит его, и поскольку строки используются в пуле String для повторного использования, существует довольно высокая вероятность того, что он останется в памяти надолго продолжительность, представляющая угрозу безопасности. Поскольку любой, у кого есть доступ к дампу памяти, может найти пароль в виде открытого текста
  2. Рекомендация Java с использованием getPassword() JPasswordField, который возвращает метод char [] и устаревший getText() который возвращает пароль в ясном тексте с указанием причины безопасности.
  3. toString () всегда существует риск печати обычного текста в файле журнала или консоли, но если вы используете Array, вы не будете печатать содержимое массива, а его память будет распечатана.

    String strPwd = "passwd";
    char[] charPwd = new char[]{'p','a','s','s','w','d'};
    System.out.println("String password: " + strPwd );
    System.out.println("Character password: " + charPwd );
    

    Строковый пароль: passwd

    Пароль персонажа: [C @ 110b2345

Заключительные мысли: хотя использование char [] не только достаточно, вам нужно стереть контент, чтобы быть более безопасным. Я также предлагаю работать с хешированным или зашифрованным паролем вместо обычного текста и освобождать его из памяти, как только будет завершена аутентификация.





java compiler-construction