Java8 Stream簡介


Stream是Java 8新增的重要特性, 它提供函數式編程支持並允許以管道方式操作集合. 流操作會遍歷數據源, 使用管道式操作處理數據后生成結果集合, 這個過程通常不會對數據源造成影響.

函數對象

使用Stream進行函數式編程時經常需要將操作作為參數傳入流方法中, 函數對象即將方法或lambda表達式作為對象.

import java.util.stream.Collectors;
List list = Arrays.asList(-1,0,1,2,3).stream()
				.filter(x -> x>0)
				.collect(Collectors.toList());

上述示例中filter的參數x -> x>0即為一個lambda表達式.

lambda表達式語法通常為(args)->{body}, 返回值的類型自動推定:

(int a, int b) -> { 
	if (a>b) {
		return a+b;
	} 
	else {
		return a * b;
	}
}

參數的類型也可以自動推定:

(a, b) -> {
  return a+b;
}

在只有一條語句的情況下{}可省略, 返回值類型與語句主體表達式一致.

(a, b) -> a+b

允許使用空參數:

() -> {System.out.println("Hello World!")}

::運算符用於將方法表示為函數對象:

List<Item> items = new ArrayList<>();
items.add(new Item("a"));
items.add(new Item("b"));
List<String> list = items.stream()
        .map(Item::getMsg)
        .collect(Collectors.toList());
for(String str : list) {
    System.out.println(str);
}

流的創建

可以使用集合類的stream()或者parallelStream()方法創建流:

import java.util.stream.Stream;
Stream<String> s1 = Arrays.asList("a","b","c").stream();
Stream<String> s2 = Arrays.asList("a","b","c").parallelStream();

java.util.stream.Stream是一個interface, 各種管道中間操作的返回值都是它的實現類, 這允許我們方便地進行參數傳遞.

Stream的靜態方法of()也可以用來創建流:

Stream.of(new int[]{1,2,3});

Arrays也提供了創建流的靜態方法stream():

Arrays.stream(new int[]{1,2,3})

一些類也提供了創建流的方法:

IntStream.range(start, stop);
BufferedReader.lines();
Random.ints();

中間操作

流操作是惰性執行的, 中間操作會返回一個新的流對象, 當執行終點操作時才會真正進行計算.下面介紹流的中間操作. 除非傳入的操作函數有副作用, 函數本身不會對數據源進行任何修改.

distinct

distinct保證數據源中的重復元素在結果中只出現一次, 它使用equals()方法判斷兩個元素是否相等.

List<String> list = Stream.of("a","b","c","b")
        .distinct()
        .collect(Collectors.toList());

filter

filter根據傳入的斷言函數對所有元素進行檢查, 只有使斷言函數返回真的元素才會出現在結果中. filter不會對數據源進行修改.

List<Integer> list = IntStream.range(1,10).boxed()
		.filter( i -> i % 2 == 0)
		.collect(Collectors.toList());

java.util.Objects提供了空元素過濾的工具:

List<MyItem> list = items.stream()
	.filter(Objects::nonNull)
	.collect(Collectors.toList());

map

map方法根據傳入的mapper函數對元素進行一對一映射, 即數據源中的每一個元素都會在結果中被替換(映射)為mapper函數的返回值.

List<String> list = Stream.of('a','b','c')
        .map( s -> s.hashCode())
        .collect(Collectors.toList());

flatMap

map不同flatMap進行多對一映射, 它要求若數據源的元素類型為R, 則mapper函數的返回值必須為Stream<R>.

flatMap會使用mapper函數將數據源中的元素一一映射為Stream對象, 然后把這些Stream拼裝成一個流.因此我們可以使用flatMap進行合並列表之類的操作:

List<Integer> list = Stream.of(
		Arrays.asList(1),
		Arrays.asList(2, 3),
		Arrays.asList(4, 5, 6)
	)
	.flatMap(l -> l.stream())
	.collect(Collectors.toList());

peek

peek方法會對數據源中所有元素進行給定操作, 但在結果中仍然是數據源中的元素. 通常我們利用操作的副作用, 修改其它數據或進行輸入輸出.

List<String> list = Stream.of('a','b','c')
        .map(s -> System.out.println(s))
        .collect(Collectors.toList());

sorted

sorted方法用於對數據源進行排序:

List<Integer> list = Arrays.asList(1,2,3,4,5,6).stream()
		.sorted((a, b) -> a-b)
		.collect(Collectors.toList());

使用java.util.Comparator是更方便的方法, 默認進行升序排序:

class Item {
	int val;
	public Item(int val) { this.val = val; }
	public int getVal() { return val; }
}

List<Item> list = Stream.of(
		new Item(1),
		new Item(2), 
		new Item(3)
	)
	.sorted(Comparator.comparingInt(Item::getVal))
	.collect(Collectors.toList());

使用reversed()方法進行降序排序:

List<Item> list = Stream.of(
		new Item(1),
		new Item(2), 
		new Item(3)
	)
	.sorted(Comparator.comparingInt(Item::getVal).reversed())
	.collect(Collectors.toList());

limit

limit(int n)當流中元素數大於n時丟棄超出的元素, 否則不進行處理, 達到限制流長度的目的.

skip

skip(int)返回丟棄了前n個元素的流. 如果流中的元素小於或者等於n,則返回空的流

終點操作

reduce

reduce(accumulator)是最基本的終點操作之一, 操作函數accumulator接受兩個參數x,y返回r.

reduce首先將數據源中的兩個元素x1x2傳給accumulator得到r1, 然后將r1x3傳入得到r2. 如此進行直到處理完整個數據流.

reduce方法還可以接受一個參數代替x1作為起始值:

Integer sum = Stream.of(1,2,3,4,5).reduce(0, (x, y) -> x +y);
String concat = Stream.of("a", "b", "c", "d").reduce("", String::concat); 
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0)
	.reduce(Double.MAX_VALUE, Double::min); 

collect

collect是使用最廣泛的終點操作, 也上文中多次出現:

List<String> list = Stream.of("a","b","c","b")
        .distinct()
        .collect(Collectors.toList())

toList()將流轉換為List實例, 是最常見的用法, java.util.Collectors類中還有求和, 計算均值, 取最值, 字符串連接等多種收集方法.

forEach

forEach方法對流中所有元素執行給定操作, 沒有返回值.

Stream.of(1,2,3,4,5).forEach(System.out::println);

其它

  • count() 返回流中的元素數

  • toArray(): 轉換為數組

並發問題

除非顯式地創建並行流, 否則默認創建的都是串行流.Collection.stream()為集合創建串行流,而Collection.parallelStream()創建並行流.

stream.parallel()方法可以將串行流轉換成並行流,stream.sequential()方法將流轉換成串行流.

流可以在非線程安全的集合上創建, 流操作不應該對非線程安全的數據源產生任何副作用, 否則將發生java.util.ConcurrentModificationException異常.

List<String> list = new ArrayList(Arrays.asList("a", "b"));
list = list.stream().forEach(s -> l.add("c")); // cause exception

對於線程安全的容器不會存在這個問題:

List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a", "b"));
list = list.stream().forEach(s -> l.add("c")); // safe operation

當然作者建議Stream操作不要對數據源進行任何修改. 當然, 修改其它數據或者輸入輸出是允許的:

list.stream().forEach(s -> {
	set.add(s); 
	System.out.println(s);
});

理想的管道操作應該是無狀態且與訪問順序無關的. 無狀態是指操作的結果只與輸入有關, 下面即是一個有狀態的操作示例:

State state = getState();
List<String> list = new ArrayList(Arrays.asList("a", "b"));
list = list.stream().map(s -> {
  if (state.isReady()) {
    return s;
  }
  else {
    return null;
  }
});

無狀態的操作保證無論系統狀態如何管道的行為不變, 與順序無關則有利於進行並行計算.

函數式接口

函數式接口會將簽名匹配的函數對象(lambda表達式或方法)視作接口的實現。

@FunctionalInterface
interface Greeter
{
    void hello(String message);
}

函數式接口中有且只有一個非抽象方法。

Greeter greeter = message -> System.out.println("Hello " + message);

這在 Java 8 之前通常使用匿名內部類實現的:

Greeter greeter = new Greeter() {
            @Override
            public void hello(String message) {
                System.out.println("Hello " + message);
            }
        };

Java 8 將已有的一些接口實現為函數式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.util.Comparator
  • java.lang.reflect.InvocationHandler
  • java.io.FileFilter
  • java.nio.file.PathMatcher

java.util.function中定義了一些常用的函數式接口:

  • Consumer: 接受參數無返回
    • Consumer<T> -> void accept(T t);
    • BiConsumer<T,U> -> void accept(T t, U u);
    • DoubleConsumer -> void accept(double value);
  • Supplier: 不接受參數有返回
    • Supplier<T> -> T get();
    • DoubleSupplier -> double getAsDouble();
  • Function: 接受參數並返回
    • Function<T, R> -> R apply(T t);
    • BiFunction<T, U, R> -> R apply(T t, U u);
    • DoubleFunction<R> -> R apply(double value);
    • DoubleToIntFunction -> int applyAsInt(double value);
    • BinaryOperator<T> extends BiFunction<T,T,T>
  • Predicate: 接受參數返回boolean
    • Predicate<T> -> boolean test(T t);
    • BiPredicate<T, U> -> boolean test(T t, U u);
    • DoublePredicate -> boolean test(double value);

默認構造器可以作為supplier: Supplier<Item> supplier = Item::new;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM