เหตุใดเราจึงต้องมีการแบ่งหลังจากคำชี้แจงกรณี


95

เหตุใดคอมไพเลอร์จึงไม่ใส่คำสั่งแบ่งโดยอัตโนมัติหลังจากบล็อกโค้ดแต่ละอันในสวิตช์ ด้วยเหตุผลทางประวัติศาสตร์หรือไม่? เมื่อใดที่คุณต้องการให้โค้ดหลายบล็อกดำเนินการ


2
ทำคำตอบเกี่ยวกับ JDK-12 breakและป้ายสวิทช์การปฏิรูปไม่อาณัติ
Naman

คำตอบ:


94

บางครั้งการมีหลายกรณีที่เกี่ยวข้องกับบล็อกรหัสเดียวกันก็เป็นประโยชน์เช่น

case 'A':
case 'B':
case 'C':
    doSomething();
    break;

case 'D':
case 'E':
    doSomethingElse();
    break;

เป็นต้นเพียงแค่ตัวอย่าง

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


30
เพียงแค่เพิ่มความคิดเห็นตามบรรทัดเสมอ// Intentional fallthrough.เมื่อคุณไม่ได้หยุดพัก มันไม่ได้เป็นรูปแบบที่แย่มากเท่ากับ "ลืมเวลาพักโดยไม่ตั้งใจ" ในความคิดของฉัน ปล. แน่นอนว่าไม่ใช่ในกรณีง่ายๆเหมือนในคำตอบ
doublep

@doublep - ฉันเห็นด้วย ในความคิดของฉันฉันควรหลีกเลี่ยงถ้าเป็นไปได้ แต่ถ้ามันสมเหตุสมผลก็ต้องแน่ใจว่ามันชัดเจนมากว่าคุณกำลังทำอะไรอยู่
WildCrustacean

6
@doublep: ฉันจะไม่รำคาญกับความคิดเห็นถ้าหลาย ๆ ตัวcaseซ้อนกันแบบนั้น หากมีรหัสอยู่ระหว่างพวกเขาแสดงว่าใช่แสดงว่าความคิดเห็นนั้นถูกต้องแล้ว
Billy ONeal

4
ฉันนึกภาพภาษาที่คุณสามารถประกาศหลายกรณีได้ในหนึ่งเดียวcaseเช่นนี้: case 'A','B','C': doSomething(); case 'D','E': doSomethingElse();โดยไม่ต้องหยุดพักระหว่างกรณี ภาษาปาสคาลสามารถทำได้: "คำสั่ง case เปรียบเทียบค่าของนิพจน์ลำดับกับตัวเลือกแต่ละตัวซึ่งอาจเป็นค่าคงที่ช่วงย่อยหรือรายการที่คั่นด้วยเครื่องหมายจุลภาค" ( wiki.freepascal.org/Case )
Christian Semrau

32

ในอดีตเป็นเพราะการcaseกำหนดจุดlabelหรือที่เรียกว่าจุดเป้าหมายของการgotoโทรเป็นหลัก คำสั่ง switch และกรณีที่เกี่ยวข้องเป็นเพียงตัวแทนของ multiway branch ที่มีจุดเข้าใช้งานที่เป็นไปได้หลายจุดในสตรีมของโค้ด

ทั้งหมดที่กล่าวมามีการระบุจำนวนครั้งที่breakเกือบจะไม่มีที่สิ้นสุดซึ่งมักจะเป็นพฤติกรรมเริ่มต้นที่คุณอยากจะมีในตอนท้ายของทุกกรณี


31

Java มาจาก C และนั่นคือไวยากรณ์จาก C

มีหลายครั้งที่คุณต้องการให้คำสั่ง case หลายรายการมีเส้นทางการดำเนินการเดียว ด้านล่างนี้คือตัวอย่างที่จะบอกคุณว่ากี่วันในหนึ่งเดือน

class SwitchDemo2 {
    public static void main(String[] args) {

        int month = 2;
        int year = 2000;
        int numDays = 0;

        switch (month) {
            case 1:
            case 3:
            case 5:
            case 7:
            case 8:
            case 10:
            case 12:
                numDays = 31;
                break;
            case 4:
            case 6:
            case 9:
            case 11:
                numDays = 30;
                break;
            case 2:
                if ( ((year % 4 == 0) && !(year % 100 == 0))
                     || (year % 400 == 0) )
                    numDays = 29;
                else
                    numDays = 28;
                break;
            default:
                System.out.println("Invalid month.");
                break;
        }
        System.out.println("Number of Days = " + numDays);
    }
}

4
มีคนเล็งลูกศรขึ้นแล้วพลาด? หรือบางทีพวกเขาอาจมีเนื้อวัวที่มีลักษณะรั้งหรือเยื้องของคุณ ...
Jim Lewis

Dunno ขอ +1 จากฉัน นี่เป็นตัวอย่างที่การล่มสลายช่วยได้แม้ว่าฉันต้องการให้ Java เลือกประโยคกรณีที่ทันสมัยกว่านี้ การประเมินเมื่อใดก็ตามของ Cobol นั้นทรงพลังกว่ามากและมาก่อน Java นิพจน์การจับคู่ของ Scala เป็นตัวอย่างที่ทันสมัยของสิ่งที่สามารถทำได้
Jim Ferrans

1
นักเรียนของฉันจะถูกเฆี่ยนต่อหน้าสาธารณชนเพราะทำเช่นนี้ โคโยตี้มันน่าเกลียด
ncmathsadist

2
@ncmathsadist มันแสดงให้เห็นถึงจุดหนึ่งในการทำบางสิ่งบางอย่าง ฉันไม่เห็นด้วยที่ตัวอย่างนี้อาจจะสุดโต่ง แต่เป็นตัวอย่างในโลกแห่งความเป็นจริงซึ่งฉันเชื่อว่าช่วยให้ผู้คนเข้าใจแนวคิดได้
Romain Hippeau

15

ฉันคิดว่ามันเป็นความผิดพลาด ในการสร้างภาษามันง่ายbreakพอ ๆ กับค่าเริ่มต้นและมีfallthroughคีย์เวิร์ดแทน โค้ดส่วนใหญ่ที่ฉันเขียนและอ่านมีการหยุดพักทุกกรณี


4
ฉันอยากจะแนะนำcontinue <case name>ว่าอนุญาตให้ระบุอย่างชัดเจนด้วยประโยคกรณีใดที่จะดำเนินการต่อ
Vilx-

4
@Vilx เมื่ออนุญาตโดยพลการcaseภายในปัจจุบันswitchสิ่งนี้จะกลายเป็นไฟล์goto. ;-)
Christian Semrau

13

คุณสามารถทำสิ่งที่น่าสนใจได้ทุกประเภทโดยใช้ case fall-through

ตัวอย่างเช่นสมมติว่าคุณต้องการดำเนินการบางอย่างสำหรับทุกกรณี แต่ในบางกรณีคุณต้องการดำเนินการนั้นบวกอย่างอื่น การใช้คำสั่ง switch กับ fall-through จะทำให้ง่ายมาก

switch (someValue)
{
    case extendedActionValue:
        // do extended action here, falls through to normal action
    case normalActionValue:
    case otherNormalActionValue:
        // do normal action here
        break;
}

แน่นอนว่าเป็นเรื่องง่ายที่จะลืมbreakข้อความในตอนท้ายของคดีและทำให้เกิดพฤติกรรมที่ไม่คาดคิด คอมไพเลอร์ที่ดีจะเตือนคุณเมื่อคุณละเว้นคำสั่งแบ่ง


สามารถใช้ switch / case กับสตริงใน Java ได้หรือไม่?
Steve Kuo

@ สตีฟ: อ๊ะฉันเดาไม่ถูกในขณะนี้ ตามstackoverflow.com/questions/338206/…สตริงจะได้รับอนุญาตใน Java เวอร์ชันอนาคต (ปัจจุบันฉันเขียนโปรแกรมส่วนใหญ่ใน C # ซึ่งอนุญาตให้ใช้สตริงในคำสั่ง switch) ฉันได้แก้ไขคำตอบเพื่อลบเครื่องหมายคำพูดที่ทำให้เข้าใจผิด
Zach Johnson

2
@ZachJohnson ในภายหลัง Java 7 ไม่อนุญาตให้เปิด Strings
Bob Cross

7

เหตุใดคอมไพเลอร์จึงไม่ใส่คำสั่งแบ่งโดยอัตโนมัติหลังจากบล็อกโค้ดแต่ละอันในสวิตช์

ทิ้งความปรารถนาดีที่จะสามารถใช้บล็อกที่เหมือนกันสำหรับหลาย ๆ กรณี (ซึ่งอาจเป็นกรณีพิเศษ) ...

ด้วยเหตุผลทางประวัติศาสตร์หรือไม่? เมื่อใดที่คุณต้องการให้โค้ดหลายบล็อกดำเนินการ

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


6

breakหลังจากที่สวิทช์cases ถูกนำมาใช้เพื่อหลีกเลี่ยงการ fallthrough ในงบสวิทช์ แม้ว่าสิ่งที่น่าสนใจในตอนนี้สามารถทำได้ผ่านป้ายสวิทช์ที่จัดตั้งขึ้นใหม่เป็นดำเนินการผ่านทางJEP-325

ด้วยการเปลี่ยนแปลงเหล่านี้คุณสามารถหลีกเลี่ยงbreakสวิตช์ทุกตัวcaseได้ดังที่แสดงเพิ่มเติม: -

public class SwitchExpressionsNoFallThrough {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int value = scanner.nextInt();
        /*
         * Before JEP-325
         */
        switch (value) {
            case 1:
                System.out.println("one");
            case 2:
                System.out.println("two");
            default:
                System.out.println("many");
        }

        /*
         * After JEP-325
         */
        switch (value) {
            case 1 ->System.out.println("one");
            case 2 ->System.out.println("two");
            default ->System.out.println("many");
        }
    }
}

เกี่ยวกับการดำเนินการดังกล่าวข้างต้นรหัสด้วย JDK-12การส่งออกเปรียบเทียบอาจจะเห็นเป็น

//input
1
// output from the implementation before JEP-325
one
two
many
// output from the implementation after JEP-325
one

และ

//input
2
// output from the implementation before JEP-325
two
many
// output from the implementation after JEP-325
two

และแน่นอนว่าสิ่งนั้นไม่เปลี่ยนแปลง

// input
3
many // default case match
many // branches to 'default' as well

4

ดังนั้นคุณไม่ต้องทำรหัสซ้ำหากคุณต้องการหลายกรณีในการทำสิ่งเดียวกัน:

case THIS:
case THAT:
{
    code;
    break;
}

หรือคุณสามารถทำสิ่งต่างๆเช่น:

case THIS:
{
   do this;
}
case THAT:
{
   do that;
}

ในรูปแบบน้ำตก

เกิดข้อผิดพลาด / ความสับสนจริงๆถ้าคุณถามฉัน


มันทำงานทั้งสองอย่างdo thisและdo thatเพื่อสิ่งนี้ แต่เพียงdo thatเพื่อสิ่งนั้น?
JonnyRaa

1
เพียงแค่อ่านเอกสาร มันแย่มาก! วิธีง่ายๆในการเขียนจุดบกพร่อง!
JonnyRaa

4

เท่าที่บันทึกทางประวัติศาสตร์กล่าวไป Tony Hoare ได้คิดค้นคำแถลงกรณีดังกล่าวในปี 1960 ระหว่างการปฏิวัติ "การเขียนโปรแกรมเชิงโครงสร้าง" คำแถลงกรณีของ Tony รองรับป้ายกำกับหลายป้ายต่อเคสและการออกอัตโนมัติโดยไม่มีbreakข้อความเหม็น ข้อกำหนดสำหรับเนื้อหาที่ชัดเจนbreakเป็นสิ่งที่มาจากบรรทัด BCPL / B / C Dennis Ritchie เขียน (ใน ACM HOPL-II):

ตัวอย่างเช่น endcase ที่หลบหนีจากคำสั่งสวิตช์ BCPL นั้นไม่มีอยู่ในภาษาเมื่อเราเรียนรู้ในทศวรรษที่ 1960 ดังนั้นการใช้คีย์เวิร์ด break มากเกินไปเพื่อหลบหนีจากคำสั่งสวิตช์ B และ C เป็นผลมาจากวิวัฒนาการที่แตกต่างกันมากกว่าที่จะใส่ใจ เปลี่ยนแปลง.

ฉันไม่สามารถหางานเขียนทางประวัติศาสตร์เกี่ยวกับ BCPL ได้ แต่ความคิดเห็นของ Ritchie ชี้ให้เห็นว่าbreakอุบัติเหตุทางประวัติศาสตร์ไม่มากก็น้อย BCPL แก้ไขปัญหาในภายหลัง แต่บางที Ritchie และ Thompson อาจยุ่งอยู่กับการประดิษฐ์ Unix ที่ต้องใส่ใจกับรายละเอียดดังกล่าว :-)


สิ่งนี้ควรได้รับการโหวตมากกว่านี้ เห็นได้ชัดว่า OP รู้อยู่แล้วว่าการละเว้นbreakทำให้ "หลายโค้ดบล็อกดำเนินการได้" และเกี่ยวข้องกับแรงจูงใจของตัวเลือกการออกแบบนี้มากกว่า คนอื่น ๆ กล่าวถึงมรดกที่เป็นที่รู้จักกันดีตั้งแต่ C ถึง Java และคำตอบนี้ผลักดันการวิจัยให้ก้าวไปสู่ช่วงก่อน C ฉันหวังว่าเราจะจับคู่รูปแบบนี้ (แม้ว่าจะดั้งเดิมมาก) ตั้งแต่เริ่มต้น
wln Nirvana

3

Java มาจาก C ซึ่งเป็นมรดกทางวัฒนธรรมรวมถึงเทคนิคที่เรียกว่าอุปกรณ์ของดัฟฟ์ เป็นการเพิ่มประสิทธิภาพที่อาศัยข้อเท็จจริงที่ว่าการควบคุมผ่านจากกรณีหนึ่งไปยังอีกกรณีหนึ่งในกรณีที่ไม่มีbreak;คำสั่ง เมื่อถึงเวลาที่ C เป็นมาตรฐานมีรหัสแบบนั้นมากมาย "อยู่ในป่า" และจะมีการต่อต้านการเปลี่ยนภาษาเพื่อทำลายโครงสร้างดังกล่าว


2

อย่างที่ผู้คนเคยกล่าวไว้ก่อนหน้านี้ว่ามันคือการปล่อยให้ผ่านไปและไม่ใช่เรื่องผิดมันเป็นคุณสมบัติ หากbreakข้อความจำนวนมากเกินไปทำให้คุณรำคาญคุณสามารถกำจัดมันได้อย่างง่ายดายโดยใช้returnคำสั่งแทน นี่เป็นแนวทางปฏิบัติที่ดีจริง ๆ เพราะวิธีการของคุณควรมีขนาดเล็กที่สุดเท่าที่จะเป็นไปได้ (เพื่อความสามารถในการอ่านและการบำรุงรักษา) ดังนั้นswitchคำแถลงจึงใหญ่พอสำหรับวิธีการหนึ่งอยู่แล้วดังนั้นวิธีการที่ดีจึงไม่ควรมีอย่างอื่นนี่คือ ตัวอย่าง:

public class SwitchTester{
    private static final Log log = LogFactory.getLog(SwitchTester.class);
    public static void main(String[] args){
        log.info(monthsOfTheSeason(Season.WINTER));
        log.info(monthsOfTheSeason(Season.SPRING));
        log.info(monthsOfTheSeason(Season.SUMMER));
        log.info(monthsOfTheSeason(Season.AUTUMN));
    }

    enum Season{WINTER, SPRING, SUMMER, AUTUMN};

    static String monthsOfTheSeason(Season season){
        switch(season){
            case WINTER:
                return "Dec, Jan, Feb";
            case SPRING:
                return "Mar, Apr, May";
            case SUMMER:
                return "Jun, Jul, Aug";
            case AUTUMN:
                return "Sep, Oct, Nov";
            default:
                //actually a NullPointerException will be thrown before reaching this
                throw new IllegalArgumentException("Season must not be null");
        }        
    }
}   

การดำเนินการพิมพ์:

12:37:25.760 [main] INFO lang.SwitchTester - Dec, Jan, Feb
12:37:25.762 [main] INFO lang.SwitchTester - Mar, Apr, May
12:37:25.762 [main] INFO lang.SwitchTester - Jun, Jul, Aug
12:37:25.762 [main] INFO lang.SwitchTester - Sep, Oct, Nov

อย่างที่คาดไว้.


0

การไม่มีตัวแบ่งอัตโนมัติที่เพิ่มโดยคอมไพลเลอร์ทำให้สามารถใช้สวิตช์ / เคสเพื่อทดสอบเงื่อนไขเช่น1 <= a <= 3โดยการลบคำสั่งเบรกออกจาก 1 และ 2

switch(a) {
  case 1: //I'm between 1 and 3
  case 2: //I'm between 1 and 3
  case 3: //I'm between 1 and 3
          break;
}

ใช่ ฉันเกลียดสิ่งนี้มาก
ncmathsadist

0

เนื่องจากมีสถานการณ์ที่คุณต้องการไหลผ่านบล็อกแรกเช่นหลีกเลี่ยงการเขียนโค้ดเดียวกันในหลายบล็อก แต่ยังคงสามารถแบ่งออกเพื่อควบคุม mroe ได้ นอกจากนี้ยังมีเหตุผลอื่น ๆ อีกมากมาย


0

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

เช่นการใช้รหัสตอบกลับ http เพื่อตรวจสอบผู้ใช้ด้วยโทเค็นเวลา

รหัสตอบกลับของเซิร์ฟเวอร์ 401 - โทเค็นล้าสมัย -> สร้างโทเค็นใหม่และเข้าสู่ระบบผู้ใช้
รหัสตอบกลับเซิร์ฟเวอร์ 200 - โทเค็นใช้ได้ -> ล็อกผู้ใช้

ในกรณีงบ:

case 404:
case 500:
        {
            Log.v("Server responses","Unable to respond due to server error");
            break;
        }
        case 401:
        {
             //regenerate token
        }
        case 200:
        {
            // log in user
            break;
        }

การใช้สิ่งนี้คุณไม่จำเป็นต้องเรียกใช้ฟังก์ชันผู้ใช้ล็อกอินสำหรับการตอบสนอง 401 เนื่องจากเมื่อสร้างโทเค็นใหม่รันไทม์จะข้ามไปที่กรณี 200


0

คุณสามารถแยกประเภทตัวเลขเดือนจำนวนอื่น ๆ ได้อย่างง่ายดาย
จะดีกว่าถ้าในกรณีนี้

public static void spanishNumbers(String span){

    span = span.toLowerCase().replace(" ", "");
    switch (span){
     case "1":    
     case "jan":  System.out.println("uno"); break;    
     case "2":      
     case "feb":  System.out.println("dos"); break;    
     case "3":     
     case "mar":  System.out.println("tres"); break;   
     case "4":   
     case "apr":  System.out.println("cuatro"); break;
     case "5":    
     case "may":  System.out.println("cinco"); break;
     case "6":     
     case "jun":  System.out.println("seis"); break;
     case "7":    
     case "jul":  System.out.println("seite"); break;
     case "8":    
     case "aug":  System.out.println("ocho"); break;
     case "9":   
     case "sep":  System.out.println("nueve"); break;
     case "10":    
     case "oct": System.out.println("diez"); break;
     }
 }

0

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

สมมติว่าคุณมีสามสถานะสถานะหนึ่งที่รอให้ผู้ใช้ป้อนตัวเลขสถานะที่สองเพื่อคำนวณและสถานะที่สามเพื่อพิมพ์ผลรวม

ในกรณีนี้คุณมี:

  1. State1 - รอให้ผู้ใช้ป้อนตัวเลข
  2. State2 - พิมพ์ผลรวม
  3. state3 - คำนวณผลรวม

มองไปที่รัฐที่คุณต้องการคำสั่งของการบีบบังคับที่จะเริ่มต้นในstate1แล้วstate3และในที่สุดก็state2 มิฉะนั้นเราจะพิมพ์เฉพาะผู้ใช้ที่ป้อนโดยไม่ต้องคำนวณผลรวม เพื่อชี้แจงอีกครั้งเรารอให้ผู้ใช้ป้อนค่าจากนั้นคำนวณผลรวมและพิมพ์ผลรวม

นี่คือตัวอย่างรหัส:

while(1){
    switch(state){
      case state1:
        // Wait for user input code
        state = state3; // Jump to state3
        break;
      case state2:
        //Print the sum code
        state = state3; // Jump to state3;
      case state3:
        // Calculate the sum code
        state = wait; // Jump to state1
        break;
    }
}

ถ้าเราไม่ได้ใช้breakก็จะดำเนินการในเรื่องนี้การสั่งซื้อstate1 , state2และstate3 แต่เมื่อใช้breakเราจะหลีกเลี่ยงสถานการณ์นี้และสามารถสั่งซื้อได้ในขั้นตอนที่ถูกต้องซึ่งเริ่มต้นด้วย state1 จากนั้น state3 และ last แต่ไม่น้อยที่สุด


-1

ถูกต้องเพราะด้วยการจัดวางที่ชาญฉลาดบางอย่างคุณสามารถดำเนินการบล็อกในน้ำตกได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.