В Java имя переменной может быть одинаковым с именем класса

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

Итак, у меня есть проблема в фрагменте кода ниже: как компилятор может отличить ClassName, он ссылается на имя переменной или класс?

В текущем результате компилятор ссылается на ClassName как имя переменной.

class ClassName{}

public class Test {
    public static void main(String[] args){
        ClassName ClassName = new ClassName();
        System.out.println(ClassName); //[email protected]
    }
}

Ответ 1

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

ClassName ClassName = new ClassName();
    1         2               3

Он может видеть, что 1 - это имя типа, поэтому он знает, что вы имеете в виду класс. Тогда 2 - это то, где ожидается имя переменной, поэтому оно знает, что это должно быть имя переменной. И 3 идет после ключевого слова new с круглыми скобками, поэтому это должно быть имя класса.

System.out.println( ClassName );

В этом случае ClassName находится в контексте передачи аргументов. Имя типа не может быть передано в качестве аргумента, поэтому вы должны иметь в виду имя переменной.

Чтобы развлечь себя, вы можете изменить оператор печати на:

System.out.println( ClassName.class );

Наведите указатель мыши на ClassName, и вы увидите, что компилятор распознает это как имя класса. Затем измените его на:

System.out.println( ClassName.getClass() );

Наведите курсор еще раз, и теперь вы увидите, что он распознает его как имя переменной. Это потому, что .class может применяться только к имени типа, а getClass() может применяться только к ссылке на объект. Результат утверждения печати будет одинаковым в обоих случаях, но с помощью разных механизмов.

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

Я не могу точно сказать, почему авторы Java предпочли не применять это соглашение (то есть дать ошибку компилятора, если имена типов начинались с строчных букв или имен переменных/методов, начинающихся с прописных букв), но я размышляю что они не хотят делать что-либо фактической ошибкой, если это фактически не вызовет двусмысленности для компилятора. Ошибки компиляции должны указывать на проблему, которая заставляет компилятор не выполнять свою работу.

Ответ 2

как компилятор может отличить "Имя класса"

Потому что есть два компонента: тип переменной и имя переменной. Вы объявляете переменную ClassName типа ClassName. Тип всегда идет первым. Классы не являются первоклассными объектами (что означает, что вы не можете иметь ссылку на класс), если вы не попадете в отражения (с свойством .class).

Поэтому в инструкции print:

System.out.println(ClassName);

Это может быть только переменная. System.out.println принимает ссылку на объект, и у вас есть объект, на который ссылается переменная с именем ClassName, поэтому компилятор может ее решить.

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

public class SomeClass {
    public void aMethod() {
        System.out.println("A method!");
    }

    public static void aMethod() {
        System.out.println("Static version!");
    }
}

public class TestClass {
    public static void main (String[] args) {
        SomeClass SomeClass = new SomeClass();
        SomeClass.aMethod();  // does this call the instance method or the static method?
    }
}

Я уверен, что компилятор обнаружит неоднозначность и обработает ее определенным образом (в спецификации Java). Вероятно, один из:

  • Не допускайте, чтобы статический и экземплярный метод имел одно и то же имя.
  • Разрешить это, и при разрешении ссылки во время компиляции предпочитайте метод экземпляра.
  • Разрешить это, и при разрешении ссылки во время компиляции предпочитайте статический метод.

Если какой-либо из последних 2, я думаю, будет зарегистрировано предупреждение компилятора.

Теперь, когда вопрос компилятора в стороне, единственным другим потребителем кода являются люди. Компиляторы могут быть в состоянии полагаться на спецификации, чтобы гарантировать логическое поведение, но люди не могут. Мы смущаемся легко. Лучший совет, который у меня есть, - просто, не делайте этого!

Нет абсолютно никаких оснований для обозначения переменной, идентичной классу. Фактически, большинство соглашений о стиле кодирования Java, которые я видел, используют lowerCamelCase для обозначения переменных и методов и UpperCamelCase для обозначения классов, поэтому нет возможности столкнуться, если вы не отклоняетесь от стандартов.

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

Для моего двусмысленного случая экземпляра и статического метода с тем же именем, может быть, есть и человеческий урок: не делайте этого!

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

Ответ 3

ClassName ClassName = new ClassName();

Если вы изучаете курс проектирования компилятора, вы узнаете, что есть шаг лексического анализа. На этом этапе вы будете писать грамматику для своего языка. например:

ClassName variableName = new ClassName();

Итак, пример выше, компилятор может понять, что второй ClassName является переменной.

Когда вы делаете что-то вроде:

ClassName.doSomething();

Java будет понимать ClassName как переменную, а не класс. И этот дизайн не будет иметь никаких ограничений. doSomething() может быть как статическим методом, так и просто методом экземпляра.

Если Java понимает ClassName здесь как класс, поэтому doSomething() не может быть методом экземпляра. Может быть, потому, что этот создатель Java выбрал выше дизайна: ClassName как переменная.

Но какая проблема, если имя переменной не может быть тем же именем с их классом. поэтому следующий пример:

ClassA ClassB = new ClassA();
ClassB.callMethodInClassB();  // should compile error or not ???!!!

Проблема все еще здесь. До сих пор существует заблуждение. Таким образом, новый дизайн должен быть:

No variable name should not has same name with **any** class name.

И вы увидите, что это утверждение делает один язык более сложным, чтобы понять и не так хорошо определить. Из вышеприведенных доказательств я думаю, что когда вы делаете что-то вроде: A A = new A(); понимаете, что A как переменная - лучший способ в дизайне языка.

Надеюсь, что эта помощь:)