Alternatywa dla instrukcji goto w Javie

84

Jaka jest alternatywna funkcja słowa kluczowego goto w Javie?

Ponieważ Java nie ma goto.

gmhk
źródło
10
@harigm: Czy możesz podać przykład rodzaju kodu, który napisałbyś, gdyby goto był dostępny w Javie? Myślę, że na tym polega ważniejsza kwestia.
poligenelubricants
3
@harigm: Drugi polygene, czy mógłbyś zaktualizować swoje pytanie, aby uwzględnić, dlaczego Twój program wymaga goto?
Wszyscy
13
Ludzie zawsze mówią, że nigdy nie używają goto, ale myślę, że istnieje naprawdę dobry przypadek użycia w świecie rzeczywistym, który jest dość dobrze znany i używany. To znaczy, upewnij się, że wykonujesz kod przed powrotem z funkcji. Zwykle jest to wydanie zamki lub co nie, ale w moim przypadku chciałbym móc wskoczyć do przerwy tuż przed powrotem, aby móc wykonać wymagane obowiązkowe sprzątanie. Oczywiście program zaśmiecony wszędzie goto byłby okropny, ale jeśli ogranicza się do treści metod, nie wydaje się tak zły, o ile przestrzegasz konwencji (tylko przeskakuj do końca funkcji, nigdy nie wykonuj kopii zapasowej)
Matt Wolfe
7
Jesteś w dobrym towarzystwie. Linus Torvalds z pasją bronił goto kerneltrap.org/node/553 . Niewątpliwie najbardziej zauważalne zaniedbanie ze strony Project Coin.
Fletch
7
@MattWolfe nie próbuje - w końcu wykonuje to zadanie w tym przykładzie?
Andres Riofrio,

Odpowiedzi:

84

Możesz użyć oznaczonego BREAK oświadczenia:

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

Jednak w odpowiednio zaprojektowanym kodzie nie powinieneś potrzebować funkcjonalności GOTO.

Padmarag
źródło
89
To nie uchroni cię przed drapieżnikami!
Bobby
25
Dlaczego nie potrzebujesz funkcji goto w odpowiednio zaprojektowanym kodzie?
rogermushroom
13
Ponieważ montaż jest jedynym, który może go używać. Instrukcja while, instrukcja for, a do-while, foreach, wywołanie funkcji, wszystko to są instrukcje GOTO używane w kontrolowany i przewidywalny sposób. Na poziomie asemblera zawsze będą to GOTO, ale nie powinieneś potrzebować funkcjonalności, którą zapewnia czyste GOTO - jego jedyną przewagą nad funkcjami i instrukcjami pętli / sterującymi jest to, że pozwala przeskoczyć w środku dowolnego kodu, w dowolnym miejscu . A ta „zaleta” jest sama w sobie definicją „kodu spaghetti”.
1
@whatsthebeef Oto słynny list Edsgera W. Dijkstry z 1968 r., który wyjaśnia, dlaczego uważał, że goto jest szkodliwy .
thomanski
10
Kontekst dla "raptors" , xkcd # 292 .
Peter Mortensen
42

Nie ma bezpośredniego odpowiednika tego gotopojęcia w Javie. Istnieje kilka konstrukcji, które umożliwiają wykonanie niektórych czynności, które można wykonać za pomocą klasyki goto.

  • Instrukcje breaki continueumożliwiają wyjście z bloku w pętli lub instrukcji switch.
  • Oznakowana instrukcja i break <label>umożliwia wyskoczenie z dowolnej instrukcji złożonej na dowolny poziom w ramach danej metody (lub bloku inicjalizatora).
  • Jeśli oznaczysz instrukcję pętli etykietą, możesz continue <label>kontynuować następną iterację zewnętrznej pętli z pętli wewnętrznej.
  • Rzucanie i wychwytywanie wyjątków pozwala (efektywnie) wyskoczyć z wielu poziomów wywołania metody. (Jednak wyjątki są stosunkowo drogie i są uważane za zły sposób wykonywania „zwykłego” przepływu sterowania 1 ).
  • Oczywiście, że jest return.

Żadna z tych konstrukcji Java nie pozwala na rozgałęzienie wstecz lub do punktu w kodzie na tym samym poziomie zagnieżdżenia, co bieżąca instrukcja. Wszystkie wyskakują z jednego lub więcej poziomów zagnieżdżenia (zakresu) i wszystkie (oprócz continue) skaczą w dół. To ograniczenie pomaga uniknąć syndromu „kodu spaghetti” goto, nieodłącznie związanego ze starymi kodami BASIC, FORTRAN i COBOL 2 .


1- Najdroższą częścią wyjątków jest faktyczne utworzenie obiektu wyjątku i jego ślad stosu. Jeśli naprawdę, naprawdę potrzebujesz obsługi wyjątków do „normalnej” kontroli przepływu, możesz albo wstępnie przydzielić / ponownie użyć obiektu wyjątku, albo utworzyć niestandardową klasę wyjątków, która przesłania fillInStackTrace()metodę. Wadą jest to, że metody wyjątku printStackTrace()nie dostarczą przydatnych informacji ... jeśli kiedykolwiek będziesz musiał je wywołać.

2 - Syndrom kodu spaghetti zapoczątkował podejście do programowania strukturalnego , w którym ograniczyłeś wykorzystanie dostępnych konstrukcji językowych. Można to zastosować do języka BASIC , Fortran i COBOL , ale wymagało to ostrożności i dyscypliny. Całkowite pozbycie gotosię było pragmatycznie lepszym rozwiązaniem. Jeśli zachowasz to w języku, zawsze znajdzie się jakiś klaun, który będzie go nadużywał.

Stephen C.
źródło
Konstrukcje pętli, takie jak while (...) {} i for (...) {} umożliwiają powtarzanie bloków kodu bez jawnego przeskakiwania do dowolnej lokalizacji. Ponadto wywołanie metody myMethod () pozwala na wykonanie kodu znalezionego w innym miejscu i powrót do bieżącego bloku po zakończeniu. Wszystkich tych można użyć do zastąpienia funkcjonalności goto, jednocześnie zapobiegając częstemu problemowi z niepowodzeniem zwrócenia, na który pozwala goto.
Chris Nava
@Chris - to prawda, ale większość języków obsługujących GOTO ma również bliższe odpowiedniki konstrukcji pętli Java.
Stephen C
1
@Chris - klasyczny FORTRAN ma pętle „for”, a klasyczny COBOL ma również konstrukcje zapętlające (chociaż nie mogę sobie przypomnieć szczegółów). Tylko klasyczny BASIC nie ma jawnych konstrukcji pętli ...
Stephen C
31

Dla zabawy, oto implementacja GOTO w Javie.

Przykład:

   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 }

Uruchamianie:

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

Czy muszę dodawać „nie używaj tego!”?

Pascal Thivent
źródło
1
Nie
użyję
2
Błąd: Math.random()może zwrócić 0. Powinno być> =
poitroae
Tak dobrze. Wszystko, co można zaimplementować tylko przez hakowanie kodu bajtowego, nie jest tak naprawdę Javą ...
Stephen C
Czy jest coś podobnego, które obsługuje etykiety zamiast numerów linii?
Behrouz.M
1
Link do implementacji jest uszkodzony.
LarsH
17

Chociaż niektórzy twierdzą, komentujących i downvoters że to nie jest goto wygenerowany kod bajtowy z niżej stwierdzeń Java rzeczywiście sugeruje, że te wypowiedzi rzeczywiście wyrażają goto semantykę.

W szczególności do {...} while(true);pętla w drugim przykładzie jest optymalizowana przez kompilatory języka Java, aby nie oceniać warunku pętli.

Skacząc do przodu

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

W kodzie bajtowym:

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

Skacząc do tyłu

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

W kodzie bajtowym:

 2  iload_1 [check]
 3  ifeq 9
 6  goto 2          // Jumping backward
 9  ..
Lukas Eder
źródło
W jaki sposób robi / podczas przeskakiwania do tyłu? Wciąż po prostu wyrywa się z bloku. Przykład licznika: label: do {System.out.println ("Backward"); kontynuuj etykietę;} while (false); Myślę, że w twoim przykładzie while (true) jest tym, co jest odpowiedzialne za skok do tyłu.
Ben Holland
@BenHolland: continue labelto skok w tył
Lukas Eder
Nadal nie jestem przekonany. Instrukcja continue pomija bieżącą iterację pętli for, while lub do-while. Kontynuacja przeskakuje do końca treści pętli, która jest następnie oceniana pod kątem wyrażenia boolowskiego kontrolującego pętlę. Tymczasem jest to, co skacze do tyłu, a nie kontynuacja. Zobacz docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html
Ben Holland,
@BenHolland: Technicznie masz rację. Z logicznego punktu widzenia, jeśli wyrażenia zainteresowania są // do stuffi // do more stuffsą, continue label „skutkuje” przeskakiwaniem do tyłu (ze względu na while(true)stwierdzenie oczywiście). Nie zdawałem sobie jednak sprawy, że to było tak precyzyjne pytanie ...
Lukas Eder
2
@BenHolland: Zaktualizowałem odpowiedź. Jest while(true)on tłumaczony przez kompilator na gotooperację kodu bajtowego. Ponieważ truejest to stały literał, kompilator może przeprowadzić tę optymalizację i nie musi niczego oceniać. Więc, mój przykład naprawdę jest goto, skacząc wstecz ...
Lukas Eder,
5

Jeśli naprawdę chcesz czegoś takiego jak instrukcje goto, zawsze możesz spróbować rozbić na nazwane bloki.

Aby przejść do etykiety, musisz znajdować się w zakresie bloku:

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

Nie będę cię pouczał, dlaczego powinieneś unikać goto - zakładam, że znasz już odpowiedź na to pytanie.

Amir Afghani
źródło
2
Próbowałem zaimplementować to w moim pytaniu SO połączonym tutaj . To właściwie nie prowadzi do „etykiety powyżej”, być może błędnie zinterpretowałem wypowiedzi autora, ale dla jasności dodam, że prowadzi do końca zewnętrznego bloku („oznaczonego” bloku), który znajduje się poniżej gdzie się łamiesz. Dlatego nie można „przełamać” w górę. Tak przynajmniej rozumiem.
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);
    }
miłośnik kodu
źródło
Spowoduje to powstanie wyjątku StackOverFlowException, jeśli uruchomisz go wystarczająco długo, stąd moja potrzeba.
Kevin Parker
3
@Kevin: Jak to doprowadzi do przepełnienia stosu? W tym algorytmie nie ma rekursji ...
Lukas Eder,
1
Wygląda to bardzo podobnie do oryginalnego dowodu, że każdy program można napisać bez użycia "goto" - zamieniając go w pętlę while ze zmienną "label", która jest dziesięć razy gorsza niż jakikolwiek goto.
gnasher729
3

StephenC pisze:

Istnieją dwie konstrukcje, które umożliwiają wykonanie niektórych czynności, które można wykonać za pomocą klasycznego goto.

Jeszcze jeden...

Matt Wolfe pisze:

Ludzie zawsze mówią, że nigdy nie używają goto, ale myślę, że istnieje naprawdę dobry przypadek użycia w świecie rzeczywistym, który jest dość dobrze znany i używany. Oznacza to, że należy wykonać kod przed powrotem z funkcji. Zwykle jest to wydanie zamki lub co nie, ale w moim przypadku chciałbym móc wskoczyć do przerwy tuż przed powrotem, aby móc wykonać wymagane obowiązkowe sprzątanie.

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

Ostatni blok jest zawsze wykonywany, gdy blok try kończy działanie. Gwarantuje to, że ostatni blok zostanie wykonany, nawet jeśli wystąpi nieoczekiwany wyjątek. Ale w końcu jest przydatny nie tylko do obsługi wyjątków - pozwala programistom uniknąć przypadkowego pominięcia kodu czyszczącego przez powrót, kontynuację lub przerwanie. Umieszczanie kodu porządkującego w bloku final jest zawsze dobrą praktyką, nawet jeśli nie przewiduje się żadnych wyjątków.

Głupie pytanie w rozmowie kwalifikacyjnej związane z wreszcie brzmi: Jeśli wrócisz z bloku try {}, ale uzyskasz zwrot również w swoim końcu {}, która wartość zostanie zwrócona?

android.weasel
źródło
2
Nie zgadzam się, że to głupie pytanie do wywiadu. Doświadczony programista Java powinien wiedzieć, co się dzieje, choćby po to, aby zrozumieć, dlaczego umieszczenie bloku returnw finallybloku jest złym pomysłem. (I nawet jeśli wiedza nie jest absolutnie konieczna, martwiłbym się programistą, który nie miałby ciekawości, aby się dowiedzieć ...)
Stephen C
1

Najłatwiejszy to:

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;
    }
}
maniak grzechotki
źródło
0

Wypróbuj poniższy kod. Mi to pasuje.

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++) {"
Mahasam
źródło
-1

Użyj oznaczonej przerwy jako alternatywy dla goto.

apoorva
źródło
Ta odpowiedź jest i zawsze była zbędna
Stephen C
-1

Java nie ma goto, ponieważ sprawia, że ​​kod jest niestrukturalny i niejasny do odczytania. Możesz jednak używać breaki continuejako cywilizowanej formy goto bez problemów.


Skok do przodu z przerwą -

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

Wyjście :

Before Break
After ahead

Przeskakiwanie do tyłu za pomocą kontynuacji

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

Spowoduje to nieskończoną pętlę, ponieważ za każdym razem, gdy linia continue beforejest wykonywana, przepływ kodu rozpocznie się ponownie odbefore .

Dezorientować
źródło
2
Twój przykład przeskakiwania wstecz przy użyciu kontynuacji jest błędny. Nie możesz użyć „continue” poza treścią pętli, powoduje to błąd kompilacji. Jednak wewnątrz pętli kontynuuj po prostu przeskakuje pętlę warunkowo. Więc coś takiego jak while (prawda) {kontynuuj;} byłoby nieskończoną pętlą, ale tak samo jest z while (prawda) {}.
Ben Holland,