第 3 章 文字列、コンパレータ、フィルタ
JDK によって導入されたいくつかのメソッドは、関数型スタイルのコードを作成するのに非常に役立ちます。 String などの JDK ライブラリの一部のクラスやインターフェイスについてはすでによく知っています。これまで慣れ親しんだ古いスタイルを取り除くには、これらの新しいメソッドを使用する機会を積極的に探す必要があります。同様に、1 つのメソッドのみで匿名内部クラスを使用する必要がある場合、以前のように面倒な記述をすることなく、それをラムダ式に置き換えることができるようになりました。
この章では、ラムダ式とメソッド参照を使用して文字列を走査し、Comparator インターフェイスを実装し、ディレクトリ内のファイルを表示し、ファイルとディレクトリの変更を監視します。前の章で紹介したメソッドの一部は、これらのタスクをより適切に完了するためにここでも引き続き使用されます。学ぶ新しいテクニックは、長くて退屈なコードを、簡潔で、実装が速く、保守が簡単なものに変えるのに役立ちます。
文字列を反復処理する
chars() メソッドは、CharSequence インターフェイスの一部である String クラスの新しいメソッドです。これは、String の文字シーケンスを素早く調べたい場合に非常に便利なツールです。この内部反復子を使用すると、文字列内の各文字を簡単に操作できます。まずは文字列の処理に使ってみてください。メソッド参照の使用方法をいくつか紹介します。
次のようにコードをコピーします。
最終文字列 str = "w00t";
str.chars()
.forEach(ch -> System.out.println(ch));
chars() メソッドは Stream オブジェクトを返します。これを内部反復子 forEach() を使用して走査できます。イテレータでは、文字列内の文字に直接アクセスできます。以下は、文字列をループして各文字を出力した出力です。
次のようにコードをコピーします。
119
48
48
116
これは私たちが望む結果ではありません。文字が表示されることを期待していますが、出力は数字です。これは、chars() メソッドが文字型ではなく整数の Stream を返すためです。まずこの API を理解してから、出力結果を最適化しましょう。
前のコードでは、forEach メソッドの入力パラメーターとしてラムダ式を作成しました。パラメータを println() メソッドに渡すだけです。この操作は非常に一般的なので、Java コンパイラを使用してこのコードを簡素化できます。 25 ページのメソッド参照の使用と同様に、メソッド参照に置き換えて、コンパイラにパラメータのルーティングを行わせます。
インスタンス メソッドへのメソッド参照を作成する方法を説明しました。たとえば、name.toUpperCase() メソッドの場合、メソッド参照は String::toUpperCase です。次の例では、System.out を静的に参照するインスタンス メソッドを呼び出しています。メソッドによって参照される 2 つのコロンの左側には、クラス名または式を指定できます。この柔軟性により、以下のように println() メソッドへの参照を簡単に作成できます。
次のようにコードをコピーします。
str.chars()
.forEach(System.out::println);
ご覧のとおり、Java コンパイラーはパラメーターのルーティングを非常にスマートに完了できます。ラムダ式とメソッド参照は、関数型インターフェイスが受け取られる場所にのみ現れることができ、Java コンパイラーはそこに対応するメソッドを生成することを思い出してください (注釈: コンパイラーは、メソッドを 1 つだけ持つ関数型インターフェイスの実装を生成します。)。前に使用したメソッドは String::toUpperCase を参照しており、生成されたメソッドに渡されるパラメーターは、最終的には、parameter.toUpperCase() のように、メソッド呼び出しのターゲット オブジェクトになります。これは、メソッド参照がクラス名 (String) に基づいているためです。上記の例のメソッド参照は、PrintStream のインスタンスであり、System.out を通じて参照される式に基づいています。メソッド呼び出しのオブジェクトはすでに存在しているため、Java コンパイラーは、生成されたメソッドのパラメーターをこの println メソッド System.out.println(name) のパラメーターとして使用することを決定します。
(注釈: 実際には、主に 2 つのシナリオがあります。メソッド参照も渡されます。1 つは走査されるオブジェクト、もちろんメソッド呼び出しのターゲット オブジェクト (name.toUpperCase など)、もう 1 つはパラメータとして使用されます) System.out.println(name) などのメソッド呼び出し。)
メソッド参照を使用するとコードははるかに単純になりますが、コードがどのように機能するかをより深く理解する必要があります。メソッド参照に慣れると、パラメータのルーティングを自分で理解できるようになります。
この例のコードは十分に簡潔ですが、出力はまだ満足のいくものではありません。文字が表示されることを期待していましたが、代わりに数字が表示されました。この問題を解決するために、int を文字として出力するメソッドを書いてみましょう。
次のようにコードをコピーします。
private static void printChar(int aChar) {
System.out.println((char)(aChar));
}
メソッド参照を使用すると、出力結果を簡単に最適化できます。
次のようにコードをコピーします。
str.chars()
.forEach(IterateString::printChar);
ここで、chars() によって返される結果は int ですが、印刷する必要がある場合はそれを文字に変換します。今回の出力は最終的に文字です。
次のようにコードをコピーします。
w
0
0
t
最初から int ではなく文字を処理したい場合は、chars を呼び出した後に int を文字に直接変換できます。
次のようにコードをコピーします。
str.chars()
.mapToObj(ch -> Character.valueOf((char)ch))
.forEach(System.out::println);
ここでは、chars() によって返される Stream の内部イテレータを使用します。 もちろん、これ以外のメソッドも使用できます。 Stream オブジェクトを取得した後、map()、filter()、reduce() などのメソッドを使用できます。 filter() メソッドを使用して、数字である文字を除外できます。
次のようにコードをコピーします。
str.chars()
.filter(ch -> Character.isDigit(ch))
.forEach(ch -> printChar(ch));
この方法で出力すると、数字のみが表示されます。
次のようにコードをコピーします。
0
0
同様に、ラムダ式を filter() メソッドと forEach() メソッドに渡すことに加えて、メソッド参照を使用することもできます。
次のようにコードをコピーします。
str.chars()
.filter(Character::isDigit)
.forEach(IterateString::printChar);
ここでのメソッド参照により、冗長なパラメーターのルーティングが排除されます。この例では、前の 2 つの方法とは異なる使用法も見られます。最初にインスタンス メソッドを参照したとき、2 回目は静的参照 (System.out) メソッドでした。今回は静的メソッドへの参照です。メソッド参照は黙って支払いを行っています。
インスタンス メソッドと静的メソッドへの参照はすべて同じように見えます (例: String::toUpperCase と Character::isDigit)。コンパイラは、メソッドがインスタンス メソッドであるか静的メソッドであるかを判断して、パラメータのルーティング方法を決定します。インスタンス メソッドの場合、メソッド呼び出しのターゲット オブジェクトとして、生成されたメソッドの入力パラメーター (パラメーター、toUpperCase() など) が使用されます (もちろん、メソッド呼び出しのターゲット オブジェクトなどの例外もあります)。 System::out.println ()) のように指定されています。さらに、静的メソッドの場合、生成されたメソッドの入力パラメータは、Character.isDigit(parameter) など、参照されるメソッドのパラメータとして使用されます。 152 ページの付録 2 には、メソッド参照とその構文の使用方法に関する詳細な手順が記載されています。
メソッド参照は便利ですが、メソッド名の競合によって生じるあいまいさという問題がまだあります。一致するメソッドがインスタンス メソッドと静的メソッドの両方である場合、コンパイラはメソッドのあいまいさのためエラーを報告します。たとえば、次のように Double::toString を記述した場合、実際には double 型を文字列に変換したいのですが、コンパイラは public String のインスタンス メソッド toString() を呼び出すべきか、public static String を呼び出すべきかわかりません。 toString.(double) メソッド。どちらのメソッドも Double クラスであるためです。このような状況に遭遇した場合でも、落胆せず、ラムダ式を使用して問題を解決してください。
関数型プログラミングに慣れたら、ラムダ式とメソッド参照を自由に行き来できるようになります。
このセクションでは、Java 8 の新しいメソッドを使用して文字列を反復処理します。 Comparator インターフェイスの改善点を見てみましょう。