JavaRush /Java Blog /Random-JA /StringUtils クラスを分解してみましょう
Roman Beekeeper
レベル 35

StringUtils クラスを分解してみましょう

Random-JA グループに公開済み
親愛なる読者の皆さん、こんにちは。今、本当に興味のあること、気になっている事について書いていきたいと思います。したがって、今日は、将来の参考として役立つ軽い読み物をいくつか紹介します。StringUtilsについて話しましょう。 StringUtils クラスを詳しく見てみましょう - 1たまたま、ある時、Apache Commons Lang 3ライブラリをバイパスしたことがありました。これは、さまざまなオブジェクトを操作するための補助クラスを備えたライブラリです。これは、文字列やコレクションなどを操作するための便利なメソッドのコレクションです。現在のプロジェクトでは、25 年前のビジネス ロジック (COBOL から Java) を変換する際に文字列をさらに詳細に扱う必要があり、 StringUtils クラスについての十分な深い知識がないことが判明しました。そのため、すべてを自分で作成する必要がありました。私が意味したのは?文字列操作を伴う特定のタスクを自分で記述する必要がなく、既製のソリューションを使用できるという事実。自分で書いて何が悪いの?少なくとも、これはかなり前にすでに書かれたコードであるという点で。追加で書かれたコードをテストするという問題も同様に差し迫った問題です。優れていることが証明されているライブラリを使用するとき、私たちはそれがすでにテストされており、それをテストするために大量のテスト ケースを作成する必要がないことを期待します。たまたま、Java で文字列を操作するためのメソッドのセットはそれほど大きくありません。仕事に役立つ方法は実際にはそれほど多くありません。このクラスは、NullPointerException のチェックを提供するためにも作成されます。私たちの記事の概要は次のとおりです。
  1. 接続方法は?
  2. 私の仕事の例: このような便利なクラスを知らずに、どのようにして自転車の松葉杖を作成したか。
  3. 私が興味深いと思った他の方法を見てみましょう。
  4. 要約しましょう。
すべてのケースは、 GitHub 上の Javarush コミュニティ組織の別のリポジトリに追加されます。それらに対する別の例とテストがあります。

0.接続方法

私と手を携えて歩いている人たちは、すでに Git と Maven の両方に多かれ少なかれ精通しているので、今後はこの知識に頼って同じことを繰り返すつもりはありません。以前の記事を見逃した方、または読み始めたばかりの方のために、ここにMavenGitに関する資料があります。もちろん、ビルド システム (Maven、Gredl) を使用せずに、すべてを手動で接続することもできますが、これは今ではおかしなことであり、そのようにする必要は絶対にありません。すべてを正しく行う方法をすぐに学ぶ方がよいでしょう。したがって、Maven を使用するには、まず適切な依存関係を追加します。
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>${apache.common.version}</version>
</dependency>
${apache.common.version} は、このライブラリのバージョンです。次に、何らかのクラスにインポートするには、インポートを追加します。
import org.apache.commons.lang3.StringUtils;
それで終わりです、すべてバッグの中にあります))

1. 実際のプロジェクトの例

  • leftPad メソッド

最初の例は今となってはとても愚かに思えるので、同僚がStringUtils.leftPadについて知っていて教えてくれたのはとても良かったと思います。タスクは何でしたか。コードは、データが完全に正しく到着しない場合にデータを変換する必要があるような方法で構築されていました。文字列フィールドは数字のみで構成されることが期待されていました。長さが 3 で値が 1 の場合、エントリは「001」でなければなりません。つまり、最初にすべてのスペースを削除し、次にゼロで覆う必要があります。タスクの本質を明確にするために、さらに例を示します。「12」 -> 「012」、「1」 -> 「001」など。私が何をしたのですか?これについてはLeftPadExampleクラスで説明しました。これをすべて実行するメソッドを書きました。
public static String ownLeftPad(String value) {
   String trimmedValue = value.trim();

   if(trimmedValue.length() == value.length()) {
       return value;
   }

   StringBuilder newValue = new StringBuilder(trimmedValue);

   IntStream.rangeClosed(1, value.length() - trimmedValue.length())
           .forEach(it -> newValue.insert(0, "0"));
   return newValue.toString();
}
基礎として、単純に元の値とトリミングされた値の差を取得し、その前にゼロを埋めればよいという考えを採用しました。これを行うために、IntStreamを使用して同じ操作を n 回実行しました。そしてこれは間違いなくテストする必要があります。StringUtils.leftPadメソッドについて事前に知っていたら、次のことができたはずです。
public static String apacheCommonLeftPad(String value) {
   return StringUtils.leftPad(value.trim(), value.length(), "0");
}
ご覧のとおり、コードは非常に少なく、全員が確認したライブラリも使用されています。この目的のために、 LeftPadExampleTestクラスに 2 つのテストを作成しました(通常、クラスのテストを計画するときは、同じパッケージ内の同じ名前 + Test を持つクラスを src/test/java にのみ作成します)。これらのテストでは、1 つのメソッドが値を正しく変換していることを確認してから、別のメソッドをチェックします。もちろん、さらに多くのテストを記述する必要がありますが、テストはこの場合の主要なトピックではありません。
package com.github.javarushcommunity.stringutilsdemo;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

@DisplayName("Unit-level testing for LeftPadExample")
class LeftPadExampleTest {

   @DisplayName("Should transform by using ownLeftPad method as expected")
   @Test
   public void shouldTransformOwnLeftPadAsExpected() {
       //given
       String value = "1   ";
       String expectedTransformedValue = "0001";

       //when
       String transformedValue = LeftPadExample.ownLeftPad(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }

   @DisplayName("Should transform by using StringUtils method as expected")
   @Test
   public void shouldTransformStringUtilsLeftPadAsExpected() {
       //given
       String value = "1   ";
       String expectedTransformedValue = "0001";

       //when
       String transformedValue = LeftPadExample.apacheCommonLeftPad(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }

}
現時点では、テストについていくつかコメントできます。これらは JUnit 5 を使用して書かれています。
  1. テストは、適切なアノテーション (@Test) が付いている場合、テストとして扱われます。
  2. テストの動作を名前で説明するのが難しい場合、または説明が長くて読みにくい場合は、@DisplayName アノテーションを追加して、テスト実行時に表示される通常の説明にすることができます。
  3. テストを作成するときは、テストを論理部分に分割する BDD アプローチを使用します。
    1. //与えられた - テスト前のデータ設定ブロック。
    2. // テストしているコードの部分が起動されるブロックがいつであるか。
    3. //then は when ブロックの結果をチェックするブロックです。
これらを実行すると、すべてが期待どおりに動作していることが確認されます。

  • ストリップスタートメソッド

ここでは、行の先頭にスペースやカンマが含まれる可能性がある行の問題を解決する必要がありました。変化した後、それらは新たな意味を持つべきではありませんでした。問題の記述がこれまで以上に明確になりました。いくつかの例で理解を深めます。 「, , 本」 -> 「本」 「,,, 本」 -> 「本」 b , 本」 -> 「b , 本」 leftPad の場合と同様に、StrimStartExampleクラスには 2 つのメソッドがあります。1 つは独自のソリューションです。
public static String ownStripStart(String value) {
   int index = 0;
   List commaSpace = asList(" ", ",");
   for (int i = 0; i < value.length(); i++) {
       if (commaSpace.contains(String.valueOf(value.charAt(i)))) {
           index++;
       } else {
           break;
       }
   }
   return value.substring(index);
}
ここでのアイデアは、スペースやカンマがなくなった位置から始まるインデックスを見つけることでした。最初にそれらがまったく存在しなかった場合、インデックスはゼロになります。2 つ目は、 StringUtilsによるソリューションです。
public static String apacheCommonLeftPad(String value) {
   return StringUtils.stripStart(value, StringUtils.SPACE + COMMA);
}
ここでは、最初の引数にどの文字列を処理しているかに関する情報を渡し、2 番目の引数にはスキップする必要がある文字で構成される文字列を渡します。同じ方法でStripStartExampleTestクラスを作成します。
package com.github.javarushcommunity.stringutilsdemo;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@DisplayName("Unit-level testing for StripStartExample")
class StripStartExampleTest {

   @DisplayName("Should transform by using stripStart method as expected")
   @Test
   public void shouldTransformOwnStripStartAsExpected() {
       //given
       String value = ", , books";
       String expectedTransformedValue = "books";

       //when
       String transformedValue = StripStartExample.ownStripStart(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }

   @DisplayName("Should transform by using StringUtils method as expected")
   @Test
   public void shouldTransformStringUtilsStripStartAsExpected() {
       //given
       String value = ", , books";
       String expectedTransformedValue = "books";

       //when
       String transformedValue = StripStartExample.apacheCommonLeftPad(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }
}

  • isEmpty メソッド

もちろん、この方法ははるかに簡単ですが、だからといって有用性が低下するわけではありません。これはString.isEmpty()メソッドの機能を拡張し、null のチェックも追加します。何のために?NullPointerException を避けるため、つまり、 nullの変数に対するメソッドの呼び出しを避けるためです。したがって、書かないようにするには、次のようにします。
if(value != null && value.isEmpty()) {
   //doing something
}
簡単にこれを行うことができます:
if(StringUtils.isEmpty(value)) {
   //doing something
}
この方法の利点は、どの方法が使用されているかがすぐにわかることです。

2. StringUtils クラスの他のメソッドの分析

さて、私の意見では、同様に注目に値するこれらの方法について話しましょう。StringUtilsについて一般的に言えば、 Stringクラスにあるメソッドと同様の null セーフ メソッドが提供されていると言えます( isEmptyメソッドの場合と同様)。それらを見てみましょう:

  • 比較メソッド

このようなメソッドはStringに存在し、2 つの文字列を比較するときに、どちらかが null の場合は NullPointerException をスローします。コード内の見苦しいチェックを避けるために、StringUtils.compare(String str1, String str2)メソッドを使用できます。このメソッドは比較の結果として int を返します。これらの値は何を意味するのでしょうか? それらが同じである場合 (または両方が null の場合)、int = 0。int < 0 (str1 が str2 より小さい場合)。int > 0 (str1 が str2 より大きい場合)。また、ドキュメントを見ると、このメソッドの Javadoc には次のシナリオが示されています。
StringUtils.compare(null, null)   = 0
StringUtils.compare(null , "a")   < 0
StringUtils.compare("a", null)    > 0
StringUtils.compare("abc", "abc") = 0
StringUtils.compare("a", "b")     < 0
StringUtils.compare("b", "a")     > 0
StringUtils.compare("a", "B")     > 0
StringUtils.compare("ab", "abc")  < 0

  • ... メソッドが含まれています

ここでユーティリティ開発者は大喜びしました。あなたが望むどんな方法でもあります。それらをまとめることにしました。
  1. contains は、期待される文字列が別の文字列の中にあるかどうかをチェックするメソッドです。これはどのように役立ちますか? テキスト内に特定の単語が含まれていることを確認する必要がある場合は、この方法を使用できます。

    例:

    StringUtils.contains(null, *)     = false
    StringUtils.contains(*, null)     = false
    StringUtils.contains("", "")      = true
    StringUtils.contains("abc", "")   = true
    StringUtils.contains("abc", "a")  = true
    StringUtils.contains("abc", "z")  = false

    ここでも、NPE (Null Pointer Exception) セキュリティが存在します。

  2. containsAny は、文字列内に存在する文字が存在するかどうかを確認するメソッドです。これも便利なことです。頻繁にこれを行う必要があります。

    ドキュメントの例:

    StringUtils.containsAny(null, *)                  = false
    StringUtils.containsAny("", *)                    = false
    StringUtils.containsAny(*, null)                  = false
    StringUtils.containsAny(*, [])                    = false
    StringUtils.containsAny("zzabyycdxx", ['z', 'a']) = true
    StringUtils.containsAny("zzabyycdxx", ['b', 'y']) = true
    StringUtils.containsAny("zzabyycdxx", ['z', 'y']) = true
    StringUtils.containsAny("aba", ['z'])             = false

  3. containsIgnoreCase は、 containsメソッドの便利な拡張機能です。実際、この方法を使用せずにそのようなケースを確認するには、いくつかのオプションを実行する必要があります。したがって、調和して使用される方法は 1 つだけです。

  4. ドキュメントからのいくつかの例:

    StringUtils.containsIgnoreCase(null, *) = false
    StringUtils.containsIgnoreCase(*, null) = false
    StringUtils.containsIgnoreCase("", "") = true
    StringUtils.containsIgnoreCase("abc", "") = true
    StringUtils.containsIgnoreCase("abc", "a") = true
    StringUtils.containsIgnoreCase("abc", "z") = false
    StringUtils.containsIgnoreCase("abc", "A") = true
    StringUtils.containsIgnoreCase("abc", "Z") = false

  5. containsNone - 名前から判断すると、何がチェックされているかがすでにわかります。内側に線があってはなりません。確かに便利なものです。不要な文字をすばやく検索します ;)。私たちの電報ボットではわいせつな内容をフィルタリングし、これらのおかしな方法を無視しません。

    例として、それらがなければどうなるでしょうか:

    StringUtils.containsNone(null, *)       = true
    StringUtils.containsNone(*, null)       = true
    StringUtils.containsNone("", *)         = true
    StringUtils.containsNone("ab", '')      = true
    StringUtils.containsNone("abab", 'xyz') = true
    StringUtils.containsNone("ab1", 'xyz')  = true
    StringUtils.containsNone("abz", 'xyz')  = false

  • デフォルト文字列メソッド

文字列が null で、デフォルト値を設定する必要がある場合に、余分な情報の追加を避けるのに役立つ一連のメソッド。あらゆる好みに合うオプションがたくさんあります。その中で主なものはStringUtils.defaultString(final String str, Final String defaultStr)です。 str が null の場合は、単純に値をdefaultStrに渡します。ドキュメントの例:
StringUtils.defaultString(null, "NULL")  = "NULL"
StringUtils.defaultString("", "NULL")    = ""
StringUtils.defaultString("bat", "NULL") = "bat"
データを含む POJO クラスを作成するときに使用すると非常に便利です。

  • deleteWhitespace メソッド

これは興味深い方法ですが、アプリケーションの選択肢はそれほど多くありません。同時に、そのようなケースが発生した場合、この方法は間違いなく非常に役立ちます。文字列からすべてのスペースを削除します。このギャップがどこにあっても、その痕跡はありません)))ドキュメントの例:
StringUtils.deleteWhitespace(null)         = null
StringUtils.deleteWhitespace("")           = ""
StringUtils.deleteWhitespace("abc")        = "abc"
StringUtils.deleteWhitespace("   ab  c  ") = "abc"

  • opensWith メソッド

それ自体が物語ります。これは非常に便利なメソッドです。文字列が提案された文字列で終わるかどうかをチェックします。これは多くの場合必要になります。もちろん、自分で小切手を書くこともできますが、既製の方法を使用する方が明らかに便利で優れています。例:
StringUtils.endsWith(null, null)      = true
StringUtils.endsWith(null, "def")     = false
StringUtils.endsWith("abcdef", null)  = false
StringUtils.endsWith("abcdef", "def") = true
StringUtils.endsWith("ABCDEF", "def") = false
StringUtils.endsWith("ABCDEF", "cde") = false
StringUtils.endsWith("ABCDEF", "")    = true
ご覧のとおり、すべてが空行で終わります))) この例(StringUtils.endsWith("ABCDEF", "") = true) は単なるおまけだと思います。これは不条理だからです)大文字と小文字は区別されません。

  • イコールメソッド

2 つの文字列を比較する null セーフなメソッドの良い例です。そこに何を入力しても、答えはそこにあり、間違いはありません。例:
StringUtils.equals(null, null)   = true
StringUtils.equals(null, "abc")  = false
StringUtils.equals("abc", null)  = false
StringUtils.equals("abc", "abc") = true
StringUtils.equals("abc", "ABC") = false
もちろん、equalsIgnoreCaseもあります。すべてがまったく同じ方法で行われますが、大文字と小文字を区別しないだけです。見てみましょう?
StringUtils.equalsIgnoreCase(null, null)   = true
StringUtils.equalsIgnoreCase(null, "abc")  = false
StringUtils.equalsIgnoreCase("abc", null)  = false
StringUtils.equalsIgnoreCase("abc", "abc") = true
StringUtils.equalsIgnoreCase("abc", "ABC") = true

  • 等しいメソッド

先に進み、 equalsメソッドを拡張しましょう。複数の等価性チェックの代わりに、1 つを実行したいとします。このために、文字列のセットを比較する文字列を渡すことができます。それらのいずれかが提案されたものと等しい場合、それは TRUE になります。文字列と文字列のコレクションを渡して、それらを相互に比較します (最初の文字列とコレクションの文字列)。難しい?意味を理解するのに役立つドキュメントの例を次に示します。
StringUtils.equalsAny(null, (CharSequence[]) null) = false
StringUtils.equalsAny(null, null, null)    = true
StringUtils.equalsAny(null, "abc", "def")  = false
StringUtils.equalsAny("abc", null, "def")  = false
StringUtils.equalsAny("abc", "abc", "def") = true
StringUtils.equalsAny("abc", "ABC", "DEF") = false
qualsAnyIgnoreCase もあります。そしてその例:
StringUtils.equalsAnyIgnoreCase(null, (CharSequence[]) null) = false
StringUtils.equalsAnyIgnoreCase(null, null, null)    = true
StringUtils.equalsAnyIgnoreCase(null, "abc", "def")  = false
StringUtils.equalsAnyIgnoreCase("abc", null, "def")  = false
StringUtils.equalsAnyIgnoreCase("abc", "abc", "def") = true
StringUtils.equalsAnyIgnoreCase("abc", "ABC", "DEF") = true

結論

その結果、StringUtils とは何か、そして StringUtils にどのような便利なメソッドがあるのか​​についての知識が得られます。まあ、このような便利なものがあり、既製の解決策の助けを借りて問題を解決できる場所で毎回松葉杖を使って囲い込む必要はないという認識があれば。一般に、私たちはメソッドの一部のみを分析しました。ご希望であれば、続けます。他にもたくさんありますが、それらは本当に注目に値します。これを他にどのように提示できるかについてのアイデアがある場合は、書いてください。私は常に新しいアイデアを歓迎します。メソッドのドキュメントは非常によく書かれており、結果を含むテスト例が追加されているため、メソッドの動作をよりよく理解できます。したがって、ドキュメントを読むことを躊躇しません。ドキュメントを読むことで、ユーティリティの機能に関する疑問が払拭されます。新しいコーディング経験を積むには、ユーティリティ クラスがどのように作成および記述されるかを調べることをお勧めします。通常、各プロジェクトには独自のスクラップ クラスがあり、それらを作成した経験が役立つため、これは将来役に立ちます。伝統的に、Github で私のアカウントを購読することをお勧めします) 電報ボットを使用した私のプロジェクトについて知らない人のために、ここに最初の記事へのリンクがあります。読んでくれた皆さん、ありがとう。以下にいくつかの便利なリンクを追加しました。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION