1. 程式人生 > 程式設計 >java8新特性之stream的collect實戰教程

java8新特性之stream的collect實戰教程

1、list轉換成list

不帶return方式

List<Long> ids=wrongTmpList.stream().map(c->c.getId()).collect(Collectors.toList());

帶return方式

// spu集合轉化成spubo集合//java8的新特性
  List<SpuBo> spuBos=spuList.stream().map(spu -> {
   SpuBo spuBo = new SpuBo();
   BeanUtils.copyProperties(spu,spuBo);
   //查詢品牌名稱
   Brand brand = this.brandMapper.selectByPrimaryKey(spu.getBrandId());
   spuBo.setBname(brand.getName());
   //查詢類別名稱
   List<String> names = this.categoryService.queryNamesByIds(Arrays.asList(spu.getCid1(),spu.getCid2(),spu.getCid3()));
   spuBo.setCname(StringUtils.join(names,"-"));
   return spuBo;
  }).collect(Collectors.toList());

2、list轉map

Map<Long,Active> activeMap = actives.stream().collect(Collectors.toMap(Active::getId,s->s));

3、分組統計計算

list轉map(根據某個屬性進行分組)

Map<Long,List<TrainPlan>> trainMaps = trainPlans.stream().collect(

Collectors.groupingBy(TrainPlan::getModuleId));

list轉map(統計計算)

 List<StatDepartment> statDepartments = projectModuleBSDao.statProModByDepartment(params);

 Map<Long,Integer> projectNumByDep = statDepartments.stream()
    .collect(Collectors.groupingBy(StatDepartment::getDepartmentId,Collectors.summingInt(StatDepartment::getProjectNum)));

補充知識:Java8新特性學習-函數語言程式設計(Stream/Function/Optional/Consumer)

Java8新引入函數語言程式設計方式,大大的提高了編碼效率。本文將對涉及的物件等進行統一的學習及記錄。

首先需要清楚一個概念:函式式介面;它指的是有且只有一個未實現的方法的介面,一般通過FunctionalInterface這個註解來表明某個介面是一個函式式介面。函式式介面是Java支援函數語言程式設計的基礎。

1 Java8函數語言程式設計語法入門

Java8中函數語言程式設計語法能夠精簡程式碼。

使用Consumer作為示例,它是一個函式式介面,包含一個抽象方法accept,這個方法只有輸入而無輸出。

現在我們要定義一個Consumer物件,傳統的方式是這樣定義的:

Consumer c = new Consumer() {
 @Override
 public void accept(Object o) {
  System.out.println(o);
 }
};

而在Java8中,針對函數語言程式設計介面,可以這樣定義:

Consumer c = (o) -> {
 System.out.println(o);
}; 

上面已說明,函數語言程式設計介面都只有一個抽象方法,因此在採用這種寫法時,編譯器會將這段函式編譯後當作該抽象方法的實現。

如果介面有多個抽象方法,編譯器就不知道這段函式應該是實現哪個方法的了。

因此,=後面的函式體我們就可以看成是accept函式的實現。

輸入:->前面的部分,即被()包圍的部分。此處只有一個輸入引數,實際上輸入是可以有多個的,如兩個引數時寫法:(a,b);當然也可以沒有輸入,此時直接就可以是()。

函式體:->後面的部分,即被{}包圍的部分;可以是一段程式碼。

輸出:函數語言程式設計可以沒有返回值,也可以有返回值。如果有返回值時,需要程式碼段的最後一句通過return的方式返回對應的值。

當函式體中只有一個語句時,可以去掉{}進一步簡化:

Consumer c = (o) -> System.out.println(o);

然而這還不是最簡的,由於此處只是進行列印,呼叫了System.out中的println靜態方法對輸入引數直接進行列印,因此可以簡化成以下寫法:

Consumer c = System.out::println;

它表示的意思就是針對輸入的引數將其呼叫System.out中的靜態方法println進行列印。

到這一步就可以感受到函數語言程式設計的強大能力。

通過最後一段程式碼,我們可以簡單的理解函數語言程式設計,Consumer介面直接就可以當成一個函數了,這個函式接收一個輸入引數,然後針對這個輸入進行處理;當然其本質上仍舊是一個物件,但我們已經省去了諸如老方式中的物件定義過程,直接使用一段程式碼來給函式式介面物件賦值。

而且最為關鍵的是,這個函式式物件因為本質上仍舊是一個物件,因此可以做為其它方法的引數或者返回值,可以與原有的程式碼實現無縫整合!

下面對Java中的幾個預先定義的函式式介面及其經常使用的類進行分析學習。

2 Java函式式介面

2.1 Consumer

Consumer是一個函數語言程式設計介面; 顧名思義,Consumer的意思就是消費,即針對某個東西我們來使用它,因此它包含有一個有輸入而無輸出的accept介面方法;

除accept方法,它還包含有andThen這個方法;

其定義如下:

default Consumer<T> andThen(Consumer<? super T> after) {
 Objects.requireNonNull(after);
 return (T t) -> { accept(t); after.accept(t); };
}

可見這個方法就是指定在呼叫當前Consumer後是否還要呼叫其它的Consumer;

使用示例:

public static void consumerTest() {
 Consumer f = System.out::println;
 Consumer f2 = n -> System.out.println(n + "-F2");

 //執行完F後再執行F2的Accept方法
 f.andThen(f2).accept("test");

 //連續執行F的Accept方法
 f.andThen(f).andThen(f).andThen(f).accept("test1");
}

2.2 Function

Function也是一個函數語言程式設計介面;它代表的含義是“函式”,而函式經常是有輸入輸出的,因此它含有一個apply方法,包含一個輸入與一個輸出;

除apply方法外,它還有compose與andThen及indentity三個方法,其使用見下述示例;

/**
 * Function測試
 */
public static void functionTest() {
 Function<Integer,Integer> f = s -> s++;
 Function<Integer,Integer> g = s -> s * 2;

 /**
  * 下面表示在執行F時,先執行G,並且執行F時使用G的輸出當作輸入。
  * 相當於以下程式碼:
  * Integer a = g.apply(1);
  * System.out.println(f.apply(a));
  */
 System.out.println(f.compose(g).apply(1));

 /**
  * 表示執行F的Apply後使用其返回的值當作輸入再執行G的Apply;
  * 相當於以下程式碼
  * Integer a = f.apply(1);
  * System.out.println(g.apply(a));
  */
 System.out.println(f.andThen(g).apply(1));

 /**
  * identity方法會返回一個不進行任何處理的Function,即輸出與輸入值相等; 
  */
 System.out.println(Function.identity().apply("a"));
}

2.3 Predicate

Predicate為函式式介面,predicate的中文意思是“斷定”,即判斷的意思,判斷某個東西是否滿足某種條件; 因此它包含test方法,根據輸入值來做邏輯判斷,其結果為True或者False。

它的使用方法示例如下:

/**
 * Predicate測試
 */
private static void predicateTest() {
 Predicate<String> p = o -> o.equals("test");
 Predicate<String> g = o -> o.startsWith("t");

 /**
  * negate: 用於對原來的Predicate做取反處理;
  * 如當呼叫p.test("test")為True時,呼叫p.negate().test("test")就會是False;
  */
 Assert.assertFalse(p.negate().test("test"));

 /**
  * and: 針對同一輸入值,多個Predicate均返回True時返回True,否則返回False;
  */
 Assert.assertTrue(p.and(g).test("test"));

 /**
  * or: 針對同一輸入值,多個Predicate只要有一個返回True則返回True,否則返回False
  */
 Assert.assertTrue(p.or(g).test("ta"));
}

3 函數語言程式設計介面的使用

通過Stream以及Optional兩個類,可以進一步利用函式式介面來簡化程式碼。

3.1 Stream

Stream可以對多個元素進行一系列的操作,也可以支援對某些操作進行併發處理。

3.1.1 Stream物件的建立

Stream物件的建立途徑有以下幾種

a. 建立空的Stream物件

Stream stream = Stream.empty();

b. 通過集合類中的stream或者parallelStream方法建立;

List<String> list = Arrays.asList("a","b","c","d");
Stream listStream = list.stream();     //獲取序列的Stream物件
Stream parallelListStream = list.parallelStream(); //獲取並行的Stream物件 

c. 通過Stream中的of方法建立:

Stream s = Stream.of("test");

Stream s1 = Stream.of("a","d");

d. 通過Stream中的iterate方法建立:

iterate方法有兩個不同引數的方法:

public static<T> Stream<T> iterate(final T seed,final UnaryOperator<T> f);

public static<T> Stream<T> iterate(T seed,Predicate<? super T> hasNext,UnaryOperator<T> next)

其中第一個方法將會返回一個無限有序值的Stream物件:它的第一個元素是seed,第二個元素是f.apply(seed); 第N個元素是f.apply(n-1個元素的值);生成無限值的方法實際上與Stream的中間方法類似,在遇到中止方法前一般是不真正的執行的。因此無限值的這個方法一般與limit等方法一起使用,來獲取前多少個元素。

當然獲取前多少個元素也可以使用第二個方法。

第二個方法與第一個方法生成元素的方式類似,不同的是它返回的是一個有限值的Stream;中止條件是由hasNext來斷定的。

第二種方法的使用示例如下:

/**
 * 本示例表示從1開始組裝一個序列,第一個是1,第二個是1+1即2,第三個是2+1即3..,直接10時中止;
 * 也可簡化成以下形式:
 *  Stream.iterate(1,*  n -> n <= 10,*  n -> n+1).forEach(System.out::println);
 * 寫成以下方式是為簡化理解
 */
Stream.iterate(1,new Predicate<Integer>() {
   @Override
   public boolean test(Integer integer) {
    return integer <= 10;
   }
  },new UnaryOperator<Integer>() {
  @Override
  public Integer apply(Integer integer) {
   return integer+1;
  }
}).forEach(System.out::println);

e. 通過Stream中的generate方法建立

與iterate中建立無限元素的Stream類似,不過它的每個元素與前一元素無關,且生成的是一個無序的佇列。也就是說每一個元素都可以隨機生成。因此一般用來建立常量的Stream以及隨機的Stream等。

示例如下:

/**
 * 隨機生成10個Double元素的Stream並將其列印
 */
Stream.generate(new Supplier<Double>() {
 @Override
 public Double get() {
  return Math.random();
 }
}).limit(10).forEach(System.out::println);

//上述寫法可以簡化成以下寫法:
Stream.generate(() -> Math.random()).limit(10).forEach(System.out::println);

f. 通過Stream中的concat方法連線兩個Stream物件生成新的Stream物件

這個比較好理解不再贅述。

3.1.2 Stream物件的使用

Stream物件提供多個非常有用的方法,這些方法可以分成兩類:

中間操作:將原始的Stream轉換成另外一個Stream;如filter返回的是過濾後的Stream。

終端操作:產生的是一個結果或者其它的複合操作;如count或者forEach操作。

其清單如下所示,方法的具體說明及使用示例見後文。

所有中間操作

方法 說明
sequential 返回一個相等的序列的Stream物件,如果原Stream物件已經是序列就可能會返回原物件
parallel 返回一個相等的並行的Stream物件,如果原Stream物件已經是並行的就會返回原物件
unordered 返回一個不關心順序的Stream物件,如果原物件已經是這型別的物件就會返回原物件
onClose 返回一個相等的Steam物件,同時新的Stream物件在執行Close方法時會呼叫傳入的Runnable物件
close 關閉Stream物件
filter 元素過濾:對Stream物件按指定的Predicate進行過濾,返回的Stream物件中僅包含未被過濾的元素
map 元素一對一轉換:使用傳入的Function物件對Stream中的所有元素進行處理,返回的Stream物件中的元素為原元素處理後的結果
mapToInt 元素一對一轉換:將原Stream中的使用傳入的IntFunction加工後返回一個IntStream物件
flatMap 元素一對多轉換:對原Stream中的所有元素進行操作,每個元素會有一個或者多個結果,然後將返回的所有元素組合成一個統一的Stream並返回;
distinct 去重:返回一個去重後的Stream物件
sorted 排序:返回排序後的Stream物件
peek 使用傳入的Consumer物件對所有元素進行消費後,返回一個新的包含所有原來元素的Stream物件
limit 獲取有限個元素組成新的Stream物件返回
skip 拋棄前指定個元素後使用剩下的元素組成新的Stream返回
takeWhile 如果Stream是有序的(Ordered),那麼返回最長命中序列(符合傳入的Predicate的最長命中序列)組成的Stream;如果是無序的,那麼返回的是所有符合傳入的Predicate的元素序列組成的Stream。
dropWhile 與takeWhile相反,如果是有序的,返回除最長命中序列外的所有元素組成的Stream;如果是無序的,返回所有未命中的元素組成的Stream。

所有終端操作

方法 說明
iterator 返回Stream中所有物件的迭代器;
spliterator 返回對所有物件進行的spliterator物件
forEach 對所有元素進行迭代處理,無返回值
forEachOrdered 按Stream的Encounter所決定的序列進行迭代處理,無返回值
toArray 返回所有元素的陣列
reduce 使用一個初始化的值,與Stream中的元素一一做傳入的二合運算後返回最終的值。每與一個元素做運算後的結果,再與下一個元素做運算。它不保證會按序列執行整個過程。
collect 根據傳入引數做相關匯聚計算
min 返回所有元素中最小值的Optional物件;如果Stream中無任何元素,那麼返回的Optional物件為Empty
max 與Min相反
count 所有元素個數
anyMatch 只要其中有一個元素滿足傳入的Predicate時返回True,否則返回False
allMatch 所有元素均滿足傳入的Predicate時返回True,否則False
noneMatch 所有元素均不滿足傳入的Predicate時返回True,否則False
findFirst 返回第一個元素的Optioanl物件;如果無元素返回的是空的Optional; 如果Stream是無序的,那麼任何元素都可能被返回。
findAny 返回任意一個元素的Optional物件,如果無元素返回的是空的Optioanl。
isParallel 判斷是否當前Stream物件是並行的

下面就幾個比較常用的方法舉例說明其用法:

3.1.2.1 filter

用於對Stream中的元素進行過濾,返回一個過濾後的Stream

其方法定義如下:

Stream<T> filter(Predicate<? super T> predicate);

使用示例:

Stream<String> s = Stream.of("test","t1","t2","teeeee","aaaa");
//查詢所有包含t的元素並進行列印
s.filter(n -> n.contains("t")).forEach(System.out::println);

3.1.2.2 map

元素一對一轉換。

它接收一個Funcation引數,用其對Stream中的所有元素進行處理,返回的Stream物件中的元素為Function對原元素處理後的結果

其方法定義如下:

<R> Stream<R> map(Function<? super T,? extends R> mapper);

示例,假設我們要將一個String型別的Stream物件中的每個元素新增相同的字尾.txt,如a變成a.txt,其寫法如下:

Stream<String> s = Stream.of("test","aaaa");

s.map(n -> n.concat(".txt")).forEach(System.out::println);

3.1.2.3 flatMap

元素一對多轉換:對原Stream中的所有元素使用傳入的Function進行處理,每個元素經過處理後生成一個多個元素的Stream物件,然後將返回的所有Stream物件中的所有元素組合成一個統一的Stream並返回;

方法定義如下:

<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper);

示例,假設要對一個String型別的Stream進行處理,將每一個元素的拆分成單個字母,並列印:

Stream<String> s = Stream.of("test","aaaa");

s.flatMap(n -> Stream.of(n.split(""))).forEach(System.out::println);

3.1.2.4 takeWhile

方法定義如下:

default Stream<T> takeWhile(Predicate<? super T> predicate)

如果Stream是有序的(Ordered),那麼返回最長命中序列(符合傳入的Predicate的最長命中序列)組成的Stream;如果是無序的,那麼返回的是所有符合傳入的Predicate的元素序列組成的Stream。

與Filter有點類似,不同的地方就在當Stream是有序時,返回的只是最長命中序列。

如以下示例,通過takeWhile查詢”test”,“t1”,“t2”,“teeeee”,“aaaa”,“taaa”這幾個元素中包含t的最長命中序列:

Stream<String> s = Stream.of("test","aaaa","taaa");
//以下結果將列印: "test","teeeee",最後的那個taaa不會進行列印 
s.takeWhile(n -> n.contains("t")).forEach(System.out::println);

3.1.2.5 dropWhile

與takeWhile相反,如果是有序的,返回除最長命中序列外的所有元素組成的Stream;如果是無序的,返回所有未命中的元素組成的Stream;其定義如下:

default Stream<T> dropWhile(Predicate<? super T> predicate)

如以下示例,通過dropWhile刪除”test”,"taaa"); //以下結果將列印:"aaaa","taaa"   s.dropWhile(n -> n.contains("t")).forEach(System.out::println);

3.1.2.6 reduce與collect

關於reduce與collect由於功能較為複雜,在後續將進行單獨分析與學習,此處暫不涉及。

3.2 Optional

用於簡化Java中對空值的判斷處理,以防止出現各種空指標異常。

Optional實際上是對一個變數進行封裝,它包含有一個屬性value,實際上就是這個變數的值。

3.2.1 Optional物件建立

它的建構函式都是private型別的,因此要初始化一個Optional的物件無法通過其建構函式進行建立。它提供了一系列的靜態方法用於構建Optional物件:

3.2.1.1 empty

用於建立一個空的Optional物件;其value屬性為Null。

如:

Optional o = Optional.empty();

3.2.1.2 of

根據傳入的值構建一個Optional物件;

傳入的值必須是非空值,否則如果傳入的值為空值,則會丟擲空指標異常。

使用:

o = Optional.of("test");

3.2.1.3 ofNullable

根據傳入值構建一個Optional物件

傳入的值可以是空值,如果傳入的值是空值,則與empty返回的結果是一樣的。

3.2.2 方法

Optional包含以下方法:

方法名 說明
get 獲取Value的值,如果Value值是空值,則會丟擲NoSuchElementException異常;因此返回的Value值無需再做空值判斷,只要沒有丟擲異常,都會是非空值。
isPresent Value是否為空值的判斷;
ifPresent 當Value不為空時,執行傳入的Consumer;
ifPresentOrElse Value不為空時,執行傳入的Consumer;否則執行傳入的Runnable物件;
filter 當Value為空或者傳入的Predicate物件呼叫test(value)返回False時,返回Empty物件;否則返回當前的Optional物件
map 一對一轉換:當Value為空時返回Empty物件,否則返回傳入的Function執行apply(value)後的結果組裝的Optional物件;
flatMap 一對多轉換:當Value為空時返回Empty物件,否則傳入的Function執行apply(value)後返回的結果(其返回結果直接是Optional物件)
or 如果Value不為空,則返回當前的Optional物件;否則,返回傳入的Supplier生成的Optional物件;
stream 如果Value為空,返回Stream物件的Empty值;否則返回Stream.of(value)的Stream物件;
orElse Value不為空則返回Value,否則返回傳入的值;
orElseGet Value不為空則返回Value,否則返回傳入的Supplier生成的值;
orElseThrow Value不為空則返回Value,否則丟擲Supplier中生成的異常物件;

3.2.3 使用場景

常用的使用場景如下:

3.2.3.1 判斷結果不為空後使用

如某個函式可能會返回空值,以往的做法:

String s = test();
if (null != s) {
 System.out.println(s);
}

現在的寫法就可以是:

Optional<String> s = Optional.ofNullable(test());

s.ifPresent(System.out::println);

乍一看程式碼複雜度上差不多甚至是略有提升;那為什麼要這麼做呢?

一般情況下,我們在使用某一個函式返回值時,要做的第一步就是去分析這個函式是否會返回空值;如果沒有進行分析或者分析的結果出現偏差,導致函式會丟擲空值而沒有做檢測,那麼就會相應的丟擲空指標異常!

而有了Optional後,在我們不確定時就可以不用去做這個檢測了,所有的檢測Optional物件都幫忙我們完成,我們要做的就是按上述方式去處理。

3.2.3.2 變數為空時提供預設值

如要判斷某個變數為空時使用提供的值,然後再針對這個變數做某種運算;

以往做法:

if (null == s) {
 s = "test";
}
System.out.println(s);

現在的做法:

Optional<String> o = Optional.ofNullable(s);

System.out.println(o.orElse("test"));

3.2.3.3 變數為空時丟擲異常,否則使用

以往寫法:

if (null == s) {
 throw new Exception("test");
}
System.out.println(s);

現在寫法:

Optional<String> o = Optional.ofNullable(s);

System.out.println(o.orElseThrow(()->new Exception("test")));

其它場景待補充。

以上這篇java8新特性之stream的collect實戰教程就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。