Alternativa a una instrucción goto en Java

84

¿Cuál es una función alternativa para la palabra clave goto en Java?

Dado que Java no tiene goto.

gmhk
fuente
10
@harigm: ¿Puede dar un ejemplo del tipo de código que escribiría si goto estuviera disponible en Java? Creo que ahí radica la cuestión más importante.
polygenelubricants
3
@harigm: Segundo polygene, ¿podría actualizar su pregunta para incluir por qué su programa necesita un goto?
Todos el
13
La gente siempre habla de no usar nunca un goto, pero creo que hay un caso de uso del mundo real realmente bueno que es bastante conocido y utilizado. Es decir, asegurarse de ejecutar algún código antes de devolver una función. Por lo general, su lanzamiento cerraduras o lo que no, pero en mi caso me encantaría poder saltar a un descanso justo antes del regreso para poder hacer la limpieza obligatoria requerida. Por supuesto, un programa lleno de goto por todas partes sería horrible, pero si se limita a los cuerpos de método, no parece tan malo siempre y cuando siga una convención (solo salte al final de las funciones, nunca retroceda)
Matt Wolfe
7
Estás en buena compañía. Linus Torvalds ha defendido apasionadamente goto kerneltrap.org/node/553 . Sin duda la omisión más notable del Proyecto Coin.
Fletch
7
@MattWolfe no lo intenta, ¿finalmente hace el trabajo en ese ejemplo?
Andrés Riofrio

Respuestas:

84

Puede usar una declaración BREAK etiquetada :

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

Sin embargo, en un código diseñado correctamente, no debería necesitar la funcionalidad GOTO.

Padmarag
fuente
89
¡Esto no te salvará de las aves rapaces!
Bobby
25
¿Por qué no debería necesitar la funcionalidad goto en un código diseñado correctamente?
Rogermushroom
13
Porque el ensamblaje es el único autorizado para usarlo. Una declaración while, una declaración for, un do-while, un foreach, una llamada de función, todas estas son declaraciones GOTO utilizadas de una manera controlada y predecible. En el nivel del ensamblador, siempre será GOTO, pero no debería necesitar la funcionalidad que le brinda GOTO puro; su única ventaja sobre las funciones y las declaraciones de bucle / control es que le permite saltar en medio de cualquier código, en cualquier lugar . Y esa "ventaja" es en sí misma la definición de "código espagueti".
1
@whatsthebeef Aquí está la famosa carta de 1968 de Edsger W. Dijkstra que explica por qué consideraba que goto era perjudicial .
thomanski
10
Contexto para "rapaces" , xkcd # 292 .
Peter Mortensen
42

No hay ningún equivalente directo al gotoconcepto en Java. Hay algunas construcciones que le permiten hacer algunas de las cosas que puede hacer con un clásico goto.

  • Las declaraciones breaky le continuepermiten saltar de un bloque en un bucle o instrucción de cambio.
  • Una declaración etiquetada y le break <label>permite saltar de una declaración compuesta arbitraria a cualquier nivel dentro de un método dado (o bloque inicializador).
  • Si etiqueta una declaración de bucle, puede continue <label>continuar con la siguiente iteración de un bucle externo desde un bucle interno.
  • Lanzar y capturar excepciones le permite saltar (efectivamente) de muchos niveles de una llamada a un método. (Sin embargo, las excepciones son relativamente caras y se consideran una mala forma de realizar un control de flujo "ordinario" 1 ).
  • Y por supuesto, hay return .

Ninguna de estas construcciones de Java le permite ramificarse hacia atrás o hacia un punto en el código en el mismo nivel de anidamiento que la declaración actual. Todos saltan uno o más niveles de anidación (alcance) y todos (excepto continue) saltan hacia abajo. Esta restricción ayuda a evitar el síndrome de goto "código espagueti" inherente al antiguo código 2 de BASIC, FORTRAN y COBOL .


1- La parte más cara de las excepciones es la creación real del objeto de excepción y su seguimiento de pila. Si realmente, realmente necesita utilizar el manejo de excepciones para el control de flujo "normal", puede preasignar / reutilizar el objeto de excepción o crear una clase de excepción personalizada que anule el fillInStackTrace()método. La desventaja es que la excepciónprintStackTrace() métodos de no le darán información útil ... en caso de que alguna vez necesite llamarlos.

2 - El síndrome del código espagueti generó el enfoque de programación estructurada , en el que limitaba el uso de las construcciones de lenguaje disponibles. Esto podría aplicarse a BASIC , Fortran y COBOL , pero requería cuidado y disciplina. Deshacerse de él por gotocompleto fue una solución pragmáticamente mejor. Si lo guardas en un idioma, siempre habrá algún payaso que abusará de él.

Esteban C
fuente
Las construcciones de bucle como while (...) {} y for (...) {} le permiten repetir bloques de código sin saltar explícitamente a una ubicación arbitraria. Además, las llamadas a métodos myMethod () le permiten ejecutar el código que se encuentra en otro lugar y volver al bloque actual cuando haya terminado. Todos estos pueden usarse para reemplazar la funcionalidad de goto mientras se previene el problema común de no devolver lo que permite goto.
Chris Nava
@Chris: eso es cierto, pero la mayoría de los lenguajes que admiten GOTO también tienen análogos más cercanos a las construcciones de bucle de Java.
Stephen C
1
@Chris: el FORTRAN clásico tiene bucles 'for' y el COBOL clásico también tiene construcciones de bucle (aunque no puedo recordar los detalles). Solo el BASIC clásico no tiene construcciones de bucle explícitas ...
Stephen C
31

Solo por diversión, aquí hay una implementación GOTO en Java.

Ejemplo:

   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 }

Ejecutarlo:

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

¿Tengo que añadir "no lo use"?

Pascal Thivent
fuente
1
No lo
usaré
2
Error: Math.random()puede devolver 0. Debería ser> =
poitroae
Si bien. Todo lo que solo pueda implementarse mediante piratería de código de bytes no es realmente Java ...
Stephen C
¿Hay algo similar que admita etiquetas en lugar de números de línea?
Behrouz.M
1
El vínculo a una implementación está roto.
LarsH
17

Si bien algunos comentaristas y votantes negativos argumentan que esto no es goto , el código de bytes generado a partir de las siguientes declaraciones de Java realmente sugiere que estas declaraciones realmente expresan la semántica de goto .

Específicamente, el do {...} while(true); compiladores de Java optimizan ciclo del segundo ejemplo para no evaluar la condición del ciclo.

Saltando hacia adelante

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

En bytecode:

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

Saltando hacia atrás

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

En bytecode:

 2  iload_1 [check]
 3  ifeq 9
 6  goto 2          // Jumping backward
 9  ..
Lukas Eder
fuente
¿Cómo salta hacia atrás el do / while? Todavía se sale del bloque. Ejemplo de contador: label: do {System.out.println ("Backward"); continuar etiqueta;} while (falso); Creo que en tu ejemplo el while (verdadero) es el responsable del salto hacia atrás.
Ben Holland
@BenHolland: continue labeles el salto hacia atrás
Lukas Eder
Todavía no estoy convencido. La instrucción continue omite la iteración actual de un bucle for, while o do-while. Continuar salta al final del cuerpo del bucle, que luego se evalúa para la expresión booleana que controla el bucle. El tiempo es lo que está saltando hacia atrás, no el continuar. Ver docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html
Ben Holland
@BenHolland: Técnicamente tienes razón. Desde una perspectiva lógica, si // do stuffy // do more stuffson las declaraciones de interés, continue label "tiene el efecto" de saltar hacia atrás (debido a la while(true)declaración, por supuesto). Sin embargo, no sabía que esta era una pregunta tan precisa ...
Lukas Eder
2
@BenHolland: Actualicé la respuesta. El while(true)compilador traduce el en una gotooperación de código de bytes. Como truees un literal constante, el compilador puede hacer esta optimización y no tiene que evaluar nada. Entonces, mi ejemplo realmente es un goto, saltando hacia atrás ...
Lukas Eder
5

Si realmente desea algo como declaraciones goto, siempre puede intentar dividir a bloques con nombre.

Tienes que estar dentro del alcance del bloque para romper con la etiqueta:

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

No te daré un sermón sobre por qué deberías evitar los goto, supongo que ya sabes la respuesta a eso.

Amir Afghani
fuente
2
Intenté implementar esto en mi pregunta SO vinculada aquí . En realidad, no lo lleva a la "etiqueta de arriba", tal vez malinterpreté las declaraciones del autor, pero para mayor claridad agregaré que lo lleva al final del bloque exterior (el bloque "etiquetado"), que está debajo donde te estás rompiendo. Por tanto, no se puede "romper" hacia arriba. Al menos eso es lo que yo entiendo.
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);
    }
amante del código
fuente
Esto dará como resultado una StackOverFlowException si lo ejecuta el tiempo suficiente, por lo que mi necesidad de ir a.
Kevin Parker
3
@Kevin: ¿Cómo conducirá esto a un desbordamiento de pila? No hay recursividad en este algoritmo ...
Lukas Eder
1
Esto se parece mucho a la prueba original de que cualquier programa se puede escribir sin usar "goto", convirtiéndolo en un bucle while con una variable de "etiqueta" que es diez veces peor que cualquier goto.
gnasher729
3

StephenC escribe:

Hay dos construcciones que le permiten hacer algunas de las cosas que puede hacer con un goto clásico.

Uno mas...

Matt Wolfe escribe:

La gente siempre habla de no usar nunca un goto, pero creo que hay un caso de uso del mundo real realmente bueno que es bastante conocido y usado. Es decir, asegurarse de ejecutar algún código antes de que una función regrese. Por lo general, su lanzamiento cerraduras o lo que no, pero en mi caso me encantaría poder saltar a un descanso justo antes del regreso para poder hacer la limpieza obligatoria requerida.

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

El bloque finalmente siempre se ejecuta cuando sale el bloque try. Esto asegura que el bloque finalmente se ejecute incluso si ocurre una excepción inesperada. Pero finalmente es útil para algo más que el manejo de excepciones: le permite al programador evitar que el código de limpieza se omita accidentalmente por un retorno, continuación o interrupción. Poner el código de limpieza en un bloque final siempre es una buena práctica, incluso cuando no se anticipan excepciones.

La pregunta tonta de la entrevista asociada con finalmente es: Si regresa de un bloque try {}, pero también tiene un retorno en su finalmente {}, ¿qué valor se devuelve?

android.weasel
fuente
2
No estoy de acuerdo con que esa sea una pregunta tonta de la entrevista. Un programador de Java experimentado debe saber lo que sucede, aunque solo sea para comprender por qué poner un returnen un finallybloque es una mala idea. (E incluso si el conocimiento no es estrictamente necesario, me preocuparía un programador que no tuviera la curiosidad de averiguarlo ...)
Stephen C
1

Lo más fácil es:

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;
    }
}
monstruo del trinquete
fuente
0

Pruebe el siguiente código. Esto funciona para mi.

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
fuente
-1

Utilice un descanso etiquetado como alternativa a goto.

apoorva
fuente
Esta respuesta es, y siempre fue redundante
Stephen C
-1

Java no lo tiene goto, porque hace que el código no esté estructurado y sea poco claro para leer. Sin embargo, puede utilizar breaky continuecomo forma civilizada de goto sin sus problemas.


Saltar hacia adelante usando el descanso -

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

Salida :

Before Break
After ahead

Saltar hacia atrás usando continuar

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

Esto dará como resultado un bucle infinito, ya que cada vez que continue beforese ejecuta la línea , el flujo de código comenzará de nuevobefore .

Confundir
fuente
2
Su ejemplo de saltar hacia atrás usando continue es incorrecto. No puede utilizar "continuar" fuera del cuerpo de un bucle, ya que provoca un error de compilación. Dentro de un bucle, sin embargo, continue simplemente salta el bucle condicional. Entonces, algo como while (verdadero) {continuar;} sería un bucle infinito, pero también lo es while (verdadero) {} para el caso.
Ben Holland