Alternative zu einer goto-Anweisung in Java


83

Was ist eine alternative Funktion für das Schlüsselwort goto in Java?

Da hat Java kein goto.


10
@harigm: Können Sie ein Beispiel für die Art von Code geben, den Sie schreiben würden, wenn er goto in Java verfügbar wäre ? Ich denke, darin liegt das wichtigere Thema.
Polygenelubricants

3
@harigm: Ich zweite Polygene, könnten Sie bitte Ihre Frage aktualisieren, um anzugeben, warum Ihr Programm ein goto benötigt?
Jeder

13
Die Leute reden immer davon, niemals ein goto zu verwenden, aber ich denke, es gibt einen wirklich guten realen Anwendungsfall, der ziemlich bekannt und verwendet ist. Das heißt, Sie müssen sicherstellen, dass Sie Code ausführen, bevor Sie von einer Funktion zurückkehren. Normalerweise wird er freigegeben Schlösser oder was nicht, aber in meinem Fall würde ich gerne kurz vor der Rückkehr zu einer Pause springen können, damit ich die erforderliche obligatorische Bereinigung durchführen kann. Natürlich wäre ein Programm, das überall mit Goto übersät ist, schrecklich, aber wenn es auf Methodenkörper beschränkt ist, scheint es nicht so schlimm zu sein, solange Sie einer Konvention folgen (nur bis zum Ende der Funktionen springen, niemals sichern)
Matt Wolfe

7
Du bist in guter Gesellschaft. Linus Torvalds hat goto kerneltrap.org/node/553 leidenschaftlich verteidigt . Zweifellos die bemerkenswerteste Auslassung von Project Coin.
Fletch

7
@MattWolfe versucht in diesem Beispiel nicht, den Job endlich zu erledigen?
Andres Riofrio

Antworten:


83

Sie können eine beschriftete BREAK- Anweisung verwenden:

search:
    for (i = 0; i < arrayOfInts.length; i++) {
        for (j = 0; j < arrayOfInts[i].length; j++) {
            if (arrayOfInts[i][j] == searchfor) {
                foundIt = true;
                break search;
            }
        }
    }

In richtig entworfenem Code sollten Sie jedoch keine GOTO-Funktionalität benötigen.


88
Dies wird dich nicht vor den Raptoren retten!
Bobby

25
Warum sollten Sie keine goto-Funktionalität für richtig gestalteten Code benötigen?
Rogermushroom

13
Weil nur die Baugruppe verwendet werden darf. Eine while-Anweisung, eine for-Anweisung, eine do-while-Anweisung, eine foreach-Anweisung, ein Funktionsaufruf - all dies sind GOTO-Anweisungen, die auf kontrollierte und vorhersehbare Weise verwendet werden. Auf der Ebene des Assemblers handelt es sich immer um GOTOs, aber Sie sollten nicht die Funktionalität benötigen, die Ihnen GOTO bietet. Der einzige Vorteil gegenüber Funktionen und Schleifen- / Steueranweisungen besteht darin, dass Sie überall in die Mitte eines Codes springen können . Und dieser "Vorteil" ist selbst die Definition von "Spaghetti-Code".

1
@whatsthebeef Hier ist der berühmte Brief von Edsger W. Dijkstra aus dem Jahr 1968, der erklärt, warum er ihn für schädlich hielt .
Thomanski


42

gotoIn Java gibt es kein direktes Äquivalent zum Konzept. Es gibt einige Konstrukte, mit denen Sie einige der Dinge tun können, die Sie mit einem Klassiker tun können goto.

  • Mit den Anweisungen breakund continuekönnen Sie in einer Schleifen- oder switch-Anweisung aus einem Block springen.
  • Eine beschriftete Anweisung break <label>, mit der Sie aus einer beliebigen zusammengesetzten Anweisung auf eine beliebige Ebene innerhalb einer bestimmten Methode (oder eines Initialisierungsblocks) springen können.
  • Wenn Sie eine Schleifenanweisung beschriften, können Sie continue <label>mit der nächsten Iteration einer äußeren Schleife von einer inneren Schleife fortfahren.
  • Durch das Auslösen und Abfangen von Ausnahmen können Sie (effektiv) aus vielen Ebenen eines Methodenaufrufs herausspringen. (Ausnahmen sind jedoch relativ teuer und werden als schlechter Weg zur Durchführung eines "normalen" Kontrollflusses angesehen. 1. )
  • Und natürlich gibt es return.

Mit keinem dieser Java-Konstrukte können Sie rückwärts oder zu einem Punkt im Code auf derselben Verschachtelungsebene wie die aktuelle Anweisung verzweigen. Sie alle springen aus einer oder mehreren Verschachtelungsebenen (Scope) und alle (abgesehen von continue) nach unten. Diese Einschränkung hilft, das Goto-Spaghetti-Code-Syndrom zu vermeiden, das dem alten BASIC-, FORTRAN- und COBOL-Code 2 innewohnt .


1- Der teuerste Teil von Ausnahmen ist die tatsächliche Erstellung des Ausnahmeobjekts und seiner Stapelverfolgung. Wenn Sie die Ausnahmebehandlung wirklich, wirklich für die "normale" Flusssteuerung verwenden müssen, können Sie das Ausnahmeobjekt entweder vorab zuordnen / wiederverwenden oder eine benutzerdefinierte Ausnahmeklasse erstellen, die die fillInStackTrace()Methode überschreibt . Der Nachteil ist, dass die printStackTrace()Methoden der Ausnahme keine nützlichen Informationen liefern ... falls Sie sie jemals aufrufen müssen.

2 - Das Spaghetti-Code-Syndrom hat den strukturierten Programmieransatz hervorgebracht , bei dem Sie die Verwendung der verfügbaren Sprachkonstrukte eingeschränkt haben. Dies könnte auf BASIC , Fortran und COBOL angewendet werden, erfordert jedoch Sorgfalt und Disziplin. gotoGanz loszuwerden war eine pragmatisch bessere Lösung. Wenn Sie es in einer Sprache halten, gibt es immer einen Clown, der es missbraucht.


Durch Schleifenkonstrukte wie while (...) {} und for (...) {} können Sie Codeblöcke wiederholen, ohne explizit an eine beliebige Stelle zu springen. Mit Methodenaufrufen von myMethod () können Sie außerdem Code ausführen, der an anderer Stelle gefunden wurde, und nach Abschluss zum aktuellen Block zurückkehren. All dies kann verwendet werden, um die Funktionalität von goto zu ersetzen und gleichzeitig das häufig auftretende Problem zu vermeiden, dass goto nicht zurückgegeben werden kann.
Chris Nava

@Chris - das stimmt, aber die meisten Sprachen, die GOTO unterstützen, haben auch engere Analoga zu Java-Schleifenkonstrukten.
Stephen C

1
@Chris - klassisches FORTRAN hat 'for'-Schleifen und klassisches COBOL hat auch Schleifenkonstrukte (obwohl ich mich nicht an die Details erinnern kann). Nur klassisches BASIC hat keine expliziten Schleifenkonstrukte ...
Stephen C

31

Nur zum Spaß, hier ist eine GOTO-Implementierung in Java.

Beispiel:

   1 public class GotoDemo {
   2     public static void main(String[] args) {
   3         int i = 3;
   4         System.out.println(i);
   5         i = i - 1;
   6         if (i >= 0) {
   7             GotoFactory.getSharedInstance().getGoto().go(4);
   8         }
   9         
  10         try {
  11             System.out.print("Hell");
  12             if (Math.random() > 0) throw new Exception();            
  13             System.out.println("World!");
  14         } catch (Exception e) {
  15             System.out.print("o ");
  16             GotoFactory.getSharedInstance().getGoto().go(13);
  17         }
  18     }
  19 }

Ausführen:

$ java -cp bin:asm-3.1.jar GotoClassLoader GotoDemo           
   3
   2
   1
   0
   Hello World!

Muss ich hinzufügen "benutze es nicht!"?


1
Ich werde es nicht benutzen, es ist extra
gmhk

1
Math.random()
Fehler

Ja, schon. Alles, was nur durch Bytecode-Hacker implementiert werden kann, ist nicht wirklich Java ...
Stephen C

Gibt es etwas Ähnliches, das Beschriftungen anstelle von Zeilennummern unterstützt?
Behrouz.M

1
Die Verknüpfung zu einer Implementierung ist unterbrochen.
LarsH

17

Während einige Kommentatoren und Downvoter argumentieren, dass dies nicht goto ist , deutet der generierte Bytecode aus den folgenden Java-Anweisungen wirklich darauf hin, dass diese Anweisungen wirklich goto- Semantik ausdrücken .

Insbesondere wird die do {...} while(true);Schleife im zweiten Beispiel von Java-Compilern optimiert, um die Schleifenbedingung nicht zu bewerten.

Vorwärts springen

label: {
  // do stuff
  if (check) break label;
  // do more stuff
}

Im Bytecode:

2  iload_1 [check]
3  ifeq 6          // Jumping forward
6  ..

Rückwärts springen

label: do {
  // do stuff
  if (check) continue label;
  // do more stuff
  break label;
} while(true);

Im Bytecode:

 2  iload_1 [check]
 3  ifeq 9
 6  goto 2          // Jumping backward
 9  ..

Wie springt das do / while rückwärts? Es bricht immer noch aus dem Block aus. Zähler Beispiel: label: do {System.out.println ("Rückwärts"); Beschriftung fortsetzen;} while (false); Ich denke in Ihrem Beispiel ist die Weile (wahr) das, was für den Rückwärtssprung verantwortlich ist.
Ben Holland

@ BenHolland: continue labelist der Sprung zurück
Lukas Eder

Ich bin immer noch nicht überzeugt. Die continue-Anweisung überspringt die aktuelle Iteration einer for-, while- oder do-while-Schleife. Das Fortsetzen springt zum Ende des Schleifenkörpers, der dann auf den booleschen Ausdruck ausgewertet wird, der die Schleife steuert. Die Weile ist das, was rückwärts springt, nicht das Weiter. Siehe docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html
Ben Holland

@ BenHolland: Technisch gesehen hast du recht. Aus logischer Sicht ", wenn // do stuffund // do more stuffsind die Aussagen von Interesse, continue label " hat den Effekt " , rückwärts zu springen (aufgrund der while(true)Aussage natürlich). Mir war jedoch nicht bewusst, dass dies eine so genaue Frage war ...
Lukas Eder

2
@ BenHolland: Ich habe die Antwort aktualisiert. Das while(true)wird vom Compiler in eine gotoBytecode-Operation übersetzt. Da truees sich um ein konstantes Literal handelt, kann der Compiler diese Optimierung durchführen und muss nichts auswerten. Also, mein Beispiel ist wirklich ein Goto, rückwärts springen ...
Lukas Eder

5

Wenn Sie wirklich so etwas wie goto-Anweisungen wollen, können Sie immer versuchen, in benannte Blöcke zu brechen.

Sie müssen sich im Rahmen des Blocks befinden, um zum Label zu gelangen:

namedBlock: {
  if (j==2) {
    // this will take you to the label above
    break namedBlock;
  }
}

Ich werde Ihnen nicht erklären, warum Sie Goto vermeiden sollten - ich gehe davon aus, dass Sie die Antwort darauf bereits kennen.


2
Ich habe versucht, dies in meiner hier verlinkten SO-Frage zu implementieren . Es führt Sie tatsächlich nicht zum "Etikett oben", vielleicht habe ich die Aussagen des Autors falsch interpretiert, aber der Klarheit halber füge ich hinzu, dass Sie zum Ende des äußeren Blocks (des "beschrifteten" Blocks) gelangen, der sich unten befindet wo du brechst. Daher kann man nicht nach oben "brechen". Zumindest ist das mein Verständnis.
NameSpace

4
public class TestLabel {

    enum Label{LABEL1, LABEL2, LABEL3, LABEL4}

    /**
     * @param args
     */
    public static void main(String[] args) {

        Label label = Label.LABEL1;

        while(true) {
            switch(label){
                case LABEL1:
                    print(label);

                case LABEL2:
                    print(label);
                    label = Label.LABEL4;
                    continue;

                case LABEL3:
                    print(label);
                    label = Label.LABEL1;
                    break;

                case LABEL4:
                    print(label);
                    label = Label.LABEL3;
                    continue;
            }
            break;
        }
    }

    public final static void print(Label label){
        System.out.println(label);
    }

Dies führt zu einer StackOverFlowException, wenn Sie sie lange genug ausführen, also muss ich gehen.
Kevin Parker

3
@ Kevin: Wie führt dies zu einem Stapelüberlauf? Es gibt keine Rekursion in diesem Algorithmus ...
Lukas Eder

1
Dies ähnelt sehr dem ursprünglichen Beweis, dass jedes Programm ohne Verwendung von "goto" geschrieben werden kann - indem es in eine while-Schleife mit einer "label" -Variablen umgewandelt wird, die zehnmal schlechter ist als jedes goto.
Gnasher729

3

StephenC schreibt:

Es gibt zwei Konstrukte, mit denen Sie einige der Dinge tun können, die Sie mit einem klassischen goto tun können.

Einer noch...

Matt Wolfe schreibt:

Die Leute reden immer davon, niemals ein goto zu verwenden, aber ich denke, es gibt einen wirklich guten realen Anwendungsfall, der ziemlich bekannt und verwendet ist. Das heißt, Sie müssen sicherstellen, dass Sie Code ausführen, bevor Sie von einer Funktion zurückkehren. Normalerweise wird er freigegeben Schlösser oder was nicht, aber in meinem Fall würde ich gerne kurz vor der Rückkehr zu einer Pause springen können, damit ich die erforderliche obligatorische Bereinigung durchführen kann.

try {
    // do stuff
    return result;  // or break, etc.
}
finally {
    // clean up before actually returning, even though the order looks wrong.
}

http://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html

Der finally-Block wird immer ausgeführt, wenn der try-Block beendet wird. Dadurch wird sichergestellt, dass der finally-Block auch dann ausgeführt wird, wenn eine unerwartete Ausnahme auftritt. Aber schließlich ist es nicht nur für die Ausnahmebehandlung nützlich - es ermöglicht dem Programmierer zu vermeiden, dass der Bereinigungscode versehentlich durch eine Rückgabe umgangen, fortgesetzt oder unterbrochen wird. Das Einfügen von Bereinigungscode in einen finally-Block ist immer eine gute Vorgehensweise, auch wenn keine Ausnahmen zu erwarten sind.

Die dumme Interviewfrage, die mit finally verbunden ist, lautet: Wenn Sie von einem try {} -Block zurückkehren, aber auch eine return in Ihrem finally {} haben, welcher Wert wird zurückgegeben?


2
Ich bin nicht der Meinung, dass dies eine dumme Interviewfrage ist. Ein erfahrener Java-Programmierer sollte wissen, was passiert, wenn er nur verstehen will , warum das Einfügen returneines finallyBlocks eine schlechte Idee ist. (Und selbst wenn das Wissen nicht unbedingt notwendig ist, würde ich mir Sorgen um einen Programmierer machen, der nicht die Neugier hatte, es herauszufinden ...)
Stephen C

1

Am einfachsten ist:

int label = 0;
loop:while(true) {
    switch(state) {
        case 0:
            // Some code
            state = 5;
            break;

        case 2:
            // Some code
            state = 4;
            break;
        ...
        default:
            break loop;
    }
}

0

Versuchen Sie den folgenden Code. Für mich geht das.

for (int iTaksa = 1; iTaksa <=8; iTaksa++) { // 'Count 8 Loop is  8 Taksa

    strTaksaStringStar[iCountTaksa] = strTaksaStringCount[iTaksa];

    LabelEndTaksa_Exit : {
        if (iCountTaksa == 1) { //If count is 6 then next it's 2
            iCountTaksa = 2;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 2) { //If count is 2 then next it's 3
            iCountTaksa = 3;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 3) { //If count is 3 then next it's 4
            iCountTaksa = 4;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 4) { //If count is 4 then next it's 7
            iCountTaksa = 7;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 7) { //If count is 7 then next it's 5
            iCountTaksa = 5;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 5) { //If count is 5 then next it's 8
            iCountTaksa = 8;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 8) { //If count is 8 then next it's 6
            iCountTaksa = 6;
            break  LabelEndTaksa_Exit;
        }

        if (iCountTaksa == 6) { //If count is 6 then loop 1  as 1 2 3 4 7 5 8 6  --> 1
            iCountTaksa = 1;
            break  LabelEndTaksa_Exit;
        }
    }   //LabelEndTaksa_Exit : {

} // "for (int iTaksa = 1; iTaksa <=8; iTaksa++) {"

-1

Verwenden Sie eine beschriftete Pause als Alternative zu goto.


Diese Antwort ist und war immer überflüssig
Stephen C

-1

Java hat keine goto, weil es den Code unstrukturiert und unklar zu lesen macht. Sie können jedoch breakund continueals zivilisierte Form von goto ohne seine Probleme verwenden.


Mit Pause nach vorne springen -

ahead: {
    System.out.println("Before break");
    break ahead;
    System.out.println("After Break"); // This won't execute
}
// After a line break ahead, the code flow starts from here, after the ahead block
System.out.println("After ahead");

Ausgabe :

Before Break
After ahead

Rückwärts springen mit weiter

before: {
    System.out.println("Continue");
    continue before;
}

Dies führt zu einer Endlosschleife, da bei jeder continue beforeAusführung der Zeile der Code-Fluss von neu gestartet wirdbefore .


2
Ihr Beispiel für das Zurückspringen mit continue ist falsch. Sie können "continue" nicht außerhalb des Hauptteils einer Schleife verwenden, da dies einen Kompilierungsfehler verursacht. In einer Schleife springt jedoch weiter einfach die bedingte Schleife. So etwas wie while (true) {continue;} wäre eine Endlosschleife, aber so ist while (true) {}.
Ben Holland
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.