Stream API簡介
官方對Stream API給出的定義:A sequence of elemets supporting sequential and parallel aggregate operations。所以Java 8中引入的Stream API是一個用來處理數組和集合的API。Stream API具有如下的一些特性:
- Stream API不是數據結構,沒有相關的內存存儲。
- 不支持索引訪問,只是對序列化的元素(數組和集合)進行處理
- 延遲計算,Stream API在進行終止操作之前不會開始計算。
- 方便的執行並行運行。
- 便捷的生成數組或集合。
- 支持過濾、查找、轉換、聚合、彙總等針對流的操作。
Stream的運行機制包含三個部分,分別是:數據源source,中間操作和終止操作。流的數據源可以是一個數組、一個集合、一個生成器或一個I/O通道等。並且一個流可以由0個或者多箇中間操作,每個中間操作都會返回一個新的流,以供下一個操作使用,一個流只能有一個終止操作。由於StreamAPI延遲計算的特點,因此stream只有在遇到終止操作時,纔會執行stream的相關操作,在此之前並不會開始計算。
下面開始從Stream包含的數據源、中間操作和終止操作三個方面來對Stream API的使用進行舉例說明。
Stream API使用詳解
創建Stream源
Stream數據源(source)可以通過如下幾種方式進行創建:
- 通過數據創建。
- 使用集合創建。
- 使用Stream.generate方法創建無限流。
- 使用Stream.iterate()方法創建無限流。
- 使用其他API創建,例如文件I/O等。
通過數組,使用Stream.of(array)創建流:
// 通過數組創建,使用Stream.of()方法
@Test
void generateDemo1() {
String[] array = {"a", "b", "1", "2"};
Stream<String> stream = Stream.of(array);
stream.forEach(System.out::println);
}
通過集合,使用.stream()方法創建流:
// 通過集合,list.stream()
@Test
void generateDemo2() {
List<String> list = Arrays.asList("a", "b", "1", "2");
Stream<String> stream = list.stream();
stream.forEach(System.out::println);
}
通過Stream.generate()方法創建流:(因爲該方法創建的爲無限流,因此在輸出時,需要使用limit方法來對流進行截取)
// 通過Stream.generate()方法
@Test
void generateDemo3() {
Stream<Integer> stream = Stream.generate(() -> 1); // 這裏是無限流,需要使用limit進行截取
stream.limit(10) // 使用limit方法獲取最前面的10個數字
.forEach(System.out::println);
}
使用Stream.iterate()方法創建無限流,並使用limit方法截取流:
// 通過Stream.iterate()方法
@Test
void generateDemo4() {
Stream<Integer> stream = Stream.iterate(1, x -> x + 1); // 同樣是無限流,需要使用limit進行截取
stream.limit(10) // 使用limit方法獲取最前面的10個數字
.forEach(System.out::println);
}
通過其他API創建流,例如如下使用str.chars()方法可以創建String對象的IntStream流:
// 通過其他的API
@Test
void generateDemo5() {
String str = "abcd1234567890";
IntStream stream = str.chars(); // 返回int類型的stream
// 使用方法的引用(終止操作進行輸出)
stream.forEach(System.out::println); // 等價於stream.forEach(x -> System.out.println(x));
}
通過文件I/O的方式來創建流,這裏輸出項目中pom.xml文件中的內容:
// 通過其他API(通過文件流)
@Test
void generateDemo6() throws IOException {
Files.lines(Paths.get("/Users/yitian/Documents/IDEAWorkspaces/LocalProjects/learning-project/pom.xml"))
.forEach(System.out::println);
}
流的終止操作
Stream流的終止操作常見的有如下幾個:
- 循環:forEach
- 計算:min、max、count、average
- 查找:anyMatch、allMatch、noneMatch、findFirst、findAny
- 匯聚:reduce
- 收集:toArray、collect
當stream執行到終止操作時,纔會真正的開始計算過程。下面爲具體的方法實例:
// 終止操作forEach
@Test
void demo1() {
// 返回集合流中所有的偶數
Arrays.asList(1, 2, 3, 4, 5).stream().filter(x -> {
System.out.println("----");
return x % 2 == 0;
}).forEach(System.out::println); // 如果沒有終止操作,filter中間操作並不會進行,stream有延遲運行的特點
}
// 終止操作:map(), sum(), count(), get(), findAny(), findFirst()
@Test
void demo2() {
// 對集合中的元素,找到偶數然後進行求和
int sum = Arrays.asList(1, 2, 3, 4, 5).stream()
.filter(x -> x % 2 == 0)
.mapToInt(x -> x)
.sum();
System.out.println(sum);
// 計算集合中最大元素
int max = Arrays.asList(1, 2, 3, 4, 5).stream()
.max((a, b) -> a - b)
.get();
System.out.println(max);
// 計算集合中最小元素
int min = Arrays.asList(1, 2, 3, 4, 5).stream()
.min((a, b) -> a - b)
.get();
System.out.println(min);
// 查找偶數並計數
long count = Arrays.asList(1, 2, 3, 4, 5).stream()
.filter(x -> x % 2 == 0)
.count();
System.out.println(count);
// 查找偶數並返回任意一個
Optional<Integer> op1 = Arrays.asList(1, 2, 3, 4, 5).stream()
.filter(x -> x % 2 == 0)
.findAny();
System.out.println(op1.get());
// 查找偶數並返回第一個元素
Optional<Integer> op2 = Arrays.asList(1, 2, 3, 4, 5).stream()
.filter(x -> x % 2 == 0)
.findFirst();
System.out.println(op2.get());
}
// 終止操作:collect
@Test
void demo3() {
// 從1到50裏面的所有偶數,放到一個list中
// Stream.iterate(1, x -> x + 1).limit(50).map(x -> x + " ").forEach(System.out::print);
List<Integer> list = Stream.iterate(1, x -> x + 1)
.limit(50)
.filter(x -> x % 2 == 0)
.collect(Collectors.toList()); // 操作後生成一個List集合
list.stream()
.map(x -> x + " ")
.forEach(System.out::print);
}
流的中間操作
Stream API允許0個或多個流的中間操作,常用的中間操作如下:
- 過濾:filter
- 去重:distinct
- 排序:sorted
- 截取:limit,skip
- 轉換:map/flatMap
- 其他:peek
元素去重distinct或使用set:
// 中間操作:distinct
@Test
void demo4() {
// 去重
Arrays.asList(1, 3, 4, 2, 2, 2, 5, 6, 7).stream()
.distinct()
.map(x -> x + " ")
.forEach(System.out::print);
System.out.println();
// 使用set去重
Set<Integer> set = Arrays.asList(1, 3, 4, 2, 2, 2, 5, 6, 7).stream()
.collect(Collectors.toSet());
set.stream().map(x -> x + " ").forEach(System.out::print);
}
元素排序sort:
// 中間操作:sort()排序
@Test
void demo5() {
// 排序操作,默認爲正序
Arrays.asList(11, 2, 5, 1, 6, 8, 7).stream()
.sorted()
.forEach(System.out::print);
System.out.println();
// 這種也爲正序
Arrays.asList(11, 2, 5, 1, 6, 8, 7).stream()
.sorted((a, b) -> a - b)
.forEach(System.out::print);
System.out.println();
// 改爲倒敘
Arrays.asList(11, 2, 5, 1, 6, 8, 7).stream()
.sorted((a, b) -> b - a)
.forEach(System.out::print);
System.out.println();
// 字符串排序(按字符長度排序),並且爲每個單詞設置中間一個空格顯示
Arrays.asList("cn", "admin", "net", "io").stream()
.map(x -> x + " ")
.sorted((a, b) -> a.length() - b.length())
.forEach(System.out::print);
System.out.println();
}
skip和limit可以實現分頁的功能:
// 中間操作:skip
@Test
void demo6() {
// skip
List<Integer> list = Stream.iterate(1, x -> x + 1)
.limit(50)
.sorted((a, b) -> b - a) // 從大到小排序
.skip(10) // skip爲忽略前十個
.limit(10)
.collect(Collectors.toList());
list.stream()
.map(x -> x + " ")
.forEach(System.out::print);
// 40 39 38 37 36 35 34 33 32 31
// 使用使用skip實現分頁
Stream.iterate(1, x -> x + 1)
.limit(50)
.skip(0) // 第一頁: 40 39 38 37 36 35 34 33 32 31
.limit(10)
.map(x -> x + " ")
.forEach(System.out::print);
Stream.iterate(1, x -> x + 1)
.limit(50)
.skip(10) // 第二頁:11 12 13 14 15 16 17 18 19 20
.limit(10)
.map(x -> x + " ")
.forEach(System.out::print);
}
轉換map操作:
// 中間操作:map轉換,mapToInt
@Test
void demo7() {
// 轉換:將str進行分割,轉換爲整數並求和
String str = "11,22,33,44,55,66";
int sum = Stream.of(str.split(","))
.map(Integer::valueOf)
.mapToInt(x -> x)
.sum();
System.out.println(sum);
sum = Stream.of(str.split(","))
.mapToInt(Integer::valueOf)
.sum();
System.out.println(sum);
}
// 中間操作:map轉換爲自定義對象
@Test
void demo8() {
String str = "tomcat, nginx, apache, jetty";
// 將上面的字符串轉換爲4個User對象,下面三種方法等價
Stream.of(str.split(", "))
.map(x -> new User(x))
.forEach(System.out::println);
Stream.of(str.split(", "))
.map(User::new)
.forEach(System.out::println);
Stream.of(str.split(", "))
.map(User::build)
.forEach(System.out::println);
}
static class User {
private String name;
public User(String name) {
this.name = name;
}
public static User build(String name) {
User user = new User(name);
return user;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User [name: "+name+"]";
}
}
中間操作peek,用於返回中間的計算結果:
// 中間操作:peek
@Test
void demo9() {
// peek方法
String str = "11,22,33,44,55,66";
int sum = Stream.of(str.split(","))
.peek(x -> System.out.print(x + " "))
.mapToInt(x -> Integer.valueOf(x))
.sum();
System.out.println(sum);
// 可以輸出中間結果的計算值: 11 22 33 44 55 66 231
}
Stream的並行處理
Stream API除了上述具有的數據源、中間操作和終止操作外,在對集合或數據進行處理的過程中,還提供了並行計算的一個特性。使用Stream API可以很方便的對集合的處理使用多線程的方式進行,從而提高大型數據集合的處理效率。
在沒有設置Stream API的並行計算時,其默認使用單線程的方式來進行運行,例如如下代碼實例:
// 默認爲一個main線程進行(同步)
@Test
void demo1() {
Optional<Integer> max = Stream.iterate(1, x -> x + 1)
.limit(200)
.peek(x -> {
System.out.println(Thread.currentThread().getName()); // 輸出中間結果
}).max(Integer::compare);
System.out.println(max);
}
在對stream中元素求最大值時,使用peek方法輸出中間結果(用於計算的線程名稱),從輸出可以看到所有的線程都是main線程一個線程在執行。
在Stream API中開啓並行處理比較簡單,直接使用.parallel()中間操作就就可以實現集合的並行處理,例如如下:
// 多線程計算
@Test
void demo2() {
Optional<Integer> max = Stream.iterate(1, x -> x + 1)
.limit(200)
.peek(x -> {
System.out.println(Thread.currentThread().getName()); // 輸出中間結果
})
.parallel() // 使用並行計算
.max(Integer::compare);
System.out.println(max);
}
此時在觀察輸出的線程名稱,可以看到除main線程之外還包含很多其他的線程在運行,如下。可以看到默認使用了8個線程來進行集合的並行處理,這裏是因爲運行該程序的物理機的CPU爲8個core,因此這裏默認的線程並行數和CPU核心數保持一致。
ForkJoinPool.commonPool-worker-7
ForkJoinPool.commonPool-worker-6
ForkJoinPool.commonPool-worker-1
ForkJoinPool.commonPool-worker-2
ForkJoinPool.commonPool-worker-5
ForkJoinPool.commonPool-worker-4
ForkJoinPool.commonPool-worker-3
main
上面加入parallel()中間操作時將流變成了並行流進行處理,其實並行流和序列流(sequence)是可以相互轉換的,例如如下。此時在進行輸出時,實際上還是隻使用一個線程進行的集合處理:
// 順序流和並行流之間的轉換,因爲stream爲延遲計算,因此誰在最後面,則爲優先級較高
@Test
void demo3() {
Optional<Integer> max = Stream.iterate(1, x -> x + 1)
.limit(200)
.peek(x -> {
System.out.println(Thread.currentThread().getName()); // 輸出中間結果
})
.parallel() // 設置爲並行流
.sequential() // 設置爲順序流
.max(Integer::compare);
System.out.println(max);
}
上面提到,加入parallel之後默認使用的並行線程數和CPU核心數保持一致,那麼如果需要更改Stream處理的並行線程數,可以進行如下的設置:
- 一是在方法中設置系統變量來進行設置:System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "5");
- 或者使用-D的方式來配置JVM的啓動參數也可以設置。
但這裏的線程數建議還是不要超過CPU和核心數爲宜,或者直接保持默認即可。
// Stream API內部是使用ForkJoinPool來實現的並行運行
// 設置並行運行的線程數時,一般和所在物理機的CPU核心數相一致
@Test
void demo4() {
// 使用設置啓動參數的方式設置:-Djava.util.concurrent.ForkJoinPool.common.parallelism=5
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "5");
Optional<Integer> max = Stream.iterate(1, x -> x + 1)
.limit(200)
.peek(x -> {
System.out.println(Thread.currentThread().getName());
}).parallel()
.max(Integer::compare);
System.out.println(max);
}
Java 8 Lambda表達式和Stream API使用實例
實例1:URL參數轉換
對於類似於URL參數的字符串:"itemId=1&userId=10000&type=20&token=111111111111111&key=index"。將其根據參數名和參數值轉換爲Map集合:
@Test
void demo1() {
String queryString = "itemId=1&userId=10000&type=20&token=111111111111111&key=index";
Map<String, String> paras = Stream.of(queryString.split("&")) // 得到string[],每個元素爲key=value
.map(x -> x.split("=")) // 對每個數組元素來進行分割,每個元素變爲String[] = [key, value]
.collect(Collectors.toMap(s -> s[0], s -> s[1])); // toMap方法將第1步轉化的數組轉化爲map
System.out.println(paras);
// 輸出結果:{itemId=1, type=20, userId=10000, key=index, token=111111111111111}
}
實例2:Book集合處理
Book對象類型如下:
public class Book {
private int id;
private String name;
private double price;
private String type;
private LocalDate publishDate;
public Book() {
}
public Book(int id, String name, double price, String type, LocalDate publishDate) {
this.id = id;
this.name = name;
this.price = price;
this.type = type;
this.publishDate = publishDate;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
", type='" + type + '\'' +
", publishDate=" + publishDate +
'}';
}
// getter and setter
}
構建Book集合:
private List<Book> books() {
List<Book> books = new ArrayList<>();
books.add(new Book(1, "tomcat", 70d, "服務器", LocalDate.parse("2014-05-17")));
books.add(new Book(2, "jetty", 60d, "服務器", LocalDate.parse("2015-12-01")));
books.add(new Book(3, "nginx", 65d, "服務器", LocalDate.parse("2016-10-17")));
books.add(new Book(4, "java", 66d, "編程語言", LocalDate.parse("2011-04-09")));
books.add(new Book(5, "ruby", 80d, "編程語言", LocalDate.parse("2013-05-09")));
books.add(new Book(6, "php", 40d, "編程語言", LocalDate.parse("2014-08-06")));
books.add(new Book(7, "html", 44d, "編程語言", LocalDate.parse("2011-01-06")));
books.add(new Book(8, "oracle", 150d, "數據庫", LocalDate.parse("2013-08-09")));
books.add(new Book(9, "mysql", 66d, "數據庫", LocalDate.parse("2015-04-06")));
books.add(new Book(10, "ssh", 70d, "編程語言", LocalDate.parse("2016-12-04")));
books.add(new Book(11, "design pattern", 81d, "軟件工程", LocalDate.parse("2017-04-08")));
books.add(new Book(12, "refactoring", 62d, "軟件工程", LocalDate.parse("2011-04-19")));
books.add(new Book(13, "agile", 72d, "軟件工程", LocalDate.parse("2016-02-18")));
books.add(new Book(14, "managing", 42d, "軟件工程", LocalDate.parse("2016-01-19")));
books.add(new Book(15, "algorithm", 66d, "軟件工程", LocalDate.parse("2010-05-08")));
books.add(new Book(16, "oracle 12c", 150d, "數據庫", LocalDate.parse("2016-05-08")));
return books;
}
處理1:將book集合中的所有的id取出來放到list的集合中
@Test
void demo2() {
List<Integer> ids1 = books().stream()
.map(book -> book.getId())
.collect(Collectors.toList());
System.out.println(ids1);
// 輸出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
// 使用方法引用
List<Integer> ids2 = books().stream()
.map(Book::getId)
.collect(Collectors.toList());
System.out.println(ids2);
// 輸出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
}
處理2:將book集合中的所有id取出來,使用逗號拼成一個字符串
@Test
void demo3() {
String str = books().stream()
.map(book -> book.getId() + "")
.collect(Collectors.joining(","));
System.out.println(str);
// 輸出:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
str = books().stream()
.map(book -> book.getId() + "")
.collect(Collectors.joining(",", "(", ")")); // 逗號隔開並使用()括起來
System.out.println(str);
// 輸出:(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)
str = books().stream()
.map(book -> "'"+book.getId()+"'")
.collect(Collectors.joining(","));
System.out.println(str);
// 輸出:'1','2','3','4','5','6','7','8','9','10','11','12','13','14','15'
}
處理3:查找books中的所有類型
// 處理3:查找books中的所有類型
@Test
void demo4() {
// 輸出所有類型
List<String> types = books().stream()
.map(book -> book.getType())
.collect(Collectors.toList());
System.out.println(types);
// 輸出:[服務器, 服務器, 服務器, 編程語言, 編程語言, 編程語言, 編程語言, 數據庫, 數據庫, 編程語言, 軟件工程, 軟件工程, 軟件工程, 軟件工程, 軟件工程]
types = books().stream()
.map(book -> book.getType())
.distinct() // 使用distinct去重
.collect(Collectors.toList());
System.out.println(types);
// 輸出:[服務器, 編程語言, 數據庫, 軟件工程]
Set<String> typeSet = books().stream()
.map(book -> book.getType())
.collect(Collectors.toSet()); // 使用Set集合去重
System.out.println(typeSet);
// 輸出:[編程語言, 服務器, 軟件工程, 數據庫]
}
處理4: 對books集合進行排序(根據不同字段)
@Test
void demo5() {
// 1. 根據價格進行排序(升序排序)
books().stream()
.sorted((book1, book2) -> Double.compare(book1.getPrice(), book2.getPrice()))
.forEach(System.out::println);
System.out.println("---------------------");
// 等價於
Comparator<Book> comparator1 = (book1, book2) -> Double.compare(book1.getPrice(), book2.getPrice());
books().stream()
.sorted(comparator1)
.forEach(System.out::println);
System.out.println("---------------------");
// 輸出:
// Book{id=6, name='php', price=40.0, type='編程語言', publishDate=2014-08-06}
// Book{id=14, name='managing', price=42.0, type='軟件工程', publishDate=2016-01-19}
// Book{id=7, name='html', price=44.0, type='編程語言', publishDate=2011-01-06}
// Book{id=2, name='jetty', price=60.0, type='服務器', publishDate=2015-12-01}
// Book{id=12, name='refactoring', price=62.0, type='軟件工程', publishDate=2011-04-19}
// Book{id=3, name='nginx', price=65.0, type='服務器', publishDate=2016-10-17}
// Book{id=4, name='java', price=66.0, type='編程語言', publishDate=2011-04-09}
// Book{id=9, name='mysql', price=66.0, type='數據庫', publishDate=2015-04-06}
// Book{id=15, name='algorithm', price=66.0, type='軟件工程', publishDate=2010-05-08}
// Book{id=1, name='tomcat', price=70.0, type='服務器', publishDate=2014-05-17}
// Book{id=10, name='ssh', price=70.0, type='編程語言', publishDate=2016-12-04}
// Book{id=13, name='agile', price=72.0, type='軟件工程', publishDate=2016-02-18}
// Book{id=5, name='ruby', price=80.0, type='編程語言', publishDate=2013-05-09}
// Book{id=11, name='design pattern', price=81.0, type='軟件工程', publishDate=2017-04-08}
// Book{id=8, name='oracle', price=150.0, type='數據庫', publishDate=2013-08-09}
// Book{id=16, name='oracle 12c', price=150.0, type='數據庫', publishDate=2016-05-08}
// 2. 降序排序
Comparator<Book> comparator2 = (book1, book2) -> Double.compare(book2.getPrice(), book1.getPrice());
books().stream()
.sorted(comparator2)
.forEach(System.out::println);
System.out.println("---------------------");
// 等價於
books().stream()
.sorted(comparator1.reversed()) // 按照正序的煩序排序
.forEach(System.out::println);
System.out.println("---------------------");
// 輸出
// Book{id=8, name='oracle', price=150.0, type='數據庫', publishDate=2013-08-09}
// Book{id=16, name='oracle 12c', price=150.0, type='數據庫', publishDate=2016-05-08}
// Book{id=11, name='design pattern', price=81.0, type='軟件工程', publishDate=2017-04-08}
// Book{id=5, name='ruby', price=80.0, type='編程語言', publishDate=2013-05-09}
// Book{id=13, name='agile', price=72.0, type='軟件工程', publishDate=2016-02-18}
// Book{id=1, name='tomcat', price=70.0, type='服務器', publishDate=2014-05-17}
// Book{id=10, name='ssh', price=70.0, type='編程語言', publishDate=2016-12-04}
// Book{id=4, name='java', price=66.0, type='編程語言', publishDate=2011-04-09}
// Book{id=9, name='mysql', price=66.0, type='數據庫', publishDate=2015-04-06}
// Book{id=15, name='algorithm', price=66.0, type='軟件工程', publishDate=2010-05-08}
// Book{id=3, name='nginx', price=65.0, type='服務器', publishDate=2016-10-17}
// Book{id=12, name='refactoring', price=62.0, type='軟件工程', publishDate=2011-04-19}
// Book{id=2, name='jetty', price=60.0, type='服務器', publishDate=2015-12-01}
// Book{id=7, name='html', price=44.0, type='編程語言', publishDate=2011-01-06}
// Book{id=14, name='managing', price=42.0, type='軟件工程', publishDate=2016-01-19}
// Book{id=6, name='php', price=40.0, type='編程語言', publishDate=2014-08-06}
// 3. 多個屬性值進行排序:先根據price排序(升序),price相同時根據publishDate進行排序(降序)
books().stream()
.sorted(comparator1.thenComparing(
(book1, book2) -> book1.getPublishDate().isAfter(book2.getPublishDate()) ? -1 : 1)
)
.forEach(System.out::println);
// 輸出
// Book{id=9, name='mysql', price=66.0, type='數據庫', publishDate=2015-04-06}
// Book{id=4, name='java', price=66.0, type='編程語言', publishDate=2011-04-09}
// Book{id=15, name='algorithm', price=66.0, type='軟件工程', publishDate=2010-05-08}
// Book{id=10, name='ssh', price=70.0, type='編程語言', publishDate=2016-12-04}
// Book{id=1, name='tomcat', price=70.0, type='服務器', publishDate=2014-05-17}
}
對上述排序代碼可以使用方法引用的方式進行簡化:
@Test
void demo6() {
// 1. 根據價格進行排序
books().stream()
.sorted(Comparator.comparing(Book::getPrice))
.forEach(System.out::println);
// 2. 根據價格進行降序排序
books().stream()
.sorted(Comparator.comparing(Book::getPrice).reversed())
.forEach(System.out::println);
// 3. 先根據價格進行排序(升序),然後在根據publishDate進行排序(降序)
books().stream()
.sorted(Comparator.comparing(Book::getPrice).thenComparing(Comparator.comparing(Book::getPublishDate)).reversed())
.forEach(System.out::println);
}
處理5:將books集合轉換爲map<bookId, Book Object>
@Test
void demo7() {
Map<Integer, Book> bookMap = books().stream()
.collect(Collectors.toMap(Book::getId, book -> book)); // 直接使用了方法的引用
System.out.println(bookMap);
// 輸出
// {
// 1=Book{id=1, name='tomcat', price=70.0, type='服務器', publishDate=2014-05-17},
// 2=Book{id=2, name='jetty', price=60.0, type='服務器', publishDate=2015-12-01},
// 3=Book{id=3, name='nginx', price=65.0, type='服務器', publishDate=2016-10-17},
// 4=Book{id=4, name='java', price=66.0, type='編程語言', publishDate=2011-04-09},
// 5=Book{id=5, name='ruby', price=80.0, type='編程語言', publishDate=2013-05-09},
// 6=Book{id=6, name='php', price=40.0, type='編程語言', publishDate=2014-08-06},
// 7=Book{id=7, name='html', price=44.0, type='編程語言', publishDate=2011-01-06},
// 8=Book{id=8, name='oracle', price=150.0, type='數據庫', publishDate=2013-08-09},
// 9=Book{id=9, name='mysql', price=66.0, type='數據庫', publishDate=2015-04-06},
// 10=Book{id=10, name='ssh', price=70.0, type='編程語言', publishDate=2016-12-04},
// 11=Book{id=11, name='design pattern', price=81.0, type='軟件工程', publishDate=2017-04-08},
// 12=Book{id=12, name='refactoring', price=62.0, type='軟件工程', publishDate=2011-04-19},
// 13=Book{id=13, name='agile', price=72.0, type='軟件工程', publishDate=2016-02-18},
// 14=Book{id=14, name='managing', price=42.0, type='軟件工程', publishDate=2016-01-19},
// 15=Book{id=15, name='algorithm', price=66.0, type='軟件工程', publishDate=2010-05-08},
// 16=Book{id=16, name='oracle 12c', price=150.0, type='數據庫', publishDate=2016-05-08}
// }
}
處理6:統計集合中的平均值、最大值、和最小值
@Test
void demo8() {
// 1. 統計list中所有書的平均價格
Double averagePrice = books().stream()
.collect(Collectors.averagingDouble(Book::getPrice));
System.out.println(averagePrice);
// 輸出:74.0
// 2. 找出價格最大或最小的書 (這裏默認是找到最大的)
Optional<Book> book = books().stream()
.max(Comparator.comparing(Book::getPrice));
System.out.println(book);
// 等價於
book = books().stream()
.collect(Collectors.maxBy(Comparator.comparing(Book::getPrice)));
System.out.println(book);
// 輸出:Optional[Book{id=8, name='oracle', price=150.0, type='數據庫', publishDate=2013-08-09}]
// 3. 找到價格最小的Book
book = books().stream()
.collect(Collectors.maxBy(Comparator.comparing(Book::getPrice).reversed()));
System.out.println(book);
// 等價於
book = books().stream()
.collect(Collectors.minBy(Comparator.comparing(Book::getPrice)));
System.out.println(book);
// 輸出:Optional[Book{id=6, name='php', price=40.0, type='編程語言', publishDate=2014-08-06}]
// 4. 根據發佈時間進行查找發佈最晚的Book
book = books().stream()
.max(Comparator.comparing(Book::getPublishDate));
System.out.println(book);
// 輸出:Optional[Book{id=11, name='design pattern', price=81.0, type='軟件工程', publishDate=2017-04-08}]
// 5. 找到價格最貴的,但出版時間最晚的一本書
Comparator<Book> comparator = Comparator.comparing(Book::getPrice);
book = books().stream()
.collect(Collectors.maxBy(comparator.thenComparing(Comparator.comparing(Book::getPublishDate))));
System.out.println(book);
// 輸出:Optional[Book{id=16, name='oracle 12c', price=150.0, type='數據庫', publishDate=2016-05-08}]
}
處理7:對集合中的元素進行分組統計
@Test
void demo9() {
// 1. 根據type進行分組,統計每個類別有多少本書
Map<String, List<Book>> booksMap = books().stream()
.collect(Collectors.groupingBy(Book::getType));
booksMap.keySet().forEach(type -> {
System.out.println(type);
System.out.println(booksMap.get(type));
System.out.println("--------------------");
});
// 編程語言
// [Book{id=4, name='java', price=66.0, type='編程語言', publishDate=2011-04-09}, Book{id=5, name='ruby', price=80.0, type='編程語言', publishDate=2013-05-09}, Book{id=6, name='php', price=40.0, type='編程語言', publishDate=2014-08-06}, Book{id=7, name='html', price=44.0, type='編程語言', publishDate=2011-01-06}, Book{id=10, name='ssh', price=70.0, type='編程語言', publishDate=2016-12-04}]
// --------------------
// 服務器
// [Book{id=1, name='tomcat', price=70.0, type='服務器', publishDate=2014-05-17}, Book{id=2, name='jetty', price=60.0, type='服務器', publishDate=2015-12-01}, Book{id=3, name='nginx', price=65.0, type='服務器', publishDate=2016-10-17}]
// --------------------
// 軟件工程
// [Book{id=11, name='design pattern', price=81.0, type='軟件工程', publishDate=2017-04-08}, Book{id=12, name='refactoring', price=62.0, type='軟件工程', publishDate=2011-04-19}, Book{id=13, name='agile', price=72.0, type='軟件工程', publishDate=2016-02-18}, Book{id=14, name='managing', price=42.0, type='軟件工程', publishDate=2016-01-19}, Book{id=15, name='algorithm', price=66.0, type='軟件工程', publishDate=2010-05-08}]
// --------------------
// 數據庫
// [Book{id=8, name='oracle', price=150.0, type='數據庫', publishDate=2013-08-09}, Book{id=9, name='mysql', price=66.0, type='數據庫', publishDate=2015-04-06}, Book{id=16, name='oracle 12c', price=150.0, type='數據庫', publishDate=2016-05-08}]
// --------------------
// 2. 統計每種類型數的數量
Map<String, Long> booksCount = books().stream()
.collect(Collectors.groupingBy(Book::getType, Collectors.counting()));
System.out.println(booksCount);
// {編程語言=5, 服務器=3, 軟件工程=5, 數據庫=3}
// 3. 統計每種類型書的總價格
Map<String, Double> priceMap = books().stream()
.collect(Collectors.groupingBy(Book::getType, Collectors.summingDouble(Book::getPrice)));
System.out.println(priceMap);
// {編程語言=300.0, 服務器=195.0, 軟件工程=323.0, 數據庫=366.0}
// 4. 統計每種類型的平均價格
Map<String, Double> averagePrice = books().stream()
.collect(Collectors.groupingBy(Book::getType, Collectors.averagingDouble(Book::getPrice)));
System.out.println(averagePrice);
// {編程語言=60.0, 服務器=65.0, 軟件工程=64.6, 數據庫=122.0}
// 5. 找到每種類型中最貴的書
Map<String, Optional<Book>> maxBooks = books().stream()
.collect(Collectors.groupingBy(Book::getType, Collectors.maxBy(Comparator.comparing(Book::getPrice))));
maxBooks.keySet().forEach(type -> {
System.out.println(type);
System.out.println(maxBooks.get(type));
System.out.println("--------------------");
});
// 編程語言
// Optional[Book{id=5, name='ruby', price=80.0, type='編程語言', publishDate=2013-05-09}]
// --------------------
// 服務器
// Optional[Book{id=1, name='tomcat', price=70.0, type='服務器', publishDate=2014-05-17}]
// --------------------
// 軟件工程
// Optional[Book{id=11, name='design pattern', price=81.0, type='軟件工程', publishDate=2017-04-08}]
// --------------------
// 數據庫
// Optional[Book{id=8, name='oracle', price=150.0, type='數據庫', publishDate=2013-08-09}]
// --------------------
// 6. 按類型查找每種類型中出版時間最晚的書
Map<String, Optional<Book>> publishBooks = books().stream()
.collect(Collectors.groupingBy(Book::getType, Collectors.maxBy(Comparator.comparing(Book::getPublishDate))));
publishBooks.keySet().forEach(type -> {
System.out.println(type);
System.out.println(publishBooks.get(type));
System.out.println("-------------------");
});
// 編程語言
// Optional[Book{id=10, name='ssh', price=70.0, type='編程語言', publishDate=2016-12-04}]
// -------------------
// 服務器
// Optional[Book{id=3, name='nginx', price=65.0, type='服務器', publishDate=2016-10-17}]
// -------------------
// 軟件工程
// Optional[Book{id=11, name='design pattern', price=81.0, type='軟件工程', publishDate=2017-04-08}]
// -------------------
// 數據庫
// Optional[Book{id=16, name='oracle 12c', price=150.0, type='數據庫', publishDate=2016-05-08}]
// -------------------
}
處理8:篩選book集合中price>90的書籍,並根據出版日期從近到遠排序
@Test
void demo10() {
// 取出price>80,出版時間從近到遠
books().stream()
.filter(book -> book.getPrice() >= 80)
.sorted(Comparator.comparing(Book::getPublishDate).reversed())
.forEach(System.out::println);
// Book{id=11, name='design pattern', price=81.0, type='軟件工程', publishDate=2017-04-08}
// Book{id=16, name='oracle 12c', price=150.0, type='數據庫', publishDate=2016-05-08}
// Book{id=8, name='oracle', price=150.0, type='數據庫', publishDate=2013-08-09}
// Book{id=5, name='ruby', price=80.0, type='編程語言', publishDate=2013-05-09}
}
實例3:交易記錄數據處理
基礎類結構
交易人員類對象:
public class Trader {
private String name;
private String city;
public Trader(String name, String city) {
this.name = name;
this.city = city;
}
// getter and setter
}
交易對象結構:
public class Transaction {
private Trader trader;
private int year;
private int value;
public Transaction(Trader trader, int year, int value) {
this.trader = trader;
this.year = year;
this.value = value;
}
}
數據構建和處理
根據上述的類來構建數據集合:
// build transaction list for query
private List<Transaction> transactions() {
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
return transactions;
}
Query1: Find all transactions from year 2011 and sort them by value
@Test
public void query1() {
List<Transaction> tr2011 = transactions().stream()
.filter(transaction -> transaction.getYear() == 2011)
.sorted(Comparator.comparing(Transaction::getValue))
.collect(Collectors.toList());
System.out.println(tr2011);
// [Transaction{trader=Trader{name='Brian', city='Cambridge'}, year=2011, value=300},
// Transaction{trader=Trader{name='Raoul', city='Cambridge'}, year=2011, value=400}]
}
Query2:What are all the unique cities where the traders work?
@Test
public void query2() {
List<String> cities = transactions().stream()
.map(transaction -> transaction.getTrader().getCity())
.distinct()
.collect(Collectors.toList());
System.out.println(cities);
// [Cambridge, Milan]
}
Query3: Find all traders from Cambridge and sort them by name
@Test
public void query3() {
List<Trader> traders = transactions().stream()
.map(Transaction::getTrader)
.filter(trader -> "Cambridge".equals(trader.getCity()))
.distinct()
.sorted(Comparator.comparing(Trader::getName))
.collect(Collectors.toList());
System.out.println(traders);
// [Trader{name='Alan', city='Cambridge'}, Trader{name='Brian', city='Cambridge'}, Trader{name='Raoul', city='Cambridge'}]
}
Query4: Return a string of all traders' names sorted alphabetically (按照字母順序排列的).
@Test
public void query4() {
String traderStr = transactions().stream()
.map(Transaction::getTrader)
.map(Trader::getName)
.distinct()
.sorted()
.reduce("", (name1, name2) -> name1 + name2);
System.out.println(traderStr);
// AlanBrianMarioRaoul
}
Query5: Are there any trader based in Milan?
@Test
public void query5() {
boolean milanBased = transactions().stream()
.anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan"));
System.out.println(milanBased);
// true
}
Query6: Update all transactions so that the traders from Milan are set to Cambridge
public void query6() {
List<Transaction> transactions = transactions();
transactions.stream()
.map(Transaction::getTrader)
.filter(trader -> trader.getCity().equals("Milan"))
.forEach(trader -> trader.setCity("Cambridge"));
System.out.println(transactions);
// [Transaction{trader=Trader{name='Brian', city='Cambridge'}, year=2011, value=300},
// Transaction{trader=Trader{name='Raoul', city='Cambridge'}, year=2012, value=1000},
// Transaction{trader=Trader{name='Raoul', city='Cambridge'}, year=2011, value=400},
// Transaction{trader=Trader{name='Mario', city='Cambridge'}, year=2012, value=710},
// Transaction{trader=Trader{name='Mario', city='Cambridge'}, year=2012, value=700},
// Transaction{trader=Trader{name='Alan', city='Cambridge'}, year=2012, value=950}]
}
Query7: What's the highest value in all the transactions?
@Test
public void query7() {
int highestValue1 = transactions().stream()
.map(Transaction::getValue)
.reduce(0, Integer::max);
int highestValue2 = transactions().stream()
.map(Transaction::getValue)
.max(Comparator.comparingInt(a -> a))
.get();
System.out.println(highestValue1);
System.out.println(highestValue2);
// 1000
// 1000
}
THE END.