【Java】lambda式と関数式インタフェースの完璧な組み合わせ
33974 ワード
ジルコニア式の導入
?なぜlambda表現が必要なの?
いくつかの栗を挙げる:
sort
方法は、Comparator
インタフェースを実現するクラスの例を伝達する必要がある.Timer
は、ActionListener
インタフェースを実装するクラスの例を入力する必要がある.Thread
は、Runnable
インタフェースを実装するクラスのインスタンスを送信する必要がある...なぜですか?本当にこのインスタンスオブジェクトが必要ですか?現象を通して本質を見る:それらが本当に必要とするのは、何によってソートされ、トリガーされた後に何を実行するか、スレッドによって何を実行するかを教える「関数」(compare、actionPerformed、run)である.しかし、Javaは「関数」をパラメータとして許可しない.だから、この「関数」(あるいはコードブロック)を、インタフェースの抽象的な方法として作成します.パラメータとして「関数」が必要な場合は、インタフェースの実装クラスのインスタンスオブジェクトをパラメータとして作成します.この使命は、オブジェクトJava 8に委任された後、パラメータとしてのオブジェクトは、lambda式に取って代わることができます.
これはコードを大幅に簡素化し、場合によっては効率を向上させるに違いない.さらに重要なのは、大勢の傾向にある「関数プログラミング」思想のもう一つの勝利である.
コンピューターのない数学の時代、論理学者Churchは関数を記号化する必要があることに気づき、ギリシャのアルファベットを使った.λ——λの発音をlambdaと言いますそれ以降、パラメータ付きの式をlambda式と言います
㈴lambda表現式の文法
「矢印関数」はlambda式の1つです.つまり
()->{}
>>> lambda 。 , lambda
// ,
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " ");
}
}).start();
// Lambda ( Runable )
new Thread(() -> System.out.println(Thread.currentThread().getName() + " ")).start();
// ,
Arrays.sort(mountains, new Comparator<Mountain>() {
@Override
public int compare(Mountain m1, Mountain m2) {
return m1.height - m2.height;
}
]);
// Lambda ( Comparator )
Arrays.sort(mountains, (Mountain m1, Mountain m2) -> {
return m1.height - m2.height;
});
// Lambda :
// 1.
// 2. ,()
// 3. ,"{}" ";" "return"
// 4. ———— 【 】
// 5. , ———— 【 】
Arrays.sort(mountains, (m1, m2) -> m1.height - m2.height);
" " ,lambda " "
public class Demo {
public static Comparator<String> getComparator() {
return new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
};
}
public static Comparator2<String> getComparator2(){
return (s1, s2)-> s1.length() - s2.length();
}
public static void main(String[] args) {
String[] arr = {"loli", "hahaha", "x"};
Arrays.sort(arr, getComparator());
Arrays.sort(arr, getComparator2());
System.out.println(Arrays.toString(arr));
}
}
, lambda , ,
, ————lmabda ,
▃関数式インタフェース
▇関数式インタフェース
抽象的なメソッドが1つしかないインタフェースがあります.
@FunctionalInterface // :
interface {
public abstract void method(); // public abstract
}
関数インタフェースがパラメータとして機能する場合、すなわちインタフェースのオブジェクトを転送する必要がある場合、lambda式を転送できます.次の考えは必ず理解して、しっかりと把握しなければなりません.
Lambda式は、インタフェースの抽象的な方法を上書きし、インタフェースのインスタンスオブジェクトを生成する関数と見なすことが望ましい.
この方法は、1つのオブジェクトに入力するよりも多くのコードを省略します(インタフェース名も省略します.導出できるからです).
▇怠惰計算
怠惰計算は遅延評価計算とも呼ばれる.文字通り.
>>> :
public class Demo1 {
public static void printer(boolean isOk, String s){ // ( isOk )
if(isOk){
System.out.println(s);
}
}
public static void main(String[] args) {
String s1 = "loli";
String s2 = "suki";
String s3 = "saikou";
printer(false, s1 + s2 + s3);
}
}
>>> " " ?
>>> isOk false , , " " ————
>>> +lambda
------- ↓ Builder.java ------------
@FunctionalInterface
public interface Builder {
public abstract String stringBilder();
}
-------- ↓ Demo2.java -------------
public class Demo2 {
public static void printer(boolean isOk, Builder bl){
if (isOk){
System.out.println(bl.stringBilder());
}
}
public static void main(String[] args) {
String s1 = "loli";
String s2 = "suki";
String s3 = "saikou";
printer(true, ()-> s1 + s2 + s3);
}
}
これは確かに理解が必要です><:
最適化されたメソッドが入力するパラメータは、接合された文字列ではなく、文字列接合の関数です.
このように、文字列の結合という動作は、このインタフェースオブジェクトに委任されます.実際に実行すると、文字列の結合という動作がトリガーされます.isOkがfalseである場合、このコードは実行されないことは明らかであり、スペル文字列の動作も自然に回避される.
したがって、このときの「つなぎ文字列」は怠惰計算であり、ifブロックに遅延された内部怠惰計算の特徴は、元のパラメータが呼び出し時に実際に実行される動作であり、その動作を記述する関数に置き換えられることである.
>>> :
func(m, n, new Date(2077, 6, 21));
↓
func(m, n, ()->new Date(2077, 6, 21)); // ( ) :Supplier
よくある関数インタフェース
Supplier-get-生産工場
Consumer-accept-消費者
Predicate-test-判断
Function-apply-タイプ変換関数インタフェースをパラメータとする意味は、次のとおりです.
私は(生産、使用、判断、タイプ変換)データが必要ですが、具体的な行動(どのようなデータを生産するか、どのようにデータを使用するか、どのように判断するか、どのように変換するか)は、その後に伝わるlambda式によって決まります.
具体的な意味とその例のコード:ここを突いて、必ず突いて!!!(★)
メソッド参照
メソッドリファレンスはlambda式の双子の兄弟であり、関数インタフェースの例として使用されます.
メソッドリファレンスはlambda式と同様にオブジェクトではありません.ただし、パラメータとしてオブジェクトメソッドが参照される構文は、3つあります(メソッド参照がlambda式に変換されます).
⑩
object::instanceMethod
(対象:一般的な実例方法)――直接パラメータを補えばいい❷
Class::instanceMethod
(クラス::一般的なインスタンスメソッド)--クラスは暗黙的なパラメータとして、パラメータを再補完します.⑪
Class::staticMethod
(類:静的方法)――直接パラメータを補えばいい1.
System.out::println => x->System.out.println(x)
separator::equals => x->separator.equals(x)
2.
String::trim => x->x.trim()
String::concat => (x, y)->x.concat(y)
3.
Integer::valueOf => x->Integer.valueOf(x)
Integer::sum => (x, y)->Integer.sum(x, y)
理解:
s->s.length()==3
では、「長さを取る」と「比較する」という2つの動作を完了する必要があります.それを方法として省略するのは難しいです.【 】
" "———— " new "
Integer::new lambda : x->new Integer(x)
String[]::new lambda : x->new String[x]
ジルコニウムlambda式の変数作用ドメイン
public class Demo {
public static String create(Supplier<String> sub){
return sub.get();
}
public static void main(String[] args) {
String str = "loli";
String s = create(()->{
return str;
});
System.out.println(s); // "loli"
}
}
>>> lambda , lambda ————str
>>> ,lambda , ()-{}
実際、lambda式の役割ドメインは、括弧ではなく、括弧の外周です.
()->{}
の言葉と同じ役割ドメインにあります.この例ではstrはlambda式で定義されるのではなく、lambda式によってキャプチャされる.このキャプチャは、より厳格なクローズドパッケージです.
閉パッケージなのは、lambda式が外部から変数をキャプチャして自分のものにし、自分のカッコに保存しているからです.
より厳密に言えば、Javaがキャプチャを要求するこの変数は、事実最終変数(effectively final)でなければならないからです.キャプチャする前に、周辺の役割ドメインで「乱動」することはできません.取得後も変更できません
public class Demo {
public static int create(Supplier<Integer> sub) {
return sub.get();
}
public static void main(String[] args) { // lambda
for (int i = 0; i < 10; i++) {
create(() -> i); // Error: , " "
}
int n = 3;
create(() -> n++); // Error: ,
}
}
♪ End
♬ By a Lolicon