Java8新特性之Stream API學習

Stream API 介紹

Java8中有兩個最爲重要的改變。第一個是Lambda表達式;另一個就是Stream API 。

Stream是Java8中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查找、過濾和映射數據等操作。使用Stream API對集合數據進行操作,就類似於使用SQL執行的數據庫查詢。也可以使用Stream API來並行執行操作。簡而言之,Stream API爲我們提供了一種高效且易於使用的處理數據的方式,代碼簡潔,優雅。

注意

  • Stream 不會存儲元素
  • Stream 不會改變數據源對象。而是返回一個持有結果的新的Stream
  • Stream 操作是延遲執行的。

操作Stream的三個步驟

  1. 創建Stream:獲取數據源的流
  2. 中間操作:一系列針對數據源數據的操作(操作連)
  3. 終止操作:執行中間的所有操作,併產生結果

實體類People

  • 方便後續程序演示(多處用到)
public class People {
	private String name;
	private Integer age;
	private Integer salary;
	
	public People() {}
	
	public People(String name) {
		this.name = name;
	}
	
	public People(String name, Integer age) {
		this.name = name;
		this.age = age;
	}
	
	public People(String name, Integer age, Integer salary) {
		this.name = name;
		this.age = age;
		this.salary = salary;
	}

	public String getName() {
		return name;
	}

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

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public Integer getSalary() {
		return salary;
	}

	public void setSalary(Integer salary) {
		this.salary = salary;
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((age == null) ? 0 : age.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + ((salary == null) ? 0 : salary.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		People other = (People) obj;
		if (age == null) {
			if (other.age != null)
				return false;
		} else if (!age.equals(other.age))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (salary == null) {
			if (other.salary != null)
				return false;
		} else if (!salary.equals(other.salary))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "People [name=" + name + ", age=" + age + ", salary=" + salary + "]";
	}
	
	
}
  • 後續程序中用到的List ps(放入測試類即可)
	private List<People> ps = Arrays.asList(
				new People("Tom", 20, 5000),
				new People("Tony", 25, 6000),
				new People("Jerry", 30, 7000),
				new People("Lucy", 35, 8000),
				new People("John", 40, 5500),
				new People("Tom", 20, 5000)
			);

Tip:下面的例子中有很多地方用到了函數式接口Lambda表達式中的知識,如方法引用等。可參考函數式接口Lambda表達式

1. 創建Stream的方法

	@Test
	public void test1(){
        //方法1.通過單列集合類的stream()方法或parallelStream()方法獲取Stream
        List<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();
        Stream<String> stream = list.parallelStream();//獲取並行流
        
        //方法2.通過Arrays中的靜態方法stream()獲取數組流
        People[] peoples = new People[10];
        Stream<People> stream2 = Arrays.stream(peoples);
 		
        //方法3.通過Stream接口的靜態方法of(T... values)
        Stream<String> stream3 = Stream.of("Stream", "API", "test");
        
        //方法4.創建無限流
        //4.1 通過迭代獲取無限流
        Stream<Integer> stream4 = Stream.iterate(0, x -> x + 2);
        //從0開始,每一次迭代+2, 一共迭代10次,並每次打印輸出
        stream4.limit(10).forEach(System.out::println);//forEach()是一個終止操作
        
        //4.2 通過生成
        //生成5個隨機小數
        Stream.generate(() -> Math.random())
              .limit(5)
              .forEach(System.out::println);
    }

2. 中間操作

2.1 篩選與切片
  • filter():過濾。接收Lambda,從流中排除某些元素,獲得一個新流
	Stream<T> filter(Predicate<? super T> predicate);
	@Test
	public void test2(){
        ps.stream()
          .filter((p) -> p.getAge() > 30)
          .forEach(System.out::println);
    }
  • limit():截斷流,使其元素不超過給定數量
	Stream<T> limit(long maxSize);
	@Test
	public void test3(){
        ps.stream()
          .filter((p) -> {
              System.out.println("短路-> " + p.getAge());
              return p.getAge() >= 25;
          })
          .limit(2)
          .forEach(System.out::println);
    }
    //執行結果:
    短路-> 20
    短路-> 25
    People [name=Tony, age=25, salary=6000]
    短路-> 30
    People [name=Jerry, age=30, salary=7000]

可以看到當得到2個符合要求的元素時,後面的元素將不再進行遍歷

  • skip(n):跳過前n個元素,返回一個扔掉了前n個元素的流。若流中元素不足n個,則返回一個空流,與limit(n)互補
	Stream<T> skip(long n);
	@Test
	public void test4(){
        ps.stream()
          .filter((p) -> p.getAge >= 25)
          .skip(2)
          .forEach(System.out::println);
    }
  • distinct():去重。通過流中元素的hashCode()方法與equals()方法去重,所以需要重寫這兩個方法
	Stream<T> distinct();
	@Test
	public void test5(){
        ps.stream()
          .distinct()
          .forEach(System.out::println);
    }
  • 注意:多箇中間操作可以連起來形成一個流水線,除非流水線上觸發終止操作,否則中間操作不會執行任何的處理,而在終止操作時一次性全部處理。看如下代碼:
	@Test 
	public void test(){
        ps.stream()
          .filter((p) -> {
              System.out.println("Stream API 的中間操作");
              return p.getAge() >= 25;    
          });
    }
	//上述代碼如果運行,將看不到任何結果。
	//但如果給流加一個終止操作:
	@Test 
	public void test(){
        ps.stream()
          .filter((p) -> {
              System.out.println("Stream API 的中間操作");
              return p.getAge() >= 25;
          })
          .forEach(System.out::println);
    }	
	//將會得到結果
2.2 映射
  • map():接收一個函數作爲參數,流中的每個元素都將應用到這個函數上,並通過這個函數返回一個新的元素
	<R> Stream<R> map(Function<? super T, ? extends R> mapper);
	//Function接口傳入T類型的參	數,返回R類型或R類型子類的結果,而map最終返回一個R類型的流
	@Test
	public void test6(){
        ps.stream()
          .map(People::getName)//方法引用->類::實例方法
          .forEach(System.out::println);//方法引用->實例::實例方法
    }
	//執行結果:將每個People對象的名字提取了出來
	Tom Tony Jerry Lucy John Tom
  • flatMap():接收一個函數作爲參數,將流中的每個元素通過該函數轉換爲另一個流,然後把所有的流連接成一個流
	<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
	//Function接口傳入T類型的參數,返回一個R類型或是R類型的子類的流,但map最後只返回一個R類型的流。據說是將中間產生的流合併爲一個流了
	@Test
	public void test7(){
        List<String> list = Arrays.asList("abc", "de", "fg");
        Stream<Character> stream = list.stream()
            							.flatMap(StreamApiTest::StringToCharacter);
        
        stream.forEach(System.out::println);
    }
	
	public static Stream<Character> StringToCharacter(String str){
        List<Character> list = new ArrayList<>(str.length());
        for(char ch : str.toCharArray()){
            list.add(ch);
        }
        return list.stream();
    }
	//執行結果:a b c d e f g
2.3 排序
  • sorted():自然排序(根據Comparable接口中的compareTo方法)
	@Test
	public void test8(){
        List<String> list = Arrays.asList("tom", "lucy", "jerry", "rose", "bob");
        list.stream()
            .sorted()
            .forEach(System.out::println);
    }
  • sorted(Comparator com) :定製排序
    Stream<T> sorted(Comparator<? super T> comparator);
	@Test
	public void test9(){
        ps.stream()
          .sorted((x, y) -> {
              if(Integer.compare(x.getAge(), y.getAge()) == 0){
                  return -Double.compare(x.getSalary(), y.getSalary());
              }
              return -Integer.compare(x.getAge(), y.getAge());
          })
          .forEach(System.out::println);
    }

3. 終止操作

3.1 查找與匹配
  • allMatch(): 檢查是否匹配所有元素,返回一個boolean值
	boolean allMatch(Predicate<? super T> predicate);
	@Test
	public void test10(){
        boolean b1 = ps.stream()
          .allMatch((p) -> p.getAge() > 25);//看是不是所有對象的年齡都大於25
		System.out.println(b1);
    }
	//執行結果:false
  • anyMatch():檢查流中是否至少有一個元素滿足條件,返回boolean值
	boolean anyMatch(Predicate<? super T> predicate);
	@Test
	public void test11(){
        boolean b1 = ps.stream()
            			.anyMatch((p) -> p.getAge() > 25);
        System.out.println(b1);
    }
	//執行結果:true
  • noneMatch():檢查流中是否所有元素都不匹配,返回boolean值
	boolean noneMatch(Predicate<? super T> predicate);
	@Test
	public void test12(){
        boolean b1 = ps.stream()
            			.noneMatch((p) -> p.getAge() < 20);
        System.out.println(b1);
    }
	//執行結果:true      (沒有一個年齡是小於20的,所有都不匹配,返回true)
  • final class Optional<T> :一個容器類,可以有效的避免空指針異常。stream api中對於結果無法確定的情況,將會把結果封裝在這個容器類中。其中兩個重要的方法如下:

    • T get():若值不爲null,則可以得到該值

          public T get() {
              if (value == null) {
                  throw new NoSuchElementException("No value present");
              }
              return value;
          }
      
    • T orElse(T other) :如果封裝的value爲null,可以通過此方法避免空指針

          public T orElse(T other) {
              return value != null ? value : other;
          }
      
  • findFirst():返回流中第一個元素,返回類型爲Optional,防止空指針異常

	Optional<T> findFirst();
	@Test 
	public void test13(){
        Optional<People> op = ps.stream()
            					.sorted((x, y) -> -Integer.compare(x.getAge(), y.getAge()))
            					.findFirst();
        System.out.println(op.get());
    }
  • findAny():返回當前流中的任意一個元素
    Optional<T> findAny();
	@Test
	public void test14(){
        Optional<People> op = ps.stream().findAny();
        System.out.println(op.get());
    }
  • count():返回流中元素總個數
	long count();
	@Test
	public void test15(){
        Long cnt = ps.stream()
            		 .filter((p) -> p.getAge() > 25)
            		 .count();
        System.out.println(cnt);
    }
  • max():返回流中最大的元素,通過自定義規則比較
	Optional<T> max(Comparator<? super T> comparator);
	@Test
	public void test16(){
        Optional<People> op = ps.stream()
            					.max((x, y) -> Double.compare(x.getSalary(), y.getSalary()));
        System.out.println(op.get());
    }//得到薪水最高的人
  • min():返回流中最小的元素,通過自定義規則比較
	Optional<T> min(Comparator<? super T> comparator);
	@Test
	public void test17(){
        Optional<Integer> op = ps.stream()
            					 .map(People::getSalary)
            					 .min(Integer::compare);
        System.out.println(op.get());
    }//先取出每個人的薪水,然後得到最低的薪水
  • forEach():內部迭代
3.2 歸約
  • reduce(T identity, BinaryOperator) / reduce(BinaryOperator):將流中元素反覆結合起來,得到一個值。注意這兩種使用方法的返回值

    • reduce(T identity, BinaryOperator)
    	T reduce(T identity, BinaryOperator<T> accumulator);
    	//identity: 累加元素的初始值
    	//public interface BinaryOperator<T> extends BiFunction<T,T,T>,所以需要實現BiFunction接口中的R apply(T t, U u);方法,即一個二元運算
    
    	@Test public void test18(){
            List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6 ,7, 8, 9, 10);
            Integer sum = list.stream()
                				.reduce(0, (x, y) -> x + y);
            System.out.println(sum);
        }//執行結果:55
    
    • reduce(BinaryOperator)
    	Optional<T> reduce(BinaryOperator<T> accumulator);
    
    	@Test public void test19(){
            Optional<Integer> op = ps.stream()
                			.map(People::getSalary)
                			.reduce((x, y) -> x + y);
            System.out.println(op.get());
        }//將所有人的薪水累加,因爲沒有初始值,所有返回Optional
    
3.3 收集

將流轉換爲其他形式。接受一個Collector接口的實現,用於流中元素做彙總

  • 可以將流中元素收集進入List,Set這一系列單列集合中
	@Test
	public void test20(){
        //將名字提取出來並收集到一個list中
        List<String> list = ps.stream()
            					.map(People::getName)
            					.collect(Collectors.toList());
        list.forEach(System.out::println); 
        System.out.println("--------------------------");
        //將名字提取出來放入set中
        Set<String> set = ps.stream()
            				.map(People::getName)
            				.collect(Collectors.toSet());
        set.forEach(System.out::println);
        System.out.println("--------------------------");
        //將年齡提取出來放入hashset中
        HashSet<Integer> hashSet = ps.stream().map(People::getAge)
        			.collect(Collectors.toCollection(() -> new HashSet<>()));
        hashSet.forEach(System.out::println);
    }

如果想將元素放入特定的集合,如HashSet,LinkedHashSet等,就需要使用Collectors中的toCollection()方法,傳入一個Supplier接口的實現即可。

  • 可以收集爲Map
	@Test
	public void test21() {
		List<String> list = Arrays.asList("abc", "def", "g");
        //將每個元素的小寫作爲key,大寫作爲value
		Map<String, String> map = list.stream()
									.collect(Collectors.toMap(s -> s, String::toUpperCase));
		map.forEach((x, y) -> System.out.println(x + "->" + y));
	}
  • 求流中的元素的總數、平均值、總和、最大值、最小值
	@Test
	public void test22() {
		//總數(過濾後元素的總數)
		Long cnt = ps.stream().filter((p) -> p.getAge() > 30)
					.collect(Collectors.counting());
		System.out.println(cnt);
		
		//平均值(求工資的平均值)
		Double salary = ps.stream().collect(Collectors.averagingInt(People::getSalary));
		System.out.println(salary);
		
		//總和(求工資總和)
		Integer sum = ps.stream().collect(Collectors.summingInt(People::getSalary));
		System.out.println(sum);
		
		//最大值(求年齡最大的員工)
		Optional<People> p = ps.stream()
			.collect(Collectors.maxBy((x, y) -> Integer.compare(x.getAge(), y.getAge())));
		System.out.println(p.get());
		
		//最小值(求最小的工資)
		Optional<Integer> min = ps.stream()
						.map(People::getSalary)
						.collect(Collectors.minBy((x,y) -> Integer.compare(x, y)));
		System.out.println(min.get());
	}
  • 分組
	@Test
	public void test23() {
		//按年齡分組
		Map<Integer, List<People>> map1 = ps.stream().collect(Collectors.groupingBy(People::getAge));
		System.out.println(map1);
		
		//多級分組(先按年齡分組,再按薪水分組)
		Map<Integer, Map<String, List<People>>> map2 = ps.stream()
				.collect(Collectors.groupingBy(People::getAge, Collectors.groupingBy((p) -> {
					if(((People)p).getSalary() > 6000) {
						return "高薪";
					}
					return "低薪";
				})));
		System.out.println(map2);
	}
  • 分區(滿足條件放在一起,true作爲key,不滿足條件放另一邊,false作爲key)
	@Test
	public void test24() {
		Map<Boolean, List<People>> map = ps.stream()
										.collect(Collectors.partitioningBy((p) -> p.getAge() > 30));
		System.out.println(map);
	}
  • Collectors.summarizing…():另一種方式求平均值、總數、總和、最大值、最小值
	@Test
	public void test25() {
		IntSummaryStatistics su = ps.stream()
									.collect(Collectors.summarizingInt(People::getSalary));
		
		System.out.println(su.getAverage());
		System.out.println(su.getCount());
		System.out.println(su.getMax());
		System.out.println(su.getMin());
		System.out.println(su.getSum());
	}
  • Collectors.joining():連接
	@Test
	public void test26() {
		String str1 = ps.stream().map(People::getName).collect(Collectors.joining());
		System.out.println(str1);
		//TomTonyJerryLucyJohnTom
		String str2 = ps.stream().map(People::getName).collect(Collectors.joining(","));
		System.out.println(str2);
		//Tom,Tony,Jerry,Lucy,John,Tom
		String str3 = ps.stream().map(People::getName).collect(Collectors.joining(",","==","=="));
		System.out.println(str3);
        //==Tom,Tony,Jerry,Lucy,John,Tom==
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章