ทางเลือกแทนคำสั่ง goto ใน Java


84

ฟังก์ชันทางเลือกสำหรับคีย์เวิร์ดgotoใน Java คืออะไร?

เนื่องจาก Java ไม่มี goto


10
@harigm: คุณสามารถยกตัวอย่างประเภทของโค้ดที่คุณจะเขียนหาก gotoมีอยู่ใน Java ได้หรือไม่? ฉันคิดว่าในนั้นเป็นปัญหาที่สำคัญกว่า
polygenelubricants

3
@harigm: ฉันเป็น polygene คนที่สองคุณช่วยอัปเดตคำถามของคุณเพื่อรวมเหตุผลที่โปรแกรมของคุณต้องการ goto ได้หรือไม่?
ทุกคนใน

13
คนมักพูดถึงว่าไม่เคยใช้ goto แต่ฉันคิดว่ามีกรณีการใช้งานในโลกแห่งความเป็นจริงที่ดีจริงๆซึ่งเป็นที่รู้จักและใช้กันดีอยู่แล้ว .. นั่นคือต้องแน่ใจว่าได้รันโค้ดก่อนที่จะกลับมาจากฟังก์ชัน .. ล็อคหรืออะไรไม่ได้ แต่ในกรณีของฉันฉันชอบที่จะสามารถข้ามไปยังจุดพักได้ก่อนกลับเพื่อที่ฉันจะได้ทำการล้างข้อมูลที่จำเป็น แน่นอนว่าโปรแกรมที่เกลื่อนไปด้วย goto ทั่วสถานที่นั้นจะน่ากลัว แต่ถ้า จำกัด ไว้ที่ method body มันก็ไม่ได้แย่ขนาดนั้นตราบเท่าที่คุณทำตามแบบแผน (ข้ามไปที่จุดสิ้นสุดของฟังก์ชั่นเท่านั้นอย่าสำรอง)
Matt Wolfe

7
คุณอยู่ใน บริษัท ที่ดี Linus Torvalds ได้รับการปกป้องจู๋จี๋ข้ามไปkerneltrap.org/node/553 การละเลยที่โดดเด่นที่สุดจาก Project Coin อย่างไม่ต้องสงสัย
Fletch

7
@MattWolfe ไม่พยายามทำงานในตัวอย่างนั้นในที่สุด?
Andres Riofrio

คำตอบ:


84

คุณสามารถใช้คำสั่งBREAK ที่มีป้ายกำกับ:

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

อย่างไรก็ตามในโค้ดที่ออกแบบมาอย่างเหมาะสมคุณไม่จำเป็นต้องใช้ฟังก์ชัน GOTO


89
สิ่งนี้จะไม่ช่วยคุณให้รอดพ้นจากแร็พเตอร์!
Bobby

25
ทำไมคุณไม่จำเป็นต้องมีฟังก์ชัน goto ในโค้ดที่ออกแบบมาอย่างเหมาะสม
rogermushroom

13
เนื่องจากแอสเซมบลีเป็นอุปกรณ์เดียวที่ได้รับอนุญาตให้ใช้งาน คำสั่ง while, คำสั่งสำหรับ, do-while, foreach, การเรียกใช้ฟังก์ชันทั้งหมดนี้เป็นคำสั่ง GOTO ที่ใช้ในลักษณะควบคุมและคาดเดาได้ ในระดับของแอสเซมเบลอร์จะเป็น GOTO เสมอ แต่คุณไม่ควรต้องการฟังก์ชันที่ GOTO บริสุทธิ์มอบให้คุณข้อได้เปรียบประการเดียวที่เหนือกว่าฟังก์ชันและคำสั่งวนซ้ำ / ควบคุมคือช่วยให้คุณสามารถข้ามไปตรงกลางของโค้ดได้ทุกที่ . และ 'ข้อได้เปรียบ' นั้นก็คือคำจำกัดความของ 'รหัสสปาเก็ตตี้'

1
@whatsthebeef ที่นี่มีชื่อเสียง 1,968 ตัวอักษรโดย Edsger Dijkstra ดับบลิวที่อธิบายว่าทำไมเขาถือว่าโกโตะที่เป็นอันตราย
thomanski


42

ไม่มีgotoแนวคิดใดเทียบเท่าโดยตรงกับแนวคิดใน Java มีโครงสร้างไม่กี่คนที่ช่วยให้คุณสามารถทำบางgotoสิ่งที่คุณสามารถทำอะไรกับคลาสสิก

  • breakและcontinueงบช่วยให้คุณสามารถกระโดดออกจากบล็อกในวงหรือสวิทช์คำสั่ง
  • คำสั่งที่มีป้ายกำกับและbreak <label>อนุญาตให้คุณกระโดดออกจากคำสั่งผสมตามอำเภอใจไปยังระดับใดก็ได้ภายในวิธีการที่กำหนด (หรือบล็อกตัวเริ่มต้น)
  • หากคุณติดป้ายกำกับคำสั่งลูปคุณสามารถcontinue <label>ดำเนินการวนซ้ำรอบนอกถัดไปจากวงใน
  • การขว้างปาและการจับข้อยกเว้นช่วยให้คุณ (มีประสิทธิภาพ) กระโดดออกจากการเรียกใช้วิธีการหลายระดับ (อย่างไรก็ตามข้อยกเว้นมีราคาค่อนข้างแพงและถือเป็นวิธีที่ไม่ดีในการควบคุมขั้นตอน "ธรรมดา" 1 )
  • returnและแน่นอนมี

โครงสร้าง Java เหล่านี้ไม่อนุญาตให้คุณแตกแขนงไปข้างหลังหรือไปยังจุดหนึ่งในโค้ดที่ระดับเดียวกับการซ้อนกันเป็นคำสั่งปัจจุบัน พวกเขาทั้งหมดกระโดดออกจากระดับการซ้อน (ขอบเขต) หนึ่งระดับขึ้นไปและพวกเขาทั้งหมด (นอกเหนือจากcontinue) กระโดดลงไปข้างล่าง ข้อ จำกัด นี้จะช่วยให้หลีกเลี่ยงการข้ามไป "รหัสปาเก็ตตี้" กลุ่มอาการของโรคอยู่ในตัวเก่าพื้นฐาน FORTRAN และ COBOL รหัส2


1- ส่วนที่แพงที่สุดของข้อยกเว้นคือการสร้างวัตถุข้อยกเว้นและ stacktrace หากคุณจำเป็นต้องใช้การจัดการข้อยกเว้นสำหรับการควบคุมโฟลว์ "ปกติ" จริงๆคุณสามารถจัดสรร / ใช้อ็อบเจ็กต์ข้อยกเว้นล่วงหน้าหรือสร้างคลาสข้อยกเว้นแบบกำหนดเองที่แทนที่fillInStackTrace()เมธอด ข้อเสียคือprintStackTrace()วิธีการของข้อยกเว้นจะไม่ให้ข้อมูลที่เป็นประโยชน์แก่คุณ ... หากคุณจำเป็นต้องโทรหาพวกเขา

2 - กลุ่มอาการของ spaghetti code ทำให้เกิดแนวทางการเขียนโปรแกรมแบบมีโครงสร้างซึ่งคุณ จำกัด การใช้โครงสร้างภาษาที่มีอยู่ สิ่งนี้สามารถนำไปใช้กับBASIC , FortranและCOBOLได้ แต่ต้องมีการดูแลและมีระเบียบวินัย การกำจัดทิ้งgotoทั้งหมดเป็นวิธีแก้ปัญหาที่ดีกว่าในทางปฏิบัติ หากคุณเก็บไว้เป็นภาษาก็มักจะมีตัวตลกบางคนที่จะใช้ในทางที่ผิด


โครงสร้างแบบวนซ้ำเช่นwhile (... ) {}และสำหรับ (... ) {}ช่วยให้คุณสามารถทำซ้ำบล็อกโค้ดได้โดยไม่ต้องกระโดดไปยังตำแหน่งที่ต้องการ นอกจากนี้วิธีการเรียกmyMethod ()ช่วยให้คุณสามารถรันโค้ดที่พบในที่อื่นและกลับไปที่บล็อกปัจจุบันเมื่อเสร็จสิ้น สิ่งเหล่านี้สามารถใช้เพื่อแทนที่การทำงานของgotoในขณะที่ป้องกันปัญหาทั่วไปของการไม่สามารถส่งคืน goto ที่อนุญาตได้
Chris Nava

@Chris - นั่นเป็นความจริง แต่ภาษาส่วนใหญ่ที่รองรับ GOTO ยังมีแอนะล็อกที่ใกล้เคียงกับโครงสร้าง Java loop
Stephen C

1
@Chris - FORTRAN แบบคลาสสิกมีลูป 'for' และ COBOL แบบคลาสสิกยังมีโครงสร้างแบบวนซ้ำ (แม้ว่าฉันจะจำรายละเอียดไม่ได้ก็ตาม) BASIC คลาสสิกเท่านั้นที่ไม่มีโครงสร้างการวนซ้ำอย่างชัดเจน ...
Stephen C

31

เพื่อความสนุกสนานนี่คือการใช้งาน GOTO ใน Java

ตัวอย่าง:

   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 }

เรียกใช้:

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

ต้องเพิ่มคำว่า "ไม่ใช้!" หรือไม่?


2
Bug: Math.random()สามารถคืนค่า 0 ได้มันควรจะเป็น> =
poitroae

ใช่ดี สิ่งใดที่สามารถดำเนินการโดย bytecode hackery ไม่ได้จริงๆ Java ...
สตีเฟ่น C

มีสิ่งที่คล้ายกันที่รองรับป้ายกำกับแทนหมายเลขบรรทัดหรือไม่?
Behrouz.M

1
ลิงก์ไปยังการนำไปใช้งานเสีย
LarsH

@poitroae เป็นไข่อีสเตอร์แสนสนุกที่ใครเห็นก็HellWorld!o World!ต้องตะลึง #AllBugsAreFeatures
ReinstateMonica3167040

18

ในขณะที่ผู้แสดงความคิดเห็นและผู้ลงคะแนนบางคนโต้แย้งว่านี่ไม่ใช่gotoแต่ bytecode ที่สร้างขึ้นจากคำสั่ง Java ด้านล่างนี้แสดงให้เห็นว่าข้อความเหล่านี้แสดงความหมายแบบgotoจริงๆ

โดยเฉพาะอย่างยิ่งdo {...} while(true);ลูปในตัวอย่างที่สองได้รับการปรับให้เหมาะสมโดยคอมไพเลอร์ Java เพื่อไม่ให้ประเมินเงื่อนไขการวนซ้ำ

กระโดดไปข้างหน้า

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

ใน bytecode:

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

กระโดดไปข้างหลัง

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

ใน bytecode:

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

ทำอย่างไร / ขณะกระโดดถอยหลัง? มันยังคงแตกออกจากบล็อก ตัวอย่างตัวนับ: label: do {System.out.println ("Backward"); ป้ายกำกับต่อ;} ในขณะที่ (เท็จ); ฉันคิดว่าในตัวอย่างของคุณ while (true) คือสิ่งที่รับผิดชอบในการกระโดดถอยหลัง
Ben Holland

@BenHolland: continue labelกระโดดถอยหลัง
Lukas Eder

ฉันยังไม่มั่นใจ คำสั่งต่อไปจะข้ามการวนซ้ำปัจจุบันของลูป for, while หรือ do-while การดำเนินการต่อจะข้ามไปยังจุดสิ้นสุดของเนื้อหาของลูปซึ่งจะถูกประเมินสำหรับนิพจน์บูลีนที่ควบคุมลูป ขณะคือสิ่งที่กระโดดไปข้างหลังไม่ใช่การดำเนินการต่อ ดูdocs.oracle.com/javase/tutorial/java/nutsandbolts/branch.html
Ben Holland

@ BenHolland: ในทางเทคนิคคุณพูดถูก จากมุมมองเชิงตรรกะหาก// do stuffและ// do more stuffเป็นข้อความที่น่าสนใจcontinue label "มีผล"ของการกระโดดถอยหลัง (เนื่องจากwhile(true)คำแถลงแน่นอน) ฉันไม่รู้ว่านี่เป็นคำถามที่แม่นยำ แต่ ...
Lukas Eder

2
@ BenHolland: ฉันอัปเดตคำตอบ while(true)ถูกแปลโดยรวบรวมเป็นgotoดำเนินการ bytecode ในฐานะที่trueเป็นตัวอักษรคงที่คอมไพเลอร์สามารถทำเพิ่มประสิทธิภาพนี้และไม่ได้มีการประเมินอะไร ตัวอย่างของฉันคือโกโตกระโดดถอยหลัง ...
Lukas Eder

5

หากคุณต้องการบางอย่างเช่นประโยค goto คุณสามารถลองทำลายบล็อกที่มีชื่อได้ตลอดเวลา

คุณต้องอยู่ในขอบเขตของบล็อกเพื่อทำลายป้ายกำกับ:

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

ฉันจะไม่บรรยายคุณว่าทำไมคุณควรหลีกเลี่ยง goto - ฉันถือว่าคุณรู้คำตอบแล้ว


2
ฉันพยายามดำเนินการนี้ในคำถามของฉันดังนั้นการเชื่อมโยงที่นี่ จริงๆแล้วไม่ได้นำคุณไปที่ "ป้ายกำกับด้านบน" บางทีฉันอาจตีความข้อความของผู้เขียนผิด แต่เพื่อความชัดเจนฉันจะเพิ่มว่าจะนำคุณไปยังจุดสิ้นสุดของบล็อกด้านนอก (บล็อก "ป้ายกำกับ") ซึ่งอยู่ด้านล่าง ที่คุณกำลังทำลาย ดังนั้นคุณไม่สามารถ "ทำลาย" ขึ้นไป อย่างน้อยนั่นคือความเข้าใจของฉัน
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);
    }

สิ่งนี้จะส่งผลให้ StackOverFlowException คือคุณเรียกใช้งานได้นานพอดังนั้นฉันจึงจำเป็นต้อง goto
Kevin Parker

3
@ เควิน: สิ่งนี้จะนำไปสู่การล้นสแต็กได้อย่างไร? ไม่มีการเรียกซ้ำในอัลกอริทึมนี้ ...
Lukas Eder

1
สิ่งนี้ดูเหมือนข้อพิสูจน์เดิมมากว่าโปรแกรมใด ๆ สามารถเขียนได้โดยไม่ต้องใช้ "goto" - โดยเปลี่ยนเป็น while loop ด้วยตัวแปร "label" ซึ่งแย่กว่า goto ถึงสิบเท่า
gnasher729

3

StephenC เขียน:

มีโครงสร้างสองแบบที่ช่วยให้คุณทำบางสิ่งที่คุณสามารถทำได้ด้วย goto แบบคลาสสิก

อีกหนึ่ง...

Matt Wolfe เขียน:

คนมักพูดถึงว่าไม่เคยใช้ goto แต่ฉันคิดว่ามีกรณีการใช้งานในโลกแห่งความเป็นจริงที่ดีจริงๆซึ่งเป็นที่รู้จักและใช้กันดีอยู่แล้ว .. นั่นคือต้องแน่ใจว่าได้รันโค้ดก่อนที่จะกลับมาจากฟังก์ชัน .. ล็อคหรืออะไรไม่ได้ แต่ในกรณีของฉันฉันชอบที่จะสามารถข้ามไปยังจุดพักได้ก่อนกลับเพื่อที่ฉันจะได้ทำการล้างข้อมูลที่จำเป็น

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

สุดท้ายบล็อกจะดำเนินการเสมอเมื่อบล็อกการลองออก สิ่งนี้ช่วยให้มั่นใจได้ว่าสุดท้ายจะดำเนินการบล็อกแม้ว่าจะมีข้อยกเว้นที่ไม่คาดคิดเกิดขึ้นก็ตาม แต่ในที่สุดก็มีประโยชน์สำหรับการจัดการข้อยกเว้น - ช่วยให้โปรแกรมเมอร์หลีกเลี่ยงการข้ามรหัสโดยไม่ได้ตั้งใจโดยการส่งคืนดำเนินการต่อหรือทำลาย การใส่รหัสล้างข้อมูลลงในบล็อกสุดท้ายเป็นแนวทางปฏิบัติที่ดีแม้ว่าจะไม่มีข้อยกเว้นใด ๆ ก็ตาม

คำถามสัมภาษณ์โง่ ๆ ที่เกี่ยวข้องในที่สุดก็คือ: หากคุณกลับมาจากการลอง {} บล็อก แต่มีผลตอบแทนในที่สุด {} ด้วยเช่นกันค่าใดจะถูกส่งกลับ


2
ฉันไม่เห็นด้วยว่านั่นเป็นคำถามสัมภาษณ์โง่ ๆ ที่มีประสบการณ์โปรแกรม Java ควรรู้ว่าจะเกิดอะไรขึ้นถ้าเพียง แต่จะเข้าใจว่าทำไมการวางreturnในfinallyบล็อกเป็นความคิดที่ไม่ดี (และแม้ว่าความรู้จะไม่จำเป็นอย่างยิ่ง แต่ฉันก็กังวลกับโปรแกรมเมอร์ที่ไม่มีความอยากรู้อยากเห็น ... )
Stephen C


0

ลองใช้รหัสด้านล่าง มันใช้ได้กับฉัน

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

Java ไม่มีgotoเนื่องจากทำให้โค้ดไม่มีโครงสร้างและอ่านไม่ชัดเจน อย่างไรก็ตามคุณสามารถใช้breakและcontinueเป็นอารยะรูปแบบของการข้ามไปได้โดยไม่มีปัญหาของมัน


กระโดดไปข้างหน้าโดยใช้การหยุดพัก -

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

เอาท์พุต :

Before Break
After ahead

กระโดดไปข้างหลังโดยใช้ต่อ

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

ซึ่งจะส่งผลในวง จำกัดเป็นทุกครั้งที่สายcontinue beforeจะถูกดำเนินการไหลของรหัสจะเริ่มต้นอีกครั้งจากbefore


2
ตัวอย่างของคุณในการกระโดดถอยหลังโดยใช้การดำเนินการต่อนั้นไม่ถูกต้อง คุณไม่สามารถใช้ "ดำเนินการต่อ" นอกเนื้อหาของลูปได้ซึ่งทำให้เกิดข้อผิดพลาดในการคอมไพล์ อย่างไรก็ตามภายในวงให้ดำเนินการต่อเพียงแค่กระโดดห่วงแบบมีเงื่อนไข ดังนั้นบางอย่างเช่น while (true) {ต่อ;} จะวนซ้ำไม่สิ้นสุด แต่ก็เป็นขณะที่ (จริง) {} สำหรับเรื่องนั้น
Ben Holland
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.