Tốt hơn là kiểm tra `c> = '0'` hoặc` c> = 48`?


46

Sau một cuộc thảo luận với một số đồng nghiệp của tôi, tôi đã có một câu hỏi "triết học" về cách xử lý kiểu dữ liệu char trong Java, tuân theo các thực tiễn tốt nhất.

Giả sử một kịch bản đơn giản (rõ ràng đây chỉ là một ví dụ rất đơn giản để đưa ra ý nghĩa thực tiễn cho câu hỏi của tôi) trong đó, với một chuỗi 'là' đầu vào, bạn phải đếm số lượng ký tự số có trong nó.

Đây là 2 giải pháp khả thi:

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

Cái nào trong hai cái này 'sạch' hơn và tuân thủ các thực tiễn tốt nhất của Java?


141
Tại sao bạn lại viết 48 và 57 khi bạn thực sự có nghĩa là '0' và '9'? Chỉ cần viết những gì bạn có ý nghĩa.
Brandin

9
Đợi bạn đang làm gì, Java có các VK_hằng số mà bạn phải sử dụng, thứ hai sử dụng mã char tốt hơn char Java là ngôn ngữ an toàn mà bạn không cần phải kiểm tra kiểu chéo. @Brandin Nó được gọi là thực hành mã hóa
Martin Barker

12
Không thèm làm gì hơn là phán xét 6 người AI NGHE ĐÂY LÀ CÂU HỎI TỐT. Bạn đang sử dụng ký tự như số? Nếu vậy sử dụng số. Bạn đang sử dụng nó như là chữ cái? Nếu vậy sử dụng chữ cái.
Alec Teal

17
@MartinBarker Các VK_*hằng số tương ứng với các khóa không phải là ký tự .
CodeInChaos

2
Tôi mất vài phút để xác định mã này làm gì liên quan đến câu hỏi của bạn. Đã không rõ ràng bởi vì nó giả sử tôi biết trong (1) rằng tôi biết đây là phạm vi chữ số của ISO-Latin 1. Vì vậy, điều này làm cho nó có vấn đề từ quan điểm bảo trì.
CyberSkull

Câu trả lời:


124

Cả hai đều kinh khủng, nhưng lần đầu còn kinh khủng hơn.

Cả hai đều bỏ qua khả năng tích hợp sẵn của Java để quyết định các ký tự là "số" (thông qua các phương thức trong Character). Nhưng cái đầu tiên không chỉ bỏ qua bản chất Unicode của chuỗi, giả sử rằng chỉ có thể có 0123456789, nó còn che khuất ngay cả lý do không hợp lệ này bằng cách sử dụng mã ký tự chỉ có ý nghĩa nếu bạn biết điều gì đó về lịch sử mã hóa ký tự.


33
Tại sao bạn cho rằng việc không từ chối các chữ số không phải ASCII là sai? Điều đó phụ thuộc vào bối cảnh.
CodeInChaos

21
@CodesInChaos Nếu bạn thực sự muốn tìm các ký tự số , việc quét tìm 0123456789 hoàn toàn sai. Nếu bạn thực sự chỉ muốn quét mười ký tự này, thì về cơ bản chúng là các mã thông báo vô nghĩa, thứ chỉ vô tình trông quen thuộc đối với những người chỉ biết ASCII / ISO-Latin. Không có gì sai với điều đó - tôi thường phải làm chính xác điều đó, ví dụ như để tương tác với phần mềm cũ mà thực sự chỉ chấp nhận mười ký tự đó. Nhưng sau đó, bạn nên làm rõ ý định của mình bằng cách sử dụng một cái gì đó như matches("[0-9]+"), thay vì khai thác các thủ thuật phạm vi có động cơ lịch sử.
Kilian Foth

15
Có các chữ số có chiều rộng đầy đủ , trông giống như các chữ số ASCII và nói chung, rất nhiều phần mềm được yêu cầu chấp nhận chúng thay cho các chữ số ASCII. (Rõ ràng rất nhiều phần mềm bị hỏng, tùy thuộc vào định nghĩa của "rất nhiều". Bạn có thể dễ dàng biết vì các nhà cung cấp phần mềm ở một quốc gia không thể bán cho quốc gia khác vì các nhà cung cấp không tôn trọng các yêu cầu của các quốc gia khác. )
rwong

37
Have a Japanese IME installed , and accidentally type in full - width all the time.
BlueRaja - Daniel Pflughoeft

14
"Cả hai đều kinh khủng", nhưng bạn đã quên nói giải pháp phù hợp ;-)
Kromster nói hỗ trợ Monica

163

Cũng không. Hãy để lớp Nhân vật tích hợp của Java tìm ra nó cho bạn.

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

Có một vài phạm vi ký tự nhiều hơn các chữ số ASCII được tính là chữ số và không có ví dụ nào bạn đăng sẽ tính chúng. Các javadoc cho Character.isDigit()danh sách các phạm vi này nhân vật như là con số hợp lệ:

Một số phạm vi ký tự Unicode có chứa các chữ số:

  • '\ u0030' đến '\ u0039', ISO-LATIN-1 chữ số ('0' đến '9')
  • '\ u0660' đến '\ u0669', chữ số Ả Rập
  • '\ u06F0' đến '\ u06F9', chữ số Ả Rập mở rộng
  • '\ u0966' đến '\ u096F', chữ số Devanagari
  • '\ uFF10' đến '\ uFF19', Chữ số toàn băng thông

Nhiều phạm vi ký tự khác cũng chứa các chữ số.

Điều đó đang được nói, người ta nên ủy thác Character.isDigit()ngay cả với danh sách này. Khi các mặt phẳng Unicode mới được điền, mã Java sẽ được cập nhật. Nâng cấp JVM có thể làm cho mã cũ hoạt động với các ký tự chữ số mới một cách liền mạch. Nó cũng là DRY : bằng cách bản địa hóa mã "đây là một chữ số" đến một nơi được tham chiếu ở nơi khác, các khía cạnh tiêu cực của sao chép mã (tức là lỗi) có thể tránh được. Cuối cùng, lưu ý dòng cuối cùng: danh sách này không đầy đủ, và có các chữ số khác.

Cá nhân, tôi thà ủy thác cho các thư viện Java cốt lõi và dành thời gian của mình cho các nhiệm vụ hiệu quả hơn là "tìm ra chữ số là gì".


Ngoại lệ duy nhất cho quy tắc này là nếu bạn thực sự cần phải kiểm tra các chữ số ASCII bằng chữ và không phải các chữ số khác. Ví dụ: nếu bạn đang phân tích cú pháp một luồng và chỉ các chữ số ASCII (trái ngược với các chữ số khác) có ý nghĩa đặc biệt, thì nó sẽ không phù hợp để sử dụng Character.isDigit().

Trong trường hợp đó, tôi sẽ viết một phương thức khác, ví dụ MyClass.isAsciiDigit()và đặt logic vào đó. Bạn nhận được những lợi ích tương tự của việc sử dụng lại mã, tên cực kỳ rõ ràng với những gì nó đang kiểm tra và logic là chính xác.


4
Câu trả lời tuyệt vời cho việc thực sự cung cấp mã sạch thực hiện thủ thuật.
Pierre Arlaud

27

Nếu bạn từng viết một ứng dụng bằng C sử dụng EBCDIC làm bộ ký tự cơ bản và cần xử lý các ký tự ASCII thì hãy sử dụng 4857. Bạn đang làm điều đó? Tôi không nghĩ vậy.

Về việc sử dụng isDigit(): nó phụ thuộc. Bạn đang viết một trình phân tích cú pháp JSON? Chỉ 0để 9được chấp nhận như là chữ số, do đó, không sử dụng isDigit(), kiểm tra >= '0'<= '9'. Bạn đang xử lý đầu vào của người dùng? Sử dụng isDigit()miễn là phần còn lại của mã của bạn thực sự có thể xử lý chuỗi và biến nó thành một số chính xác.


3
Trên thực tế, bạn có thể viết các ứng dụng bằng Java, nhận và trả về EBCDIC. Đây không phải là niềm vui.
Thorbjørn Ravn Andersen

Tương tự 'không vui' đã trải qua mã được viết bằng các giá trị thập phân của các ký tự EBCDIC khi chuyển đổi nó sang môi trường đa nền tảng ...
Gwyn Evans

1
Nếu bạn đang xử lý dữ liệu EBCDIC trong Java thì có lẽ bạn nên chuyển đổi nó thành bộ ký tự UTF-16 gốc Java trước khi xử lý nó dưới dạng ký tự. Nhưng tôi đoán điều đó thực sự phụ thuộc vào ứng dụng; hy vọng nếu chương trình của bạn phải đối phó với EBCDIC, thì bạn sẽ hiểu những gì cần phải làm.
Michael Burr

1
Điểm chính là để xử lý EBCDIC trong Java cả '0' và 48 đều sai khi phát hiện một chữ số 0. Hiện tại, trong C, C ++, v.v. kiểm tra '\ n' và '\ r'.
gnasher729

12

Ví dụ thứ hai rõ ràng là vượt trội. Ý nghĩa của ví dụ thứ hai là rõ ràng ngay lập tức khi bạn nhìn vào mã. Ý nghĩa của ví dụ đầu tiên chỉ rõ ràng nếu bạn đã ghi nhớ toàn bộ bảng ASCII trong đầu.

Bạn nên phân biệt giữa việc kiểm tra một ký tự cụ thể hoặc kiểm tra một phạm vi hoặc lớp ký tự.

1) Kiểm tra một nhân vật cụ thể.

Đối với các ký tự bình thường, sử dụng ký tự theo nghĩa đen, vd if(ch=='z').... Nếu bạn kiểm tra các ký tự đặc biệt như tab hoặc ngắt dòng, bạn nên sử dụng các lối thoát, như thế nào if (ch=='\n').... Nếu ký tự bạn đang kiểm tra không bình thường (ví dụ: không thể nhận ra ngay lập tức hoặc không có sẵn trên bàn phím tiêu chuẩn), bạn có thể sử dụng mã ký tự hex thay vì ký tự bằng chữ. Nhưng vì mã hex là "giá trị ma thuật", bạn sẽ trích xuất nó thành hằng số và ghi lại nó:

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

Mã hex là cách tiêu chuẩn để xác định mã ký tự.

2) Kiểm tra một lớp nhân vật hoặc phạm vi

Bạn thực sự không nên làm điều này trực tiếp trong mã ứng dụng, nhưng nên gói gọn nó trong một lớp riêng biệt chỉ liên quan đến phân loại ký tự. Và bạn nên thay đổi điều này, vì các thư viện đã tồn tại cho mục đích này và việc phân loại ký tự thường phức tạp hơn bạn nghĩ, ít nhất là nếu bạn xem xét các ký tự nằm ngoài phạm vi ASCII.

Nếu bạn chỉ quan tâm đến các ký tự trong phạm vi ASCII, bạn có thể sử dụng các ký tự trong thư viện này, nếu không bạn có thể sử dụng các ký tự hex. Nếu bạn xem mã nguồn của thư viện ký tự dựng sẵn Java, thì nó cũng đề cập đến các giá trị và phạm vi ký tự bằng cách sử dụng thập lục phân, vì đây là cách chúng được chỉ định trong tiêu chuẩn Unicode.


1
Tôi cũng khuyên bạn nên viết ký tự bằng chữ hex bằng cách sử dụng '\x2603'thay vì rõ ràng rằng bạn đang kiểm tra giá trị cho một ký tự bằng mã hóa thập lục phân chứ không chỉ bất kỳ số ngẫu nhiên nào.
wefwefa3

-4

Luôn luôn tốt hơn để sử dụng c >= '0'vì đối với c >= 48bạn cần chuyển đổi c trong mã ascii.


3
Câu trả lời này nói gì mà chưa được nói trong các câu trả lời trước đó từ một tuần trước?

-5

Biểu thức chính quy ( RegEx ) có một lớp ký tự cụ thể cho các chữ số - \d- có thể được sử dụng để xóa bất kỳ ký tự nào khác khỏi chuỗi của bạn. Độ dài của chuỗi kết quả là giá trị mong muốn.

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

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

Tuy nhiên, lưu ý rằng các RegEx đòi hỏi tính toán cao hơn các giải pháp được đề xuất khác do đó chúng không nên được ưa thích nói chung .


Cách rất thanh lịch để làm kiểm tra!
Kevin Robatel

Regexes quá mức cần thiết cho một nhiệm vụ như thế này
Pharap

2
@StefanoBragaglia Sau khi đọc lại câu trả lời của bạn, tôi nghĩ rằng nó không thực sự trả lời câu hỏi.
Pharap

2
Câu trả lời của bạn cung cấp một cách khác để giải quyết vấn đề "làm thế nào để tôi đếm các chữ số trong một chuỗi". Nó không trả lời vấn đề cơ bản với các mẫu mã và biểu diễn các hằng số - dưới dạng số hoặc ký tự.

2
Điều này không thực sự đếm các chữ số (nó chỉ cho bạn biết độ dài của chuỗi là gì sau khi bạn xóa tất cả các chữ số, không có ở đây cũng không có), nhưng tôi đồng ý rằng nó không thực sự trả lời câu hỏi. Ví dụ như, không ai hỏi về việc xóa các ký tự khỏi chuỗi. Câu hỏi chỉ hỏi về cách thực hành tốt nhất phù hợp để kiểm tra xem số của một nhân vật.
doppelgreener
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.