Java 8 Stream.distinct() 列表去重的操作
在這篇文章里,我們將提供Java8 Stream distinct()示例。 distinct()返回由該流的不同元素組成的流。distinct()是Stream接口的方法。
distinct()使用hashCode()和equals()方法來(lái)獲取不同的元素。因此,我們的類必須實(shí)現(xiàn)hashCode()和equals()方法。
如果distinct()正在處理有序流,那么對(duì)于重復(fù)元素,將保留以遭遇順序首先出現(xiàn)的元素,并且以這種方式選擇不同元素是穩(wěn)定的。
在無(wú)序流的情況下,不同元素的選擇不一定是穩(wěn)定的,是可以改變的。distinct()執(zhí)行有狀態(tài)的中間操作。
在有序流的并行流的情況下,保持distinct()的穩(wěn)定性是需要很高的代價(jià)的,因?yàn)樗枰罅康木彌_開銷。如果我們不需要保持遭遇順序的一致性,那么我們應(yīng)該可以使用通過(guò)BaseStream.unordered()方法實(shí)現(xiàn)的無(wú)序流。
1. Stream.distinct()
distinct()方法的聲明如下:
Stream<T> distinct()
它是Stream接口的方法。在此示例中,我們有一個(gè)包含重復(fù)元素的字符串?dāng)?shù)據(jù)類型列表
DistinctSimpleDemo.java
package com.concretepage; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class DistinctSimpleDemo { public static void main(String[] args) { List<String> list = Arrays.asList("AA", "BB", "CC", "BB", "CC", "AA", "AA"); long l = list.stream().distinct().count(); System.out.println("No. of distinct elements:"+l); String output = list.stream().distinct().collect(Collectors.joining(",")); System.out.println(output); } }
Output
No. of distinct elements:3
AA,BB,CC
2. Stream.distinct() with List of Objects
在此示例中,我們有一個(gè)Book對(duì)象列表。 為了對(duì)列表進(jìn)行去重,該類將重寫hashCode()和equals()。
Book.java
package com.concretepage; public class Book { private String name; private int price; public Book(String name, int price) { this.name = name; this.price = price; } public String getName() { return name; } public int getPrice() { return price; } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } final Book book = (Book) obj; if (this == book) { return true; } else { return (this.name.equals(book.name) && this.price == book.price); } } @Override public int hashCode() { int hashno = 7; hashno = 13 * hashno + (name == null ? 0 : name.hashCode()); return hashno; } }
DistinctWithUserObjects.java
package com.concretepage; import java.util.ArrayList; import java.util.List; public class DistinctWithUserObjects { public static void main(String[] args) { List<Book> list = new ArrayList<>(); { list.add(new Book("Core Java", 200)); list.add(new Book("Core Java", 200)); list.add(new Book("Learning Freemarker", 150)); list.add(new Book("Spring MVC", 300)); list.add(new Book("Spring MVC", 300)); } long l = list.stream().distinct().count(); System.out.println("No. of distinct books:"+l); list.stream().distinct().forEach(b -> System.out.println(b.getName()+ "," + b.getPrice())); } }
Output
No. of distinct books:3 Core Java,200 Learning Freemarker,150 Spring MVC,300
3. Distinct by Property
distinct()不提供按照屬性對(duì)對(duì)象列表進(jìn)行去重的直接實(shí)現(xiàn)。它是基于hashCode()和equals()工作的。
如果我們想要按照對(duì)象的屬性,對(duì)對(duì)象列表進(jìn)行去重,我們可以通過(guò)其它方法來(lái)實(shí)現(xiàn)。
如下代碼段所示:
static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) { Map<Object,Boolean> seen = new ConcurrentHashMap<>(); return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; }
上面的方法可以被Stream接口的 filter()接收為參數(shù),如下所示:
list.stream().filter(distinctByKey(b -> b.getName()));
distinctByKey()方法返回一個(gè)使用ConcurrentHashMap 來(lái)維護(hù)先前所見(jiàn)狀態(tài)的 Predicate 實(shí)例,如下是一個(gè)完整的使用對(duì)象屬性來(lái)進(jìn)行去重的示例。
DistinctByProperty.java
package com.concretepage; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; public class DistinctByProperty { public static void main(String[] args) { List<Book> list = new ArrayList<>(); { list.add(new Book("Core Java", 200)); list.add(new Book("Core Java", 300)); list.add(new Book("Learning Freemarker", 150)); list.add(new Book("Spring MVC", 200)); list.add(new Book("Hibernate", 300)); } list.stream().filter(distinctByKey(b -> b.getName())) .forEach(b -> System.out.println(b.getName()+ "," + b.getPrice())); } private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) { Map<Object,Boolean> seen = new ConcurrentHashMap<>(); return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; } }
Output
Core Java,200 Learning Freemarker,150 Spring MVC,200 Hibernate,300
from : https://www.concretepage.com/java/jdk-8/java-8-distinct-example
補(bǔ)充知識(shí):List集合常規(guī)去重與java8新特性去重方法
一、常規(guī)去重
碰到List去重的問(wèn)題,除了遍歷去重,我們常常想到利用Set集合不允許重復(fù)元素的特點(diǎn),通過(guò)List和Set互轉(zhuǎn),來(lái)去掉重復(fù)元素。
// 遍歷后判斷賦給另一個(gè)list集合,保持原來(lái)順序 public static void ridRepeat1(List<String> list) { System.out.println("list = [" + list + "]"); List<String> listNew = new ArrayList<String>(); for (String str : list) { if (!listNew.contains(str)) { listNew.add(str); } } System.out.println("listNew = [" + listNew + "]"); } // set集合去重,保持原來(lái)順序 public static void ridRepeat2(List<String> list) { System.out.println("list = [" + list + "]"); List<String> listNew = new ArrayList<String>(); Set set = new HashSet(); for (String str : list) { if (set.add(str)) { listNew.add(str); } } System.out.println("listNew = [" + listNew + "]"); } // Set去重 由于Set的無(wú)序性,不會(huì)保持原來(lái)順序 public static void ridRepeat3(List<String> list) { System.out.println("list = [" + list + "]"); Set set = new HashSet(); List<String> listNew = new ArrayList<String>(); set.addAll(list); listNew.addAll(set); System.out.println("listNew = [" + listNew + "]"); } // Set去重(將ridRepeat3方法縮減為一行) 無(wú)序 public static void ridRepeat4(List<String> list) { System.out.println("list = [" + list + "]"); List<String> listNew = new ArrayList<String>(new HashSet(list)); System.out.println("listNew = [" + listNew + "]"); } // Set去重并保持原先順序 public static void ridRepeat5(List<String> list) { System.out.println("list = [" + list + "]"); List<String> listNew2= new ArrayList<String>(new LinkedHashSet<String>(list)); System.out.println("listNew = [" + listNew + "]"); }
二、java8的stream寫法實(shí)現(xiàn)去重
1、distinct去重
//利用java8的stream去重 List uniqueList = list.stream().distinct().collect(Collectors.toList()); System.out.println(uniqueList.toString());
distinct()方法默認(rèn)是按照父類Object的equals與hashCode工作的。所以:
上面的方法在List元素為基本數(shù)據(jù)類型及String類型時(shí)是可以的,但是如果List集合元素為對(duì)象,卻不會(huì)奏效。不過(guò)如果你的實(shí)體類對(duì)象使用了目前廣泛使用的lombok插件相關(guān)注解如:@Data,那么就會(huì)自動(dòng)幫你重寫了equals與hashcode方法,當(dāng)然如果你的需求是根據(jù)某幾個(gè)核心字段屬性判斷去重,那么你就要在該類中自定義重寫equals與hashcode方法了。
2、也可以通過(guò)新特性簡(jiǎn)寫方式實(shí)現(xiàn)
不過(guò)該方式不能保持原列表順序而是使用了TreeSet按照字典順序排序后的列表,如果需求不需要按原順序則可直接使用。
//根據(jù)name屬性去重 List<User> lt = list.stream().collect( collectingAndThen( toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList::new)); System.out.println("去重后的:" + lt); //根據(jù)name與address屬性去重 List<User> lt1 = list.stream().collect( collectingAndThen( toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getName() + ";" + o.getAddress()))), ArrayList::new)); System.out.println("去重后的:" + lt);
當(dāng)需求中明確有排序要求也可以按上面簡(jiǎn)寫方式再次加工處理使用stream流的sorted()相關(guān)API寫法。
List<User> lt = list.stream().collect( collectingAndThen( toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))),v -> v.stream().sorted().collect(Collectors.toList())));
3、通過(guò) filter() 方法
我們首先創(chuàng)建一個(gè)方法作為 Stream.filter() 的參數(shù),其返回類型為 Predicate,原理就是判斷一個(gè)元素能否加入到 Set 中去,代碼如下:
private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) { Set<Object> seen = ConcurrentHashMap.newKeySet(); return t -> seen.add(keyExtractor.apply(t)); }
使用如下:
@Test public void distinctByProperty() throws JsonProcessingException { // 這里第二種方法我們通過(guò)過(guò)濾來(lái)實(shí)現(xiàn)根據(jù)對(duì)象某個(gè)屬性去重 ObjectMapper objectMapper = new ObjectMapper(); List<Student> studentList = getStudentList(); System.out.print("去重前 :"); System.out.println(objectMapper.writeValueAsString(studentList)); studentList = studentList.stream().distinct().collect(Collectors.toList()); System.out.print("distinct去重后:"); System.out.println(objectMapper.writeValueAsString(studentList)); // 這里我們將 distinctByKey() 方法作為 filter() 的參數(shù),過(guò)濾掉那些不能加入到 set 的元素 studentList = studentList.stream().filter(distinctByKey(Student::getName)).collect(Collectors.toList()); System.out.print("根據(jù)名字去重后 :"); System.out.println(objectMapper.writeValueAsString(studentList)); }
去重前:
[{"stuNo":"001","name":"Tom"},{"stuNo":"001","name":"Tom"},{"stuNo":"003","name":"Tom"}]
distinct去重后:
[{"stuNo":"001","name":"Tom"},{"stuNo":"003","name":"Tom"}]
根據(jù)名字去重后 :
[{"stuNo":"001","name":"Tom"}]
三、相同元素累計(jì)求和等操作
除了集合去重意外,工作中還有一種常見(jiàn)的需求,例如:在所有商品訂單中,計(jì)算同一家店鋪不同商品名稱的商品成交額,可以直接通過(guò)sql語(yǔ)句獲取,這里寫一下如何通過(guò)java簡(jiǎn)單實(shí)現(xiàn)。舉一個(gè)類似的案例:計(jì)算相同姓名與住址的用戶年齡之和。
User.java
package com.example.demo.dto; import java.io.Serializable; import java.util.Objects; /** * @author: shf * description: * date: 2019/10/30 10:21 */ public class User implements Serializable { private static final long serialVersionUID = 1L; private Long id; private String name; private String address; private Integer age; public User() { } public User(String name, String address, Integer age) { this.name = name; this.address = address; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", address='" + address + '\'' + ", age=" + age + '}'; } @Override public boolean equals(Object obj) { if (this == obj) { return true;//地址相等 } if (obj == null) { return false;//非空性:對(duì)于任意非空引用x,x.equals(null)應(yīng)該返回false。 } if (obj instanceof User) { User other = (User) obj; //需要比較的字段相等,則這兩個(gè)對(duì)象相等 if (Objects.equals(this.name, other.name) && Objects.equals(this.address, other.address)) { return true; } } return false; } @Override public int hashCode() { return Objects .hash(name, address); } }
測(cè)試代碼:
package com.example.demo; import com.example.demo.dto.User; import java.util.*; import java.util.stream.Collectors; public class FirCes { public static void main(String[] args) { /*構(gòu)建測(cè)試數(shù)據(jù)集合*/ User user1 = new User("a小張1", "a1", 10); User user2 = new User("b小張2", "a2", 10); User user3 = new User("c小張3", "a3", 10); User user3_3 = new User("c小張3", "a", 10); User user33 = new User("c小張3", "a3", 10); User user4 = new User("d小張4", "a4", 10); User user5 = new User("e小張5", "a5", 10); List<User> list = new ArrayList<>(); list.add(user1); list.add(user2); list.add(user3); list.add(user3_3); list.add(user33); list.add(user4); list.add(user5); //按相同name與address屬性分組User用戶 Map<User, List<User>> listMap = list.stream().collect(Collectors.groupingBy(v -> v)); /*先看一下分組效果*/ listMap.forEach((key, value) -> { System.out.println("========"); System.out.println("key:" + key); value.forEach(obj -> { System.out.println(obj); }); }); /*最終執(zhí)行結(jié)果*/ List<User> listNew = listMap.keySet().stream().map(u -> { int sum = listMap.get(u).stream().mapToInt(i -> i.getAge()).sum(); //需要注意的是:這里也會(huì)改變?cè)璴ist集合中的原數(shù)據(jù)。因?yàn)檫@里的u分組時(shí)就是來(lái)自原集合中的一個(gè)地址對(duì)象, // 即:指向了原集合中的一個(gè)對(duì)象的地址。如果不想原集合被影響,這里可以new User()新的對(duì)象賦值并返回新對(duì)象 u.setAge(sum); return u; }).collect(Collectors.toList()); System.out.println("listNew:" + listNew); System.err.println("list:" + list); //但是一個(gè)實(shí)體類只能重寫一次equals方法,如果有多種判別需求就不好滿足了, // 可以定義多個(gè)不同類名相同屬性的類或者下面這種方式解決 Map<String, List<User>> listMap1 = list.stream().collect(Collectors .groupingBy(v -> Optional.ofNullable(v.getName()).orElse("") + "_" + Optional.ofNullable(v.getAddress()).orElse(""))); /*先看一下分組效果*/ listMap1.forEach((key, value) -> { System.out.println("========"); System.out.println("key:" + key); value.forEach(obj -> { System.out.println(obj); }); }); /*最終執(zhí)行結(jié)果*/ List<User> listNew1 = listMap1.keySet().stream().map(u -> { int sum = listMap1.get(u).stream().mapToInt(i -> i.getAge()).sum(); User user = listMap1.get(u).get(0); //這里和上面一樣的原理,也會(huì)影響原list集合中的被指向的地址的對(duì)象數(shù)據(jù) user.setAge(sum); return user; }).collect(Collectors.toList()); System.out.println("listNew1:" + listNew1); System.err.println("list:" + list); } }
打印日志:
======== key:User{name='b小張2', address='a2', age=10} User{name='b小張2', address='a2', age=10} ======== key:User{name='c小張3', address='a', age=10} User{name='c小張3', address='a', age=10} ======== key:User{name='c小張3', address='a3', age=10} User{name='c小張3', address='a3', age=10} User{name='c小張3', address='a3', age=10} ======== key:User{name='a小張1', address='a1', age=10} User{name='a小張1', address='a1', age=10} ======== key:User{name='d小張4', address='a4', age=10} User{name='d小張4', address='a4', age=10} ======== key:User{name='e小張5', address='a5', age=10} User{name='e小張5', address='a5', age=10} listNew:[User{name='b小張2', address='a2', age=10}, User{name='c小張3', address='a', age=10}, User{name='c小張3', address='a3', age=20}, User{name='a小張1', address='a1', age=10}, User{name='d小張4', address='a4', age=10}, User{name='e小張5', address='a5', age=10}] list:[User{name='a小張1', address='a1', age=10}, User{name='b小張2', address='a2', age=10}, User{name='c小張3', address='a3', age=20}, User{name='c小張3', address='a', age=10}, User{name='c小張3', address='a3', age=10}, User{name='d小張4', address='a4', age=10}, User{name='e小張5', address='a5', age=10}] ======== key:a小張1_a1 User{name='a小張1', address='a1', age=10} ======== key:c小張3_a User{name='c小張3', address='a', age=10} ======== key:d小張4_a4 User{name='d小張4', address='a4', age=10} ======== key:e小張5_a5 User{name='e小張5', address='a5', age=10} ======== key:b小張2_a2 User{name='b小張2', address='a2', age=10} ======== key:c小張3_a3 User{name='c小張3', address='a3', age=20} User{name='c小張3', address='a3', age=10} listNew1:[User{name='a小張1', address='a1', age=10}, User{name='c小張3', address='a', age=10}, User{name='d小張4', address='a4', age=10}, User{name='e小張5', address='a5', age=10}, User{name='b小張2', address='a2', age=10}, User{name='c小張3', address='a3', age=30}] list:[User{name='a小張1', address='a1', age=10}, User{name='b小張2', address='a2', age=10}, User{name='c小張3', address='a3', age=30}, User{name='c小張3', address='a', age=10}, User{name='c小張3', address='a3', age=10}, User{name='d小張4', address='a4', age=10}, User{name='e小張5', address='a5', age=10}] Process finished with exit code 0
以上這篇Java 8 Stream.distinct() 列表去重的操作就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
如何通過(guò)Java生成一個(gè)隨機(jī)數(shù)
當(dāng)我們需要在Java中生成隨機(jī)數(shù)時(shí),可以借助JDK中提供的Random類來(lái)實(shí)現(xiàn),通過(guò)使用Random類,我們可以輕松地生成各種類型的隨機(jī)數(shù),下面我們就來(lái)看看如何利用Random類生成隨機(jī)數(shù)吧2023-09-09java啟動(dòng)jar包設(shè)置啟動(dòng)參數(shù)的實(shí)現(xiàn)
本文主要介紹了java啟動(dòng)jar包設(shè)置啟動(dòng)參數(shù)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06mybatis項(xiàng)目CRUD步驟實(shí)例詳解
這篇文章主要介紹了mybatis項(xiàng)目CRUD步驟,包括pom.xml引入相應(yīng)的依賴,在resources目錄下寫配置文件,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09詳解Springboot-MyBatis配置-配置端口號(hào)與服務(wù)路徑(idea社區(qū)版2023.1.4+apache-mav
這篇文章主要介紹了Springboot-MyBatis配置-配置端口號(hào)與服務(wù)路徑(idea社區(qū)版2023.1.4+apache-maven-3.9.3-bin),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07Java FileInputStream讀中文亂碼問(wèn)題解決方案
這篇文章主要介紹了Java FileInputStream讀中文亂碼問(wèn)題解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10java實(shí)時(shí)監(jiān)控文件行尾內(nèi)容的實(shí)現(xiàn)
這篇文章主要介紹了java實(shí)時(shí)監(jiān)控文件行尾內(nèi)容的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02Java Web項(xiàng)目中驗(yàn)證碼功能的制作攻略
使用servlet制作驗(yàn)證碼中最關(guān)鍵的部分是緩存的使用,驗(yàn)證session中的字符串,接下來(lái)我們就來(lái)看一下Java Web項(xiàng)目中驗(yàn)證碼功能的制作攻略2016-05-05