1. 程式人生 > 實用技巧 >java8 lambda表示式與集合類批處理操作

java8 lambda表示式與集合類批處理操作

一、基本概念

λ表示式可以被當做是一個Object。λ表示式的型別,叫做“目標型別(target type)”。λ表示式的目標型別是“函式介面(functional interface)”,這是Java8新引入的概念。它的定義是:一個介面,如果只有一個顯式宣告的抽象方法,那麼它就是一個函式介面。一般用@FunctionalInterface標註出來(也可以不標)。

@FunctionalInterface
public interface Runnable { void run(); }
    
public interface Callable<V> { V call() throws Exception; }
    
public interface ActionListener { void actionPerformed(ActionEvent e); }
    
public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); }
注意最後這個Comparator介面。它裡面聲明瞭兩個方法,貌似不符合函式介面的定義,但它的確是函式介面。這是因為equals方法是Object的,所有的介面都會宣告Object的public方法——雖然大多是隱式的。所以,Comparator顯式的聲明瞭equals不影響它依然是個函式介面

集合類的批處理操作API的目的是實現集合類的“內部迭代”,並期望充分利用現代多核CPU進行平行計算。
Java8之前集合類的迭代(Iteration)都是外部的,即客戶程式碼,不能充分利用cpu的多核資源。而內部迭代意味著改由Java類庫來進行迭代,而不是客戶程式碼.

Java8為集合類引入了一個重要概念:流(stream)。一個流通常以一個集合類例項為其資料來源,然後在其上定義各種操作。流的API設計使用了管道(pipelines)模式。對流的一次操作會返回另一個流。如同IO的API或者StringBuffer的append方法那樣,從而多個不同的操作可以在一個語句裡串起來.

二、λ表示式的使用

一個λ表示式只有在轉型成一個函式介面後才能被當做Object使用

可以用一個λ表示式為一個函式介面賦值,然後再賦值給一個Object

一個λ表示式可以有多個目標型別(函式介面),只要函式匹配成功即可。但需注意一個λ表示式必須至少有一個目標型別

λ表示式主要用於替換以前廣泛使用的內部匿名類,各種回撥,比如事件響應器、傳入Thread類的Runnable等

new Thread( () -> {
        System.out.println("This is from an anonymous method (lambda exp).");
    } );
注意執行緒裡的λ表示式,你並不需要顯式地把它轉成一個Runnable,因為Java能根據上下文自動推斷出來:一個Thread的建構函式接受一個Runnable引數,而傳入的λ表示式正好符合其run()函式,所以Java編譯器推斷它為Runnable。

filter方法的引數是Predicate型別,forEach方法的引數是Consumer型別,它們都是函式介面,所以可以使用λ表示式

三、程式碼樣例

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {
	private static List<Person> persons = Arrays.asList(new Person("Joe", 12),
			new Person("Jim", 19), new Person("John", 21));
	public static void main(String[] args) throws Exception {
		// testStreamAPI();
		// testStreamMap();
		// testStreamPerformance();
		testInt(2, 3, 4, 2, 3, 5, 1);
		testOccurrence(2, 3, 4, 2, 3, 5, 1);
		distinctSum(2, 3, 4, 2, 3, 5, 1);
		testNestLambda();
	}

	public static void testStreamAPI() {
		// 列印年齡大於12的人
		System.out.println("使用順序流序列列印");
		persons.stream().filter(p -> p.getAge() > 12)
				.collect(Collectors.toCollection(ArrayList::new))
				.forEach(p -> {
					System.out.println(p);
				});
		System.out.println("使用並行流並行列印,即利用多核技術可將大資料通過多核並行處理");
		persons.parallelStream().filter(p -> p.getAge() > 12)
				.collect(Collectors.toCollection(ArrayList::new))
				.forEach(p -> {
					System.out.println(p);
				});
	}

	public static void testStreamMap() {
		// 應該用filter過濾,然後再使用map進行轉換
		persons.parallelStream().map(p -> {
			if (p.getAge() > 18)
				return p;
			return null;
		}).collect(Collectors.toCollection(ArrayList::new)).forEach(p -> {
			if (p != null)
				System.out.println(p);
		});

		persons.parallelStream().filter(p -> p.getAge() > 18)
				.map(p -> new Adult(p))
				.collect(Collectors.toCollection(ArrayList::new))
				.forEach(p -> {
					if (p != null)
						System.out.println(p);
				});
	}

	public static void testStreamReduce() {
		persons.parallelStream().filter(p -> p.getAge() > 18)
				.map(p -> new Adult(p))
				.collect(Collectors.toCollection(ArrayList::new))
				.forEach(p -> {
					if (p != null)
						System.out.println(p);
				});
	}

	public static void testStreamPerformance() {
		// 初始化一個範圍100萬整數流,求能被2整除的數字,toArray()是終點方法
		long start1 = System.nanoTime();
		int a[] = IntStream.range(0, 1_000_000).filter(p -> p % 2 == 0)
				.toArray();
		System.out.printf("測試順序流的效能: %.2fs",
				(System.nanoTime() - start1) * 1e-9);

		long start2 = System.nanoTime();
		int b[] = IntStream.range(0, 1_000_000).parallel()
				.filter(p -> p % 2 == 0).toArray();
		System.out.printf(" 測試並行流的效能: %.2fs",
				(System.nanoTime() - start2) * 1e-9);
		// 本機的測試結果是:測試順序流的效能: 0.02s 測試並行流的效能: 0.01s
		// 在100萬時,併發流快些,1000萬,併發流反而會慢些,估計和執行緒的頻繁切換有關(本機是8執行緒CPU)
	}

	public static void testInt(Integer... numbers) {
		List<Integer> l = Arrays.asList(numbers);
		List<Integer> r = l.stream()
				.map(e -> new Integer(e))
				.filter(e -> e > 2)
				.distinct()
				.collect(Collectors.toList());
		System.out.println("testInt result is: " + r);
	}

	public static void testOccurrence(Integer... numbers) {
		List<Integer> l = Arrays.asList(numbers);
		Map<Integer, Integer> r = l
				.stream()
				.map(e -> new Integer(e))
				.collect(
						Collectors.groupingBy(p -> p,
								Collectors.summingInt(p -> 1)));
		System.out.println("testOccurrence result is: " + r);
	}

	public static void distinctSum(Integer... numbers) {
		List<Integer> l = Arrays.asList(numbers);
		int sum = l.stream()
				.map(e -> new Integer(e))
				.distinct()
				.reduce(0, (x, y) -> x + y); // equivalent to .sum()
		System.out.println("distinctSum result is: " + sum);
	}
	
	public static void testNestLambda() throws Exception{
		 Callable<Runnable> c1 = () -> () -> { System.out.println("Nested lambda"); };
		 c1.call().run();
		// 用在條件表示式中
		Callable<Integer> c2 = false ? (() -> 42) : (() -> 24);
		System.out.println(c2.call());
	}
}

class Person {
	private String name;
	private int age;

	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		System.out.println(name);
		return name;
	}

	public int getAge() {
		return age;
	}

	@Override
	public String toString() {
		return "name:" + name + " age:" + age;
	}
}

class Adult extends Person {
	public Adult(Person p) {
		super(p.getName(), p.getAge());
	}
}

四、參考資料

http://openjdk.java.net/projects/lambda/

http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

來源:站長資訊