เหตุใดคอมไพเลอร์จึงไม่ใส่คำสั่งแบ่งโดยอัตโนมัติหลังจากบล็อกโค้ดแต่ละอันในสวิตช์ ด้วยเหตุผลทางประวัติศาสตร์หรือไม่? เมื่อใดที่คุณต้องการให้โค้ดหลายบล็อกดำเนินการ
เหตุใดคอมไพเลอร์จึงไม่ใส่คำสั่งแบ่งโดยอัตโนมัติหลังจากบล็อกโค้ดแต่ละอันในสวิตช์ ด้วยเหตุผลทางประวัติศาสตร์หรือไม่? เมื่อใดที่คุณต้องการให้โค้ดหลายบล็อกดำเนินการ
คำตอบ:
บางครั้งการมีหลายกรณีที่เกี่ยวข้องกับบล็อกรหัสเดียวกันก็เป็นประโยชน์เช่น
case 'A':
case 'B':
case 'C':
doSomething();
break;
case 'D':
case 'E':
doSomethingElse();
break;
เป็นต้นเพียงแค่ตัวอย่าง
จากประสบการณ์ของฉันโดยปกติแล้วมันเป็นรูปแบบที่ไม่ดีที่จะ "ล้มเหลว" และมีการดำเนินการโค้ดหลายบล็อกสำหรับหนึ่งกรณี แต่อาจมีการใช้งานในบางสถานการณ์
// Intentional fallthrough.
เมื่อคุณไม่ได้หยุดพัก มันไม่ได้เป็นรูปแบบที่แย่มากเท่ากับ "ลืมเวลาพักโดยไม่ตั้งใจ" ในความคิดของฉัน ปล. แน่นอนว่าไม่ใช่ในกรณีง่ายๆเหมือนในคำตอบ
case
ซ้อนกันแบบนั้น หากมีรหัสอยู่ระหว่างพวกเขาแสดงว่าใช่แสดงว่าความคิดเห็นนั้นถูกต้องแล้ว
case
เช่นนี้: case 'A','B','C': doSomething(); case 'D','E': doSomethingElse();
โดยไม่ต้องหยุดพักระหว่างกรณี ภาษาปาสคาลสามารถทำได้: "คำสั่ง case เปรียบเทียบค่าของนิพจน์ลำดับกับตัวเลือกแต่ละตัวซึ่งอาจเป็นค่าคงที่ช่วงย่อยหรือรายการที่คั่นด้วยเครื่องหมายจุลภาค" ( wiki.freepascal.org/Case )
ในอดีตเป็นเพราะการcase
กำหนดจุดlabel
หรือที่เรียกว่าจุดเป้าหมายของการgoto
โทรเป็นหลัก คำสั่ง switch และกรณีที่เกี่ยวข้องเป็นเพียงตัวแทนของ multiway branch ที่มีจุดเข้าใช้งานที่เป็นไปได้หลายจุดในสตรีมของโค้ด
ทั้งหมดที่กล่าวมามีการระบุจำนวนครั้งที่break
เกือบจะไม่มีที่สิ้นสุดซึ่งมักจะเป็นพฤติกรรมเริ่มต้นที่คุณอยากจะมีในตอนท้ายของทุกกรณี
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);
}
}
ฉันคิดว่ามันเป็นความผิดพลาด ในการสร้างภาษามันง่ายbreak
พอ ๆ กับค่าเริ่มต้นและมีfallthrough
คีย์เวิร์ดแทน โค้ดส่วนใหญ่ที่ฉันเขียนและอ่านมีการหยุดพักทุกกรณี
continue <case name>
ว่าอนุญาตให้ระบุอย่างชัดเจนด้วยประโยคกรณีใดที่จะดำเนินการต่อ
case
ภายในปัจจุบันswitch
สิ่งนี้จะกลายเป็นไฟล์goto
. ;-)
คุณสามารถทำสิ่งที่น่าสนใจได้ทุกประเภทโดยใช้ 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
ข้อความในตอนท้ายของคดีและทำให้เกิดพฤติกรรมที่ไม่คาดคิด คอมไพเลอร์ที่ดีจะเตือนคุณเมื่อคุณละเว้นคำสั่งแบ่ง
เหตุใดคอมไพเลอร์จึงไม่ใส่คำสั่งแบ่งโดยอัตโนมัติหลังจากบล็อกโค้ดแต่ละอันในสวิตช์
ทิ้งความปรารถนาดีที่จะสามารถใช้บล็อกที่เหมือนกันสำหรับหลาย ๆ กรณี (ซึ่งอาจเป็นกรณีพิเศษ) ...
ด้วยเหตุผลทางประวัติศาสตร์หรือไม่? เมื่อใดที่คุณต้องการให้โค้ดหลายบล็อกดำเนินการ
ส่วนใหญ่มีไว้สำหรับความเข้ากันได้กับ C และเป็นเนื้อหาที่น่าจะเป็นการแฮ็กโบราณตั้งแต่สมัยก่อนที่goto
คำหลักท่องไปทั่วโลก มันไม่ช่วยให้สิ่งที่น่าอัศจรรย์บางส่วนของหลักสูตรเช่นอุปกรณ์ของดัฟฟ์แต่ไม่ว่าจะเป็นจุดในการสนับสนุนหรือต่อต้านคือ ... โต้แย้งที่ดีที่สุด
break
หลังจากที่สวิทช์case
s ถูกนำมาใช้เพื่อหลีกเลี่ยงการ 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
ดังนั้นคุณไม่ต้องทำรหัสซ้ำหากคุณต้องการหลายกรณีในการทำสิ่งเดียวกัน:
case THIS:
case THAT:
{
code;
break;
}
หรือคุณสามารถทำสิ่งต่างๆเช่น:
case THIS:
{
do this;
}
case THAT:
{
do that;
}
ในรูปแบบน้ำตก
เกิดข้อผิดพลาด / ความสับสนจริงๆถ้าคุณถามฉัน
do this
และdo that
เพื่อสิ่งนี้ แต่เพียงdo that
เพื่อสิ่งนั้น?
เท่าที่บันทึกทางประวัติศาสตร์กล่าวไป 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 ที่ต้องใส่ใจกับรายละเอียดดังกล่าว :-)
break
ทำให้ "หลายโค้ดบล็อกดำเนินการได้" และเกี่ยวข้องกับแรงจูงใจของตัวเลือกการออกแบบนี้มากกว่า คนอื่น ๆ กล่าวถึงมรดกที่เป็นที่รู้จักกันดีตั้งแต่ C ถึง Java และคำตอบนี้ผลักดันการวิจัยให้ก้าวไปสู่ช่วงก่อน C ฉันหวังว่าเราจะจับคู่รูปแบบนี้ (แม้ว่าจะดั้งเดิมมาก) ตั้งแต่เริ่มต้น
Java มาจาก C ซึ่งเป็นมรดกทางวัฒนธรรมรวมถึงเทคนิคที่เรียกว่าอุปกรณ์ของดัฟฟ์ เป็นการเพิ่มประสิทธิภาพที่อาศัยข้อเท็จจริงที่ว่าการควบคุมผ่านจากกรณีหนึ่งไปยังอีกกรณีหนึ่งในกรณีที่ไม่มีbreak;
คำสั่ง เมื่อถึงเวลาที่ C เป็นมาตรฐานมีรหัสแบบนั้นมากมาย "อยู่ในป่า" และจะมีการต่อต้านการเปลี่ยนภาษาเพื่อทำลายโครงสร้างดังกล่าว
อย่างที่ผู้คนเคยกล่าวไว้ก่อนหน้านี้ว่ามันคือการปล่อยให้ผ่านไปและไม่ใช่เรื่องผิดมันเป็นคุณสมบัติ หาก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
อย่างที่คาดไว้.
การไม่มีตัวแบ่งอัตโนมัติที่เพิ่มโดยคอมไพลเลอร์ทำให้สามารถใช้สวิตช์ / เคสเพื่อทดสอบเงื่อนไขเช่น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;
}
เนื่องจากมีสถานการณ์ที่คุณต้องการไหลผ่านบล็อกแรกเช่นหลีกเลี่ยงการเขียนโค้ดเดียวกันในหลายบล็อก แต่ยังคงสามารถแบ่งออกเพื่อควบคุม mroe ได้ นอกจากนี้ยังมีเหตุผลอื่น ๆ อีกมากมาย
มันเป็นคำถามเก่า แต่จริงๆแล้วฉันพบว่าใช้เคสโดยไม่มีคำสั่งหยุดพักในวันนี้ การไม่ใช้ตัวแบ่งมีประโยชน์มากเมื่อคุณต้องการรวมฟังก์ชันต่างๆตามลำดับ
เช่นการใช้รหัสตอบกลับ 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
คุณสามารถแยกประเภทตัวเลขเดือนจำนวนอื่น ๆ ได้อย่างง่ายดาย
จะดีกว่าถ้าในกรณีนี้
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;
}
}
ตอนนี้ฉันกำลังทำโปรเจ็กต์ที่ฉันต้องการbreak
ในคำสั่ง switch มิฉะนั้นโค้ดจะไม่ทำงาน อดทนกับฉันและฉันจะให้ตัวอย่างที่ดีว่าทำไมคุณถึงต้องการbreak
ในคำสั่งสวิตช์ของคุณ
สมมติว่าคุณมีสามสถานะสถานะหนึ่งที่รอให้ผู้ใช้ป้อนตัวเลขสถานะที่สองเพื่อคำนวณและสถานะที่สามเพื่อพิมพ์ผลรวม
ในกรณีนี้คุณมี:
มองไปที่รัฐที่คุณต้องการคำสั่งของการบีบบังคับที่จะเริ่มต้นใน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 แต่ไม่น้อยที่สุด
ถูกต้องเพราะด้วยการจัดวางที่ชาญฉลาดบางอย่างคุณสามารถดำเนินการบล็อกในน้ำตกได้
break
และป้ายสวิทช์การปฏิรูปไม่อาณัติ