Alternativa a un'istruzione goto in Java


84

Qual è una funzione alternativa per la parola chiave goto in Java?

Poiché Java non ha un file goto.


10
@harigm: Puoi fornire un esempio del tipo di codice che scriveresti se goto fosse disponibile in Java? Penso che qui risieda la questione più importante.
lubrificanti poligenici il

3
@harigm: secondo polygene, potresti aggiornare la tua domanda per includere il motivo per cui il tuo programma ha bisogno di un goto?
Tutti

13
La gente parla sempre di non usare mai un goto, ma penso che ci sia davvero un buon caso d'uso nel mondo reale che è abbastanza noto e usato .. Cioè, assicurandosi di eseguire del codice prima di un ritorno da una funzione .. Di solito il suo rilascio serrature o cosa no, ma nel mio caso mi piacerebbe poter saltare a una pausa proprio prima del ritorno in modo da poter fare la pulizia obbligatoria richiesta. Ovviamente un programma disseminato di goto dappertutto sarebbe orribile, ma se limitato ai corpi dei metodi non sembra poi così male fintanto che segui una convenzione (salta solo alla fine delle funzioni, mai indietro)
Matt Wolfe

7
Sei in buona compagnia. Linus Torvalds ha difeso con passione goto kerneltrap.org/node/553 . Indubbiamente l'omissione più notevole da Project Coin.
Fletch

7
@ MattWolfe non ci prova, finalmente fai il lavoro in questo esempio?
Andres Riofrio

Risposte:


84

Puoi utilizzare un'istruzione BREAK etichettata :

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;
            }
        }
    }

Tuttavia, in un codice progettato correttamente, non dovrebbe essere necessaria la funzionalità GOTO.


89
Questo non ti salverà dai rapaci!
Bobby

25
Perché non dovresti aver bisogno della funzionalità goto nel codice progettato correttamente?
rogermushroom

13
Perché l'assemblaggio è l'unico autorizzato a usarlo. Un'istruzione while, un'istruzione for, un do-while, un foreach, una chiamata di funzione, tutte queste sono istruzioni GOTO usate in modo controllato e prevedibile. A livello dell'assemblatore, saranno sempre GOTO, ma non dovresti aver bisogno della funzionalità che ti offre il GOTO puro: il suo unico vantaggio rispetto alle funzioni e alle istruzioni di loop / controllo è che ti consente di saltare nel mezzo di qualsiasi codice, ovunque . E quel "vantaggio" è esso stesso la definizione di "codice degli spaghetti".

1
@ whatsthebeef Ecco la famosa lettera del 1968 di Edsger W. Dijkstra che spiega perché considerava goto dannoso .
thomanski


42

Non esiste alcun equivalente diretto al gotoconcetto in Java. Ci sono alcuni costrutti che ti permettono di fare alcune delle cose che puoi fare con un classico goto.

  • Le istruzioni breake continueti consentono di saltare fuori da un blocco in un ciclo o in un'istruzione switch.
  • Un'istruzione etichettata e break <label>consente di saltare da un'istruzione composta arbitraria a qualsiasi livello all'interno di un determinato metodo (o blocco di inizializzazione).
  • Se etichettate un'istruzione di ciclo, potete continue <label>continuare con l'iterazione successiva di un ciclo esterno da un ciclo interno.
  • Lanciare e catturare eccezioni consente di saltare (efficacemente) da molti livelli di una chiamata al metodo. (Tuttavia, le eccezioni sono relativamente costose e sono considerate un cattivo modo per eseguire il flusso di controllo "ordinario" 1 ).
  • E ovviamente c'è return.

Nessuno di questi costrutti Java consente di ramificarsi all'indietro o fino a un punto del codice allo stesso livello di nidificazione dell'istruzione corrente. Tutti saltano fuori da uno o più livelli di nidificazione (ambito) e tutti (a parte continue) saltano verso il basso. Questa restrizione aiuta ad evitare la sindrome del "codice spaghetti" goto insita nei vecchi codici BASIC, FORTRAN e COBOL 2 .


1- La parte più costosa delle eccezioni è la creazione effettiva dell'oggetto eccezione e del suo stacktrace. Se hai davvero, davvero bisogno di usare la gestione delle eccezioni per il controllo del flusso "normale", puoi preallocare / riutilizzare l'oggetto eccezione o creare una classe di eccezione personalizzata che sovrascrive il fillInStackTrace()metodo. Lo svantaggio è che i printStackTrace()metodi dell'eccezione non ti daranno informazioni utili ... se dovessi mai aver bisogno di chiamarli.

2 - La sindrome del codice spaghetti ha generato l' approccio di programmazione strutturata , dove hai limitato l'uso dei costrutti linguistici disponibili. Questo potrebbe essere applicato a BASIC , Fortran e COBOL , ma richiedeva cura e disciplina. Liberarsi del gototutto era una soluzione pragmaticamente migliore. Se lo mantieni in una lingua, c'è sempre qualche clown che ne abusa.


I costrutti di ciclo come while (...) {} e for (...) {} consentono di ripetere blocchi di codice senza saltare esplicitamente a una posizione arbitraria. Inoltre, le chiamate al metodo myMethod () consentono di eseguire codice trovato altrove e di tornare al blocco corrente al termine. Tutti questi possono essere utilizzati per sostituire la funzionalità di goto evitando il problema comune di non riuscire a restituire che goto consente.
Chris Nava

@Chris - questo è vero, ma la maggior parte dei linguaggi che supportano GOTO hanno anche analoghi più vicini ai costrutti di loop Java.
Stephen C

1
@Chris - il classico FORTRAN ha loop "for" e il classico COBOL ha anche costrutti di loop (anche se non riesco a ricordare i dettagli). Solo il BASIC classico non ha costrutti di loop espliciti ...
Stephen C

31

Solo per divertimento, ecco un'implementazione GOTO in Java.

Esempio:

   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 }

Eseguendolo:

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

Devo aggiungere "non usarlo!"?


2
Bug: Math.random()può restituire 0. Dovrebbe essere> =
poitroae

Si Bene. Tutto ciò che può essere implementato solo da bytecode hackery non è realmente Java ...
Stephen C

C'è qualcosa di simile che supporta le etichette invece dei numeri di riga?
Behrouz.M

1
Il collegamento a un'implementazione è interrotto.
LarsH

@poitroae È un divertente Easter Egg che qualcuno troverà HellWorld!o World!output e rimarrà sconcertato. #AllBugsAreFeatures
ReinstateMonica3167040

18

Mentre alcuni commentatori e downvoters sostengono che questo non è goto , il bytecode generato dalle istruzioni Java seguenti suggerisce davvero che queste istruzioni esprimono davvero la semantica goto .

In particolare, il do {...} while(true);ciclo nel secondo esempio è ottimizzato dai compilatori Java per non valutare la condizione del ciclo.

Saltando in avanti

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

In bytecode:

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

Saltando all'indietro

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

In bytecode:

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

Come fa / mentre salta all'indietro? Ancora appena esce dal blocco. Esempio di contatore: label: do {System.out.println ("Backward"); continue label;} while (false); Penso che nel tuo esempio il while (vero) sia ciò che è responsabile del salto all'indietro.
Ben Holland

@BenHolland: continue labelè il salto all'indietro
Lukas Eder

Non sono ancora convinto. L'istruzione continue salta l'iterazione corrente di un ciclo for, while o do-while. Il continue salta alla fine del corpo del ciclo, che viene quindi valutato per l'espressione booleana che controlla il ciclo. Il tempo è ciò che salta all'indietro, non il continuare. Vedi docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html
Ben Holland

@ BenHolland: tecnicamente hai ragione. Da un punto di vista logico, se // do stuffe // do more stuffsono le dichiarazioni di interesse, continue label "ha l'effetto" di saltare all'indietro (a causa della while(true)dichiarazione ovviamente). Non sapevo che fosse una domanda così precisa, però ...
Lukas Eder

2
@ BenHolland: ho aggiornato la risposta. Il while(true)è tradotto dal compilatore in un'operazione gotobytecode. Poiché trueè una costante letterale, il compilatore può eseguire questa ottimizzazione e non deve valutare nulla. Quindi, il mio esempio è davvero un goto, che salta all'indietro ...
Lukas Eder

5

Se vuoi davvero qualcosa come le istruzioni goto, puoi sempre provare a spezzare i blocchi con nome.

Devi essere nell'ambito del blocco per rompere l'etichetta:

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

Non ti spiegherò perché dovresti evitare i goto - presumo che tu sappia già la risposta a questo.


2
Ho provato a implementarlo nella mia domanda SO collegata qui . In realtà non ti porta all '"etichetta sopra", forse ho interpretato male le affermazioni dell'autore, ma per chiarezza aggiungo che ti porta alla fine del blocco esterno (il blocco "etichettato"), che è sotto dove ti stai rompendo. Quindi non puoi "spezzarti" verso l'alto. Almeno questa è la mia comprensione.
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);
    }

Ciò si tradurrà in una StackOverFlowException se lo esegui abbastanza a lungo, quindi è necessario che tu vada.
Kevin Parker

3
@ Kevin: in che modo questo porterà a un overflow dello stack? Non c'è ricorsione in questo algoritmo ...
Lukas Eder

1
Questo assomiglia molto alla prova originale che qualsiasi programma può essere scritto senza usare "goto" - trasformandolo in un ciclo while con una variabile "label" che è dieci volte peggiore di qualsiasi goto.
gnasher729

3

StephenC scrive:

Ci sono due costrutti che ti permettono di fare alcune delle cose che puoi fare con un goto classico.

Ancora uno ...

Matt Wolfe scrive:

La gente parla sempre di non usare mai un goto, ma penso che ci sia un ottimo caso d'uso nel mondo reale che è abbastanza noto e usato .. Cioè, assicurandosi di eseguire del codice prima di un ritorno da una funzione .. Di solito il suo rilascio serrature o cosa no, ma nel mio caso mi piacerebbe poter saltare a una pausa proprio prima del ritorno in modo da poter fare la pulizia obbligatoria richiesta.

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

Il blocco finalmente viene eseguito sempre quando il blocco try esce. Ciò garantisce che il blocco finalmente venga eseguito anche se si verifica un'eccezione imprevista. Ma alla fine è utile per qualcosa di più della semplice gestione delle eccezioni: consente al programmatore di evitare che il codice di pulizia venga ignorato accidentalmente da un ritorno, continua o interruzione. Mettere il codice di pulizia in un blocco finally è sempre una buona pratica, anche quando non sono previste eccezioni.

La stupida domanda dell'intervista associata a finalmente è: se torni da un blocco try {}, ma hai un ritorno anche nel tuo finalmente {}, quale valore viene restituito?


2
Non sono d'accordo che sia una stupida domanda da intervista. Un programmatore Java esperto dovrebbe sapere cosa succede, se non altro per capire perché mettere un returnin un finallyblocco è una cattiva idea. (E anche se la conoscenza non fosse strettamente necessaria, sarei preoccupato da un programmatore che non ha avuto la curiosità di scoprirlo ...)
Stephen C

1

Il più semplice è:

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

Prova il codice qui sotto. Per me funziona.

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

Usa un'interruzione etichettata come alternativa a goto.


Questa risposta è, ed è sempre stata ridondante
Stephen C

-1

Java non ce l'ha goto, perché rende il codice non strutturato e poco chiaro da leggere. Tuttavia, puoi usare breake continuecome forma civile di goto senza i suoi problemi.


Saltare in avanti usando break -

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");

Uscita :

Before Break
After ahead

Saltare all'indietro usando continua

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

Ciò risulterà in un ciclo infinito poiché ogni volta che continue beforeviene eseguita la riga , il flusso di codice ricomincerà dabefore .


2
Il tuo esempio sul saltare all'indietro usando continue è sbagliato. Non è possibile utilizzare "continue" al di fuori del corpo di un ciclo, causa un errore di compilazione. All'interno di un ciclo, invece, continua semplicemente salta il ciclo condizionale. Quindi qualcosa come while (true) {continue;} sarebbe un ciclo infinito, ma lo è anche while (true) {}.
Ben Holland
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.