Apakah lebih baik untuk memeriksa `c> = '0'` atau` c> = 48`?

46

Setelah berdiskusi dengan beberapa kolega saya, saya memiliki pertanyaan 'filosofis' tentang bagaimana memperlakukan tipe data char di Jawa, mengikuti praktik terbaik.

Misalkan skenario sederhana (jelas ini hanya contoh yang sangat sederhana untuk memberikan makna praktik pada pertanyaan saya) di mana, dengan memberikan String 's sebagai input, Anda harus menghitung jumlah karakter numerik yang ada di dalamnya.

Ini adalah 2 solusi yang mungkin:

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

Manakah dari keduanya yang lebih 'bersih' dan sesuai dengan praktik terbaik Java?

wyr0
sumber
141
Mengapa Anda menulis 48 dan 57 ketika Anda benar-benar berarti '0' dan '9'? Tulis saja yang Anda maksud.
Brandin
9
Tunggu apa yang Anda lakukan, Java memiliki VK_konstanta yang seharusnya Anda gunakan, kedua menggunakan kode char lebih baik daripada char Java adalah jenis bahasa yang aman Anda tidak seharusnya melakukan pemeriksaan cross-type. @Brandin Ini disebut praktik pengkodean
Martin Barker
12
Tanpa repot-repot melakukan lebih dari menilai 6 orang SIAPA YANG PIKIR INI ADALAH PERTANYAAN YANG BAIK. Apakah Anda menggunakan karakter sebagai angka? Jika demikian gunakan angka. Apakah Anda menggunakannya sebagai surat? Jika demikian gunakan huruf.
Alec Teal
17
@ MartinBarker VK_*Konstanta berhubungan dengan kunci bukan karakter .
CodesInChaos
2
Butuh beberapa menit untuk menentukan apa yang dilakukan kode ini terkait dengan pertanyaan Anda. Sudah tidak jelas karena menganggap saya tahu di (1) bahwa saya tahu ini adalah rentang angka ISO-Latin 1. Jadi ini membuatnya bermasalah dari sudut pandang pemeliharaan.
CyberSkull

Jawaban:

124

Keduanya mengerikan, tetapi yang pertama lebih mengerikan.

Keduanya mengabaikan kemampuan bawaan Java untuk memutuskan karakter apa yang "numerik" (melalui metode dalam Character). Tetapi yang pertama tidak hanya mengabaikan sifat Unicode dari string, dengan asumsi bahwa hanya ada 0123456789, itu juga mengaburkan alasan yang tidak valid ini dengan menggunakan kode karakter yang masuk akal hanya jika Anda tahu sesuatu tentang sejarah pengkodean karakter.

Kilian Foth
sumber
33
Mengapa Anda berasumsi bahwa angka non-ASCII yang tidak ditolak itu salah? Itu tergantung pada konteksnya.
CodesInChaos
21
@CodesInChaos Jika Anda benar-benar ingin menemukan karakter numerik , pemindaian untuk 0123456789 jelas salah. Jika Anda benar-benar ingin memindai hanya sepuluh karakter ini, maka mereka pada dasarnya token tidak berarti yang hanya secara tidak sengaja terlihat akrab bagi orang-orang yang hanya tahu ASCII / ISO-Latin. Tidak ada yang salah dengan itu - saya sering harus melakukan itu, misalnya untuk berinteraksi dengan perangkat lunak lama yang benar-benar hanya menerima sepuluh karakter tersebut. Tetapi kemudian Anda harus memperjelas niat Anda dengan menggunakan sesuatu seperti matches("[0-9]+"), daripada mengeksploitasi trik rentang yang termotivasi secara historis.
Kilian Foth
15
Ada digit lebar penuh , yang terlihat seperti sama dengan digit ASCII, dan secara umum banyak perangkat lunak diharuskan untuk menerimanya sebagai pengganti digit ASCII. (Jelas banyak perangkat lunak yang rusak, tergantung pada definisi "banyak". Anda dapat dengan mudah memberi tahu karena vendor perangkat lunak di satu negara merasa tidak mungkin untuk menjual ke negara lain karena vendor tidak menghormati persyaratan negara lain. )
rwong
37
Installed have a Japanese IME installed , and accidentally type in full - width all all the time.
BlueRaja - Danny Pflughoeft
14
"Keduanya mengerikan", tetapi Anda lupa mengatakan solusi yang tepat ;-)
Kromster mengatakan mendukung Monica
163

Tidak juga. Biarkan kelas karakter bawaan Java mengetahuinya untuk Anda.

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

Ada beberapa rentang karakter lebih banyak daripada digit ASCII yang dihitung sebagai digit, dan tidak ada contoh yang Anda poskan yang akan menghitungnya. The javadoc untuk Character.isDigit()daftar rentang karakter ini sebagai angka yang valid:

Beberapa rentang karakter Unicode yang berisi angka:

  • '\ u0030' hingga '\ u0039', ISO-LATIN-1 digit ('0' hingga '9')
  • '\ u0660' hingga '\ u0669', digit Arab-Indic
  • '\ u06F0' hingga '\ u06F9', Diperpanjang digit Indikator Arab
  • '\ u0966' hingga '\ u096F', digit Devanagari
  • '\ uFF10' hingga '\ uFF19', digit bandwidth penuh

Banyak rentang karakter lainnya juga mengandung angka.

Yang sedang berkata, seseorang harus mendelegasikan Character.isDigit()bahkan dengan daftar ini. Saat pesawat Unicode baru terisi, kode Java akan diperbarui. Memutakhirkan JVM dapat membuat kode lama berfungsi dengan karakter digit baru dengan mulus. Ini juga KERING : dengan melokalkan kode "apakah ini angka" ke satu tempat yang dirujuk di tempat lain, aspek negatif dari duplikasi kode (yaitu bug) dapat dihindari. Akhirnya, perhatikan baris terakhir: daftar ini tidak lengkap, dan ada angka lainnya.

Secara pribadi, saya lebih suka mendelegasikan ke perpustakaan Java inti dan menghabiskan waktu saya untuk tugas-tugas yang lebih produktif daripada "mencari apa yang digit."


Satu-satunya pengecualian untuk aturan ini adalah jika Anda benar-benar perlu menguji digit ASCII literal dan bukan digit lainnya. Misalnya, jika Anda menguraikan aliran dan hanya digit ASCII (yang bertentangan dengan digit lainnya) yang memiliki makna khusus, maka itu tidak akan sesuai untuk digunakan Character.isDigit().

Dalam hal ini, saya akan menulis metode lain, misalnya MyClass.isAsciiDigit()dan memasukkan logika di sana. Anda mendapatkan manfaat yang sama dari penggunaan kembali kode, namanya sangat jelas untuk memeriksa, dan logikanya benar.


sumber
4
Jawaban yang bagus untuk benar-benar memberikan kode bersih yang berfungsi.
Pierre Arlaud
27

Jika Anda pernah menulis aplikasi dalam C yang menggunakan EBCDIC sebagai set karakter dasar dan perlu memproses karakter ASCII maka gunakan 48dan 57. Apakah kamu melakukan itu? Saya kira tidak.

Tentang menggunakan isDigit(): itu tergantung. Apakah Anda menulis parser JSON? Hanya 0untuk 9diterima sebagai angka, jadi jangan gunakan isDigit(), periksa >= '0'dan <= '9'. Apakah Anda memproses input pengguna? Gunakan isDigit()selama sisa kode Anda benar-benar dapat menangani string dan mengubahnya menjadi angka dengan benar.

gnasher729
sumber
3
Sebenarnya Anda bisa menulis aplikasi dalam Java yang mendapat dan mengembalikan EBCDIC. Ini tidak menyenangkan.
Thorbjørn Ravn Andersen
Serupa 'tidak menyenangkan' sedang melalui kode yang ditulis menggunakan nilai desimal dari karakter EBCDIC ketika mengubahnya menjadi lingkungan lintas-platform ...
Gwyn Evans
1
Jika Anda sedang memproses data EBCDIC di Jawa maka Anda mungkin harus mengonversinya ke karakter asli Java UTF-16 sebelum memprosesnya sebagai karakter. Tapi saya kira itu sangat tergantung pada aplikasi; mudah-mudahan jika program Anda harus berurusan dengan EBCDIC, maka Anda akan mengerti apa yang perlu dilakukan.
Michael Burr
1
Poin utama adalah bahwa untuk memproses EBCDIC di Jawa baik '0' dan 48 salah untuk mendeteksi angka nol. Lebih terkini, di C, C ++ dll. '\ N' dan '\ r' adalah implementasi yang ditentukan jadi jika Anda ingin mendeteksi pasangan Windows CR / LF dalam file menggunakan kompiler non-windows, lebih baik periksa nilai desimal daripada memeriksa '\ n' dan '\ r'.
gnasher729
12

Contoh kedua jelas lebih unggul. Arti dari contoh kedua langsung jelas ketika Anda melihat kode. Arti dari contoh pertama hanya jelas jika Anda telah menghafal seluruh tabel ASCII di kepala Anda.

Anda harus membedakan antara memeriksa karakter tertentu, atau memeriksa rentang atau kelas karakter.

1) Memeriksa karakter tertentu.

Untuk karakter biasa, gunakan karakter literal, mis if(ch=='z').... , . Jika Anda mengecek karakter khusus seperti tab atau break baris, Anda harus menggunakan lolos, seperti if (ch=='\n').... Jika karakter yang Anda periksa tidak biasa (mis. Tidak segera dikenali atau tidak tersedia pada keyboard standar), Anda mungkin menggunakan kode karakter hex daripada karakter literal. Tetapi karena kode hex adalah "nilai ajaib", Anda akan mengekstraknya ke sebuah konstanta dan mendokumentasikannya:

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

Kode hex adalah cara standar untuk menentukan kode karakter.

2) Memeriksa kelas atau rentang karakter

Anda benar-benar tidak boleh melakukan ini secara langsung dalam kode aplikasi, tetapi harus merangkumnya dalam kelas terpisah yang hanya berkaitan dengan klasifikasi karakter. Dan Anda harus bervariasi dari ini, karena perpustakaan sudah ada untuk tujuan ini, dan klasifikasi karakter biasanya lebih kompleks daripada yang Anda pikirkan, setidaknya jika Anda mempertimbangkan karakter di luar rentang ASCII.

Jika Anda hanya peduli tentang karakter dalam rentang ASCII, Anda bisa menggunakan literal karakter di perpustakaan ini, jika tidak, Anda mungkin akan menggunakan hex-literal. Jika Anda melihat kode sumber untuk pustaka karakter Java builtin, itu juga merujuk ke nilai karakter dan rentang menggunakan heksadesimal, karena ini adalah bagaimana mereka ditentukan dalam standar Unicode.

JacquesB
sumber
1
Saya juga merekomendasikan untuk menulis karakter literal dalam hex menggunakan '\x2603'bukannya secara eksplisit bahwa Anda menguji nilai untuk karakter dengan pengkodean heksadesimal dan bukan sembarang nomor acak.
wefwefa3
-4

Itu selalu lebih baik untuk digunakan c >= '0'karena untuk c >= 48Anda perlu mengkonversi c dalam kode ascii.

Prem Patel
sumber
3
Apa yang dinyatakan oleh jawaban ini yang belum dikatakan dalam jawaban sebelumnya dari seminggu yang lalu?
-5

Ekspresi Reguler ( RegEx s) memiliki kelas karakter khusus untuk digit - \d- yang dapat digunakan untuk menghapus karakter lain dari string Anda. Panjang string yang dihasilkan adalah nilai yang diinginkan.

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

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

Perhatikan, bagaimanapun, bahwa RegEx s secara komputasi lebih menuntut daripada solusi yang diusulkan lainnya karena itu mereka seharusnya tidak disukai secara umum .

Stefano Bragaglia
sumber
Cara yang sangat elegan untuk melakukan pemeriksaan!
Kevin Robatel
Regex berlebihan untuk tugas seperti ini
Pharap
2
@StefanoBragaglia Setelah membaca kembali jawaban Anda, saya pikir itu tidak benar-benar menjawab pertanyaan.
Pharap
2
Jawaban Anda memberikan cara berbeda untuk menyelesaikan masalah "bagaimana cara saya menghitung angka dalam sebuah string". Itu tidak menjawab masalah mendasar dengan sampel kode dan representasi konstanta - baik sebagai angka atau karakter.
2
Ini tidak benar-benar menghitung digit (itu hanya memberi tahu Anda berapa panjang string setelah Anda menghapus semua digit, yang tidak ada di sini atau di sana), tapi saya setuju itu tidak benar-benar menjawab pertanyaan. Seperti, misalnya, tidak ada yang bertanya tentang menghapus karakter dari string. Pertanyaannya hanya bertanya tentang cara praktik terbaik yang sesuai untuk memeriksa apakah numerik karakter.
doppelgreener