`c> = '0'`または` c> = 48`をチェックする方が良いですか?


46

同僚と話し合った後、ベストプラクティスに従って、Javaでcharデータ型をどのように扱うかについて「哲学的な」質問をしました。

入力として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
「0」と「9」を実際に意味するのに、なぜ48と57を書くのでしょうか?あなたの言っていることを書いてください。
ブランディン

9
JavaにはVK_使用する定数があります。次に、charコードを使用する方がcharよりも優れていますJavaは、タイプ間チェックを行わないタイプセーフ言語です。@Brandinコーディングプラクティスと呼ばれる
マーティンバーカー

12
これが良い質問だと思った6人を判断する以上のことをせずに。数字として文字を使用していますか?その場合は数字を使用します。手紙として使っていますか?その場合、文字を使用します。
アレックティール

17
@MartinBarker VK_*定数は、文字ではなくキーに対応します
CodesInChaos

2
あなたの質問に関連してこのコードが何をするかを判断するのに数分かかりました。(1)で、これがISO-Latin 1の桁範囲であることを知っていることを前提としているため、すでに明確ではありません。
Cyber​​Skull

回答:


124

両方とも恐ろしいですが、最初のほうが恐ろしいです。

どちらもJavaの組み込み機能を無視して、どの文字が「数値」であるかを決定します(のメソッドを使用Character)。しかし、最初のものは、文字列のUnicodeの性質を無視するだけでなく、0123456789のみが存在する可能性があると仮定すると、文字エンコードの履歴について何かを知っている場合にのみ意味のある文字コードを使用することにより、この無効な推論さえ覆い隠します。


33
非拒否の非ASCII数字が間違っていると仮定しているのはなぜですか?それはコンテキストに依存します。
-CodesInChaos

21
@CodesInChaos本当に数字を見つけたい場合、0123456789のスキャンは間違いです。実際にこれらの10文字のみをスキャンしたい場合、これらは本質的に無意味なトークンであり、ASCII / ISO-Latinのみを知っている人には偶然に見覚えがあるだけです。それには何の問題もありません。たとえば、実際にこれらの10文字のみを受け入れるレガシーソフトウェアとやり取りするために、私はしばしば正確にそれをしなければなりません。しかし、その後matches("[0-9]+")、歴史的に動機付けられた範囲のトリックを活用するのではなく、のようなものを使用して意図を明確にする必要があります。
キリアンフォス

15
ASCII数字と同じように見える全角数字があり、一般に、ASCII数字の代わりにそれらを受け入れるために多くのソフトウェアが必要です。(明らかに、「多くの」の定義に応じて、多くのソフトウェアが壊れています。ある国のソフトウェアベンダーは他の国の要件を守っていないため、別の国に販売することは不可能だとわかります。 )
rwong

37
私は現在、日本に来ており、その間、たまたまタイピングを行っています。
BlueRaja-ダニーPflughoeft

14
「どちらも恐ろしい」が、あなたは正しい解決策を言うのを忘れていた;-)
KromsterはサポートMonica

163

どちらでもない。JavaのビルトインCharacterクラスでそれを理解してください。

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」、デーバナーガリー数字
  • 「\ uFF10」から「\ uFF19」、全角数字

他の多くの文字範囲にも数字が含まれています。

そうは言ってCharacter.isDigit()も、このリストを使用して委任する必要があります。新しいUnicodeプレーンが追加されると、Javaコードが更新されます。JVMをアップグレードすると、古いコードが新しい数字とシームレスに機能するようになります。また、DRYです。「これは数字ですか」コードを他の場所で参照されている1つの場所にローカライズすることにより、コードの重複(つまりバグ)の側面を回避できます。最後に、最後の行に注意してください。このリストは完全ではなく、他の数字があります。

個人的には、コアJavaライブラリに委任し、「数字とは何かを設定する」よりも生産的なタスクに時間を費やします。


このルールの唯一の例外は、他の数字ではなくリテラルASCII数字をテストする必要がある場合です。たとえば、ストリームを解析していて、(他の数字とは対照的に)ASCII数字のみが特別な意味を持つ場合、を使用することは適切ではありませんCharacter.isDigit()

その場合、別のメソッドを記述し、たとえばMyClass.isAsciiDigit()そこにロジックを配置します。コードの再利用と同じ利点が得られ、名前はチェック対象について非常に明確であり、ロジックは正しいです。


4
トリックを行うクリーンなコードを実際に提供することに対する素晴らしい答えです。
ピエールアラード

27

EBCDICを基本文字セットとして使用し48、ASCII文字を処理する必要があるアプリケーションをCで記述する場合は、とを使用し57ます。あなたはそれをやっていますか?そうは思いません。

使用についてisDigit():依存します。JSONパーサーを書いていますか?だけ0にする9ので使用しないで、数字として受け入れられているisDigit()かどうかを確認、>= '0'<= '9'。ユーザー入力を処理していますか?isDigit()残りのコードが実際に文字列を処理でき、それを正しく数値に変換できる限り使用します。


3
実際、EBCDICを取得して返すJavaでアプリケーションを作成できます。これは面白くない。
トールビョールンラヴンアンデルセン

同様の「は楽しい」コードを経由してクロスプラットフォーム環境にそれを変換するときにEBCDIC文字の小数点以下の値を使用して書かれたもの...
グウィン・エヴァンス

1
JavaでEBCDICデータを処理している場合は、文字として処理する前に、おそらくJavaネイティブUTF-16文字セットに変換する必要があります。しかし、それは本当にアプリケーションに依存していると思います。あなたのプログラムがEBCDICに対処しなければならないなら、何をする必要があるか理解できるでしょう。
マイケルバー

1
主なポイントは、JavaでEBCDICを処理する場合、数字のゼロを検出するのに「0」と「48」の両方が間違っいることです。C、C ++などでは、より最新のものです。「\ n」と「\ r」は実装定義であるため、Windows以外のコンパイラを使用してファイル内のWindows CR / LFペアを検出する場合は、 「\ n」および「\ r」を確認します。
gnasher729

12

2番目の例は明らかに優れています。2番目の例の意味は、コードを見るとすぐにわかります。最初の例の意味は、頭の中にASCIIテーブル全体を記憶している場合にのみ明らかです。

特定の文字のチェックと、文字の範囲またはクラスのチェックを区別する必要があります。

1)特定のキャラクターの確認。

通常の文字の場合、文字リテラルを使用します(例:)if(ch=='z')...。タブや改行などの特殊文字をチェックする場合は、などのエスケープを使用する必要がありますif (ch=='\n')...。チェックする文字が異常な場合(たとえば、すぐに認識できないか、標準キーボードで使用できない場合)、リテラル文字ではなく16進文字コードを使用することがあります。ただし、16進コードは「魔法の値」なので、定数に抽出して文書化します。

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

16進コードは、文字コードを指定する標準的な方法です。

2)文字クラスまたは範囲の確認

アプリケーションコードでこれを直接行うべきではありませんが、文字の分類のみに関係する別のクラスにカプセル化する必要があります。また、この目的のためにライブラリが既に存在し、少なくともASCII範囲外の文字を考慮する場合、文字の分類は通常、思っているよりも複雑であるため、これを変更する必要があります。

ASCII範囲の文字のみに関心がある場合は、このライブラリで文字リテラルを使用できます。それ以外の場合は、おそらく16進リテラルを使用します。Java組み込み文字ライブラリのソースコードを見ると、16進数を使用して文字値と範囲も参照しています。これは、Unicode標準で指定されている方法だからです。


1
また、'\x2603'代わりに16進数で文字リテラルを書くことをお勧めします。これは、乱数だけでなく、16進エンコードで文字の値をテストすることを明示するためです。
-wefwefa3

-4

ASCIIコードでcを変換する必要があるc >= '0'ため、常に使用することをc >= 48お勧めします。


3
この回答は、1週間前の以前の回答でまだ言われていないことを示していますか?

-5

正規表現RegEx)には、数字用の特定の文字クラスがあります-- \d文字列から他の文字を削除するために使用できます。結果の文字列の長さは目的の値です。

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

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

ただし、RegExは他の提案されたソリューションよりも計算的に要求が厳しいため、一般的には推奨されません


チェックを行う非常にエレガントな方法!
ケビンロバテル

正規表現はこのようなタスクには過剰です
-Pharap

2
@StefanoBragagliaあなたの答えを読み直した後、私はそれが本当に質問に答えていないと思います。
ファラプ

2
あなたの答えは、「文字列の数字をどのように数えるか」という問題を解決する別の方法を提供します。コードサンプルと定数の表現に関する根本的な問題(数値または文字)には答えません。

2
これは実際には数字をカウントしません(すべての数字を削除した後の文字列の長さを伝えますが、これはここにもそこにもありません)が、実際には質問に答えないことに同意します。たとえば、文字列から文字を削除することについて誰も尋ねていませんでした。質問は、文字の数字かどうかを確認するための適切なベストプラクティスの方法について尋ねているだけです。
doppelgreener
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.