Отсутствует оператор return в методе Non-void компилируется

я столкнулся с ситуацией, когда не-void метод отсутствует возвращение заявление и код все еще компилируется. Я знаю, что операторы после цикла while являются недоступен(мертвый код) и никогда не будут казнены. Но почему компилятор даже не предупреждает о возврате чего-то? Или почему язык позволяет нам иметь непустой метод, имеющий бесконечный цикл и ничего не возвращающий?

public int doNotReturnAnything() {
    while(true) {
        //do something
    }
    //no return statement
}

Если я добавлю перерыв оператор (даже условный) в цикле while компилятор жалуется на печально известные ошибки: "метод не возвращает значение" (Eclipse) и "не все пути кода возвращают значение" (Visual Studio)

public int doNotReturnAnything() {
    while(true) {
        if(mustReturn) break;
        //do something
    }
    //no return statement
}

Это верно как для Java, так и для C#

13 ответов


почему язык позволяет нам иметь непустой метод, имеющий бесконечный цикл и ничего не возвращающий?

правило для непустых методов -каждый возвращаемый путь кода должен возвращать значение, и это правило выполняется в вашей программе: ноль из нулевых путей кода, которые возвращают, возвращают значение. Правило не является "каждый метод Non-void должен иметь путь кода, который возвращает".

это позволяет писать заглушки-методы например:

IEnumerator IEnumerable.GetEnumerator() 
{ 
    throw new NotImplementedException(); 
}

это не-void метод. Это и быть Non-void методом для того чтобы удовлетворять интерфейс. Но кажется глупым делать эту реализацию незаконной, потому что она ничего не возвращает.

что ваш метод имеет недостижимую конечную точку из-за goto (помните, a while(true) - это просто более приятный способ писать goto) вместо throw (что является еще одной формой goto) не относится.

почему разве компилятор даже не предупреждает о возврате чего-то?

потому что у компилятора нет хороших доказательств того, что код неверен. Кто-то написал while(true) и кажется вероятным, что человек, который это сделал, знал, что делает.

где я могу узнать больше об анализе достижимости в C#?

см. мои статьи на эту тему, здесь:

ATBG: де-факто и де-юре достижимость

и вы также можете прочитать спецификацию C#.


Java-компилятор достаточно умен, чтобы найти недостижимый код ( код после while loop)

С недоступен, нет смысла в добавляем return заявление (после while заканчивается)

то же самое с условным if

public int get() {
   if(someBoolean) {   
     return 10;
   }
   else {
     return 5;
   }
   // there is no need of say, return 11 here;
}

так как логическое условие someBoolean можно оценить только либо true или false, нет необходимости предоставлять return явно после if-else, потому что этот код недоступен, и Java не жалуется на это.


компилятор знает, что while цикл никогда не прекратит выполнение, следовательно, метод никогда не завершится, следовательно,return оператор не требуется.


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

Если вы используете переменную - компилятор будет применять правило:

это не будет компилироваться:

// Define other methods and classes here
public int doNotReturnAnything() {
    var x = true;

    while(x == true) {
        //do something
    }
    //no return statement - won't compile
}

спецификация Java определяет концепцию под названием Unreachable statements. Вам не разрешено иметь недостижимый оператор в коде (это ошибка времени компиляции). Вам даже не разрешено иметь оператор return после while (true); оператор в Java. А while(true); оператор делает следующие операторы недоступными по определению, поэтому вам не нужен return заявление.

обратите внимание, что в то время как проблема останова неразрешимо в общем случае, определение Недостижимое утверждение более строго, чем просто остановка. Это решение очень конкретных случаев, когда программа определенно не останавливается. Теоретически компилятор не может обнаружить все бесконечные циклы и недостижимые операторы, но он должен обнаружить конкретные случаи, определенные в спецификации (например,while(true) случае)


компилятор достаточно умен, чтобы узнать, что ваш while цикл бесконечен.

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

Итак, чтобы ответить на ваш вопрос:

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

там могут быть законные причины для бесконечного цикла. Например, многие приложения используют бесконечный основной цикл. Другим примером является веб-сервер, который может бесконечно ждать запросов.


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


в теории типов есть что-то, называемое снизу тип который является подклассом любого другого типа (!) и используется для указания на прекращение, среди прочего. (Исключения могут считаться типом non-termination-вы не завершаете работу по обычному пути.)

таким образом, с теоретической точки зрения, эти утверждения, которые не заканчиваются, можно считать возвращающими что - то типа Bottom, который является подтипом int, поэтому вы (вроде) получаете свое возвращение значение в конце концов с точки зрения типа. И совершенно нормально, что не имеет никакого смысла, что один тип может быть подклассом всего остального, включая int, потому что вы никогда не возвращаете его.

в любом случае, с помощью явной теории типов или нет, компиляторы (авторы компиляторов) признают, что запрашивать возвращаемое значение после неокончаемого оператора глупо: нет возможного случая, в котором вам могло бы понадобиться это значение. (Может быть приятно, чтобы ваш компилятор предупреждал вас, когда он знает, что что-то не закончится, но похоже, вы хотите, чтобы он что-то вернул. Но это лучше оставить для style-checkers a la lint, так как, возможно, вам нужна подпись типа таким образом по какой-то другой причине (например, подклассы), но вы действительно хотите non-termination.)


Visual studio имеет интеллектуальный движок для обнаружения, если вы ввели тип возврата, то он должен иметь оператор return с функцией / методом.

Как и в PHP, ваш тип возврата true, если вы ничего не вернули. компилятор получает 1, если ничего не вернулось.

Как это

public int doNotReturnAnything() {
    while(true) {
        //do something
    }
    //no return statement
}

компилятор знает, что в то время как сам оператор имеет бесконечную природу, поэтому не рассматривать его. и компилятор php автоматически получит true, если вы напишете условие в выражение while.

но не в случае VS он вернет вам ошибку в стеке .


ваш цикл while будет работать вечно и, следовательно, не выйдет наружу, пока; он будет продолжать выполняться. Следовательно, внешняя часть while{} недоступна, и нет смысла писать return или нет. Компилятор достаточно умен, чтобы выяснить, какая часть достижима, а какая нет.

пример:

public int xyz(){
    boolean x=true;

    while(x==true){
        // do something  
    }

    // no return statement
}

приведенный выше код не будет компилироваться, потому что может быть случай, когда значение переменной x изменяется внутри тела цикла while. Так это делает внешняя часть цикла while добраться! И, следовательно, компилятор выдаст ошибку "не найден оператор return".

компилятор недостаточно умен (или, скорее, ленив;)), чтобы выяснить, будет ли изменено значение x или нет. Надеюсь, это все прояснит.


" почему компилятор даже не предупреждает о возврате чего-либо? Или почему язык позволяет нам иметь непустой метод, имеющий бесконечный цикл и ничего не возвращающий?".

этот код действителен и на всех других языках (возможно, кроме Haskell!). Потому что первое предположение-мы "намеренно" пишем какой-то код.

и есть ситуации, когда этот код может быть полностью действительным, например, если вы собираетесь использовать его в качестве потока; или если он возвращал Task<int>, вы можете сделать некоторую проверку ошибок на основе возвращенного значения int-которое не должно быть возвращено.


Я могу ошибаться, но некоторые отладчики позволяют изменять переменные. Здесь, пока x не изменен кодом, и он будет оптимизирован JIT, можно изменить x на false, и метод должен что-то вернуть (если это разрешено отладчиком C#).


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

в частности, правила заключаются в том, что метод с возвращаемым типом не должен иметь возможности вполне нормально и должен вместо этого всегда завершать резко (резко здесь, указывая через оператор возврата или исключение) per JLS 8.4.7.

если объявлен метод с возвращаемым типом, то время компиляции ошибка возникает, если тело метода может завершиться нормально. Другими словами, метод с типом return должен возвращать только с помощью оператор return, который предоставляет значение return;не допускается "опустите конец его тела".

компилятор смотрит, есть ли нормальное завершение можно на основе правил, определенных в JLS 14.21 Недостижимые Утверждения а также определяет правила для нормального завершения работы.

Примечательно, что правила для недостижимых операторов делают особый случай только для циклов, которые имеют определенный true константное выражение:

оператор while может выполнить нормально iff хотя бы один из верно следующее:

  • пока оператор достижим, и выражение условия не является константным выражением (§15.28) с значение true.

  • существует оператор reachable break, который выходит из оператора while.

так если while заявление вполне нормально, то оператор возврата ниже необходимо, так как код считается достижимым, и любой while цикл без достижимого оператора break или константы true выражение считается способным нормально завершаться.

эти правила означают, что ваш while заявление с постоянным истинным выражением и без break is никогда не считалось завершить нормально, и поэтому любой код ниже никогда не считалось достижимым. Конец метода находится ниже цикла, и поскольку все ниже цикла недоступно, то и конец метода, и, следовательно, метод не может быть вполне нормально (который является то, что компилятор ищет).

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

сравниваем:

// I have a compiler error!
public boolean testReturn()
{
    final boolean condition = true;

    if (condition) return true;
}

С:

// I compile just fine!
public boolean testReturn()
{
    final boolean condition = true;

    while (condition)
    {
        return true;
    }
}

причина различия довольно интересна и связана с желанием разрешить условные флаги компиляции, которые не вызывают ошибок компилятора (из JLS):

можно ожидать, что оператор if будет обрабатываться в следующем манера:

  • оператор if-then может нормально завершить iff по крайней мере один из верно следующее:

    • оператор if-then достижим, а выражение условия-нет постоянное выражение, значение которого равно true.

    • then-statement может завершаться нормально.

    оператор then-достижим, если оператор if-then достижим и выражение условия не постоянное выражение, значение которого ложный.

  • оператор if-then-else может нормально завершить IFF оператор then может завершаться нормально или else-оператор может завершаться нормально.

    • оператор then достижим, если оператор if-then-else достижим, и выражение условия не является константным выражением значение которого false.

    • оператор else - достижимый, если оператор if-then-else достижим, и выражение условия не является константным выражением значением которого является True.

этот подход соответствовал бы обработке другого контроля структуры. Однако, чтобы разрешить использование оператора if удобно для целей "условной компиляции", фактические правила отличаться.

в качестве примера, следующий оператор приводит к во время компиляции ошибка:

while (false) { x=3; } потому что заявление x=3; недоступен; но внешне похожий случай:

if (false) { x=3; } не приводит к ошибке времени компиляции. - оптимизирующий компилятор может понять, что оператор x=3; никогда не будет выполняется и может выбрать опустить код для этого оператора из сгенерированный файл класса, но оператор x=3; не считается "недостижимый" в техническом смысле здесь.

обоснование этого отличающегося лечения состоит в том, чтобы позволить программистам определите "переменные флага", такие как:

static final boolean DEBUG = false; а затем напишите код, например:

if (DEBUG) { x=3; } идея в том, что это должно быть возможно изменить значение DEBUG от false до true или от true до false, а затем скомпилируйте код правильно, без каких-либо других изменений в тексте программы.

почему условный разрыв оператор приводит к ошибке компилятора?

как указано в правилах достижимости цикла, цикл while также может завершаться нормально, если он содержит оператор reachable break. Поскольку правила для достижимости if заявление затем предложение не принимает условие if в соображение вообще, такое условное if заявление затем предложение всегда считается достижимым.

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