Thay thế cho câu lệnh goto trong Java


83

Hàm thay thế cho từ khóa goto trong Java là gì?

Vì Java không có goto.


10
@harigm: Bạn có thể cho một ví dụ về loại mã mà bạn sẽ viết nếu goto có trong Java không? Tôi nghĩ trong đó nằm ở vấn đề quan trọng hơn.
polygenelubricants

3
@harigm: Tôi là polygene thứ hai, bạn có thể vui lòng cập nhật câu hỏi của mình để bao gồm lý do tại sao chương trình của bạn cần goto không?
Mọi người

13
Mọi người luôn nói về việc không bao giờ sử dụng goto, nhưng tôi nghĩ rằng có một trường hợp sử dụng trong thế giới thực thực sự tốt, được khá nhiều người biết đến và sử dụng .. Đó là, đảm bảo thực thi một số mã trước khi trả về từ một hàm .. Thường là việc phát hành khóa hoặc không, nhưng trong trường hợp của tôi, tôi muốn có thể nghỉ ngơi ngay trước khi quay lại để tôi có thể thực hiện dọn dẹp bắt buộc. Tất nhiên một chương trình rải rác với goto ở khắp nơi sẽ rất kinh khủng, nhưng nếu bị giới hạn trong các phần thân của phương thức, nó có vẻ không tệ miễn là bạn tuân theo một quy ước (chỉ nhảy đến cuối các hàm, không bao giờ sao lưu)
Matt Wolfe

7
Bạn đang ở trong một công ty tốt. Linus Torvalds đã nhiệt tình bảo vệ goto kerneltrap.org/node/553 . Không nghi ngờ gì nữa, sự thiếu sót đáng chú ý nhất từ ​​Project Coin.
Fletch

7
@MattWolfe không cố gắng-cuối cùng thực hiện công việc trong ví dụ đó?
Andres Riofrio

Câu trả lời:


83

Bạn có thể sử dụng câu lệnh BREAK được gắn nhãn :

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

Tuy nhiên, trong mã được thiết kế phù hợp, bạn không cần chức năng GOTO.


88
Điều này sẽ không cứu bạn khỏi các loài ăn thịt!
Bobby

25
Tại sao bạn không cần chức năng goto trong mã được thiết kế phù hợp?
rogermushroom

13
Bởi vì lắp ráp là người duy nhất được phép sử dụng nó. Câu lệnh while, câu lệnh for, do-while, foreach, lời gọi hàm, tất cả đều là câu lệnh GOTO được sử dụng theo cách có kiểm soát và có thể dự đoán được. Ở cấp độ của trình hợp dịch, nó sẽ luôn là GOTO, nhưng bạn không nên cần chức năng mà GOTO thuần túy cung cấp cho bạn - lợi thế duy nhất của nó so với các chức năng và câu lệnh lặp / điều khiển là nó cho phép bạn nhảy vào giữa bất kỳ mã nào, ở bất kỳ đâu. . Và 'lợi thế' đó chính là định nghĩa của 'mã spaghetti'.

1
@whatsthebeef Đây là bức thư nổi tiếng năm 1968 của Edsger W. Dijkstra giải thích lý do tại sao ông coi goto là có hại .
thomanski


42

Không có bất kỳ tương đương trực tiếp nào với gotokhái niệm trong Java. Có một số cấu trúc cho phép bạn thực hiện một số điều bạn có thể làm với cổ điển goto.

  • Câu lệnh breakcontinuecho phép bạn nhảy ra khỏi một khối trong một vòng lặp hoặc câu lệnh chuyển đổi.
  • Một câu lệnh được gắn nhãn và break <label>cho phép bạn nhảy ra khỏi một câu lệnh ghép tùy ý đến bất kỳ mức nào trong một phương thức nhất định (hoặc khối khởi tạo).
  • Nếu bạn gắn nhãn một câu lệnh lặp, bạn có thể continue <label>tiếp tục với lần lặp tiếp theo của vòng lặp bên ngoài từ vòng lặp bên trong.
  • Việc ném và bắt các ngoại lệ cho phép bạn (một cách hiệu quả) nhảy ra khỏi nhiều cấp độ của một cuộc gọi phương thức. (Tuy nhiên, các ngoại lệ tương đối tốn kém và được coi là một cách không tốt để thực hiện quy trình điều khiển "thông thường" 1. )
  • Và tất nhiên, có return.

Không có cấu trúc Java nào trong số này cho phép bạn rẽ nhánh ngược hoặc tới một điểm trong mã ở cùng cấp độ lồng như câu lệnh hiện tại. Tất cả chúng đều nhảy ra một hoặc nhiều cấp độ (phạm vi) lồng nhau và tất cả chúng (ngoài continue) đều nhảy xuống dưới. Hạn chế này giúp tránh hội chứng goto "mã spaghetti" vốn có trong mã BASIC, FORTRAN và COBOL cũ 2 .


1- Phần tốn kém nhất của các ngoại lệ là việc tạo thực tế đối tượng ngoại lệ và ngăn xếp của nó. Nếu bạn thực sự, thực sự cần sử dụng xử lý ngoại lệ cho điều khiển luồng "bình thường", bạn có thể phân bổ trước / sử dụng lại đối tượng ngoại lệ hoặc tạo một lớp ngoại lệ tùy chỉnh ghi đè fillInStackTrace()phương thức. Nhược điểm là các printStackTrace()phương thức của ngoại lệ sẽ không cung cấp cho bạn thông tin hữu ích ... nếu bạn cần gọi chúng.

2 - Hội chứng mã spaghetti sinh ra phương pháp lập trình có cấu trúc , nơi bạn hạn chế sử dụng các cấu trúc ngôn ngữ có sẵn. Điều này có thể được áp dụng cho BASIC , FortranCOBOL , nhưng nó đòi hỏi sự cẩn thận và kỷ luật. Loại bỏ gotohoàn toàn là một giải pháp thực dụng tốt hơn. Nếu bạn giữ nó trong một ngôn ngữ, luôn có một số tên hề sẽ lạm dụng nó.


Các cấu trúc lặp như while (...) {}for (...) {} cho phép bạn lặp lại các khối mã mà không cần chuyển đến một vị trí tùy ý một cách rõ ràng. Ngoài ra, phương thức gọi myMethod () cho phép bạn thực thi mã được tìm thấy ở nơi khác và quay trở lại khối hiện tại khi hoàn tất. Tất cả những thứ này có thể được sử dụng để thay thế chức năng của goto trong khi ngăn chặn vấn đề phổ biến là không trả lại được mà goto cho phép.
Chris Nava,

@Chris - điều đó đúng, nhưng hầu hết các ngôn ngữ hỗ trợ GOTO cũng có các cấu trúc tương tự gần hơn với cấu trúc vòng lặp Java.
Stephen C

1
@Chris - FORTRAN cổ điển có vòng lặp 'for' và COBOL cổ điển cũng có cấu trúc lặp (mặc dù tôi không thể nhớ lại chi tiết). Chỉ cổ điển BASIC không có cấu trúc vòng lặp rõ ràng ...
Stephen C

31

Nói cho vui, đây là cách triển khai GOTO trong Java.

Thí dụ:

   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 }

Chạy nó:

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

Tôi có cần thêm "không sử dụng nó!"?


1
Tôi sẽ không sử dụng nó, nó bổ sung
gmhk

1
Bug: Math.random()có thể trở lại 0. Nó phải là> =
poitroae

Vâng tốt thôi. Bất cứ điều gì mà chỉ có thể được thực hiện bởi bytecode hackery không phải là thực sự Java ...
Stephen C

Có thứ gì đó tương tự hỗ trợ nhãn thay vì số dòng không?
Behrouz.M

1
Liên kết đến một triển khai bị hỏng.
LarsH

17

Trong khi một số người bình luận và người phản đối cho rằng đây không phải là goto , mã bytecode được tạo ra từ các câu lệnh Java bên dưới thực sự gợi ý rằng những câu lệnh này thực sự diễn đạt ngữ nghĩa goto .

Cụ thể, do {...} while(true);vòng lặp trong ví dụ thứ hai được tối ưu hóa bởi trình biên dịch Java để không đánh giá điều kiện của vòng lặp.

Nhảy về phía trước

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

Trong bytecode:

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

Nhảy lùi

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

Trong bytecode:

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

Làm thế nào để làm / trong khi nhảy ngược lại? Nó vẫn chỉ thoát ra khỏi khối. Bộ đếm Ví dụ: label: do {System.out.println ("Lùi"); nhãn tiếp tục;} while (false); Tôi nghĩ trong ví dụ của bạn, while (true) là nguyên nhân gây ra cú nhảy ngược.
Ben Holland,

@BenHolland: continue labellà bước nhảy lùi
Lukas Eder

Tôi vẫn chưa bị thuyết phục. Câu lệnh continue bỏ qua lần lặp hiện tại của vòng lặp for, while hoặc do-while. Tiếp tục bỏ qua đến cuối phần nội dung của vòng lặp, sau đó được đánh giá cho biểu thức boolean điều khiển vòng lặp. Trong khi là những gì đang nhảy ngược lại không phải là tiếp tục. Xem docs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html
Ben Holland

@BenHolland: Về mặt kỹ thuật, bạn đúng. Từ góc độ logic, nếu // do stuff// do more stufflà các câu lệnh quan tâm, continue label "có tác dụng" nhảy ngược lại (do while(true)câu lệnh tất nhiên). Tuy nhiên, tôi không biết rằng đây là một câu hỏi chính xác như vậy ...
Lukas Eder

2
@BenHolland: Tôi đã cập nhật câu trả lời. Nó while(true)được dịch bởi trình biên dịch thành một gotohoạt động bytecode. Như truemột nghĩa đen không đổi, trình biên dịch có thể thực hiện việc tối ưu hóa này và không phải đánh giá bất cứ điều gì. Vì vậy, ví dụ của tôi thực sự là một con goto, nhảy ngược ...
Lukas Eder

5

Nếu bạn thực sự muốn một cái gì đó giống như câu lệnh goto, bạn luôn có thể thử phá vỡ các khối được đặt tên.

Bạn phải ở trong phạm vi của khối để phá vỡ nhãn:

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

Tôi sẽ không giảng cho bạn lý do tại sao bạn nên tránh goto - Tôi cho rằng bạn đã biết câu trả lời cho điều đó.


2
Tôi đã thử thực hiện điều này trong câu hỏi SO của tôi được liên kết ở đây . Nó thực sự không đưa bạn đến "nhãn ở trên", có lẽ tôi đã hiểu sai tuyên bố của tác giả, nhưng để rõ ràng hơn, tôi sẽ nói thêm rằng nó sẽ đưa bạn đến cuối khối bên ngoài (khối "được gắn nhãn"), nằm bên dưới nơi bạn đang phá vỡ. Do đó bạn không thể "phá vỡ" lên trên. Ít nhất thì đó là sự hiểu biết của tôi.
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);
    }

Điều này sẽ dẫn đến một StackOverFlowException là bạn chạy nó đủ lâu, do đó tôi cần phải làm.
Kevin Parker

3
@Kevin: Làm thế nào điều này sẽ dẫn đến tràn ngăn xếp? Không có đệ quy trong thuật toán này ...
Lukas Eder

1
Điều này trông rất giống bằng chứng ban đầu rằng bất kỳ chương trình nào cũng có thể được viết mà không cần sử dụng "goto" - bằng cách biến nó thành một vòng lặp while với một biến "label" tệ hơn bất kỳ goto nào mười lần.
gnasher729

3

StephenC viết:

Có hai cấu trúc cho phép bạn thực hiện một số điều bạn có thể làm với goto cổ điển.

Một lần nữa...

Matt Wolfe viết:

Mọi người luôn nói về việc không bao giờ sử dụng goto, nhưng tôi nghĩ rằng có một trường hợp sử dụng thực sự tốt trong thế giới thực khá nổi tiếng và được sử dụng. Đó là, đảm bảo thực thi một số mã trước khi trả về từ một hàm .. Thường là việc phát hành khóa hoặc không, nhưng trong trường hợp của tôi, tôi muốn có thể nghỉ ngơi ngay trước khi quay lại để tôi có thể thực hiện dọn dẹp bắt buộc.

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

Khối cuối cùng luôn thực thi khi khối try thoát. Điều này đảm bảo rằng khối cuối cùng được thực thi ngay cả khi một ngoại lệ không mong muốn xảy ra. Nhưng cuối cùng hữu ích cho nhiều hơn là chỉ xử lý ngoại lệ - nó cho phép lập trình viên tránh việc mã dọn dẹp vô tình bị bỏ qua khi trả về, tiếp tục hoặc ngắt. Đặt mã dọn dẹp trong một khối cuối cùng luôn là một phương pháp hay, ngay cả khi không có ngoại lệ nào được dự đoán trước.

Câu hỏi phỏng vấn ngớ ngẩn được liên kết với cuối cùng là: Nếu bạn quay lại từ khối try {}, nhưng cũng có lợi nhuận trong khối {} cuối cùng, thì giá trị nào được trả về?


2
Tôi không đồng ý rằng đó là một câu hỏi phỏng vấn ngớ ngẩn. Một lập trình viên Java có kinh nghiệm nên biết điều gì sẽ xảy ra, nếu chỉ để hiểu tại sao đặt returnmột finallykhối vào một khối là một ý tưởng tồi. (Và thậm chí nếu những kiến thức không phải là thực sự cần thiết, tôi muốn được lo lắng bởi một lập trình viên người không có sự tò mò để tìm hiểu ...)
Stephen C

1

Đơn giản nhất là:

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

Hãy thử mã bên dưới. Nó làm việc cho tôi.

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

Sử dụng dấu ngắt được gắn nhãn thay thế cho goto.


Câu trả lời này, và luôn luôn là dư thừa
Stephen C

-1

Java không có goto, bởi vì nó làm cho mã không có cấu trúckhông rõ ràng để đọc. Tuy nhiên, bạn có thể sử dụng breakcontinuenhư một hình thức văn minh của goto mà không gặp vấn đề gì.


Nhảy về phía trước bằng cách sử dụng 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");

Đầu ra :

Before Break
After ahead

Nhảy lùi bằng cách sử dụng tiếp tục

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

Điều này sẽ dẫn đến một vòng lặp vô hạn vì mỗi khi dòng continue befoređược thực thi, dòng mã sẽ bắt đầu lại từ đóbefore .


2
Ví dụ của bạn về việc nhảy lùi bằng cách sử dụng tiếp tục là sai. Bạn không thể sử dụng "continue" bên ngoài phần thân của vòng lặp, nó gây ra lỗi biên dịch. Tuy nhiên, bên trong một vòng lặp, tiếp tục chỉ cần nhảy vòng lặp có điều kiện. Vì vậy, một cái gì đó như while (true) {continue;} sẽ là một vòng lặp vô hạn, nhưng while (true) {} cũng vậy cho vấn đề đó.
Ben Holland
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.