JDK1.8 Lambda式とStream

10309 ワード

一、概説
      jdk1.8 Lambda式をサポートし、Streamは集合の関数式プログラミングをより容易にするために実現した.本文は主にjLambda式とStreamのいくつかの一般的な使用方法を紹介し、いくつかのコードの小さな例を通じてどのように使用するかを示します.
二、関数式インタフェース
関数インタフェースはいつですか?
関数インタフェース(Functional Interface)は、抽象メソッドが1つしかないが、非抽象メソッドが複数あるインタフェースである.
関数インタフェースはどのように書きますか?
Java 1.8では、関数インタフェースに特化した新しい注釈が導入されています:@FunctionalInterface.この注釈は、インタフェースの定義に使用できます.注釈を使用してインタフェースを定義すると、コンパイラはインタフェースが確かに存在し、抽象的な方法が1つしかないかどうかを強制的にチェックします.そうしないと、エラーが発生します.
JDK 1.8以前に既存の関数インタフェース:
  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.util.Comparator
  • java.io.FileFilter
  • java.nio.file.PathMatcher
  • java.lang.reflect.InvocationHandler
  • java.beans.PropertyChangeListener
  • java.awt.event.ActionListener
  • javax.swing.event.ChangeListener

  •     JDK1.8後に追加された関数インタフェース
    インタフェース
    説明
    BiConsumer
    2つの入力パラメータを受け入れる操作を表し、結果は返されません.
    BiFunction
    2つの入力パラメータを受け入れる方法を表し、結果を返します.
    BinaryOperator
    2つの同じタイプのオペレータに作用する操作を表し、オペレータの同じタイプの結果を返します.
    BiPredicate
    2つのパラメータを表すboolean値法
    BooleanSupplier
    boolean値の結果を表すプロバイダ
    Consumer
    入力パラメータを受け入れ、返さない操作を表します.
    Function
    入力パラメータを受け入れ、結果を返します.
    Predicate
    入力パラメータを受け入れ、ブール値の結果を返します.
    Supplier
    パラメータなしで、結果を返します.
    これらのインタフェースは多く、ここでは一つ一つ説明しないが、これらのインタフェースは主に4つのsupplier生産データ関数式インタフェース、Consumer消費データ関数式インタフェース、Predicate判断関数式インタフェース、Functionタイプ変換関数式インタフェースに分けられる.
    三、Lambda式
    実はLambda式の本質は「文法糖」にすぎず、コンパイラによって推定され、通常のコードに変換されるので、同じ機能を実現するためにより少ないコードを使用することができます.
    Lambda式構文
    Lambda式はさらに3つの部分で構成されています(パラメータリスト-)->(式またはコードブロック)
    次の例を示します.
    (int x, int y) -> x + y
    
    () -> 42
    
    (String s) -> { System.out.println(s); }

    Lambda式は匿名の内部クラスに等価であるが,ここでは関数インタフェースを実現した匿名の内部クラスにすぎない.
    public class RunnableTest {
    
        @Test
        public void RunnableLambdaTest(){
    
            //            
            Runnable r1 = new Runnable() {
                public void run() {
                    System.out.println("this is r1");
                }
            };
    
            //       ,           
            Runnable r2 = () -> System.out.println("this is r2");
    
            //   
            r1.run();
            r2.run();
        }
    }

    三、Stream
    流れとは?
    Streamは集合要素ではなく、データ構造ではなく、データを保存しません.アルゴリズムと計算に関するもので、より高度なバージョンのIteratorに似ています.元のバージョンのIteratorでは、ユーザーは要素を明示的に1つずつ遍歴し、いくつかの操作を実行するしかありません.高度なバージョンのStreamでは、ユーザーが含む要素に対してどのような操作を行う必要があるかを指定します.たとえば、「長さ10より大きい文字列をフィルタリング」、「文字列ごとの頭文字を取得」など、Streamは暗黙的に内部を遍歴し、対応するデータ変換を行います.
    Streamは反復器(Iterator)のようなもので、一方向で、往復できず、データは一度しか遍歴できず、一度遍歴した後に使い果たし、流水が目の前を流れ、一度行っても戻らないようなものだ.
    反復器とは異なり、Streamは並列化され、反復器は命令的にシリアル化された操作しかできない.名前の通り、シリアル方式で遍歴すると、各itemが読み終わってから次のitemを読みます.パラレルパスを使用すると、データは複数のセグメントに分割され、それぞれが異なるスレッドで処理され、結果が一緒に出力されます.
    次に、データ構造をStreamに包装すると、中の要素の操作が始まります.一般的な操作は次のように分類できます.
    中間操作:
  • map(mapToInt,flatMapなど)、filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered
  • 端末操作:
  • forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

  • 次に、これらの操作を一例で紹介します.これらの操作を紹介する前に、簡単なUserクラスを宣言します.すべてのStream操作はこのUserセットに基づいて紹介されています.
    package com.sun.liems.model;
    
    /***
     *        lambda    Stream    
     * @author swh
     *
     */
    public class User {
    	//   ID
    	private String userId;
    	//     
    	private String userName;
    	//   
    	private String sex;
    	//   
    	private int age;
    	
    	/***
    	 *    
    	 * @param userId   id
    	 * @param userName   
    	 * @param sex   
    	 * @param age   
    	 */
    	public User(String userId,String userName,String sex,int age) {
    		this.userId = userId;
    		this.userName =userName;
    		this.sex = sex;
    		this.age = age;
    	}
    	public String getUserId() {
    		return userId;
    	}
    	public void setUserId(String userId) {
    		this.userId = userId;
    	}
    	public String getUserName() {
    		return userName;
    	}
    	public void setUserName(String userName) {
    		this.userName = userName;
    	}
    	public String getSex() {
    		return sex;
    	}
    	public void setSex(String sex) {
    		this.sex = sex;
    	}
    	public int getAge() {
    		return age;
    	}
    	public void setAge(int age) {
    		this.age = age;
    	}
    	@Override
    	public String toString() {
    		return "  :"+this.userId+"   :"+this.userName+"   "+this.sex+"   "+this.age;
    	}
    }
    

    1、map
    1つのタイプの値を別のタイプに変換することができ、map操作はこの関数を使用して、1つのストリームの値を新しいストリームに変換することができます.実はFunctionインタフェースを実現しています.
       Stream map(Function super T, ? extends R> mapper);

    1人のユーザを文字列に変換
    /***
    	 *              
    	 * @return
    	 */
    	public static List map(){
    		return userList.stream()
    		        .map(s->{
    		        	return s.toString();
    		        })
    		        .collect(Collectors.toList());
    	}

    2、flatMap
    flatmapとmapの機能の差は多くなく、mapの出力が1つの要素に対応しているのとは異なり、必然的に1つの要素(nullも返す)、flatmapは0または複数の要素です.(nullの場合は実は0要素です).flatmapの意味は、一般的なjavaメソッドはいずれも1つの結果を返すことですが、結果の数が不確定な場合はmapというjavaメソッドでは、あまり柔軟ではないのでflatmapが導入されています.flatMapは、複数のプロセスを1つのストリームに戻すことができます.
    シーンの使用:mapが返す配列またはコレクションの場合、flatmapでこれらの戻りを同じストリームに変換できます.扁平化処理を行う.
    public static void flatMap(){
            String[] words = new String[]{"Hello","World"};
            List a = Arrays.stream(words)
                    .map(word -> word.split(""))
                    .flatMap(s->Arrays.stream(s))
                    .collect(Collectors.toList());
            a.forEach(System.out::print);
    	}

    3、filter
    フィルタ操作はデータフィルタリングに使用されます.
    人員配列内のすべての女性を除去
    public static List filter(){
    		return userList.stream()
    		        .filter(s->s.getSex().equals(" "))
    		        .collect(Collectors.toList());
    	}

    4、distinct
    distinct操作はデータの重量除去に用いられる.distinctはhashCodeとequals法を用いて異なる要素を取得する.したがって,我々のクラスはhashCodeとequalsメソッドを実装しなければならない.
    public static List distinct(){
    		return userList.stream()
    		        .distinct()
    		        .collect(Collectors.toList());
    	}
    @Override
    	public boolean equals(Object o) {
    		User u = (User) o;
    		return this.getUserId().equals(u.getUserId());
    	} 
    	@Override
    	public int hashCode() {
    		return this.getUserId().hashCode();
    	}

    5、sorted
    sortedオペレーションは、自然な順序でソートするために使用されます.
    人員を年齢順に並べる
    public static List sorted(){
    		return userList.stream()
    		        .sorted(Comparator.comparing(s->s.getAge()))
    		        .collect(Collectors.toList());
    	}

    6、peek
    peekは戻り値のないλ式は、いくつかの出力、外部処理などを行うことができます.mapは戻り値のあるλ式、その後Streamの汎用タイプがmapパラメータに変換されますλ式が返すタイプ.forEachとは違います.peekは中間操作でforEachは中間操作です
    public static List peek(){
    		return userList.stream()
    				.filter(s->s.getSex().equals(" "))
    				.peek(s->System.out.println(s))
    		        .collect(Collectors.toList());
    	}

    7、limit
    Limit:1つのStreamを遮断し、その前のN個の要素を取得し、元のStreamに含まれる要素の個数がNより小さい場合、そのすべての要素を取得します.
        /***
    	 *   4   
    	 * @return
    	 */
    	public static List limit(){
    		return userList.stream()
                    .limit(4)
    				.collect(Collectors.toList());
    	}

    8、skip
    skip:元のStreamの前のN個の要素を捨てた後に残った要素からなる新しいStreamを返し、元のStreamに含まれる要素の数がNより小さい場合、空のStreamを返します.
    	/***
    	 *     4      
    	 * @return
    	 */
    	public static List skip(){
    		return userList.stream()
                    .skip(4)
    				.collect(Collectors.toList());
    	}

    9、forEach
    役割は、容器内の各要素に対してactionで指定された動作、すなわち要素を遍歴することである.
    全員を印刷する
    10、reduce
    reduce操作は、値のセットから値を生成することを実現することができる.上記の例で用いられるcount,min,maxメソッドは,よく用いられるため標準ライブラリに組み込まれる.実際、これらの方法はすべてreduce操作です.
    	/***
    	 *          
    	 */
    	public static void reduce() {
    		int ageSum = userList.stream()
    		        .map(u->u.getAge())
    		        .reduce(0,(sum,u)-> sum+u);
    		System.out.println(ageSum);
    	}

    11、max及びmin
    最大値と最小値を求めて、この時maxとminはComparator super T>comparatorを受け入れます
    	/***
    	 *         
    	 */
    	public static void max() {
    		User user = userList.stream()
    		        .max(Comparator.comparing(u->u.getAge()))
    		        .get();
    		System.out.println(user);
    	}

    12、collect
    collectはコレクタであり,Streamは汎用的でストリームから複雑な値を生成する構造である.これをcollectメソッド,すなわちいわゆる変換メソッドに渡すと,所望のデータ構造が生成される.ここでは、Collectorsというツールライブラリは、対応する変換方法をカプセル化しています.もちろん、Collectorsツールライブラリは、一般的なシーンをカプセル化しているだけで、特別なニーズがあればカスタマイズします.
    方法
    用途
    toList
    結果をリストに変換
    toSet
    結果を一つのセットに変換する
    groupingBy
    分類用、例えばある属性別に分類してmapに変換する
    toMap
     
        /**
    	 *         
    	 *
    	 */
    	public static void collect() {
    		Map> map = userList.stream()
    		            .collect(Collectors.groupingBy(u->u.getSex()));
    		System.out.println(map.size());
    	}
    	/***
    	 *               
    	 */
    	public static void toMap() {
    		Map map = userList.stream()
    		            .collect(Collectors.toMap(u->u.getUserId(), u->u.getUserName()));
    		map.entrySet()
    		   .forEach(e->{
    			   System.out.println(e.getKey()+":"+e.getValue());
    		   });
    	}

    streamのtoMap()関数を使用すると、keyが重複すると、同じkeyがmapを形成できないとエラーが発生します.この問題を解決する必要があります.1つは、同じkeyの場合、重複を捨てて1つだけ残します.
    13、マッチング方法anyMatch:前のいずれかにマッチするとBoolean allMatchを返す:すべての要素にマッチするとBooleanを返す
    NoneMatchはallMatchとは逆に、条件の要素を判断し、すべてではなくtrueを返します.
    Optionalは、Null以外の値を含めることができるコンテナオブジェクトまたは含まないコンテナオブジェクトです.
        /***
    	 *   
    	 */
    	public static void match() {
    		 //       
    		 boolean flagA  = userList.stream()
    		            .anyMatch(u->u.getUserName().equals("  "));
    		 System.out.println( flagA);
    		 //        18 
    		 boolean flagB  = userList.stream()
    		            .allMatch(u->u.getAge()==18);
    		 System.out.println( flagB);
    		 //           
    		 boolean flagC  = userList.stream()
    		            .noneMatch(u->u.getAge()<18);
    		 System.out.println(flagC);
    
    	}

    四、まとめ
    steamが提供するいくつかの方法は、集合に対する操作を簡略化し、コードロジックだけに注目し、コードテンプレートに注目する必要はありません.コードロジックを明確にすることができます.コードの実行効率は実は同じです.しかし、私たちの開発効率を向上させることができます.
    上の例のソースコードをダウンロードしたい場合は下のリンクをクリックしてください
       https://github.com/sunwnehongl/LambadAndStream