Czy lepiej jest sprawdzić „c> =” 0 ”czy„ c> = 48 ”?

46

Po dyskusji z kilkoma moimi kolegami mam „filozoficzne” pytanie o to, jak traktować typ danych char w Javie, zgodnie z najlepszymi praktykami.

Załóżmy, że jest to prosty scenariusz (oczywiście jest to tylko bardzo prosty przykład, aby nadać praktyce znaczenie moje pytanie), w którym biorąc pod uwagę ciąg znaków jako dane wejściowe, musisz policzyć liczbę obecnych w nim znaków numerycznych.

Oto 2 możliwe rozwiązania:

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

Który z nich jest bardziej „czysty” i zgodny z najlepszymi praktykami Java?

wyr0
źródło
141
Dlaczego miałbyś pisać 48 i 57, skoro tak naprawdę masz na myśli „0” i „9”? Po prostu napisz, co masz na myśli.
Brandin,
9
Poczekaj, co robisz, Java ma VK_stałe, których powinieneś używać, po drugie używanie kodów char jest lepsze niż char Java to bezpieczny język typu, którego nie powinieneś sprawdzać między typami. @Brandin Nazywa się to praktykami kodowania
Martin Barker,
12
Bez zawracania sobie głowy robieniem więcej niż osądzanie 6 osób, KTÓRZY MYŚLIŁO, ŻE TO DOBRE PYTANIE. Czy używasz znaków jako liczb? Jeśli tak, użyj liczb. Czy używasz go jako liter? Jeśli tak, użyj liter.
Alec Teal,
17
@MartinBarker VK_*Stałe odpowiadają kluczom, a nie znakom .
CodesInChaos
2
Kilka minut zajęło mi ustalenie, co robi ten kod w odniesieniu do twojego pytania. Już nie jest jasne, ponieważ zakłada, że ​​wiem w (1), że wiem, że jest to zakres cyfr ISO-Latin 1. To sprawia, że ​​jest to problematyczne z punktu widzenia konserwacji.
CyberSkull

Odpowiedzi:

124

Oba są okropne, ale pierwszy jest bardziej okropny.

Oba ignorują wbudowaną zdolność Javy do decydowania, które znaki są „numeryczne” (poprzez metody w Character). Ale pierwszy nie tylko ignoruje unikodową naturę ciągów, zakładając, że może istnieć tylko 0123456789, ale również zaciemnia to niepoprawne rozumowanie za pomocą kodów znaków, które mają sens tylko wtedy, gdy wiesz coś o historii kodowania znaków.

Kilian Foth
źródło
33
Dlaczego zakładasz, że nie odrzucanie cyfr spoza ASCII jest błędne? To zależy od kontekstu.
CodesInChaos
21
@CodesInChaos Jeśli naprawdę chcesz znaleźć znaki numeryczne , skanowanie w poszukiwaniu 0123456789 jest po prostu błędne. Jeśli faktycznie chcesz skanować tylko w poszukiwaniu tych dziesięciu znaków, to są one w zasadzie bezsensownymi tokenami, które tylko przypadkowo wyglądają znajomo dla osób znających tylko ASCII / ISO-Latin. Nie ma w tym nic złego - często muszę to dokładnie robić, np. Wchodzić w interakcje ze starszym oprogramowaniem, które naprawdę akceptuje tylko te dziesięć znaków. Ale powinieneś wyjaśnić swoje intencje, używając czegoś podobnego matches("[0-9]+"), zamiast wykorzystywać historycznie motywowaną sztuczkę z zasięgiem.
Kilian Foth,
15
Istnieją cyfry o pełnej szerokości , które wyglądają tak samo jak cyfry ASCII, i na ogół wiele oprogramowania jest wymagane, aby zaakceptować je zamiast cyfr ASCII. (Oczywiście wiele oprogramowania jest zepsutych, w zależności od definicji „dużo”. Można łatwo stwierdzić, ponieważ dostawcy oprogramowania w jednym kraju nie mogą sprzedawać w innym kraju, ponieważ dostawcy nie spełniają wymagań innych krajów. )
rwong,
37
I have a Japanese IME installed , and accidentally type in in full - width all the time.
BlueRaja - Danny Pflughoeft
14
„Oba są okropne”, ale zapomniałeś powiedzieć właściwe rozwiązanie ;-)
Kromster mówi o wsparciu Monice
163

Ani. Pozwól, aby wbudowana klasa postaci w Javie zrozumiała to dla Ciebie.

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

Istnieje kilka zakresów znaków więcej niż cyfry ASCII, które liczą się jako cyfry, i żaden opublikowany przykład nie będzie ich liczyć. JavaDoc dla Character.isDigit()list tych zakresów znaków jako ważne cyfry:

Niektóre zakresy znaków Unicode zawierające cyfry:

  • „\ u0030” do „\ u0039”, cyfry ISO-LATIN-1 (od „0” do „9”)
  • od „\ u0660” do „\ u0669”, cyfry w języku arabskim
  • „\ u06F0” do „\ u06F9”, rozszerzone cyfry arabsko-indyjskie
  • „od” do „”, cyfry Devanagari
  • „\ uFF10” do „\ uFF19”, cyfry pełnej szerokości

Wiele innych zakresów znaków zawiera również cyfry.

Biorąc to pod uwagę, należy delegować Character.isDigit()nawet na tę listę. W miarę zapełniania się nowych samolotów Unicode kod Java będzie aktualizowany. Aktualizacja JVM może sprawić, że stary kod będzie działał płynnie z nowymi cyframi. Jest to również OSUSZANIE : lokalizując kod „czy to cyfra” w jednym miejscu, do którego odwołuje się gdzie indziej, można uniknąć negatywnych aspektów powielania kodu (tj. Błędów). Na koniec zwróć uwagę na ostatnią linię: ta lista nie jest wyczerpująca i są inne cyfry.

Osobiście wolałbym oddelegować do podstawowych bibliotek Java i spędzać czas na bardziej produktywnych zadaniach niż „zastanawiać się, co to jest cyfra”.


Jedynym wyjątkiem od tej reguły jest to, że naprawdę potrzebujesz przetestować dosłowne cyfry ASCII, a nie inne cyfry. Na przykład, jeśli analizujesz strumień i tylko cyfry ASCII (w przeciwieństwie do innych cyfr) mają specjalne znaczenie, to nie byłoby właściwe użycie Character.isDigit().

W takim przypadku napisałbym inną metodę, np. MyClass.isAsciiDigit()I umieściłbym tam logikę. Otrzymujesz te same korzyści z ponownego użycia kodu, nazwa jest bardzo jasna, co sprawdza, a logika jest poprawna.


źródło
4
Świetna odpowiedź na dostarczenie czystego kodu, który załatwi sprawę.
Pierre Arlaud,
27

Jeśli kiedykolwiek napiszesz aplikację w C, która używa EBCDIC jako podstawowego zestawu znaków i musi przetwarzać znaki ASCII, użyj 48i 57. Robisz to Nie wydaje mi się

O użyciu isDigit(): to zależy. Czy piszesz parser JSON? Tylko 0jako 9są akceptowane jako cyfry, więc nie używaj isDigit(), sprawdzaj >= '0'i <= '9'. Czy przetwarzasz dane wprowadzone przez użytkownika? Użyj isDigit()tak długo, jak reszta kodu będzie w stanie obsłużyć ciąg i przekształcić go poprawnie w liczbę.

gnasher729
źródło
3
W rzeczywistości możesz pisać aplikacje w Javie, które pobierają i zwracają EBCDIC. To nie jest fajne.
Thorbjørn Ravn Andersen
Podobny „brak zabawy” przechodził przez kod napisany przy użyciu wartości dziesiętnych znaków EBCDIC podczas konwersji do środowiska międzyplatformowego ...
Gwyn Evans
1
Jeśli przetwarzasz dane EBCDIC w Javie, prawdopodobnie powinieneś przekonwertować je na natywny zestaw znaków UTF-16 Java przed przetworzeniem ich jako znaków. Ale myślę, że to naprawdę zależy od aplikacji; mam nadzieję, że jeśli twój program ma do czynienia z EBCDIC, to zrozumiesz, co należy zrobić.
Michael Burr
1
Najważniejsze jest to, że podczas przetwarzania EBCDIC w Javie zarówno „0”, jak i 48 błędnie wykrywają cyfrę zero. Bardziej aktualne, w C, C ++ itp. „\ N” i „\ r” są zdefiniowane w implementacji, więc jeśli chcesz wykryć parę Windows CR / LF w pliku za pomocą kompilatora innego niż Windows, lepiej sprawdź wartości dziesiętne zamiast sprawdzanie „\ n” i „\ r”.
gnasher729,
12

Drugi przykład jest wyraźnie lepszy. Znaczenie drugiego przykładu jest natychmiast oczywiste, gdy spojrzysz na kod. Znaczenie pierwszego przykładu jest oczywiste tylko wtedy, gdy zapamiętałeś całą tabelę ASCII w swojej głowie.

Należy rozróżnić między sprawdzaniem określonego znaku, a sprawdzaniem zakresu lub klasy znaków.

1) Sprawdzanie określonej postaci.

W przypadku zwykłych znaków użyj literału znaku, np if(ch=='z').... Jeśli porównujesz ze znakami specjalnymi, takimi jak tabulacja lub podział wiersza, powinieneś użyć znaków zmiany znaczenia, takich jak if (ch=='\n').... Jeśli znak, którego szukasz, jest nietypowy (np. Nie można go natychmiast rozpoznać lub nie jest dostępny na standardowej klawiaturze), możesz użyć kodu szesnastkowego zamiast literalnego. Ale ponieważ kod szesnastkowy jest „magiczną wartością”, wyodrębnisz go do stałej i udokumentujesz:

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

Kody szesnastkowe to standardowy sposób określania kodów znaków.

2) Sprawdzanie klasy lub zasięgu postaci

Naprawdę nie powinieneś robić tego bezpośrednio w kodzie aplikacji, ale umieść go w osobnej klasie zajmującej się tylko klasyfikacją znaków. I powinieneś się różnić, ponieważ biblioteki już istnieją w tym celu, a klasyfikacja znaków jest zwykle bardziej złożona niż myślisz, przynajmniej jeśli weźmiesz pod uwagę znaki spoza zakresu ASCII.

Jeśli martwisz się tylko znakami z zakresu ASCII, możesz użyć literałów znaków w tej bibliotece, w przeciwnym razie prawdopodobnie użyłbyś literałów szesnastkowych. Jeśli spojrzysz na kod źródłowy wbudowanej biblioteki znaków Java, odnosi się on również do wartości i zakresów znaków za pomocą szesnastkowej, ponieważ w ten sposób są one określone w standardzie Unicode.

JacquesB
źródło
1
Poleciłbym również napisanie literału znaku szesnastkowego, używając '\x2603'zamiast tego, aby być jawnym, że testujesz wartość znaku z kodowaniem szesnastkowym, a nie tylko dowolną liczbą losową.
wefwefa3
-4

Zawsze lepiej jest używać, c >= '0'ponieważ c >= 48musisz przekonwertować c w kodzie ascii.

Prem Patel
źródło
3
Co oznacza ta odpowiedź, której nie powiedziano już w poprzednich odpowiedziach sprzed tygodnia?
-5

Wyrażenia regularne ( RegEx ) mają określoną klasę znaków dla cyfr - \d- której można użyć do usunięcia dowolnego innego znaku z łańcucha. Długość wynikowego ciągu jest pożądaną wartością.

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

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

Zauważ jednak, że RegEx są obliczeniowo bardziej wymagające niż inne proponowane rozwiązania, dlatego nie powinny być ogólnie preferowane .

Stefano Bragaglia
źródło
Bardzo elegancki sposób na sprawdzenie!
Kevin Robatel
Regeksy to przesada w przypadku takiego zadania
Pharap
2
@StefanoBragaglia Po ponownym przeczytaniu twojej odpowiedzi myślę, że tak naprawdę nie odpowiada na pytanie.
Pharap
2
Twoja odpowiedź stanowi inny sposób rozwiązania problemu „jak liczyć cyfry w ciągu”. Nie rozwiązuje podstawowego problemu z przykładami kodu i reprezentacją stałych - jako liczb lub znaków.
2
To tak naprawdę nie liczy cyfr (pokazuje tylko, jaka jest długość łańcucha po usunięciu wszystkich cyfr, których nie ma ani tutaj, ani tam), ale zgadzam się, że tak naprawdę nie odpowiada na pytanie. Na przykład nikt nie pytał o usunięcie znaków z ciągów. Pytanie tylko pyta o odpowiedni najlepszy sprawdzony sposób sprawdzenia, czy postać jest liczbą.
doppelgreener