non-void methodにreturnステートメントがなく、コードがまだコンパイルされる状況に遭遇しました。 whileループの後のステートメントはnreachable(dead code)であり、決して実行されないことを知っています。しかし、なぜコンパイラは何かを返すことについて警告しないのでしょうか?または、言語によって、無限ループを持ち、何も返さない非voidメソッドを使用できるのはなぜですか?
public int doNotReturnAnything() {
while(true) {
//do something
}
//no return statement
}
Whileループにbreakステートメント(条件付きステートメントを含む)を追加すると、コンパイラは悪名高いエラーについて文句を言います:Method does not return a value
EclipseおよびNot all code paths return a value
Visual Studioで。
public int doNotReturnAnything() {
while(true) {
if(mustReturn) break;
//do something
}
//no return statement
}
これは、JavaとC#の両方に当てはまります。
なぜ言語は、無限ループを持ち、何も返さない非voidメソッドを持つことができるのでしょうか?
非voidメソッドのルールはを返すすべてのコードパスは値を返す必要があり、そのルールはプログラムで満たされます:ゼロコードからゼロ戻るパスは値を返します。ルールは、「すべての非voidメソッドに返されるコードパスが必要」ではありません。
これにより、次のようなスタブメソッドを記述できます。
_IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
_
それは非voidメソッドです。インターフェイスを満足させるために、非hasメソッドをvoid以外のメソッドにする必要があります。しかし、何も返さないため、この実装を違法にするのはばかげているようです。
goto
(goto
の代わりにwhile(true)
がthrow
を書くのにより快適な方法であることを思い出してください) ] _(goto
の別の形式)は関係ありません。
なぜコンパイラは何かを返すことについて警告さえしないのですか?
コンパイラには、コードが間違っているという十分な証拠がないためです。誰かがwhile(true)
を書いたので、それをした人は自分が何をしているか知っていたようです。
C#での到達可能性分析に関する詳細はどこで参照できますか?
このテーマに関する私の記事を参照してください。
また、C#仕様を読むことも検討してください。
Javaコンパイラーは、到達不能コード(while
ループの後のコード)を見つけるのに十分スマートです。
そして、到達不能なので、ポイントがないに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
のいずれかにしか評価できないため、if-else
の後にreturn
explicitlyを指定する必要はありません。そのコードは到達不能であり、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
_と呼ばれる概念を定義します。コードに到達不能なステートメントを含めることは許可されていません(コンパイル時エラーです)。 while(true);の後のreturnステートメント; Javaのステートメントwhile(true);
ステートメントは、定義により次のステートメントに到達できないため、return
ステートメントは不要です。
Halting problem は一般的なケースでは決定不能ですが、到達不能ステートメントの定義は、停止するだけではありません。プログラムが確実に停止しない非常に具体的なケースを決定しています。コンパイラーは理論的にはすべての無限ループと到達不能ステートメントを検出することはできませんが、仕様で定義されている特定のケース(たとえば、while(true)
ケース)を検出する必要があります
型理論では、bottom typeと呼ばれるものがあります。これは、他のすべての型のサブクラス(!)であり、他のものの中で終了しないことを示すために使用されます。 (例外は、非終了のタイプとしてカウントできます。通常のパスを介して終了することはできません。)
したがって、理論的な観点からは、これらの終了しないステートメントは、intのサブタイプであるBottomタイプの何かを返すと見なすことができます。したがって、結局、タイプの観点から戻り値を取得します。また、1つの型がintを含む他のすべてのサブクラスになり得るということは、実際には1つを返さないため、まったく意味をなさないことはまったく問題ありません。
いずれにせよ、明示的な型理論の有無にかかわらず、コンパイラー(コンパイラーライター)は、終了しないステートメントの後に戻り値を要求するのはばかげていると認識します。その値が必要になる可能性はありません。 (何かが終了しないことを知っているときにコンパイラに警告してもらうといいかもしれませんが、何かを返すようにしたいようです。何らかの他の理由(サブクラス化など)のための方法ですが、本当に終了しないことを望みます。)
コンパイラは、あなたのwhile
ループが無限であることを知るのに十分賢いです。
そのため、コンパイラはあなたのために考えることができません。それは推測できませんなぜあなたはそのコードを書きました。メソッドの戻り値も同じです。 Javaメソッドの戻り値で何もしなくても文句は言いません。
だから、あなたの質問に答えるために:
コンパイラーはコードを分析し、実行パスが関数の終わりから抜けないことを発見した後、OKで終了します。
無限ループの正当な理由があるかもしれません。たとえば、多くのアプリは無限のメインループを使用します。別の例は、リクエストを無期限に待機するWebサーバーです。
関数が適切な値を返さずに最後に到達できる状況はありません。したがって、コンパイラーが文句を言うことはありません。
Visual Studioには、ユーザーが戻り値の型を入力したかどうかを検出するスマートエンジンがあり、関数/メソッドにreturnステートメントが必要です。
PHPのように、何も返されなかった場合、戻り値の型はtrueです。何も返されなかった場合、コンパイラは1を取得します。
この時点で
public int doNotReturnAnything() {
while(true) {
//do something
}
//no return statement
}
コンパイラは、ステートメント自体には無限の性質があるため、考慮しないようにします。 whileの式で条件を記述すると、phpコンパイラは自動的にtrueになります。
しかし、VSの場合ではなく、スタック内のエラーを返します。
「なぜコンパイラは何かを返すことについて警告さえしないのですか?または、言語が無限ループを持ち、何も返さない非voidメソッドを持つことを許可するのはなぜですか?」.
このコードは他のすべての言語でも有効です(おそらくHaskellを除く!)。最初の仮定は、「意図的に」いくつかのコードを書いているからです。
また、このコードをスレッドとして使用する場合など、完全に有効な場合があります。または、Task<int>
を返していた場合、返されたint値に基づいてエラーチェックを行うことができます-返されるべきではありません。
Whileループは永久に実行されるため、whileの外に出ることはありません。実行を継続します。したがって、while {}の外側の部分は到達不能であり、書面で返品するかどうかは意味がありません。コンパイラーは、どの部分が到達可能で、どの部分が到達可能でないかを判断するのに十分なほどインテリジェントです。
例:
public int xyz(){
boolean x=true;
while(x==true){
// do something
}
// no return statement
}
変数xの値がwhileループの本体内で変更される場合があるため、上記のコードはコンパイルされません。これにより、whileループの外側の部分が到達可能になります!したがって、コンパイラは「リターンステートメントが見つかりません」というエラーをスローします。
コンパイラーは、xの値が変更されるかどうかを判断するのに十分なほどインテリジェントではありません(むしろ、怠zyな;))。これですべてがクリアされることを願っています。
私は間違っているかもしれませんが、一部のデバッガーは変数の変更を許可します。ここで、xはコードによって変更されず、JITによって最適化されますが、xをfalseに変更すると、メソッドは何かを返すはずです(そのようなことがC#デバッガーで許可されている場合)。
このためのJavaケース(おそらくC#のケースに非常によく似ている)の詳細は、Javaコンパイラがメソッドが返すことができます。
具体的には、戻り値型のメソッドは正常に完了することはできず、代わりに常に突然に完了する(ここでは、return文または例外を介して JLS 8.4.7 ごとに突然示されます。
メソッドに戻り型があると宣言されている場合、メソッドの本体が正常に完了するとコンパイル時エラーが発生します。つまり、戻り値の型を持つメソッドは、値を返すreturnステートメントを使用することによってのみ戻ります。 「ボディの端からドロップオフ」することはできません。
コンパイラは、 JLS 14.21 Unreachable Statements で定義された規則に基づいて正常終了が可能かどうかを確認します。これは、正常終了の規則も定義しているためです。
特に、到達不能ステートメントの規則は、定義されたtrue
定数式を持つループに対してのみ特別なケースを作成します。
Whileステートメントは、次のうち少なくとも1つが当てはまる場合に正常に完了します。
Whileステートメントは到達可能であり、条件式は値trueの定数式(§15.28)ではありません。
Whileステートメントを終了する到達可能なbreakステートメントがあります。
したがって、while
ステートメントが正常に完了できる場合、コードは到達可能と見なされるため、その下にreturnステートメントが必要であり、while
ループ到達可能なbreakステートメントまたは定数true
式がなければ、正常に完了できると見なされます。
これらのルールは、while
が定数で真の式を持つbreak
ステートメントが正常に完了すると見なされないことを意味します 、したがって、以下のコードは到達可能とは見なされません。メソッドの終わりはループの下にあり、ループの下にあるものはすべて到達不能であるため、メソッドの終わりもそうであるため、メソッドは正常に完了できない可能性があります(コンパイラが探すもの)。
一方、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ステートメントは、次の少なくとも1つが当てはまる場合に正常に完了できます。
If-thenステートメントは到達可能であり、条件式は値がtrueの定数式ではありません。
Then-statementは正常に完了できます。
If-thenステートメントが到達可能で、条件式が値がfalseの定数式でない場合、then-ステートメントは到達可能です。
If-then-elseステートメントは、then-statementが正常に完了するか、else-statementが正常に完了する場合に限り、正常に完了することができます。
If-then-elseステートメントが到達可能で、条件式が値がfalseの定数式ではない場合、then-statementは到達可能です。
If-then-elseステートメントが到達可能で、条件式が値がtrueの定数式でない場合、else-statementは到達可能です。
このアプローチは、他の制御構造の処理と一致します。ただし、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に変更し、プログラムテキストに他の変更を加えずにコードを正しくコンパイルできることです。
条件付きbreakステートメントがコンパイラエラーになるのはなぜですか?
ループ到達可能性ルールで引用されているように、whileループは、到達可能なbreakステートメントが含まれている場合にも正常に完了できます。 if
ステートメントのthen節の到達可能性の規則は、if
の条件をまったく考慮しないため、このような条件付きif
ステートメントのthen句は常に到達可能と見なされます。
break
が到達可能であれば、ループ後のコードも到達可能と見なされます。ループの後にabrupt終了となる到達可能なコードがないため、メソッドは正常に完了できると見なされ、コンパイラはエラーとしてフラグを立てます。