ควรตรวจสอบ `c> = '0'` หรือ` c> = 48` หรือไม่?


46

หลังจากการสนทนากับเพื่อนร่วมงานของฉันฉันได้รับคำถาม 'ปรัชญา' เกี่ยวกับวิธีจัดการกับชนิดข้อมูลถ่านใน Java ตามแนวทางปฏิบัติที่ดีที่สุด

สมมติว่าสถานการณ์ง่าย ๆ(เห็นได้ชัดว่านี่เป็นเพียงตัวอย่างง่ายๆเพื่อให้ความหมายในการฝึกฝนสำหรับคำถามของฉัน)โดยที่ String 's' เป็นอินพุตคุณต้องนับจำนวนอักขระที่เป็นตัวเลขในนั้น

นี่คือโซลูชันที่เป็นไปได้ 2 แบบ:

1)

    for(int i=0; i<s.length(); i++) {
        if(s.charAt(i) >= 48 && s.charAt(i) <= 57) {
            n++;
        }
    }

2)

    for(int i=0; i<s.length(); i++) {
        if(s.charAt(i) >= '0' && s.charAt(i) <= '9' ) {
            n++;
        }
    }

ข้อใดที่สอง 'สะอาด' และสอดคล้องกับแนวทางปฏิบัติที่ดีที่สุดของ Java?


141
ทำไมคุณถึงเขียน 48 และ 57 เมื่อคุณหมายถึง '0' และ '9' แค่เขียนสิ่งที่คุณหมายถึง
Brandin

9
รอสิ่งที่คุณทำ Java มีVK_ค่าคงที่ที่คุณควรใช้สองการใช้รหัส char จะดีกว่า char Java เป็นภาษาที่ปลอดภัยที่คุณไม่ควรทำการตรวจสอบข้ามประเภท @Brandin เรียกว่าการเขียนโค้ด
Martin Barker

12
โดยไม่ต้องสนใจที่จะทำมากกว่าตัดสินคนทั้ง 6 คนที่คิดว่านี่เป็นคำถามที่ดี คุณใช้ตัวอักษรเป็นตัวเลขหรือไม่? ถ้าเป็นเช่นนั้นใช้ตัวเลข คุณใช้มันเป็นตัวอักษร? ถ้าเป็นเช่นนั้นใช้ตัวอักษร
Alec Teal

17
@MartinBarker VK_*คงสอดคล้องกับคีย์ไม่ได้ตัวละคร
CodesInChaos

2
ฉันใช้เวลาสองสามนาทีเพื่อตรวจสอบว่ารหัสนี้เกี่ยวข้องกับคำถามของคุณอย่างไร แล้วมันไม่ชัดเจนเพราะถือว่าฉันรู้ใน (1) ที่ฉันรู้ว่านี่คือช่วงหลักของ ISO-Latin 1 ดังนั้นสิ่งนี้ทำให้เกิดปัญหาจากมุมมองการบำรุงรักษา
CyberSkull

คำตอบ:


124

ทั้งคู่เป็นคนที่น่ากลัว แต่คนแรกนั้นน่ากลัวกว่ากัน

ทั้งสองไม่สนใจความสามารถในตัวของ Java ในการตัดสินใจว่าอักขระใดเป็น "ตัวเลข" (ผ่านวิธีการในCharacter) แต่สิ่งแรกไม่เพียง แต่ละเว้นลักษณะ Unicode ของสายอักขระสมมติว่ามีเพียง 0123456789 มันยังปิดบังแม้เหตุผลที่ไม่ถูกต้องนี้โดยใช้รหัสอักขระที่เหมาะสมถ้าคุณรู้อะไรเกี่ยวกับประวัติของการเข้ารหัสอักขระ


33
ทำไมคุณสมมติว่าการไม่ปฏิเสธตัวเลขที่ไม่ใช่ ASCII นั้นผิด ขึ้นอยู่กับบริบท
CodesInChaos

21
@CodesInChaos หากคุณต้องการค้นหาตัวอักษรตัวเลขการสแกน 0123456789 นั้นผิดปกติ หากคุณต้องการสแกนหาเพียงสิบตัวอักษรเหล่านี้จริงๆแล้วพวกเขาจะไม่มีโทเค็นที่ไม่มีความหมายซึ่งดูเหมือนคุ้นเคยโดยเฉพาะกับคนที่รู้เพียง ASCII / ISO-Latin ไม่มีอะไรผิดปกติ - ฉันมักต้องทำอย่างนั้นเช่นการมีปฏิสัมพันธ์กับซอฟต์แวร์ดั้งเดิมที่ยอมรับเฉพาะอักขระสิบตัวเหล่านั้นเท่านั้น แต่คุณควรทำให้ความตั้งใจของคุณชัดเจนโดยใช้สิ่งที่ชอบmatches("[0-9]+")แทนที่จะใช้ประโยชน์จากเล่ห์เหลี่ยมช่วงประวัติศาสตร์
Kilian Foth

15
มีตัวเลขเต็มความกว้างซึ่งมีลักษณะเหมือนกับหลัก ASCII และโดยทั่วไปจะต้องใช้ซอฟต์แวร์จำนวนมากเพื่อยอมรับพวกเขาแทนตัวเลข ASCII (เห็นได้ชัดว่ามีซอฟต์แวร์จำนวนมากเสียขึ้นอยู่กับคำจำกัดความของ "มาก" คุณสามารถบอกได้อย่างง่ายดายเพราะผู้จำหน่ายซอฟต์แวร์ในประเทศหนึ่งพบว่าเป็นไปไม่ได้ที่จะขายให้กับประเทศอื่นเพราะผู้ขายไม่ปฏิบัติตามข้อกำหนดของประเทศอื่น )

37
I have a Japanese IME installed, and accidentally type in full - - all all...
BlueRaja - Danny Pflughoeft

14
"ทั้งคู่น่ากลัว" แต่คุณลืมพูดวิธีแก้ปัญหาที่ถูกต้อง ;-)
Kromster พูดว่าสนับสนุน Monica

163

ทั้ง ปล่อยให้คลาสตัวละครในตัวของ Java คิดออกมาเพื่อคุณ

for (int i = 0; i < s.length(); ++i) {
  if (Character.isDigit(s.charAt(i))) {
    ++n;
  }
}

มีช่วงอักขระไม่กี่ตัวมากกว่าตัวเลข ASCII ที่นับเป็นตัวเลขและไม่มีตัวอย่างที่คุณโพสต์ไว้ JavaDocสำหรับCharacter.isDigit()รายการเหล่านี้ช่วงตัวอักษรเป็นตัวเลขที่ถูกต้อง:

ช่วงอักขระ Unicode บางตัวที่มีตัวเลข:

  • '\ u0030' ถึง '\ u0039', ตัวเลข ISO-LATIN-1 ('0' ถึง '9')
  • '\ u0660' ถึง '\ u0669', ตัวเลขอารบิคภาษาอาหรับ
  • '\ u06F0' ถึง '\ u06F9', ตัวเลขอารบิคภาษาอาหรับแบบขยาย
  • '\ u0966' ถึง '\ u096F', ตัวเลข Devanagari
  • '\ uFF10' ถึง '\ uFF19', ตัวเลขเต็มความกว้าง

ช่วงอักขระอื่น ๆ จำนวนมากมีตัวเลขเช่นกัน

ที่ถูกกล่าวว่าหนึ่งควรมอบให้Character.isDigit()กับรายการนี้ เนื่องจากมีการเติมข้อมูล Unicode ใหม่รหัส Java จะถูกปรับปรุง การอัพเกรด JVM สามารถทำให้โค้ดเก่าทำงานด้วยอักขระตัวเลขใหม่ได้อย่างราบรื่น นอกจากนี้ยังเป็นDRY : โดยการแปลรหัส "นี่คือตัวเลข" ให้เป็นสถานที่เดียวที่อ้างอิงถึงที่อื่นจะสามารถหลีกเลี่ยงแง่ลบของการทำสำเนารหัส (เช่นข้อบกพร่อง) ได้ สุดท้ายให้สังเกตบรรทัดสุดท้าย: รายการนี้ไม่ครบถ้วนสมบูรณ์และมีตัวเลขอื่น ๆ

โดยส่วนตัวแล้วฉันอยากจะมอบหมายให้ห้องสมุดหลักของจาวาและใช้เวลาในการทำงานที่มีประสิทธิผลมากกว่าการหาว่าตัวเลขคืออะไร


ข้อยกเว้นสำหรับกฎนี้คือถ้าคุณจำเป็นต้องทดสอบหลัก ASCII ตามตัวอักษรจริงๆไม่ใช่ตัวเลขอื่น ๆ ตัวอย่างเช่นถ้าคุณกำลังแยกกระแสและเพียงตัวเลข ASCII (เมื่อเทียบกับตัวเลขอื่น ๆ ) มีความหมายพิเศษแล้วมันจะไม่ได้Character.isDigit()มีความเหมาะสมกับการใช้งาน

ในกรณีนั้นฉันจะเขียนวิธีอื่นเช่นMyClass.isAsciiDigit()และวางตรรกะไว้ที่นั่น คุณจะได้รับประโยชน์เหมือนกันกับการนำโค้ดกลับมาใช้ใหม่ชื่อมีความชัดเจนมากเหมือนกับสิ่งที่กำลังตรวจสอบและตรรกะนั้นถูกต้อง


4
คำตอบที่ดีสำหรับการให้รหัสที่สะอาดซึ่งเป็นการหลอกลวง
Pierre Arlaud

27

ถ้าคุณเคยเขียนใบสมัครใน C ที่ใช้ EBCDIC เป็นชุดตัวอักษรพื้นฐานและความต้องการที่จะอักขระ ASCII กระบวนการแล้วใช้และ48 57คุณกำลังทำอย่างนั้น? ฉันไม่คิดอย่างนั้น

เกี่ยวกับการใช้isDigit(): มันขึ้นอยู่กับ คุณกำลังเขียนโปรแกรมวิเคราะห์คำ JSON หรือไม่ เท่านั้น0ที่จะ9ได้รับการยอมรับเป็นหลักจึงไม่ใช้isDigit()ตรวจสอบและ>= '0' <= '9'คุณกำลังประมวลผลอินพุตของผู้ใช้หรือไม่ ใช้isDigit()ตราบเท่าที่ส่วนที่เหลือของรหัสของคุณสามารถจัดการกับสตริงและเปลี่ยนเป็นตัวเลขได้อย่างถูกต้อง


3
ที่จริงคุณสามารถเขียนแอปพลิเคชันใน Java ซึ่งรับและส่งคืน EBCDIC นี่ไม่ใช่เรื่องสนุก
Thorbjørn Ravn Andersen

ที่คล้ายกัน 'ไม่สนุก' กำลังจะผ่านรหัสที่ถูกเขียนโดยใช้ค่าทศนิยมของตัวละคร EBCDIC เมื่อมีการแปลงไปยังสภาพแวดล้อมข้ามแพลตฟอร์ม ...
กวินอีแวนส์

1
หากคุณกำลังประมวลผลข้อมูล EBCDIC ใน Java คุณควรแปลงเป็นชุดอักขระ UTF-16 ของ Java ดั้งเดิมก่อนประมวลผลเป็นอักขระ แต่ฉันคิดว่ามันขึ้นอยู่กับแอปพลิเคชันจริงๆ หวังว่าหากโปรแกรมของคุณต้องจัดการกับ EBCDIC คุณจะเข้าใจว่าต้องทำอะไรบ้าง
Michael Burr

1
ประเด็นหลักคือการประมวลผล EBCDIC ใน Java ทั้ง '0' และ 48 ผิดในการตรวจสอบศูนย์หลัก มีความเป็นปัจจุบันมากกว่าใน C, C ++ ฯลฯ '\ n' และ '\ r' ได้ถูกกำหนดไว้ดังนั้นหากคุณต้องการตรวจจับคู่ Windows CR / LF ในไฟล์โดยใช้คอมไพเลอร์ที่ไม่ใช่หน้าต่างให้ตรวจสอบค่าทศนิยมแทน กำลังตรวจหา '\ n' และ '\ r'
gnasher729

12

ตัวอย่างที่สองดีกว่าอย่างเห็นได้ชัด ความหมายของตัวอย่างที่สองนั้นชัดเจนทันทีเมื่อคุณดูโค้ด ความหมายของตัวอย่างแรกนั้นชัดเจนหากคุณจดจำตาราง ASCII ทั้งหมดในหัวของคุณ

คุณควรแยกแยะระหว่างการตรวจสอบอักขระเฉพาะหรือตรวจสอบช่วงหรือคลาสของอักขระ

1) ตรวจสอบอักขระเฉพาะ

สำหรับตัวอักษรธรรมดาให้ใช้ตัวอักษรตามตัวอักษรเช่นif(ch=='z').... if (ch=='\n')...หากคุณตรวจสอบกับตัวอักษรพิเศษเช่นแท็บหรือแบ่งบรรทัดคุณควรใช้หนีเช่น หากตัวละครที่คุณกำลังตรวจสอบนั้นผิดปกติ (เช่นไม่สามารถจดจำได้ทันทีหรือไม่สามารถใช้กับแป้นพิมพ์มาตรฐานได้) คุณอาจใช้รหัสตัวอักษรฐานสิบหกแทนตัวอักษร แต่เนื่องจากรหัสเลขฐานสิบหกเป็น "ค่าเวทย์มนตร์" คุณจะแยกมันเป็นค่าคงที่และบันทึก:

const char snowman = 0x2603; // snowman char used to detect encoding issues
...
if (ch==showman)...

Hex codes เป็นวิธีมาตรฐานในการระบุรหัสอักขระ

2) ตรวจสอบคลาสอักขระหรือช่วง

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

หากคุณกังวลเกี่ยวกับตัวละครในช่วง ASCII เท่านั้นคุณสามารถใช้ตัวอักษรในไลบรารีนี้มิฉะนั้นคุณอาจใช้ตัวอักษรฐานสิบหก หากคุณดูซอร์สโค้ดสำหรับไลบรารีอักขระ Java builtin มันจะอ้างถึงค่าอักขระและช่วงโดยใช้เลขฐานสิบหกเนื่องจากเป็นวิธีที่ระบุไว้ในมาตรฐาน Unicode


1
ฉันขอแนะนำให้เขียนอักขระตามตัวอักษรในฐานสิบหกโดยใช้'\x2603'แทนเพื่อให้ชัดเจนว่าคุณกำลังทดสอบค่าสำหรับอักขระที่มีการเข้ารหัสเลขฐานสิบหกไม่ใช่เฉพาะตัวเลขสุ่มใด ๆ
wefwefa3

-4

มันจะดีกว่าที่จะใช้c >= '0'เพราะc >= 48คุณจำเป็นต้องแปลง c ในรหัส ascii


3
คำตอบนี้ระบุว่าอะไรที่ไม่ได้กล่าวไว้ในคำตอบก่อนหน้านี้เมื่อสัปดาห์ที่แล้ว?

-5

นิพจน์ทั่วไป ( RegEx s) มีคลาสอักขระเฉพาะสำหรับตัวเลข\d- ซึ่งสามารถใช้เพื่อลบอักขระอื่น ๆ ออกจากสตริงของคุณ ความยาวของสตริงผลลัพธ์เป็นค่าที่ต้องการ

public static int countDigits(String str) {
    str = Objects.requireNonNull(str).trim();

    return str.replaceAll("[^\\d]", "").length();
}

แจ้งให้ทราบล่วงหน้า แต่ที่RegEx s มีการคำนวณเรียกร้องมากขึ้นกว่าโซลูชั่นที่นำเสนออื่น ๆ ดังนั้นพวกเขาไม่ควรได้รับการแนะนำโดยทั่วไป


วิธีที่สง่างามมากที่จะทำการตรวจสอบ!
Kevin Robatel

Regexes overkill สำหรับงานเช่นนี้
Pharap

2
@StefanoBragaglia หลังจากอ่านคำตอบของคุณอีกครั้งฉันคิดว่ามันไม่ได้ตอบคำถามจริงๆ
Pharap

2
คำตอบของคุณให้วิธีที่แตกต่างในการแก้ปัญหา "ฉันจะนับตัวเลขในสตริงได้อย่างไร" ไม่ตอบปัญหาพื้นฐานเกี่ยวกับตัวอย่างโค้ดและการแสดงค่าคงที่ - ไม่ว่าจะเป็นตัวเลขหรือตัวอักษร

2
สิ่งนี้ไม่ได้นับจำนวนหลัก (มันแค่บอกคุณว่าความยาวของสายคืออะไรหลังจากที่คุณลบตัวเลขทั้งหมดซึ่งไม่ได้อยู่ที่นี่หรือที่นั่น) แต่ฉันยอมรับว่ามันไม่ได้ตอบคำถามจริงๆ ตัวอย่างเช่นไม่มีใครถามเกี่ยวกับการลบอักขระออกจากสตริง คำถามนั้นถามถึงวิธีปฏิบัติที่เหมาะสมในการตรวจสอบว่าเป็นตัวเลขหรือไม่
doppelgreener
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.