Ответ 1
1. Что означает ошибка "Не удается найти символ"?
Во-первых, это ошибка компиляции 1. Это означает, что либо проблема в исходном коде Java, либо в способе его компиляции.
Ваш исходный код Java состоит из следующих вещей:
- Ключевые слова: например,
true
,false
,class
,while
и т.д. - Литералы: как
42
и'X'
и"Hi mum!"
. - Операторы и другие не алфавитно-цифровые токены: например,
+
,=
,{
и т.д. - Идентификаторы: например,
Reader
,i
,toString
,processEquibalancedElephants
и т.д. - Комментарии и пробелы.
Ошибка "Не удается найти символ" связана с идентификаторами. Когда ваш код скомпилирован, компилятору необходимо выяснить, что означает каждый идентификатор в вашем коде.
Ошибка "Не удается найти символ" означает, что компилятор не может этого сделать. Похоже, ваш код ссылается на что-то, чего компилятор не понимает.
2. Что может вызвать ошибку "Не удается найти символ"?
Как первый заказ, есть только одна причина. Компилятор просмотрел все места, где должен быть определен идентификатор, и не смог найти определение. Это может быть вызвано рядом вещей. Наиболее распространенными являются следующие:
- Для идентификаторов в целом:
- Возможно, вы написали имя неправильно; то есть
StringBiulder
вместоStringBuilder
. Java не может и не будет пытаться компенсировать ошибки в орфографии или опечатках. - Возможно, вы ошиблись; то есть
stringBuilder
вместоStringBuilder
. Все идентификаторы Java чувствительны к регистру. - Возможно, вы неправильно использовали подчеркивание; то есть
mystring
иmy_string
различны. (Если вы будете придерживаться правил стиля Java, вы будете в значительной степени защищены от этой ошибки...) - Возможно, вы пытаетесь использовать то, что было объявлено "где-то еще"; то есть в другом контексте, в котором вы явно указали компилятору искать. (Другой класс? Другая область применения? Другой пакет? Другая база кода?)
- Возможно, вы написали имя неправильно; то есть
- Для идентификаторов, которые должны ссылаться на переменные:
- Возможно, вы забыли объявить переменную.
- Возможно, объявление переменной выходит за рамки того, что вы пытались использовать. (См. пример ниже)
Для идентификаторов, которые должны быть именами методов или полей:
- Возможно, вы пытаетесь сослаться на унаследованный метод или поле, которое не было объявлено в классах или интерфейсах родителя/предка.
- Возможно, вы пытаетесь сослаться на метод или поле, которые не существуют (то есть не были объявлены) в используемом вами типе; например
"someString".push()
2. - Возможно, вы пытаетесь использовать метод в качестве поля или наоборот; например
"someString".length
илиsomeArray.length()
. Возможно, вы ошибочно работаете с массивом, а не с элементом массива; например,
String strings[] = ... if (strings.charAt(3)) { ... } // maybe that should be 'strings[0].charAt(3)'
Для идентификаторов, которые должны быть именами классов:
- Возможно, вы забыли импортировать класс.
- Возможно, вы использовали импорт "звезда", но класс не определен ни в одном из импортированных вами пакетов.
Возможно, вы забыли
new
как в:String s = String(); // should be 'new String()'
Для случаев, когда тип или экземпляр не имеют члена, которого вы ожидали иметь:
- Возможно, вы объявили вложенный класс или универсальный параметр, который скрывает тип, который вы хотели использовать.
- Возможно, вы скрываете статическую переменную или переменную экземпляра.
- Возможно, вы импортировали неправильный тип; например из-за завершения IDE или автокоррекции.
- Возможно, вы используете (компилируете) не ту версию API.
- Возможно, вы забыли привести свой объект к соответствующему подклассу.
Проблема часто заключается в сочетании вышеперечисленного. Например, может быть, вы "звездой" импортировали java.io.*
, а затем пытались использовать класс Files
... который находится в java.nio
, а не в java.io
. Или, может быть, вы хотели написать File
... который является классом в java.io
.
Вот пример того, как неправильная область видимости переменной может привести к ошибке "Не удается найти символ":
List<String> strings = ...
for (int i = 0; i < strings.size(); i++) {
if (strings.get(i).equalsIgnoreCase("fnord")) {
break;
}
}
if (i < strings.size()) {
...
}
Это приведет к ошибке "Не удается найти символ" для i
в операторе if
. Хотя мы ранее объявили i
, это объявление находится только в области действия оператора for
и его тела. Ссылка на i
в операторе if
не может видеть это объявление i
. Это выходит за рамки.
(Подходящим исправлением здесь может быть перемещение оператора if
внутри цикла или объявление i
до начала цикла.)
Вот пример, который вызывает недоумение, когда опечатка приводит к, казалось бы, необъяснимой ошибке "Не удается найти символ":
for (int i = 0; i < 100; i++); {
System.out.println("i is " + i);
}
Это даст вам ошибку компиляции в вызове println
, говорящую о том, что i
не может быть найден. Но (я слышу, вы говорите), я это объявил!
Проблема в хитрой точке с запятой (;
) перед {
. Синтаксис языка Java определяет точку с запятой в этом контексте как пустой оператор. Пустой оператор становится телом цикла for
. Так что этот код на самом деле означает это:
for (int i = 0; i < 100; i++);
// The previous and following are separate statements!!
{
System.out.println("i is " + i);
}
Блок { ... }
НЕ является телом цикла for
, поэтому предыдущее объявление i
в операторе for
находится вне области действия в блоке.
Вот еще один пример ошибки "Не удается найти символ", вызванной опечаткой.
int tmp = ...
int res = tmp(a + b);
Несмотря на предыдущее объявление, выражение tmp
в выражении tmp(...)
является ошибочным. Компилятор будет искать метод с именем tmp
и не найдет его. Ранее объявленный tmp
находится в пространстве имен для переменных, а не в пространстве имен для методов.
В приведенном мной примере программист фактически исключил оператор. Он хотел написать следующее:
int res = tmp * (a + b);
Есть еще одна причина, по которой компилятор может не найти символ, если вы компилируете из командной строки. Возможно, вы просто забыли скомпилировать или перекомпилировать какой-то другой класс. Например, если у вас есть классы Foo
и Bar
, где Foo
использует Bar
. Если вы никогда не компилировали Bar
и запускали javac Foo.java
, вы наверняка обнаружите, что компилятор не может найти символ Bar
. Простой ответ - скомпилировать Foo
и Bar
вместе; например javac Foo.java Bar.java
или javac *.java
. Или лучше использовать инструмент сборки Java; например Муравей, мавен, Градл и т.д.
Есть и другие неясные причины, о которых я расскажу ниже.
3. Как я могу исправить эти ошибки?
Вообще говоря, вы начинаете с выяснения причины ошибки компиляции.
- Посмотрите на строку в файле, обозначенную сообщением об ошибке компиляции.
- Определите символ, о котором говорится в сообщении об ошибке.
- Выясните, почему компилятор говорит, что он не может найти символ; см. выше!
Затем вы думаете о том, что ваш код должен говорить. Затем, наконец, вы решаете, какое исправление необходимо внести в исходный код, чтобы сделать то, что вы хотите.
Обратите внимание, что не каждая "коррекция" является правильной. Учтите это:
for (int i = 1; i < 10; i++) {
for (j = 1; j < 10; j++) {
...
}
}
Предположим, что компилятор говорит "Не удается найти символ" для j
. Есть много способов, которыми я мог бы это исправить:
- Я мог бы изменить внутренний
for
наfor (int j = 1; j < 10; j++)
- возможно, правильный. - Я мог бы добавить объявление для
j
перед внутренним цикломfor
или внешним цикломfor
- возможно, правильным. - Я мог бы изменить
j
наi
во внутреннем циклеfor
- возможно, неправильно! - и так далее.
Дело в том, что вам нужно понять, что пытается сделать ваш код, чтобы найти правильное решение.
4. Непонятные причины
Вот пара случаев, когда "Не удается найти символ", казалось бы, необъяснимо... пока вы не посмотрите ближе.
Неверные зависимости. Если вы используете IDE или инструмент сборки, который управляет путем сборки и зависимостями проекта, возможно, вы допустили ошибку с зависимостями; например исключил зависимость или выбрал неверную версию. Если вы используете инструмент сборки (Ant, Maven, Gradle и т.д.), Проверьте файл сборки проекта. Если вы используете IDE, проверьте конфигурацию пути сборки проекта.
Вы не перекомпилируете: иногда новые программисты на Java не понимают, как работает цепочка инструментов Java, или не реализуют повторяемый "процесс сборки"; например используя IDE, Ant, Maven, Gradle и так далее. В такой ситуации программист может в итоге гоняться за хвостом в поисках иллюзорной ошибки, которая на самом деле вызвана неправильной перекомпиляцией кода и т.п....
Проблема с более ранней сборкой. Возможно, что более ранняя сборка завершилась неудачно, что дало файл JAR с отсутствующими классами. Такой сбой, как правило, будет замечен, если вы используете инструмент сборки. Однако, если вы получаете файлы JAR от кого-то другого, вы зависите от их правильной сборки и замечаете ошибки. Если вы подозреваете это, используйте
tar -tvf
, чтобы просмотреть содержимое подозрительного JAR файла.Проблемы IDE: Люди сообщали о случаях, когда их IDE путается, и компилятор в IDE не может найти существующий класс... или обратная ситуация.
Это может произойти, если IDE была настроена с неверной версией JDK.
Это может произойти, если кэши IDE не синхронизируются с файловой системой. Есть конкретные способы исправить это в среде IDE.
Это может быть ошибка IDE. Например, @Joel Costigliola описывает сценарий, в котором Eclipse неправильно обрабатывает "тестовое" дерево Maven: см. Этот ответ.
Проблемы с Android: если вы программируете для Android и у вас есть ошибки "Не удается найти символ", связанные с
R
, имейте в виду, что символыR
определены в файлеcontext.xml
. Убедитесь, что ваш файлcontext.xml
правильный и в правильном месте, и что соответствующий файл классаR
был сгенерирован/скомпилирован. Обратите внимание, что символы Java чувствительны к регистру, поэтому соответствующие идентификаторы XML также чувствительны к регистру.Другие ошибки символов в Android, скорее всего, связаны с ранее упомянутыми причинами; например отсутствующие или неверные зависимости, неправильные имена пакетов, методы или поля, которых нет в определенной версии API, ошибки правописания/ввода и т.д.
Переопределение системных классов: я видел случаи, когда компилятор жалуется, что
substring
является неизвестным символом в чем-то вроде следующегоString s = ... String s1 = s.substring(1);
Оказалось, что программист создал свою собственную версию
String
и что его версия класса не определила методыsubstring
.Урок: не определяйте свои собственные классы с такими же именами, как у обычных библиотечных классов!
Гомоглифы: Если вы используете кодировку UTF-8 для своих исходных файлов, возможно, что идентификаторы выглядят одинаково, но на самом деле они разные, поскольку содержат гомоглифы. Смотрите эту страницу для получения дополнительной информации.
Этого можно избежать, ограничившись ASCII или Latin-1 в качестве кодировки исходного файла и используя Java
\uxxxx
для других символов.
1 - If, perchance, you do see this in a runtime exception or error message, then either you have configured your IDE to run code with compilation errors, or your application is generating and compiling code .. at runtime.
2 - The three basic principles of Civil Engineering: water does not flow uphill, a plank is stronger on its side, and you can't push on a string.