Java 8の機能:拡張集合処理Stream操作

61595 ワード

StreamはJava 8のハイライトとして、コレクション(Collection)に対して非常に便利で効率的な集約操作(aggregate operation)、または大量のデータ操作(bulk data operation)を行うことに集中しています.Stream APIは、同じように新しく登場したLambda式を利用して、プログラミング効率とプログラム可読性を大幅に向上させます.同時に、シリアルとパラレルの2つのモードを集約し、マルチコアプロセッサの利点を十分に利用し、fork/joinパラレル方式を使用してタスクを分割し、処理プロセスを加速させることができます.通常、パラレルコードを書くのは難しく、エラーが発生しやすいが、StreamAPIを使用すると、マルチスレッドのコードを1行書く必要がなく、高性能なコンカレントプログラムを簡単に書くことができる.

1.Streamの基本紹介


Streamはデータ・ストリームを表し、ストリーム内の要素は有限または無限である可能性があります.
Streamと他の集合クラスの違いは、他の集合クラスは主に限られた数のデータへのアクセスと効率的な管理(添削)に注目しているが、Streamは要素へのアクセスと管理を提供する方式ではなく、データソースを宣言する方式を通じて、計算可能な操作を利用してデータソース上で実行し、もちろんBaseStreamである.iterator()とBaseStream.spliterator()操作は、要素を巡回する方法を提供します.
Streamのタイプは、IntStream、LongStream、DoubleStreamです.

2.Streamの作成


一般的なStreamの作成方法は次のとおりです.
  • Streamインタフェースを介して提供する静的プラント法、Stream.of()、Stream.generate()など;
  • Collectionインタフェースのデフォルトメソッド、stream()、parallelStream()など;

  • 他にも方法がありますstream()、StreamSupport.stream() .

    2.1 Streamインタフェースによる静的アプローチ


    Stream.of()
    @SafeVarargs
    @SuppressWarnings("varargs") // Creating a stream from an array is safe
    public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }
    

    内部はArrays.stream()を作成します.
    例:
    Stream<String> streamStr = Stream.of("a","b","c");
    Stream<Integer> stream = Stream.of(1,2,3,4,5);
    

    Stream.generate()
    public static<T> Stream<T> generate(Supplier<T> s) {
        Objects.requireNonNull(s);
        return StreamSupport.stream(
                new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
    }
    

    内部はStreamSupportで生成されます.
    例:
    Stream.generate(()->{
    	return Math.random();
    });
    
    Stream.generate(new Supplier<Integer>() {
    
    	@Override
    	public Integer get() {
    		return Math.round(5);
    	}
    });
    

    2.2 Collectionによるデフォルトのアプローチ


    JDK 1.8では、streamを変換するdefaultメソッドを含むCollectionインタフェースが変更されました.
    /**
     * Returns a sequential {@code Stream} with this collection as its source.
     *
     * 

    This method should be overridden when the {@link #spliterator()} * method cannot return a spliterator that is {@code IMMUTABLE}, * {@code CONCURRENT}, or late-binding. (See {@link #spliterator()} * for details.) * * @implSpec * The default implementation creates a sequential {@code Stream} from the * collection's {@code Spliterator}. * * @return a sequential {@code Stream} over the elements in this collection * @since 1.8 */

    default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); } /** * Returns a possibly parallel {@code Stream} with this collection as its * source. It is allowable for this method to return a sequential stream. * *

    This method should be overridden when the {@link #spliterator()} * method cannot return a spliterator that is {@code IMMUTABLE}, * {@code CONCURRENT}, or late-binding. (See {@link #spliterator()} * for details.) * * @implSpec * The default implementation creates a parallel {@code Stream} from the * collection's {@code Spliterator}. * * @return a possibly parallel {@code Stream} over the elements in this * collection * @since 1.8 */

    default Stream<E> parallelStream() { return StreamSupport.stream(spliterator(), true); }

    例:
    public static void main(String[] args) {
    	List<Integer> list = new ArrayList<>();
    	list.add(1);
    	list.add(2);
    	list.add(3);
    	list.add(4);
    	list.stream();
    	list.parallelStream();
    }
    

    実際の開発では,一般にCollectionのdefault法により集合に対応するStreamを取得し,そのStreamに対して処理する.

    3.Streamの変換


    ストリームの変換は、主にストリームに対して提供される方法であり、フィルタリング、判断、カウントなどのストリームに対する処理を行う.

    3.1 forEach(Consumer super T> action)


    要素を巡回し、Consumerインタフェースはパラメータを入力し、結果を返さない.
    例:
    public static void main(String[] args) {
    	List<Integer> list = new ArrayList<>();
    	list.add(1);
    	list.add(2);
    	list.add(3);
    	list.add(4);
    	list.stream().forEach((x)->{
    		System.out.println(x);
    	});
    }
    // 
    1
    2
    3
    4
    

    ここの例ではlambda式を直接用いて書く.

    3.2 distinct()


    デリバリーは、SQLのキーワードと同じ役割を果たし、distinct処理されたStreamには重複要素が含まれていません.
    例:
    public static void main(String[] args) {
    	List<Integer> list = new ArrayList<>();
    	list.add(1);
    	list.add(2);
    	list.add(3);
    	list.add(4);
    	list.add(1);
    	list.add(3);
    	list.stream().distinct().forEach(x ->{
    		System.out.println(x);
    	});
    }
    // 
    1
    2
    3
    4
    

    3.3 filter(Predicate super T> predicate)


    指定したpredicate関数に従ってStream内の要素をフィルタリングし、新しく生成されたStreamには、以降の要素のみが含まれます.Predicateインタフェースは、パラメータを入力し、ブールタイプを返します.
    例:
    public static void main(String[] args) {
    	List<Integer> list = new ArrayList<>();
    	list.add(1);
    	list.add(2);
    	list.add(3);
    	list.add(4);
    	list.add(1);
    	list.add(3);
    	list.stream().filter(x->{
    		if (x > 2) {
    			return true;
    		}
    		return false;
    	}).forEach(x->{
    		System.out.println(x);
    	});;
    }
    // 
    3
    4
    3
    

    3.4 map(Function super T, ? extends R> mapper)


    Streamに含まれる要素に対して所定の変換関数を使用して変換操作を行う場合、新しく生成されたStreamには変換生成された要素のみが含まれます.この方法には,mapToInt,mapToLong,mapToDoubleの3つの元のタイプに対する変種法がある.この3つの方法も理解しやすいです.例えばmapToIntは元のStreamを新しいStreamに変換し、この新しく生成されたStreamの要素はintタイプです.このような3つの変種方法がある理由は、自動梱包/解体の余分な消費を免除することができる.
    public static void main(String[] args) {
    	List<Integer> list = new ArrayList<>();
    	list.add(1);
    	list.add(2);
    	list.add(3);
    	list.add(4);
    	list.add(1);
    	list.add(3);
    	list.stream().map((x)->{
    		if (x > 2) {
    			return x * 2;
    		}else {
    			return x * 3;
    		}
    	}).forEach(x->{
    		System.out.println(x);
    	});;
    }
    // 
    3
    6
    6
    8
    3
    6
    

    3.5 flatMap(Function super T, ? extends Stream extends R>> mapper)


    与えられた要素をStreamストリームに変換して出力します.
    例:
    public static void main(String[] args) {
    	List<Integer> list = new ArrayList<>();
    	list.add(1);
    	list.add(2);
    	list.add(3);
    	list.add(4);
    	list.add(1);
    	list.add(3);
    	
    	list.stream().flatMap(x ->{
    		return Stream.of(x * 2);
    	}).forEach(x->{
    		System.out.println(x);
    	});;
    }
    

    3.6 sorted()


    を行ないます.
    例:
    public static void main(String[] args) {
    	List<Integer> list = new ArrayList<>();
    	list.add(1);
    	list.add(2);
    	list.add(3);
    	list.add(4);
    	list.add(1);
    	list.add(3);
    	
    	list.stream().sorted().forEach(x->{
    		System.out.println(x);
    	});;
    }
    

    3.7 peek(Consumer super T> action)


    元のStreamのすべての要素を含む新しいStreamが生成され、同時に消費関数(Consumerインスタンス)が提供され、新しいStreamの各要素が消費されると、指定された消費関数が実行されます.
    例:
    public static void main(String[] args) {
    	List<Integer> list = new ArrayList<>();
    	list.add(1);
    	list.add(2);
    	list.add(3);
    	
    	list.stream().peek(x->{
    		x = x + 10;
    		System.out.println(x+"--");
    	}).forEach(x->{
    		System.out.println(x);
    	});
    }
    // 
    11--
    1
    12--
    2
    13--
    3
    

    3.8 limit(long maxSize)


    1つのStreamをトランケートし、その前のN個の要素を取得し、元のStreamに含まれる要素の数がNより小さい場合、そのすべての要素を取得します.
    例:
    public static void main(String[] args) {
    	List<Integer> list = new ArrayList<>();
    	list.add(1);
    	list.add(2);
    	list.add(3);
    	
    	list.stream().limit(2).forEach(x->{
    		System.out.println(x);
    	});
    }
    // 
    1
    2
    

    3.9 skip(long n)


    元のStreamの最初のN要素を捨てた後に残った要素からなる新しいStreamを返します.元のStreamに含まれる要素の数がNより小さい場合は、空のStreamを返します.
    例:
    public static void main(String[] args) {
    	List<Integer> list = new ArrayList<>();
    	list.add(1);
    	list.add(2);
    	list.add(3);
    	
    	list.stream().skip(1).forEach(x->{
    		System.out.println(x);
    	});
    }
    //  
    2
    3
    

    3.10 reduce操作


    Streamにはreduceオペレーションが提供され、reduceオペレーションはStreamの要素から組み合わせられ、特定のルールに従って計算されます.たとえば、sum、min、max、average計算操作が一般的です.reduce操作には、3つのリロード関数があります.
  • T reduce(T identity, BinaryOperator accumulator);
  • Optional reduce(BinaryOperator accumulator);
  • U reduce(U identity, BiFunction accumulator, BinaryOperator combiner);

  • ここではBinaryOperatorインタフェースを導入し,このインタフェースはBiFunctionインタフェースを継承する.
    @FunctionalInterface
    public interface BiFunction<T, U, R> {
    
        R apply(T t, U u);
    
        default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t, U u) -> after.apply(apply(t, u));
        }
    }
    

    2つのパラメータを含むapplyメソッド.
    sum操作
    public static void main(String[] args) {
        Stream<Integer> list = Stream.of(1,2,3,4,5,6);
        Optional<Integer> result = list.reduce((x, y) -> x + y);
        System.out.println(result.get());
    }
    

    max操作
    public static void main(String[] args) {
    	BinaryOperator<Integer> binaryOperator = (Integer x, Integer y) -> {
    		if (x < y) {
    			return y;
    		} else {
    			return x;
    		}
    	};
    	
    	Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
    	System.out.println("max:" + stream.reduce(binaryOperator).get());
    	
    	Optional<Integer> optional = stream.reduce((Integer x, Integer y) -> {
    		if (x < y) {
    			return y;
    		} else {
    			return x;
    		}
    	});
    	
    	System.out.println("max:" + optional.get());
    }
    

    上記の例では、BinaryOperationを定義することによって操作ロジックを実現したり、システムパッケージされた関数:Integer::maxを使用して処理したりすることができます.
    min操作
    public static void main(String[] args) {
    	BinaryOperator<Integer> binaryOperator = (Integer x, Integer y) -> {
    		if (x < y) {
    			return x;
    		} else {
    			return y;
    		}
    	};
    	
    	Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
    	System.out.println("min:" + stream.reduce(binaryOperator).get());
    	
    	Optional<Integer> optional = Stream.of(1,2,3,4,5,6).reduce((Integer x, Integer y) -> {
    		if (x < y) {
    			return x;
    		} else {
    			return y;
    		}
    	});
    	
    	System.out.println("min:" + optional.get());
    }
    

    まとめ


    このセクションでは、Java 8のセットに対する強化操作について説明します.これらの方法を熟練して使用すると、コードの簡潔性が向上します.