1. インターフェース
ラムダ関数とは何かを理解するには、まずインターフェイスとは何かを理解する必要があります。それでは、要点を思い出してみましょう。
インターフェイスはクラスの概念のバリエーションです。大幅に切り捨てられたクラスだとしましょう。クラスとは異なり、インターフェイスは独自の変数 (静的変数を除く) を持つことができません。また、タイプがインターフェイスであるオブジェクトを作成することもできません。
- クラスの変数を宣言することはできません
- オブジェクトを作成することはできません
例:
interface Runnable
{
void run();
}
インターフェースの使用
では、なぜインターフェースが必要なのでしょうか? インターフェイスは継承とともにのみ使用されます。同じインターフェイスを異なるクラスに継承することもできます。また、よく言われるように、クラスはインターフェイスを実装します。
クラスがインターフェイスを実装する場合、インターフェイスによって宣言されていても実装されていないメソッドを実装する必要があります。例:
interface Runnable
{
void run();
}
class Timer implements Runnable
{
void run()
{
System.out.println(LocalTime.now());
}
}
class Calendar implements Runnable
{
void run()
{
var date = LocalDate.now();
System.out.println("Today: " + date.getDayOfWeek());
}
}
クラスはインターフェイスTimer
を実装するRunnable
ため、インターフェイスにあるすべてのメソッドをクラス自体の内部で宣言してRunnable
実装する必要があります。つまり、メソッド本体にコードを記述する必要があります。クラスも同様ですCalendar
。
ただし、Runnable
変数にはインターフェイスを実装するオブジェクトへの参照を保存できるようになりましたRunnable
。
例:
コード | ノート |
---|---|
|
run() クラス内のメソッドが呼び出さTimer れますクラス内のメソッドが呼び出されますクラス内のメソッドが呼び出されます run() Timer run() Calendar |
その型がオブジェクトの祖先クラスの 1 つである限り、いつでもオブジェクト参照を任意の型の変数に割り当てることができます。Timer
およびクラスには、と のCalendar
2 つのタイプがあります。Object
Runnable
オブジェクト参照を変数に割り当てる場合はObject
、クラス内で宣言されたメソッドのみを呼び出すことができますObject
。また、オブジェクト参照を変数に割り当てるとRunnable
、その型のメソッドを呼び出すことができますRunnable
。
例 2:
ArrayList<Runnable> list = new ArrayList<Runnable>();
list.add (new Timer());
list.add (new Calendar());
for (Runnable element: list)
element.run();
Timer
およびCalendar
オブジェクトには完全に正常に動作するメソッドが実行されているため、このコードは機能します。したがって、電話をかけることは問題ありません。両方のクラスに run() メソッドを追加しただけでは、このような単純な方法でそれらを呼び出すことはできません。
基本的に、Runnable
インターフェイスは run メソッドを配置する場所としてのみ使用されます。
2. 仕分け
より実践的なことに移りましょう。たとえば、文字列の並べ替えを見てみましょう。
文字列のコレクションをアルファベット順に並べ替えるために、Java には次のような優れたメソッドがあります。Collections.sort(collection);
この静的メソッドは、渡されたコレクションを並べ替えます。そして、並べ替えのプロセスでは、要素を交換する必要があるかどうかを理解するために、要素のペアごとの比較を実行します。
並べ替え中、これらの比較は () メソッドを使用して実行されますcompareTo
。このメソッドは、すべての標準クラスにあります: Integer
、String
、 ...
Integer クラスの CompareTo() メソッドは 2 つの数値の値を比較しますが、String クラスの CompareTo() メソッドは文字列のアルファベット順を調べます。
したがって、数値のコレクションは昇順でソートされ、文字列のコレクションはアルファベット順にソートされます。
代替の並べ替えアルゴリズム
しかし、文字列をアルファベット順ではなく長さによって並べ替えたい場合はどうすればよいでしょうか? 数値を降順に並べ替えたい場合はどうすればよいでしょうか? この場合どうしますか?
このような状況に対処するために、クラスには2 つのパラメーターを持つCollections
別のメソッドがあります。sort()
Collections.sort(collection, comparator);
ここで、comparator は、並べ替え操作中にコレクション内のオブジェクトを比較する方法を認識する特別なオブジェクトです。コンパレーターという用語は、英語のcomparatorに由来し、これは「比較する」を意味する Compare に由来します。
では、この特別なオブジェクトとは何でしょうか?
Comparator
インターフェース
まあ、すべては非常に簡単です。sort()
メソッドの 2 番目のパラメータの型は次のとおりです。Comparator<T>
ここで、 T はコレクション内の要素の型を示す型パラメータであり、単一のメソッドComparator
を持つインターフェイスです。int compare(T obj1, T obj2);
つまり、コンパレータ オブジェクトは、Comparator インターフェイスを実装する任意のクラスのオブジェクトです。Comparator インターフェイスは非常にシンプルに見えます。
public interface Comparator<Type>
{
public int compare(Type obj1, Type obj2);
}
このcompare()
メソッドは、渡された 2 つの引数を比較します。
メソッドが負の数を返した場合、それは を意味しますobj1 < obj2
。メソッドが正の数を返した場合、それは を意味しますobj1 > obj2
。メソッドが 0 を返した場合、それは を意味しますobj1 == obj2
。
以下は、文字列を長さで比較するコンパレーター オブジェクトの例です。
public class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
文字列の長さを比較するには、一方の長さをもう一方の長さから減算するだけです。
文字列を長さで並べ替えるプログラムの完全なコードは次のようになります。
public class Solution
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Hello", "how's", "life?");
Collections.sort(list, new StringLengthComparator());
}
}
class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
3. 糖衣構文
このコードをもっとコンパクトに書くことはできないでしょうか? 基本的に、有用な情報が含まれる行は 1 行だけです — obj1.length() - obj2.length();
。
しかし、コードはメソッドの外部に存在できないため、compare()
メソッドを追加する必要があり、そのメソッドを保存するには新しいクラスを追加する必要がありましたStringLengthComparator
。そして、変数の型も指定する必要があります...すべてが正しいようです。
ただし、このコードを短くする方法はあります。糖衣構文をいくつか用意しました。スコップ2杯!
匿名の内部クラス
コンパレータ コードをmain()
メソッド内に直接記述することができ、残りの作業はコンパイラが行います。例:
public class Solution
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Hello", "how's", "life?");
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
Collections.sort(list, comparator);
}
}
Comparator
明示的にクラスを作成しなくても、インターフェイスを実装するオブジェクトを作成できます。コンパイラはこれを自動的に作成し、一時的な名前を付けます。比較してみましょう:
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
Comparator<String> comparator = new StringLengthComparator();
class StringLengthComparator implements Comparator<String>
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
}
2 つの異なるケースで同じコード ブロックを示すために同じ色が使用されます。実際にはその違いは非常に小さいです。
コンパイラはコードの最初のブロックを検出すると、対応するコードの 2 番目のブロックを生成し、クラスにランダムな名前を付けます。
4. Java のラムダ式
コード内で匿名の内部クラスを使用することにしたとします。この場合、次のようなコード ブロックが作成されます。
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
ここでは、変数の宣言と匿名クラスの作成を組み合わせます。しかし、このコードを短くする方法があります。たとえば、次のようになります。
Comparator<String> comparator = (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
};
ここでは暗黙のクラス宣言だけでなく変数の作成も行うため、セミコロンが必要です。
このような表記をラムダ式と呼びます。
コンパイラーは、コード内でこのような表記を見つけた場合、単にコードの冗長バージョンを (匿名の内部クラスを使用して) 生成します。
ラムダ式を記述するときに、クラスの名前だけでなくメソッドの名前も省略したことに注意してください。Comparator<String>
int compare()
ラムダ式は単一のメソッドを持つインターフェイスに対してのみ記述できるため、コンパイルでは問題なくメソッドを決定できます。ちなみに、このルールを回避する方法がありますが、それについては、OOP をより深く学習し始めるときに学習します (デフォルトのメソッドについて話しています)。
コードの詳細バージョンをもう一度見てみましょう。ただし、ラムダ式を記述するときに省略できる部分はグレー表示されています。
Comparator<String> comparator = new Comparator<String>()
{
public int compare (String obj1, String obj2)
{
return obj1.length() – obj2.length();
}
};
重要なものは何も省略されていないようです。実際、Comparator
インターフェイスにメソッドが 1 つしかない場合compare()
、コンパイラは残りのコードからグレー表示されたコードを完全に復元できます。
仕分け
ちなみに、ソートコードは次のように書けるようになりました。
Comparator<String> comparator = (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
};
Collections.sort(list, comparator);
あるいは次のようにもできます。
Collections.sort(list, (String obj1, String obj2) ->
{
return obj1.length() – obj2.length();
}
);
comparator
変数を、その変数に割り当てられた値で即座に置き換えただけですcomparator
。
型推論
しかし、それだけではありません。これらの例のコードはさらにコンパクトに記述することができます。obj1
まず、コンパイラは、およびobj2
変数が であることを自ら判断できますStrings
。次に、メソッド コードにコマンドが 1 つしかない場合は、中括弧と return ステートメントも省略できます。
短縮バージョンは次のようになります。
Comparator<String> comparator = (obj1, obj2) ->
obj1.length() – obj2.length();
Collections.sort(list, comparator);
そして、変数を使用する代わりにcomparator
その値をすぐに使用すると、次のバージョンが得られます。
Collections.sort(list, (obj1, obj2) -> obj1.length() — obj2.length() );
さて、それについてどう思いますか?余分な情報を含まないたった 1 行のコード (変数とコードのみ)。短くする方法はありません!それともありますか?
5. 仕組み
実際、コードはさらにコンパクトに記述することができます。しかし、それについては後で詳しく説明します。
単一のメソッドでインターフェイス型を使用するラムダ式を作成できます。
たとえば、コードでは、メソッドのシグネチャが次のようになっているため、ラムダ式を作成できます。Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());
sort()
sort(Collection<T> colls, Comparator<T> comp)
コレクションを最初の引数として sort メソッドに渡すとArrayList<String>
、コンパイラーは 2 番目の引数の型が であると判断できました。このことから、このインターフェイスには 1 つのメソッドがあると結論付けられました。それ以外はすべて技術的なものです。Comparator<String>
int compare(String obj1, String obj2)
GO TO FULL VERSION