JDK8の新機能 [Lambda、関数型インターフェース、Stream]

Properties集合

1.1 概要

java.util.Propertiesから継承され、Hashtable永続的なプロパティ セットを表します。キーと値の構造を使用してデータを保存します。各キーとそれに対応する値は文字列です。このクラスは多くの Java クラスでも使用され、たとえば、システム プロパティを取得する場合、System.getPropertiesメソッドはPropertiesオブジェクトを返します。

1.2 プロパティクラス

施工方法

  • public Properties(): 空のプロパティ リストを作成します。

ストリームに関連するメソッド

  • public void load(InputStream inStream): バイト入力ストリームからキーと値のペアを読み取ります。

  • public void load(Reader reader): 文字入力ストリームからキーと値のペアを読み取ります。

バイト入力ストリームはパラメータで使用され、ストリーム オブジェクトを通じてファイルに関連付けることができ、テキスト内のデータをロードできます。テキストデータ形式:

ファイル名=a.txt
長さ=209385038
場所=D:\a.txt

コードのデモをロードします。

public static void main(String[] args) throws FileNotFoundException { 
    // プロパティ セット オブジェクトを作成します
    Properties pro = new Properties(); 
    // テキスト内の情報をプロパティ セットにロードします
    pro.load(new FileInputStream("read. txt")) ; 
    // コレクションを走査して出力
    Set<String> strings = pro.stringPropertyNames(); 
    for (String key : strings ) { 
        System.out.println(key+" -- "+pro.getProperty(key) ); 
    } 
}

ラムダ式

2.1 関数型プログラミングのアイデアの概要

数学では、関数は入力と出力、つまり「何かをどうするか」を含む一連の計算スキームです。相対的に言えば、オブジェクト指向は「物事はオブジェクトの形式で行われなければならない」ことに重点を置きすぎますが、関数型思考はオブジェクト指向の複雑な構文を無視しようとし、どの形式で実行するかではなく、何を実行するかを強調します

y = 2*x + 5;

パブリック クラス A {

public int メソッド(int x) {

2*x + 5 を返します。

}

}

A a = 新しい A();

int y = a.method(5);

java.util.Scannerクラス

スキャナー sc = ...;

int num = sc.nextInt();

どのようにするかではなく、何をするか

本当に匿名の内部クラス オブジェクトを作成したいのでしょうか? いいえ。これを行うためだけにオブジェクトを作成する必要があります。私たちが本当にやりたいことは、runメソッド本体のコードをThreadクラスに渡すことです。

コードの一部を渡します- それが私たちがここにいる本当の目的です。また、オブジェクトの作成は、オブジェクト指向構文の制限により取らなければならない手段にすぎません。それで、もっと簡単な方法はありますか?「どうするか」から「何をするか」という本質に焦点を戻すと、目的をよりよく達成できれば、プロセスや形は重要ではないことがわかります。

2.2 ラムダの最適化

タスクを完了するためにスレッドを開始する必要がある場合、java.lang.Runnable通常、タスクの内容はインターフェイスを通じて定義され、java.lang.Threadスレッドはクラスを使用して開始されます。

従来の書き方、コードは次のとおりです。

public class Demo03Thread { 
    public static void main(String[] args) { 
        new Thread(new Runnable() { 
            @Override 
            public void run() { 
                System.out.println("マルチスレッドタスクの実行!"); 
            } 
        }) .start(); 
    } 
}

「すべてはオブジェクトである」という考え方に沿って、このアプローチは理解できます。まず、Runnableタスクの内容を指定するためにインターフェイスの匿名の内部クラス オブジェクトを作成し、それをスレッドに渡して開始します。

コード分​​析:

Runnable匿名内部クラスの使用法について、いくつかのことを分析できます。

  • ThreadこのクラスはRunnableパラメータとしてインターフェイスを必要とし、抽象runメソッドはスレッド タスクの内容を指定するために使用されるコアです。

  • 指定されたrunメソッド本体については、インターフェイスの実装クラスが必須である必要がありますRunnable

  • 実装クラスを定義する手間を省くにはRunnableImpl匿名の内部クラスを使用する必要があります。

  • 抽象runメソッドはオーバーライドする必要があるため、メソッド名、メソッドのパラメータ、およびメソッドの戻り値を再度記述する必要があり、間違って記述することはできません。

  • 実際には、メソッド本体のみがキーであるようです

ラムダ式の書き方、コードは以下の通りです。

Java 8 の新しい構文の助けを借りて、Runnable上記のインターフェイスの匿名内部クラス記述メソッドは、より単純な Lambda 式を通じて同等になります。

public class Demo04LambdaRunnable { 
    public static void main(String[] args) { 
        new Thread(() -> System.out.println("マルチスレッドタスク実行!")).start(); // スレッドを開始します
    } 
}

このコードは先ほどの実行結果とまったく同じであり、コンパイル レベル 1.8 以上で渡せます。これはコードのセマンティクスからわかります。スレッドを開始し、スレッド タスクの内容がより簡潔な形式で指定されます。

「インターフェイス オブジェクトを作成しなければならない」という制約もなくなり、「抽象メソッドの上書きと書き換え」の負担もなくなり、とても簡単です。

2.3 ラムダの形式

標準形式:

Lambda ではオブジェクト指向のルールや規制が省略されており、形式は3 つの部分で構成されます。

  • いくつかのパラメータ

  • 矢印

  • コードの一部

ラムダ式の標準形式は次のとおりです。

(パラメータ型パラメータ名) -> { コード文 }

フォーマット仕様:

  • 括弧内の構文は従来のメソッドのパラメータ リストと同じです。パラメータがない場合は空白のままにし、複数のパラメータはカンマで区切ります。

  • ->は新しく導入された文法形式で、ポインティング アクションを表します。

  • 中括弧内の構文は、基本的に従来のメソッド本体の要件と一致しています。

匿名内部クラスとラムダの比較:

new Thread(new Runnable() { 
            @Override 
            public void run() { 
                System.out.println("マルチスレッドタスク実行!"); 
            } 
}).start();

コードを注意深く分析してください。Runnableインターフェイスにはrunメソッド定義が 1 つだけあります。

  • public abstract void run();

つまり、物事を行うための計画 (実際には方法) が策定されました。

  • パラメーターなし: シナリオを実行するために条件は必要ありません。

  • 戻り値なし: プログラムは結果を生成しません。

  • コード ブロック(メソッド本体): ソリューションの特定の実行ステップ。

同じセマンティクスがLambda構文にも反映されていますが、構文はより単純です。

() -> System.out.println("マルチスレッドタスク実行!")
  • 前の括弧のペアはrunメソッドのパラメーター (なし) であり、条件が必要ないことを意味します。

  • 中央の矢印は、前のパラメータを次のコードに渡すことを表します。

  • 次の出力ステートメントはビジネス ロジック コードです。

パラメータと戻り値:

次の例は、java.util.Comparator<T>インターフェイスの使用シナリオ コードを示しています。抽象メソッドは次のように定義されています。

  • public abstract int compare(T o1, T o2);

オブジェクトの配列を並べ替える必要がある場合、Arrays.sortメソッドにはComparator並べ替え規則を指定するインターフェイス インスタンスが必要です。2 つのメンバー変数Personを持つクラスがあるとしますString nameint age

public class Person { 
    private String name; 
    private int age; 
    
    // コンストラクター、toString メソッド、Getter Setter を省略
}

伝統的な書き方

従来のコードを使用してPerson[]配列を並べ替える場合、コードは次のように記述されます。

public class Demo05Comparator { 
    public static void main(String[] args) { 
        // オブジェクト配列
        Person[] array = { new Person("Guli Nazha", 19), new Person("Di Lie Bar", 18), new Person ("Malzaha", 20) }; 
//
        匿名内部クラス
        Comparator<person> comp = new Comparator<person>() { 
            @Override 
            public int Compare(Person o1, Person o2 ) { 
                return o1.getAge() - o2 .getAge(); 
            } 
        }; 
        Arrays.sort(array, comp); // 2 番目のパラメータは並べ替えルールであり、これは Comparator インターフェイスのインスタンスです
(
        person person : array) { 
            System.out.println(person) ; 
        } 
    }
}

このアプローチは、オブジェクト指向の考え方では「自然」であるように見えます。このうち、Comparatorインターフェースのインスタンス(匿名内部クラスを使用)は、「年齢順に最年少から最年長まで」というソートルールを表しています。

コード分​​析

上記のコードが実際に何をしているのかを見てみましょう。

  • ソートの場合、Arrays.sortメソッドにはソート ルール、つまりComparatorインターフェイスのインスタンスが必要であり、抽象メソッドがcompare鍵となります。

  • 指定されたcompareメソッド本体については、インターフェイスの実装クラスが必須である必要がありますComparator

  • 実装クラスを定義する手間を省くにはComparatorImpl匿名の内部クラスを使用する必要があります。

  • 抽象compareメソッドはオーバーライドする必要があるため、メソッド名、メソッドのパラメータ、およびメソッドの戻り値を再度記述する必要があり、間違って記述することはできません。

  • 実際、キーとなるのはパラメータとメソッド本体だけです

ラムダ書き込み

public class Demo06ComparatorLambda { 
    public static void main(String[] args) { 
        Person[] array = { 
            new Person("Guli Nazha", 19), 
            new Person("Dilraba", 18), 
            new Person( "Malzahar", 20 ) }; 
Arrays.sort
        (array, (人 a, 人 b) -> { 
            return a.getAge() - b.getAge(); 
        }); 
for
        (人 person : array ) { 
            System.out.println (人); 
        } 
    } 
}

形式を省略します:

ルールの省略

Lambda 標準形式に基づく、省略記号の使用ルールは次のとおりです。

  1. 括弧内のパラメータのタイプは省略できます。

  2. 括弧内のパラメータが 1 つだけの場合は、括弧を省略できます。

  3. 中括弧内にステートメントが 1 つだけある場合は、戻り値の有無に関係なく、中括弧、return キーワード、およびステートメントのセミコロンを省略できます。

注: これらの省略ルールを習得したら、この章の冒頭にあるマルチスレッドのケースを適宜見直してください。

推定または省略できる

Lambda は「どうやって行うか」ではなく「何を行うか」を重視するため、導き出される情報は省略できます。たとえば、上記の例では Lambda の省略を使用することもできます。

実行可能なインターフェイスの簡略化: 
1. () -> System.out.println("マルチスレッド タスクの実行!")
コンパレータ インターフェイスの簡略化: 
2. Arrays.sort(array, (a, b) -> a.getAge() - b .getAge());

2.4 Lambda の前提条件

Lambda の構文は非常に簡潔で、オブジェクト指向の複雑さの制約がありません。ただし、使用する場合には特別な注意が必要な問題がいくつかあります。

  1. Lambda を使用するにはインターフェイスが必要で、そのインターフェイスには抽象メソッドが 1 つだけ必要です。JDK 組み込みインターフェイスであってもRunnableカスタムComparatorインターフェイスであっても、Lambda はインターフェイス内の抽象メソッドが存在し、一意である場合にのみ使用できます。

  2. Lambda を使用するには、メソッドのパラメータとしてインターフェイスが必要です。つまり、Lambda をインターフェイスのインスタンスとして使用できるように、メソッドのパラメーターまたはローカル変数の型は Lambda に対応するインターフェイスの型である必要があります。

注: 抽象メソッドが 1 つだけあるインターフェイスは、「関数インターフェイス」と呼ばれます。

機能インターフェイス

3.1 概要

Java の関数インターフェイスは、1 つだけの抽象メソッドを持つインターフェイスを指します。

関数型インターフェイス、つまり関数型プログラミングのシナリオに適したインターフェイス。Java における関数型プログラミングの具体化は Lambda であるため、関数型インターフェイスは Lambda に適用できるインターフェイスです。インターフェイスに抽象メソッドが 1 つだけ存在することを保証することによってのみ、Java の Lambda をスムーズに推定できます。

備考: アプリケーションレベルから見ると、Java の Lambda は匿名内部クラスの簡略化された形式とみなすことができますが、この 2 つは原理的に異なります。

フォーマット

インターフェイスに抽象メソッドが 1 つだけあることを確認してください。

修飾子インターフェース インターフェース名 { 
    public abstract return value type メソッド名 (オプションのパラメータ情報); 
    // その他の非抽象メソッドの内容
}

インターフェイス内の抽象メソッドはpublic abstract省略できるため、関数型インターフェイスの定義は非常に簡単です。

パブリックインターフェイスMyFunctionalInterface {     
    void myMethod(); 
}

FunctionalInterface アノテーション

@Overrideアノテーションの役割と同様に、Java 8 では、関数インターフェース専用の新しいアノテーションが導入されています@FunctionalInterfaceこのアノテーションはインターフェースの定義で使用できます。

@FunctionalInterface
パブリックインターフェイスMyFunctionalInterface { 
    void myMethod(); 
}

このアノテーションを使用してインターフェイスを定義すると、コンパイラはインターフェイスに抽象メソッドが 1 つだけあるかどうかを強制的にチェックします。そうでない場合はエラーが報告されます。ただし、このアノテーションを使用しなくても、関数インタフェースの定義を満たしていれば関数インタフェースであるため、同様に使用できます。

3.2 一般的に使用される機能インターフェイス

JDK は、Lambda の一般的な使用シナリオを充実させるために、一般的に使用される関数インターフェイスを多数提供しており、それらは主にjava.util.functionパッケージで提供されます。前述のMySupplierインターフェイスは、機能インターフェイスをシミュレートしていますjava.util.function.Supplier<T>実際には他にもたくさんありますが、最も単純なインターフェイスと使用例を以下に示します。

サプライヤーインターフェース

java.util.function.Supplier<T>「供給」を意味するインターフェース。対応する Lambda 式は、ジェネリック型に準拠したオブジェクト データを「提供」する必要があります。

抽象メソッド: get

パラメーターなしのメソッドが 1 つだけ含まれています: T get()ジェネリックパラメータで指定されたタイプのオブジェクトデータを取得するために使用されます。

public class Demo08Supplier { 
    private static String getString(Supplier<String> function) { 
        return function.get(); 
    } 
public
    static void main(String[] args) { 
        String msgA = "Hello"; 
        文字列 msgB = "世界"; 
        System.out.println(getString(() -> msgA + msgB)); 
    } 
}

配列要素の最大値を求める

Supplierインターフェイスをメソッドのパラメーターの型として使用し、ラムダ式を通じて int 配列の最大値を見つけます。ヒント: インターフェイス ジェネリックスの場合は、java.lang.Integerクラスを使用してください。

コード例:

public class DemoIntArray { 
    public static void main(String[] args) { 
        int[] array = { 10, 20, 100, 30, 40, 50 }; 
        printMax(() -> { 
            int max = array[0]; 
            for (int i = 1; i < array.length; i++) { 
                if (array[i] > max) {               
                    max = array[i]; 
                } 
            }
            最大値を返します; 
        }); 
    } 
private
    static void printMax(Supplier<Integer> サプライヤー) { 
        int max =supplier.get(); 
        System.out.println(max); 
    } 
}

消費者向けインターフェース

java.util.function.Consumer<T>インターフェイスはその逆で、データを生成する代わりにデータを消費し、そのデータ型はジェネリック パラメーターによって決定されます。

抽象メソッド: accept

Consumerインターフェイスにはvoid accept(T t)、指定されたジェネリック型のデータを消費するための抽象メソッドが含まれています。基本的な使い方は次のとおりです。

// 文字列を指定します。大文字で使用してください
import java.util.function.Consumer; 
public
class Demo09Consumer { 
    public static void main(String[] args) { 
        String str = "Hello World"; 
        // 1. Lambda式の標準形式
        fun(str,(String s)->{ 
            System.out.println(s.toUpperCase()); 
        }); 
        //2. ラムダ式の簡略化形式
        fun(str,s-> System.out.println (s.toUpperCase()); 
    } 
    /*
        メソッドを定義し、パラメータとして Consumer インターフェイスを使用します
        fun メソッド: String 型の変数を消費します
     */ 
    public static void fun(String s,Consumer<String> con) { 
        con .accept(s); 
    } 
}

関数インターフェース

java.util.function.Function<T,R>インターフェースは、ある種類のデータを元に別の種類のデータを取得するために使用され、前者を事前条件、後者を事後条件と呼びます。inとoutがあるので「Function」と呼びます。

抽象メソッド: apply

Functionインターフェイスの主な抽象メソッドは次のとおりですR apply(T t)。 T 型のパラメータに従って R 型の結果を取得します。使用されるシナリオ:String型からInteger型への変換など。

//String 数値を与えられたので、それを int 数値に変換します
public class Demo11FunctionApply { 
    private static void method(Function<String, Integer> function, Str str) { 
        int num = function.apply(str); 
        System.out .println(num + 20); 
    } 
public
    static void main(String[] args) { 
        method(s -> Integer.parseInt(s) , "10"); 
    } 
}

述語インターフェース

ブール値の結果を得るために、特定のタイプのデータを判断する必要がある場合があります。java.util.function.Predicate<T>ここでインターフェースを使用できます。

抽象メソッド: テスト

Predicateインターフェイスには 1 つの抽象メソッドが含まれていますboolean test(T t)条件付き判断のシナリオで使用されます。条件付き判断の基準は、受信した Lambda 式のロジックです。文字列の長さが 5 より大きい限り、非常に長いとみなされます。

//1. 演習: 文字列の長さが 5 より大きいかどうかを判断します
//2. 演習: 文字列に「H」が含まれているかどうかを判断します
public class Demo15PredicateTest { 
    private static void method(Predicate<String> predicate,String str) { 
        boolean VeryLong = predicate .test(str); 
        System.out.println("文字列は非常に長いですか: " + VeryLong); 
    } 
public
    static void main(String[] args) { 
        method(s -> s.length() > 5, "HelloWorld"); 
    } 
}

第4章 ストリーム

Java 8 では、Lambda によってもたらされた関数型プログラミングのおかげで、既存のコレクション ライブラリの既存の欠点を解決するために新しい Stream コンセプトが導入されました。

4.1 はじめに

従来のコレクションのマルチステップ トラバーサル コード

ほとんどすべてのコレクション (CollectionインターフェイスやMapインターフェイスなど) は、直接または間接的なトラバーサル操作をサポートします。必要な追加、削除、取得に加えて、コレクション内の要素を操作する必要がある場合、最も一般的なのはコレクションの走査です。例えば:

public class Demo10ForEach { 
    public static void main(String[] args) { 
        List<String> list = new ArrayList<>(); 
        list.add("Zhang Wiji"); 
        list.add("Zhou Zhiruo");
        リスト。 add( "趙敏"); 
        list.add("張強"); 
        list.add("張三峰"); 
        for (文字列名 : list) { 
            System.out.println(name); 
        } 
    }   
}

これは非常に単純なコレクションの走査操作です。コレクション内の各文字列を出力します。

ループのデメリット

Java 8 の Lambda を使用すると、どのように実行するか(How)ではなく、何を実行するか(What)に重点を置くことができます。これは以前に内部クラスと比較されました。上記のコード例を詳しく見てみると、次のことがわかります。

  • for ループの構文は「やり方」です。

  • for ループのループ本体は「何をするか」です。

なぜループを使用するのでしょうか? トラバースのせいで。しかし、ループすることが唯一の横断方法なのでしょうか? トラバーサルとは、最初から最後まで順番に処理されるループではなく、各要素が 1 つずつ処理されることを意味します前者は目的であり、後者は方法です。

想像してみてください。コレクション内の要素をフィルターしたい場合は次のようになります。

  1. 条件 1 に従って、セット A をサブセット Bにフィルターします。

  2. 次に、2 番目の条件に従ってそれをサブセット Cにフィルタリングします。

じゃあ何をすればいいの?Java 8 より前の方法は次のとおりです。

このコードには 3 つのループが含まれており、それぞれに異なる効果があります。

  1. まず、Zhang という姓を持つすべての人々をフィルタリングします。

  2. 次に、名前に 3 文字が含まれる人をスクリーニングします。

  3. 最後に、結果を印刷します。

public class Demo11NormalFilter { 
    public static void main(String[] args) { 
        List<String> list = new ArrayList<>(); 
        list.add("张无忌"); 
        list.add("周芷若"); 
        list.add("赵敏"); 
        list.add("张强"); 
        list.add("张三丰"); 
List
        <String> zhangList = new ArrayList<>(); 
        for (文字列名 : list) { 
            if (name.startsWith("张")) { 
                zhangList.add(name); 
            List 
        <String> 
shortList
        = new ArrayList<>(); 
        for (文字列名:
                shortList.add(名前); 
            } 
        } 
for
        (文字列名 : shortList) { 
            System.out.println(name); 
        } 
} 
    }

コレクション内の要素を操作する必要がある場合は、常にループ、ループ、リサイクルする必要があります。これは当然のことだと思われていますか?いいえ。ループは物事を行うための手段であり、目的ではありません。一方、線形ループを使用すると、ループを 1 回しか通過できないことになります。もう一度横断したい場合は、別のループを使用して最初から開始するしかありません。

それでは、Lambda の派生ストリームはどのようなよりエレガントな方法を私たちにもたらしてくれるでしょうか?

ストリームを記述するためのより良い方法

Java 8 の Stream API を使用して何がエレガントになっているのかを見てみましょう。

public class Demo12StreamFilter { 
    public static void main(String[] args) { 
        List<String> list = new ArrayList<>(); 
        list.add("Zhang Wiji"); 
        list.add("Zhou Zhiruo");
        リスト。 add( "趙敏"); 
        list.add("張強"); 
        list.add("張三峰"); 
list.stream
        () 
            .filter(s -> s.startsWith("張")) 
            。 filter(s - > s.length() == 3) 
            .forEach(s -> System.out.println(s)); 
    } 
}

コードの文字通りの意味を直接読み取ると、無関係なロジック メソッドのセマンティクスを完全に表示できます。つまり、ストリームを取得し、姓をフィルタリングし、長さを 3 にフィルタリングし、1 つずつ出力しますこのコードには線形ループやその他の走査アルゴリズムの使用が反映されておらず、実際に実行したい内容がより適切にコードに反映されています。

4.2 ストリーム思考の概要

注: 従来の IO ストリームに固有の印象を一時的に忘れてください。

全体として、ストリーミングのアイデアは工場現場の「生産ライン」に似ています。

 

複数の要素を操作する必要がある場合 (特に複数ステップの操作)、パフォーマンスと利便性を考慮して、まず「モデル」のステップ計画を作成し、その計画に従って実行する必要があります。

 

この図は、フィルタリング、マッピング、スキップ、カウントなどの多段階の操作を示したコレクション要素の処理スキームであり、このスキームは「関数モデル」です。図内の各ボックスは「フロー」であり、指定されたメソッドを呼び出すことで、あるフロー モデルから別のフロー モデルに変換できます。そして一番右の3番が最終結果です。

filtermapおよびここにskipあるものはすべて関数モデル上で動作しており、コレクション要素は実際には処理されません。最後のメソッドcountが実行される場合にのみ、モデル全体が指定された戦略に従って操作を実行します。これは、Lambda の遅延実行機能の恩恵を受けます。

備考: 「ストリーム」は実際にはコレクション要素の機能モデルであり、コレクションやデータ構造ではなく、要素 (またはそのアドレス値) を格納しません。

4.3 ストリームモードの取得

java.util.stream.Stream<T>Java 8 で新たに追加された、最もよく使われるストリーム インターフェイスです。(これは機能的なインターフェイスではありません。)

ストリームの取得は非常に簡単で、一般的な方法がいくつかあります。

  • すべてのCollectionコレクションはstreamデフォルトの方法でストリームを取得できます。

  • Streamインターフェースの静的メソッドは、of配列に対応するストリームを取得できます。

方法 1: コレクションに従ってストリームを取得する

public default Stream<E> stream(): Collection コレクション オブジェクトに対応する Stream ストリーム オブジェクトを取得します。

まず、ストリームを取得するためのjava.util.Collectionデフォルト メソッドがインターフェイスに追加され、そのすべての実装クラスがストリームを取得できるようになります。stream

import java.util.*; 
import java.util.stream.Stream; 
/*
    ストリームの取得方法
1.
    コレクションストリームのメソッド
        stream() 2.
    コレクションストリームの静的メソッド
        of(T...t)ストリーム インターフェイスはストリームに送信します
 */ 
public class Demo13GetStream { 
    public static void main(String[] args) { 
        List<String> list = new ArrayList<>(); 
        // ... 
        Stream<String> stream1 = list.stream( ); 
Set
        <String> set = new HashSet<>(); 
        // ... 
        Stream<String> stream2 = set.stream(); 
    } 
}

方法 2: 配列に従ってストリームを取得する

コレクションやマップではなく配列を使用している場合は、配列オブジェクトにデフォルトのメソッドを追加することができないため、Streamインターフェイスで静的メソッドが提供されますof。これは非常に簡単に使用できます。

import java.util.stream.Stream; 
public
class Demo14GetStream { 
    public static void main(String[] args) { 
        String[] array = { "Zhang Wiji"、"Zhang Cuishan"、"Zhang Sanfeng"、"Zhang Yiyuan" };
        ストリーム <String> stream = Stream.of(array); 
    } 
}

備考:ofメソッドのパラメータは実際には可変パラメータであるため、配列がサポートされます。

4.4 一般的な方法

ストリーム モデルの操作は非常に豊富で、ここでは一般的に使用される API をいくつか紹介します。これらの方法は次の 2 つのタイプに分類できます。

  • 最終メソッドStream: 戻り値の型はインターフェイス独自の型のメソッドではなくなったため、StringBuilderそのようなチェーン呼び出しはサポートされなくなりました。このセクションでは、最終的なメソッドにcountは と のforEachメソッドが含まれます。

  • 非終端メソッド: 戻り値の型は引き続きStreamインターフェイス独自の型のメソッドであるため、チェーン呼び出しがサポートされます。(final メソッドを除き、残りのメソッドは非終端メソッドです。)

備考: このセクション以外のメソッドについては、API ドキュメントをご自身で参照してください。

forEach : 1つずつ処理します

メソッド名は と呼ばれますがforEach、for ループ内の "for-each" ニックネームとは異なり、このメソッドは要素ごとの消費アクションがストリーム内で順番に実行されることを保証しません

void forEach(Consumer<? super T> アクション);

このメソッドはConsumerインターフェイス関数を受け取り、各ストリーム要素を処理のために関数に渡します。例えば:

import java.util.stream.Stream; 
public
class Demo15StreamForEach { 
    public static void main(String[] args) { 
        Stream<String> stream = Stream.of("big baby", "two baby", "three baby", "4 人の赤ちゃん"、"5 人の赤ちゃん"、"6 人の赤ちゃん"、"7 人の赤ちゃん"、"おじいちゃん"、"スネーク エッセンス"、"サソリ エッセンス"); //Stream<String> stream = Stream.of("Zhang Wiji 
        " , "張三峰", "周志若"); 
        stream.forEach((String str)->{System.out.println(str);}); 
    } 
}

ここで、ラムダ式は(String str)->{System.out.println(str);}Consumer 関数インターフェイスの例です。

フィルター: フィルター

メソッドを使用して、filterストリームを別のサブセット ストリームに変換できます。メソッド宣言:

Stream<T> フィルター(Predicate<? super T> 述語);

このインターフェイスは、Predicate関数インターフェイス パラメーター (Lambda の場合もあります) をフィルター条件として受け取ります。

基本的な使い方

Stream ストリームのfilterメソッドで使用される基本コードは次のとおりです。

public class Demo16StreamFilter { 
    public static void main(String[] args) { 
        Stream<String>original = Stream.of("Zhang Wiji", "Zhang Sanfeng", "Zhou Zhiruo"); 
        Stream<String> result =original.filter ((String s) -> {return s.startsWith("Zhang");}); 
    } 
}

ここでは、フィルタ条件はラムダ式で指定されています。姓は Zhang である必要があります。

count: 統計の数

古いコレクションCollectionのメソッドと同様にsize、ストリームには、countその中の要素の数をカウントするメソッドが用意されています。

長いカウント();

このメソッドは、要素の数を表す long 値を返します (古いコレクションのような int 値ではなくなりました)。基本的な使い方:

public class Demo17StreamCount { 
    public static void main(String[] args) { 
        Stream<String>original = Stream.of("Zhang Wiji", "Zhang Sanfeng", "Zhou Zhiruo"); 
        Stream<String> result =original.filter (s - > s.startsWith("Zhang")); 
        System.out.println(result.count()); // 2 
    } 
}

制限: 最初の数個を取る

limitこのメソッドはストリームをインターセプトし、最初の n 個のストリームのみを使用できます。メソッドのシグネチャ:

Stream<T> limit(long n): Stream ストリーム オブジェクトの最初の n 要素を取得し、新しい Stream ストリーム オブジェクトを返します。

パラメータはlong型です。コレクションの現在の長さがパラメータより大きい場合はインターセプトされ、それ以外の場合は操作は実行されません。基本的な使い方:

import java.util.stream.Stream; 
public
class Demo18StreamLimit { 
    public static void main(String[] args) { 
        Stream<String> Original = Stream.of("Zhang Wiji", "Zhang Sanfeng", "Zhou Zhiruo") ; 
        Stream< String> result =original.limit(2); 
        System.out.println(result.count()); // 2 
    } 
}

スキップ: 最初のいくつかをスキップします

skip最初のいくつかの要素をスキップしたい場合は、インターセプト後に新しいストリームを取得するメソッドを使用できます。

Stream<T> Skip(long n): Stream オブジェクトの最初の n 要素をスキップし、新しい Stream オブジェクトを返します。

ストリームの現在の長さが n より大きい場合は、最初の n 個のストリームがスキップされ、それ以外の場合は、長さ 0 の空のストリームが取得されます。基本的な使い方:

import java.util.stream.Stream; 
public
class Demo19StreamSkip { 
    public static void main(String[] args) { 
        Stream<String> Original = Stream.of("Zhang Wiji", "Zhang Sanfeng", "Zhou Zhiruo") ; 
        Stream< String> result =original.skip(2); 
        System.out.println(result.count()); // 1 
    } 
}

concat: 組み合わせ

2 つのストリームがあり、それらを 1 つのストリームにマージする場合は、Streamインターフェイスの静的メソッドを使用できますconcat

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b): パラメーター リスト内の 2 つの Stream オブジェクト a と b を新しい Stream オブジェクトにマージします。

備考: これは静的メソッドであり、java.lang.Stringその中のconcatメソッドとは異なります。

このメソッドの基本的な使用コードは次のとおりです。

import java.util.stream.Stream; 
public
class Demo20StreamConcat { 
    public static void main(String[] args) { 
        Stream<String> streamA = Stream.of("张无忌"); 
        Stream<String> streamB = Stream.of("张翠山"); 
        Stream<String> 結果 = Stream.concat(streamA, streamB); 
    } 
}

4.5 ストリームの包括的なケース

チーム内の複数のメンバーの名前を保存するコレクションが 2 つありArrayList、従来の for ループ (または拡張された for ループ) を使用して次の手順を順番に実行する必要があります。

  1. 最初のチームには、名前が 3 文字のメンバーの名前のみが必要です。

  2. 最初のチームが審査された後、上位 3 名のみが必要とされます。

  3. 2 番目のチームには、Zhang という姓のメンバーの名前だけが必要です。

  4. 2 番目のチームが審査された後、上位 2 人は必要ありません。

  5. 2 つのチームを 1 つのチームに統合します。

  6. チーム全体の名前情報を出力します。

2 つのチーム (セット) のコードは次のとおりです。

public class Demo21ArrayListNames { 
    public static void main(String[] args) { 
        List<String> one = new ArrayList<>(); 
        one.add("Dilraba"); 
        one.add("Song Yuanqiao"); 
        one .add ("蘇星河"); 
        one.add("老子"); 
        one.add("荘子"); 
        one.add("孫"); 
        one.add("宏気功"); 
List
        < String> two = new ArrayList<>(); 
        two.add("Guli Nazha"); 
        two.add("張無忌"); two.add("張三峰"); two.add("趙立英
        "); two.add("張無忌"); two.add("張三峰"); 
        two.add("趙立英"); 
        two add("張二溝"); 
        2つ。add("張天愛"); 
        two.add("張三"); 
        // .... 
    } 
}

伝統的な方法

for ループを使用したサンプルコード:

get(i)); 
}
        
        // 2 番目のチームには Zhang という名前のメンバーの名前だけが必要です; 
        List<String> twoA = new ArrayList<>(); 
        for (String name : two) { 
            if (name.startsWith("Zhang")) { 
                twoA. add( name); 
            } 
        } 
//
        2 番目のチームが審査された後、最初の 2 人は不要です; 
        List<String> twoB = new ArrayList<>(); 
        for (int i = 2; i < twoA.size (); i++) { 
            twoB.add(twoA.get(i)); 
        } 
//
        2 つのチームを 1 つのチームにマージします; 
        List<String> totalNames = new ArrayList<>(); 
        totalNames.addAll(oneB); 
        totalNames .addAll(twoB );         
//
        チーム全体の名前情報を出力します。
        for (文字列名 : totalNames) { 
            System.out.println(name); 
        } 
} 
    }

操作の結果は次のようになります。

宋元橋・
蘇星河
・ホン気功・張
二溝
・張天愛
・張三

ストリームモード

同等の Stream ストリーミング コードは次のとおりです。

public class Demo23StreamNames { 
    public static void main(String[] args) { 
        List<String> one = new ArrayList<>(); 
        // ... 
List
        <String> two = new ArrayList<>(); 
        // 。 .. 
//
        最初のチームには 3 文字のメンバー名のみが必要です; 
        // 最初のチームにはスクリーニング後の最初の 3 人のメンバーのみが必要です; 
        Stream<String> streamOne = one.stream().filter(s -> s .length () == 3).limit(3); 
//
        2 番目のチームには、Zhang という名前のメンバーの名前だけが必要です; 
        // 2 番目のチームの審査後、最初の 2 人は必要ありません; 
        Stream<String> streamTwo = two.stream ().filter(s -> s.startsWith("Zhang")).skip(2); // 
        2 つのチームを 1 つのチームに
マージします
        // 名前に基づいて Person オブジェクトを作成します; 
        // 印刷チーム全体の人物オブジェクト情報。
        Stream.concat(streamOne, streamTwo).forEach(s->System.out.println(s)); 
    } 
}

まったく同じように動作します。

宋元橋・
蘇星河
・ホン気功・張
二溝
・張天愛
・張三

4.6 関数の連結と確定方法

上記で紹介したさまざまなメソッドのうち、戻り値がStreamインターフェイスのままであるものは関数結合メソッドStreamであり、連鎖呼び出しをサポートしていますが、戻り値がインターフェイスでなくなっているものはターミナルメソッドであり、連鎖呼び出しはサポートされなくなりました。以下の表に示すように:

メソッド名 メソッドの役割 メソッドの種類 チェーンコールをサポートするかどうか
カウント 統計 終わり いいえ
それぞれに 一つ一つ処理していきます 終わり いいえ
フィルター フィルター 関数のスプライシング はい
限界 最初の数枚を取り上げます 関数のスプライシング はい
スキップ 最初のいくつかを飛ばしてください 関数のスプライシング はい
連結 組み合わせ 関数のスプライシング はい

第 5 章 メソッドリファレンス

5.1 概要とメソッドのリファレンス

Lambda 式を適用し、accept メソッドで文字列を受け取る単純な関数インターフェイスを見てみましょう。目的は文字列を出力して表示することです。Lambda 経由でそれを使用するコードは非常に簡単です。

public class DemoPrintSimple { 
    private static void printString(Consumer<String> data, String str) { 
        data.accept(str); 
    public static void main(String[] args) { 
        printString(s -> System.out.println(s), "Hello World") 
    ; 
    } 
}

実装済みの println メソッドはラムダ式内で呼び出されるため、ラムダ式の代わりにメソッド参照を使用できます。

記号の意味: ::

シンボルの説明:二重コロンはメソッド参照演算子であり、これが配置されている式はメソッド参照と呼ばれます。

適用シナリオ: Lambda で表現される関数スキームが特定のメソッドの実装に既に存在する場合、メソッド参照を使用できます。

上の例のように、System.out オブジェクトには println(String) メソッドがあり、これがまさに必要なものであり、Consumer インターフェイスをパラメータとして使用する場合、次の 2 つの書き込みメソッドは完全に同等です。

  • ラムダ式の書き方: s -> System.out.println(s); パラメータを取得したら、System.out.println メソッドに渡して Lambda で処理します。

  • メソッド参照の記述: System.out::println は、Lambda を System.out の println メソッドに直接置き換えます。

導出と省略: Lambda を使用する場合、「導出可能は省略可能」の原則に従って、パラメーターの型を指定する必要はなく、オーバーロードされた形式を指定する必要もありません。それらは自動的に推定されます。また、メソッド参照を使用すると、コンテキストに応じてそれを推測することもできます。関数インターフェイスは Lambda の基礎であり、メソッド参照は Lambda の簡略化された形式です。

5.2 メソッド参照の簡略化

過去を「引用」するだけです。

public class DemoPrintRef { 
    private static void printString(Consumer<String> data, String str) { 
        data.accept(str); 
    public static void main(String[] args) { 
        printString(System.out::println, "HelloWorld") 
    ; 
    } 
}

二重コロンは「メソッド参照::」と呼ばれ、二重コロンは新しい構文であることに注意してください。

5.3 拡張参照方法

オブジェクト名 - 参照メンバーメソッド

これは最も一般的な使用法であり、前の例と同じです。メンバー メソッドがクラスにすでに存在する場合、オブジェクト名でメンバー メソッドを参照できます。コードは次のとおりです。

public class DemoMethodRef { 
     public static void main(String[] args) { 
        String str = "hello"; 
        printUP(str::toUpperCase); 
    } 
public
    static void printUP(Supplier< String> sup ){ 
        String apply =sup.get(); 
        System.out.println(apply); 
    } 
}

クラス名 -- 静的メソッドを参照

java.lang.Math静的メソッドはすでに class に存在するためrandom、Lambda 経由でメソッドを呼び出す必要がある場合は、次のように記述されたメソッド参照を使用できます。

public class DemoMethodRef { 
  public static void main(String[] args) { 
        printRanNum(Math::random); 
    } 
public
    static void printRanNum(Supplier<Double> sup ){ 
        Double apply =sup.get(); 
        System.out.println(apply); 
    } 
}

この例では、次の 2 つの書き方は同等です。

  • ラムダ式:n -> Math.abs(n)

  • メソッドリファレンス:Math::abs

クラス -- 構築リファレンス

コンストラクタ名はクラス名と全く同じなので固定ではありません。したがって、コンストラクター参照では类名称::newフォーマット表現が使用されます。まず簡単なPersonクラス:

public class Person { 
    private 文字列名; 
    public Person(文字列名) { 
        this.name = 名前; 
    public 
    String getName() {
        名前を返します。
    } 
}

この関数インターフェイスを使用するには、メソッド参照によって渡します。

//文字列 String (name) を取得し、それを Person オブジェクトに変換します
public class Demo09Lambda { 
    public static void main(String[] args) { 
        String name = "tom"; 
        Person person = createPerson(Person::new, name ); 
        System.out.println(person); 
        
    } 
public
    static person createperson(Function<String, Person> fun , String name){ 
        person p = fun.apply(name); 
        return p; 
    }
 
}

この例では、次の 2 つの書き方は同等です。

  • ラムダ式:name -> new Person(name)

  • メソッドリファレンス:Person::new

配列 -- 構築リファレンス

配列も のObjectサブクラス オブジェクトであるため、コンストラクターもありますが、構文は少し異なります。Lambda の使用シナリオに対応する場合は、関数インターフェイスが必要です。

このインターフェイスを適用する場合、メソッド参照によって渡すことができます。

//整数を与え、配列を取得します。配列の長さは指定された整数になります
public class Demo11ArrayInitRef {    
   public static void main(String[] args) { 
int
        [] array = createArray(int[]:: new , 3); 
        System.out.println(array.length); 
    }
 
public
    static int[] createArray(Function<Integer , int[]> fun , int n){ 
        int[] p = fun.apply(n ) ; 
        p を返します; 
    }
 
}

この例では、次の 2 つの書き方は同等です。

  • ラムダ式:length -> new int[length]

  • メソッドリファレンス:int[]::new

注: メソッド参照は、特定の状況を満たすラムダ式の略語です。これにより、ラムダ式がより合理化され、ラムダ式の略語として理解することもできます。学生は、 の前にラムダを使用して、それをメソッド参照として書き換えることができます。ただし、メソッド参照は既存のメソッドのみを「参照」できることに注意してください。

おすすめ

転載: blog.csdn.net/qq_46020806/article/details/130831178