java8新特性——StreamAPI

什麼是Stream?

是數據渠道,用於操作數據源(集合數組等)所生成的元素序列

“集合講的是數據,流講的是計算”

注意:

  1. Strram自己不會存儲元素。
  2. Stream不會改變源對象。相反,他們會返回一個持有結果的新Stream
  3. Stream操作時延遲執行的。這意味着他們會等到需要結果的時候才執行

Stream的三個操作步驟:

  1. 創建Stream
  2. 中間操作
  3. 終止操作(終端操作)

創建Stream

  1. 可以通過Collection系列集合提供的stream()或parallelStream()

    • stream():串行流

    • parallelStream():並行流

    • List<Object> list = new ArrayList<>();
      Stream<Object> stream = list.stream();//得到一個流
      
  2. 通過Arrays中的靜態方法stream()獲取數組流

    • String[] emps = new String[10];
      Stream<String> stream1 = Arrays.stream(emps);
      
  3. 通過Stream類中的靜態方法 of()

    • Stream<String> stream2 = Stream.of("aa", "bb", "cc");  //可變數組
      
  4. 創建無限流

    1. 迭代

      • Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2);
        //stream3.forEach(System.out::println);
        //按照一元運算的規律  從0開始,無限+2
        
        //添加限制操作  限制只打印10個
        stream3.limit(10).forEach(System.out::println);
        
    2. 生成

      • Stream<Double> generate = Stream.generate(Math::random); 
        //生成隨機數
        generate.limit(2).forEach(System.out::println);
        

創建實體類

public class Employee {
	private String name;
	private  Integer age;
	private  double salary;
	private Status status;

	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 double getSalary() {
		return salary;
	}

	public void setSalary(double salary) {
		this.salary = salary;
	}

	public Status getStatus() {
		return status;
	}

	public void setStatus(Status status) {
		this.status = status;
	}

	public Employee(String name, Integer age, double salary, Status status) {
		this.name = name;
		this.age = age;
		this.salary = salary;
		this.status = status;
	}

	public Employee() {

	}

	@Override
	public String toString() {
		return "Employee{" +
				"name='" + name + '\'' +
				", age=" + age +
				", salary=" + salary +
				", status=" + status +
				'}';
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		Employee employee2 = (Employee) o;
		return Double.compare(employee2.salary, salary) == 0 &&
				Objects.equals(name, employee2.name) &&
				Objects.equals(age, employee2.age) &&
				status == employee2.status;
	}

	@Override
	public int hashCode() {
		return Objects.hash(name, age, salary, status);
	}

	public enum Status{
		FREE,	//空閒
		BUSY,	//忙
		VOCATION	//休假
	}
}

中間操作與終止操作的一些說明

  • 如果沒有終止操作,那麼中間操作不會執行
  • 多箇中間操作可以連接起來形成一個流水線,除非流水線上觸發終止操作,
  • 否則中間操作不會執行,任何的處理
  • 而在終止操作時一次性全部處理,成爲惰性求值

例子:

@Test
public void test3() {
    Stream<Employee> stream = employees.stream()//創建流 獲取流
        .filter((x) -> {
            System.out.println("中間操作");
            return x.getSalary() > 6000;
        });

}

這段代碼沒有任何運行結果,因爲沒有終止操作。

中間操作

篩選與切片:

方法 介紹
filte 接收Lambda ,從流中排除某些元素
limit 截斷流,使其元素不超過給定數量。
skip(n) 跳過元素,返回一個扔掉了前n個元素的流。若流中元素不足n個,則返回一個空流。與limit(n)互補
distinct 篩選,通過流所生成元素的hashCode()和equals()去重複元素

filter

  • 接收Lambda ,從流中排除某些元素
@Test
public void test2() {
    employees.stream()//創建流 獲取流
        .filter((x) -> x.getSalary() > 6000)  
        //入參斷言型接口 boolean TraderTest(T t);
        .forEach(System.out::println);        //終止操作
}

結果:
Employee{name='張三', age=18, salary=9999.99, status=FREE}
Employee{name='趙六', age=36, salary=6666.66, status=BUSY}
Employee{name='趙六', age=36, salary=6666.66, status=FREE}
Employee{name='田七', age=12, salary=8888.88, status=BUSY}

limit

/**
	 * limit —— 截斷流,使其元素不超過給定數量。
	 */
@Test
public void test5() {

    employees.stream()
        .filter((x) ->
                {
                    System.out.println("短路");
                    return x.getSalary() > 6000;
                }
               )
        .limit(2)  //篩選完後只打印兩個
        .forEach(System.out::println);

    //這裏找到符合條件的兩個就停止,後續的操作就沒有了,
    //說明所有的中間操作都是同步進行的,增加了效率

}

結果:
短路
Employee{name='張三', age=18, salary=9999.99, status=FREE}
短路
短路
短路
Employee{name='趙六', age=36, salary=6666.66, status=BUSY}

skip(n)

/**
	 * skip(n) —— 跳過元素,返回一個扔掉了前n個元素的流。
	 * 若流中元素不足n個,則返回一個空流。與limit(n)互補
	 */
@Test
public void test6() {
    employees.stream()
        .filter((x) ->
                {
                    System.out.println("skip");
                    return x.getSalary() > 6000;
                }
               )
        .skip(2)
        .forEach(System.out::println);
}

結果:
skip
skip
skip
skip
skip
Employee{name='趙六', age=36, salary=6666.66, status=FREE}
skip
Employee{name='田七', age=12, salary=8888.88, status=BUSY}

distinct

/**
	 * distinct —— 篩選,通過流所生成元素的hashCode()和equals()去重複元素
	 *
	 * 需要重寫hashCode和equals
	 */
@Test
public void test7() {
    employees.stream()
        .distinct()
        .forEach(System.out::println);
}

結果:
Employee{name='張三', age=18, salary=9999.99, status=FREE}
Employee{name='李四', age=58, salary=5555.55, status=VOCATION}
Employee{name='王五', age=26, salary=3333.33, status=VOCATION}
Employee{name='趙六', age=36, salary=6666.66, status=BUSY}
Employee{name='趙六', age=36, salary=6666.66, status=FREE}
Employee{name='田七', age=12, salary=8888.88, status=BUSY}

映射

方法 介紹
map 接收Lambda,將元素轉換成其他形式或提取信息。接收一個函數作爲參數,該函數會被應用到每個元素上,並將其映射成一個新的元素。
flatMap 接收一個函數作爲參數,將流中的每個值都換成另一個流,然後把所有的流連成一個流

map

/**
	 * map——接收Lambda,將元素轉換成其他形式或提取信息。接收一個函數作爲參數,
	 * 		該函數會被應用到每個元素上,並將其映射成一個新的元素。
	 */
	@Test
	public void test8(){
		List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");
		list.stream()
				.map((x) -> x.toUpperCase()) //把集合所有內容轉大寫
				.forEach(System.out::println);
		//AAA
		//BBB
		//CCC
		//DDD

		System.out.println("---------------------");

		employees.stream()
				.map((x) -> x.getName())
				.forEach(System.out::println);
		//張三
		//李四
		//王五
		//趙六
		//趙六
		//田七

	}

flatMap

先聲明一個方法

//穿進去一個字符串,把一個字符串中所有的字符一個一個提取出來
	 static Stream<Character> filterCharacter(String str){
		List<Character> list = new ArrayList<>();

		for (Character character : str.toCharArray()) {
			list.add(character);
		}

		return list.stream();
	}

使用flapmap

/**
	 * flatMap——接收一個函數作爲參數,將流中的每個值都換成另一個流,然後把所有的流連成一個流
	 */
@Test
public void test9(){
    System.out.println("\n\n---------普通字符串---------");
    //普通字符串
    String str = "helloworld";
    filterCharacter(str).forEach(System.out::print);
    //helloworld

    System.out.println("\n\n---------集合---------");
    //集合
    List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");
    Stream<Stream<Character>> stream = list.stream()
        .map(demo1::filterCharacter);  //{{a,a,a},{b,b,b},{c,c,c},...}
    //得到的是流的集合,集合裏還是個流,然後二層遍歷
    stream.forEach(
        (x) -> x.forEach(System.out::print)
    );
    //結果:aaabbbcccddd


    System.out.println("\n\n---------flatMap---------");
    //扁平化,平鋪的意思
    Stream<Character> stream1 = list.stream()//{a,a,a,b,b,b,c,c,c,...}
        .flatMap((x) -> filterCharacter(x));
    stream1.forEach(System.out::print);
    //結果:aaabbbcccddd


}

map和flatmap的區別:

  • map是把多個流放入一個流 形成{ { }, { }, { } }的形式
  • flapmap是把每個流中的元素,作爲單獨的元素加入到新的流中
  • 相當於集合add() 和 addAll()方法

測試集合add() 和 addAll()方法

/**
	 * function:測試集合add() 和  addAll()方法
	 */
	@Test
	public void test10(){

		List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");

		List list2 = new ArrayList<>();

		list2.add(111);
		list2.add(222);
		list2.add(list);
		System.out.println(list2);
		//結果:[111, 222, [aaa, bbb, ccc, ddd]]


		List list3 = new ArrayList<>();

		list3.add(111);
		list3.add(222);
		list3.addAll(list);
		System.out.println(list3);
		//結果:[111, 222, aaa, bbb, ccc, ddd]
	}

結論:

  • add是整個集合傳進來
  • addAll()是將集合中的元素拆分,作爲單獨的元素傳進來
  • 所以map相當於把流加到當前的流中
  • flatMap相當於把流中的元素,加到新流中

排序

方法 介紹
sorted() 自然排序
sorted(Comparator com) 定製排序

sorted()

/**
	 	排序
	 	sorted() 自然排序
	 	sorted(Comparator com) 定製排序
	 */
@Test
public void test11(){
    List<String> list = Arrays.asList("eee","ccc","bbb","aaa","ddd");
    list.stream()
        .sorted()
        .forEach(System.out::println);
    //結果:
    //aaa
    //bbb
    //ccc
    //ddd
    //eee

}

sorted(Comparator com)

/**
	 * sorted(Comparator com) 定製排序
	 */
	@Test
	public void test111(){
		List<String> list = Arrays.asList("eee","ccc","bbb","aaa","ddd");
		list.stream()
				.sorted((x,y) -> -x.compareTo(y))
				.forEach(System.out::println);
		//結果:
		//eee
		//ddd
		//ccc
		//bbb
		//aaa

		//定製排序:按照年齡排序,年齡一樣按照姓名排
		employees.stream()
				.sorted((x,y) -> {
					if(x.getAge().equals(y.getAge())){
						return x.getName().compareTo(y.getName());
					}	else {
						return x.getAge().compareTo(y.getAge());
					}

				}).forEach(System.out::println);
	}
結果:
Employee{name='田七', age=12, salary=8888.88, status=BUSY}
Employee{name='張三', age=18, salary=9999.99, status=FREE}
Employee{name='王五', age=26, salary=3333.33, status=VOCATION}
Employee{name='趙六', age=36, salary=6666.66, status=BUSY}
Employee{name='趙六', age=36, salary=6666.66, status=FREE}
Employee{name='李四', age=58, salary=5555.55, status=VOCATION}

終止操作

查找與匹配

方法 介紹
allMatch(Predicate p) 檢查是否匹配所有元素
anyMatch(Predicate p) 檢查是否至少匹配一個元素
noneMatch(Predicate p) 檢查是否沒有匹配所有元素
findFirst() 返回第一個元素
findAny() 返回當前流中的任意元素
count() 返回流中元素的總個數
max() 返回流中最大值
min() 返回流中最小值

allMatch(Predicate p)

anyMatch(Predicate p)

noneMatch(Predicate p)

findFirst()

findAny()

/**
    allMatch(Predicate p)

    anyMatch(Predicate p)

    noneMatch(Predicate p)

    findFirst()

    findAny()	
*/
@Test
public void test12(){

    //判斷是否所有員工都是BUSY狀態
    boolean b = employees.stream()
        .allMatch((x) -> x.getStatus().equals(Employee.Status.BUSY));
    System.out.println("b = " + b);	//b = false

    //判斷是否至少有一位員工是BUSY狀態
    boolean b1 = employees.stream()
        .anyMatch((x) -> x.getStatus().equals(Employee.Status.BUSY));
    System.out.println("b1 = " + b1);//b1 = true

    //判斷是否沒有休假的員工
    boolean b2 = employees.stream()
        .noneMatch((x) -> x.getStatus().equals(Employee.Status.VOCATION));
    System.out.println("b2 = " + b2);//b2 = false

    //返回工資最低的員工 findFirst()
    Optional<Employee> first = employees.stream()
        .sorted((x,y) -> Double.compare(x.getSalary(),y.getSalary()))
        .findFirst();
    System.out.println("first = " + first.get());
    //first = Employee{name='王五', age=26, salary=3333.33, status=VOCATION}

    //任意找一個空閒狀態的員工(串行流,挨個兒找)
    Optional<Employee> any = employees.stream()
        .filter((x) -> x.getStatus().equals(Employee.Status.FREE))
        .findAny();
    System.out.println("any = " + any.get());
    //any = Employee{name='趙六', age=36, salary=6666.66, status=FREE}


    //任意找一個空閒狀態的員工(並行流,多個線程一起找,誰先找到算誰)
    Optional<Employee> any2 = employees.parallelStream()
        .filter((x) -> x.getStatus().equals(Employee.Status.FREE))
        .findAny();
    System.out.println("any2 = " + any2.get());
    //any = Employee{name='趙六', age=36, salary=6666.66, status=FREE}

}

count()

max()

min()

/**
	 * 	count()				返回流中元素的總個數
	 * 	max()				返回流中最大值
	 * 	min()				返回流中最小值
	 */
@Test
public void test13(){
    long count = employees.stream()
        .count();
    System.out.println("count = " + count); //count = 6

    //獲取工資最高的
    Optional<Employee> max = employees.stream()
        .max((x, y) -> Double.compare(x.getSalary(), y.getSalary()));
    System.out.println("max = " + max.get().getSalary());  //max = 9999.99

    //獲取工資最低的
    Optional<Double> min = employees.stream()
        .map(Employee::getSalary)
        .min(Double::compareTo);
    System.out.println("min = " + min.get());  //min = 3333.33


}

歸約

  • 可以將流中元素反覆結合起來,得到一個值
方法 介紹
reduce(T identity, BinaryOperator) 返回T identity 起始值 BinaryOperator二元運算
reduce(BinaryOperator) 返回Optional

reduce(T identity, BinaryOperator)

reduce(BinaryOperator)

/**
	 * function:
	 */
@Test
public void test14(){
    List<Integer> list = Arrays.asList(1,2,3,4,5);

    Integer sum = list.stream()
        .reduce(0, (x, y) -> x + y);
    System.out.println("sum = " + sum);  //sum = 15


    Integer sum2 = list.stream()
        .reduce(10, (x, y) -> x + y);
    System.out.println("sum2 = " + sum2);  //sum2 = 25


    //首先將起始值作爲x,然後從流中取出一個元素給y,
    //然後把運算的結果給x,再從集合中取值運算,直到最後


    System.out.println("----------工資的總和-------------");
    Optional<Double> sumSalary = employees.stream()
        .map((x) -> x.getSalary())
        .reduce((x, y) -> x + y);
    System.out.println("sumSalary= " + sumSalary.get()); 
    //sumSalary = 41111.07

}

這裏想一下,爲什麼上面的返回值是原始值,而工資的返回值是Optional

上面的有初始值,一定不會爲空,,而下面的工資沒有初始值,可能爲空

Optional的原理就是,只要返回的對象有可能爲空,那麼就封裝到Optional中去

備註:map和reduce的連接稱爲map-reduce模式,因Google用它類進行網絡搜索而出名

收集

collect(Collector c)——將流轉換爲其他形式。接收一個Collector接口的實現,用於給Stream中元素做彙總的方法

Collector接口中方法的實現決定了如何對流執行收集操作(如蒐集到List,Set,Map)

但是Collectors實用類提供了很多靜態方法,可以方便地創建常見收集器實例,

Collectors.toList()

/**
	 * function:把當前公司員工的名字提取出來,並放到一個集合中
	 */
@Test
public void test15(){

    //收集到list集合中
    List<String> list = employees.stream()
        .map(Employee::getName)
        .collect(Collectors.toList());
    System.out.println("list = " + list);
    //list = [張三, 李四, 王五, 趙六, 趙六, 田七]



    //收集到set集合中,可去重
    Set<String> set = employees.stream()
        .map(Employee::getName)
        .collect(Collectors.toSet());
    System.out.println("set = " + set);
    //set = [李四, 張三, 王五, 趙六, 田七]


    //可以在Collectors.toCollection()中指定任意集合
    HashSet<String> hashSet = employees.stream()
        .map(Employee::getName)
        .collect(Collectors.toCollection(HashSet::new));
    System.out.println("hashSet = " + hashSet);

}

操作數字

/**
	 * function:操作數字
	 */
@Test
public void test16(){

    //總數
    Long size = employees.stream()
        .collect(Collectors.counting());
    System.out.println("集合總數 = " + size);

    //平均值
    Double avg = employees.stream()
        .collect(Collectors.averagingDouble((x) -> x.getSalary()));
    System.out.println("工資平均值"+avg);

    //計算總和
    Double total = employees.stream()
        .collect(Collectors.summingDouble((x) -> x.getSalary()));
    System.out.println("工資總數: = " + total);


    //最大值  方法一、
    Optional<Employee> max = employees.stream()
        .collect(Collectors.maxBy((x, y) -> Double.compare(x.getSalary(), y.getSalary())));
    System.out.println("max = " + max.get());
    //max = Employee{name='張三', age=18, salary=9999.99, status=FREE}

    //最大值  方法二、
    Optional<Employee> max1 = employees.stream()
        .max((x, y) -> Double.compare(x.getSalary(), y.getSalary()));
    System.out.println("max1 = " + max1.get());
    //max = Employee{name='張三', age=18, salary=9999.99, status=FREE}


    //最小值
    Optional<Double> min = employees.stream()
        .map((x) -> x.getSalary())
        .collect(Collectors.minBy(Double::compare));
    System.out.println("min = " + min.get());
    //min = 3333.33


}

Collectors.groupingBy()

/**
	 * function: 分組
	 */
	@Test
	public void test17(){
		//按照狀態分組  返回map,key-狀態  value-狀態下的內容
		//map沒有foreach方法
		Map<Employee.Status, List<Employee>> map = employees.stream()
				.collect(Collectors.groupingBy((x) -> x.getStatus()));
		System.out.println("map = " + map);
	}

結果:
map = {
	 BUSY=[Employee{name='趙六', age=36, salary=6666.66, status=BUSY}, Employee{name='田七', age=12, salary=8888.88, status=BUSY}],
	 VOCATION=[Employee{name='李四', age=58, salary=5555.55, status=VOCATION}, Employee{name='王五', age=26, salary=3333.33, status=VOCATION}],
	 FREE=[Employee{name='張三', age=18, salary=9999.99, status=FREE}, Employee{name='趙六', age=36, salary=6666.66, status=FREE}]
	}

多級分組

/**
	 * function:多級分組
	 * 可以在Collectors.groupingBy後再跟一個Collectors.groupingBy
	 * 先按照狀態分,分完之後按照年齡分
	 */
@Test
public void test18(){
Map<Employee.Status, Map<String, List<Employee>>> map = employees.stream()
        .collect(Collectors.groupingBy(Employee::getStatus,
           Collectors.groupingBy((e) -> {
                  if (e.getAge() <= 35) {
                         return "青年";
                   } else if (e.getAge() > 35 && e.getAge() <= 50) {
                          return "中年";
                   } else {
                          return "老年";
                    }
              })
));
    System.out.println("map = " + map);

/*
結果:
map = {
BUSY={
	青年=[Employee{name='田七', age=12, salary=8888.88, status=BUSY}],
	中年=[Employee{name='趙六', age=36, salary=6666.66, status=BUSY}]
     },
VOCATION={
	青年=[Employee{name='王五', age=26, salary=3333.33, status=VOCATION}],
	老年=[Employee{name='李四', age=58, salary=5555.55, status=VOCATION}]
		},
FREE={
	青年=[Employee{name='張三', age=18, salary=9999.99, status=FREE}],
	中年=[Employee{name='趙六', age=36, salary=6666.66, status=FREE}]
	 }
}

*/

}

分片,分區

/**
	 * function: 分片,分區
	 *
	 * 符合條件的一個區,不滿足條件的一個區
	 */
@Test
public void test19(){
    Map<Boolean, List<Double>> map = employees.stream()
        .map(Employee::getSalary)
        .collect(Collectors.partitioningBy((x) -> x > 6000));
    System.out.println(map);
    
//結果:
//{
//   false=[5555.55, 3333.33],
//   true=[9999.99, 6666.66, 6666.66, 8888.88]
// }


}

Collectors.summarizingDouble

/**
	 * function: 計算數字總和平均值最大最小
	 */
@Test
public void test20(){
    DoubleSummaryStatistics dss = employees.stream()
        .collect(Collectors.summarizingDouble(Employee::getSalary));

    double sum = dss.getSum();
    System.out.println("sum = " + sum); //sum = 41111.07

    double max = dss.getMax();
    System.out.println("max = " + max); //max = 9999.99
}

joining()

/**
	 * function:連接 joining
	 */
@Test
public void test21(){
    //連接字符串
    String collect = employees.stream()
        .map(Employee::getName)
        .collect(Collectors.joining());
    System.out.println("collect = " + collect); 
    //collect = 張三李四王五趙六趙六田七


    //還可以加逗號,去除前後的逗號
    String collect1 = employees.stream()
        .map(Employee::getName)
        .collect(Collectors.joining(","));
    System.out.println("collect1 = " + collect1); 
    //collect1 = 張三,李四,王五,趙六,趙六,田七


    //如果要首尾添加
    String collect2 = employees.stream()
        .map(Employee::getName)
        .collect(Collectors.joining(",", "*****", "###"));
    System.out.println("collect2 = " + collect2);  
    //collect2 = *****張三,李四,王五,趙六,趙六,田七###

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章