lambda表示式-高階集合類和收集器
阿新 • • 發佈:2018-12-18
lambda表示式提供了很多的集合類和收集器來簡化程式設計,使之更加方便和美觀,所以這裡介紹一些常用的集合類和收集器來處理繁雜的程式碼。
1、方法引用:形如 User::getName,TreeSet::new 等價於user.getName(), new TreeSet<>();
可以引用靜態方法、例項物件方法、構造方法等。
/** * 方法引用 */ @Test public void refTest() { List<String> collect = list.stream().map(u -> u.getName()).collect(Collectors.toList()); System.out.println(collect); // 等價於 User::getName System.out.println(list.stream().map(User::getName).collect(Collectors.toList())); // TreeSet::new Stream<Integer> integerStream = Stream.of(1, 2, 3); TreeSet<Integer> integerTreeSet = integerStream.collect(Collectors.toCollection(TreeSet::new)); System.out.println(integerTreeSet); }
2、流中元素的順序,如果集合本身無序,則轉換的流仍然無序,如HashSet(進入順序)
/** * 方法引用 */ @Test public void refTest() { List<String> collect = list.stream().map(u -> u.getName()).collect(Collectors.toList()); System.out.println(collect); // 等價於 User::getName System.out.println(list.stream().map(User::getName).collect(Collectors.toList())); // TreeSet::new Stream<Integer> integerStream = Stream.of(1, 2, 3); TreeSet<Integer> integerTreeSet = integerStream.collect(Collectors.toCollection(TreeSet::new)); System.out.println(integerTreeSet); }
3、maxBy(比較器),minBy(..), 找出最大最小值
/** * 轉換成值 */ @Test public void biggestGroupTest() { Optional<Artist> optional = biggestGroup(artists.stream()); System.out.println(optional.get().getName()); // -> ace } public Optional<Artist> biggestGroup(Stream<Artist> artists) { Function<Artist, Long> getCount = artist -> artist.getMembers().count(); return artists.collect(maxBy(comparing(getCount))); // 找出最大成員數目 }
4、averagingDouble(),求平均值
/**
* 找出一組專輯上曲目的平均數
*/
@Test
public void test() {
System.out.println(averageNumberOfTracks(albums));
}
public double averageNumberOfTracks(List<Album> albums) {
return albums.stream().collect(averagingDouble(album -> album.getTrackList().size()));
}
5、資料分塊 partitioningBy,根據true, false分成兩塊資料
/**
* 資料分塊 partitioningBy, true or false值劃分, 分成true or false 兩部分
*/
@Test
public void partitioningByTest() {
Map<Boolean, List<Artist>> booleanListMap = partitioningByNationality(artists.stream());
List<Artist> trueList = booleanListMap.get(true);
System.out.println(trueList); // out -> each
List<Artist> falseList = booleanListMap.get(false);
System.out.println(falseList); // out -> ace
}
public Map<Boolean, List<Artist>> partitioningByNationality(Stream<Artist> artists) {
// 將國籍分成中國和其他兩組
return artists.collect(Collectors.partitioningBy(artist -> "china".equals(artist.getNationality())));
}
6、資料分組 groupingBy(),可以根據任意值進行分組
/**
* 資料分組 groupingBy, 任意值劃分, 分成true or false 兩部分
*/
@Test
public void groupingByTest() {
Map<String, List<Artist>> stringListMap = groupingByNationality(artists.stream());
List<Artist> list = stringListMap.get("china"); // out -> each
System.out.println(list);
List<Artist> list1 = stringListMap.get("US"); // out -> ace
System.out.println(list1);
Map<Artist, List<Album>> streamListMap = groupingByArtist(albums.stream());
System.out.println(streamListMap.get(artist1)); // 專輯1, 專輯2
}
public Map<String, List<Artist>> groupingByNationality(Stream<Artist> artists) {
// 按照國籍分組
return artists.collect(Collectors.groupingBy(artist -> artist.getNationality()));
}
/** 按照主唱分組 */
public Map<Artist, List<Album>> groupingByArtist(Stream<Album> albums) {
return albums.collect(groupingBy(album -> album.getMainMusician()));
}
7、字串拼裝 Collectors.joining, 按照指定的格式拼裝字串
/**
* 輸出字串
*/
@Test
public void joiningTest() {
String joiningName = joiningName(artists.stream());
System.out.println(joiningName); // out -> [ace,each]
}
public String joiningName(Stream<Artist> artists) {
return artists.map(Artist::getName).collect(Collectors.joining(",", "[", "]"));
}
8、單一的收集器已經能實現很多基礎的功能,組合方式更顯強大
// 組合收集器
/** ===== 下游收集器: 例子中用到的第二個收集器, 用來收集最終結果的一個子集 ===== */
/**
* 統計某個藝術家專輯數量
*/
@Test
public void combineTest() {
long braden = numberOfAlbums(albums.stream(), "bradenlei");
System.out.println(braden);
}
public long numberOfAlbums(Stream<Album> albums, String artistName) {
return albums.map(album -> album.getMusicians())
.filter(musicians -> musicians.filter(musician -> artistName.equals(musician.getName())).count() > 0)
.count();
}
/**
* 計算每個藝術家的專輯數(counting)
*/
@Test
public void combineTest2() {
Map<Artist, Long> count = count(albums.stream());
System.out.println(count.get(artist1)); // out -> 2
}
public Map<Artist, Long> count(Stream<Album> albums) {
return albums.collect(groupingBy(album -> album.getMainMusician(), counting()));
}
/**
* 收集每個藝術家的專輯名稱 (mapping)
*/
@Test
public void combineTest3() {
Map<Artist, List<String>> artistListMap = collectName(albums.stream());
System.out.println(artistListMap.get(artist1)); // out -> [專輯1, 專輯2]
}
public Map<Artist, List<String>> collectName(Stream<Album> alubms) {
return alubms.collect(groupingBy(Album::getMainMusician, mapping(Album::getName, toList())));
}
9、有些時候現有的收集器不能滿足我們的開發需求,當然也就可以重構和定製收集器了
/** =====重構和定製收集器===== */
/**
* 使用除joining之外的方法拼接字串
*
* 獲取藝術家姓名 (初步)
*/
@Test
public void jointTest() {
StringBuilder reduced = artists.stream()
.map(Artist::getName)
.reduce(new StringBuilder(), (builder, name) -> {
if (builder.length() > 0) builder.append(", ");
return builder.append(name);
}, (left, right) -> left.append(right));
reduced.insert(0, "[");
reduced.append("]");
String result = reduced.toString();
System.out.println(result); // out -> [ace, each]
}
/**
* 優化(自定義StringCombiner)
*/
@Test
public void jointTest2() {
StringCombiner reduced = artists.stream()
.map(Artist::getName)
.reduce(new StringCombiner(", ", "[", "]"),
StringCombiner::add,
StringCombiner::merge); // 這裡實際沒有呼叫merge
String string = reduced.toString();
System.out.println(string); // out -> [ace, each]
}
/***
* 進一步優化(使用定製收集器StringCollector收集)
*/
@Test
public void jointTest3() {
String collect = artists.stream()
.map(Artist::getName)
.collect(new StringCollector(", ", "[", "]"));
System.out.println(collect); // out -> [ace, each]
}
/**
* 用reducing代替自定義收集器
*/
@Test
public void jointtTest4() {
// 這種方法的缺點是第二個引數每次都需要new一個物件
StringCombiner collect = artists.stream()
.map(Artist::getName)
.collect(Collectors.reducing(
new StringCombiner(", ", "[", "]"),
name -> new StringCombiner(", ", "[", "]").add(name),
StringCombiner::merge)); // 這裡會呼叫merge, 每次都是兩個StringCombiner物件合併, 所以修改StringCombiner.add即可得到正確格式[ace, each]
String s = collect.toString();
System.out.println(s); // out -> [ace[each]
}
自定義類:
public class StringCombiner {
// 字首
private String prefix;
// 分隔符
private String delim;
// 字尾
private String suffix;
private StringBuilder builder;
public StringCombiner(String delim, String prefix, String suffix) {
this.delim = delim;
this.prefix = prefix;
this.suffix = suffix;
builder = new StringBuilder();
}
public StringCombiner add(String element) {
if(builder.length() > 0) {
builder.append(delim);
} else {
builder.insert(0, prefix);
}
builder.append(element);
return this;
}
public StringCombiner merge(StringCombiner combiner) {
builder.append(combiner.builder);
return this;
}
@Override
public String toString() {
if ("".equals(suffix)) {
return builder.toString();
} else {
int tmp = builder.length();
String result = builder.append(suffix).toString();
builder.setLength(tmp);
return result;
}
}
}
其中:StringCombiner功能仿製java提供的StringJoiner
/**
* String 待收集的元素字串
* StringCombiner 累加器型別
* String 最終結果型別
*/
public class StringCollector implements Collector<String, StringCombiner, String> {
// 字首
private String prefix;
// 分隔符
private String delim;
// 字尾
private String suffix;
public StringCollector(String delim, String prefix, String suffix) {
this.delim = delim;
this.prefix = prefix;
this.suffix = suffix;
}
@Override
public Supplier<StringCombiner> supplier() {
return () -> new StringCombiner(delim, prefix, suffix);
}
@Override
public BiConsumer<StringCombiner, String> accumulator() {
return StringCombiner::add;
}
@Override
public BinaryOperator<StringCombiner> combiner() {
return StringCombiner::merge;
}
@Override
public Function<StringCombiner, String> finisher() {
return StringCombiner::toString;
}
@Override
public Set<Characteristics> characteristics() {
Set<Collector.Characteristics> emptySet = Collections.emptySet();
return emptySet;
}
}
10、完整demo:
public class CollectorDemo {
private static User user;
private static List<User> list;
@Before
public void init() {
if (user == null) {
user = new User("張三", 11001);
}
if (list == null) {
list = new ArrayList<>();
list.add(new User("李四", 11002));
list.add(new User("王五", 11003));
list.add(new User("周莊", 11004));
}
}
/**
* 方法引用
*/
@Test
public void refTest() {
List<String> collect = list.stream().map(u -> u.getName()).collect(Collectors.toList());
System.out.println(collect);
// 等價於 User::getName
System.out.println(list.stream().map(User::getName).collect(Collectors.toList()));
// TreeSet::new
Stream<Integer> integerStream = Stream.of(1, 2, 3);
TreeSet<Integer> integerTreeSet = integerStream.collect(Collectors.toCollection(TreeSet::new));
System.out.println(integerTreeSet);
}
/**
* 元素順序, 如果集合本身無序, 則轉換的流也是無序的如, HashSet
*/
@Test
public void orderTest() {
// 進來的流無序, 則出去的流也是無序
List<Integer> origin = Arrays.asList(1, 2, 3, 4);
List<Integer> integerList = origin.stream().collect(Collectors.toList());
assertEquals(origin, integerList); // ok
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
Set<Integer> integerSet = set.stream().collect(Collectors.toSet());
assertEquals(set, integerSet); // 不能保證每次通過
List<Integer> list = Arrays.asList(2, 1, 4, 3);
List<Integer> collect = list.stream().sorted().collect(Collectors.toList());
assertEquals(origin, collect); // ok
}
private static List<Artist> artists = null;
private static List<Artist> artists2 = null;
private static List<Track> tracks = null;
private static List<Track> tracks2 = null;
private static List<Album> albums = null;
private static Artist artist1 = null;
private static Artist artist2 = null;
static {
Artist artist = new Artist("braden", "china");
artist1 = new Artist("ace", Arrays.asList(artist), "US");
artist2 = new Artist("each", "china");
artists = Arrays.asList(artist1, artist2);
tracks = Arrays.asList(new Track("love you", 3), new Track("Shape of you", 5), new Track("夜上海", 4));
tracks2 = Arrays.asList(new Track("喜歡你", 4), new Track("不分手的戀愛", 5));
artists2 = Arrays.asList(new Artist("bradenlei","china"), new Artist("Mr.zhang","UK"));
albums = Arrays.asList(new Album("專輯1", tracks, artists), new Album("專輯2", tracks2, artists), new Album("專輯3", tracks, artists2));
}
/**
* 轉換成值
*/
@Test
public void biggestGroupTest() {
Optional<Artist> optional = biggestGroup(artists.stream());
System.out.println(optional.get().getName()); // -> ace
}
public Optional<Artist> biggestGroup(Stream<Artist> artists) {
Function<Artist, Long> getCount = artist -> artist.getMembers().count();
return artists.collect(maxBy(comparing(getCount))); // 找出最大成員數目
}
/**
* 找出一組專輯上曲目的平均數
*/
@Test
public void test() {
System.out.println(averageNumberOfTracks(albums));
}
public double averageNumberOfTracks(List<Album> albums) {
return albums.stream().collect(averagingDouble(album -> album.getTrackList().size()));
}
/**
* 資料分塊 partitioningBy, true or false值劃分, 分成true or false 兩部分
*/
@Test
public void partitioningByTest() {
Map<Boolean, List<Artist>> booleanListMap = partitioningByNationality(artists.stream());
List<Artist> trueList = booleanListMap.get(true);
System.out.println(trueList); // out -> each
List<Artist> falseList = booleanListMap.get(false);
System.out.println(falseList); // out -> ace
}
public Map<Boolean, List<Artist>> partitioningByNationality(Stream<Artist> artists) {
// 將國籍分成中國和其他兩組
return artists.collect(Collectors.partitioningBy(artist -> "china".equals(artist.getNationality())));
}
/**
* 資料分組 groupingBy, 任意值劃分, 分成true or false 兩部分
*/
@Test
public void groupingByTest() {
Map<String, List<Artist>> stringListMap = groupingByNationality(artists.stream());
List<Artist> list = stringListMap.get("china"); // out -> each
System.out.println(list);
List<Artist> list1 = stringListMap.get("US"); // out -> ace
System.out.println(list1);
Map<Artist, List<Album>> streamListMap = groupingByArtist(albums.stream());
System.out.println(streamListMap.get(artist1)); // 專輯1, 專輯2
}
public Map<String, List<Artist>> groupingByNationality(Stream<Artist> artists) {
// 按照國籍分組
return artists.collect(Collectors.groupingBy(artist -> artist.getNationality()));
}
/** 按照主唱分組 */
public Map<Artist, List<Album>> groupingByArtist(Stream<Album> albums) {
return albums.collect(groupingBy(album -> album.getMainMusician()));
}
/**
* 輸出字串
*/
@Test
public void joiningTest() {
String joiningName = joiningName(artists.stream());
System.out.println(joiningName); // out -> [ace,each]
}
public String joiningName(Stream<Artist> artists) {
return artists.map(Artist::getName).collect(Collectors.joining(",", "[", "]"));
}
// 組合收集器
/** ===== 下游收集器: 例子中用到的第二個收集器, 用來收集最終結果的一個子集 ===== */
/**
* 統計某個藝術家專輯數量
*/
@Test
public void combineTest() {
long braden = numberOfAlbums(albums.stream(), "bradenlei");
System.out.println(braden);
}
public long numberOfAlbums(Stream<Album> albums, String artistName) {
return albums.map(album -> album.getMusicians())
.filter(musicians -> musicians.filter(musician -> artistName.equals(musician.getName())).count() > 0)
.count();
}
/**
* 計算每個藝術家的專輯數(counting)
*/
@Test
public void combineTest2() {
Map<Artist, Long> count = count(albums.stream());
System.out.println(count.get(artist1)); // out -> 2
}
public Map<Artist, Long> count(Stream<Album> albums) {
return albums.collect(groupingBy(album -> album.getMainMusician(), counting()));
}
/**
* 收集每個藝術家的專輯名稱 (mapping)
*/
@Test
public void combineTest3() {
Map<Artist, List<String>> artistListMap = collectName(albums.stream());
System.out.println(artistListMap.get(artist1)); // out -> [專輯1, 專輯2]
}
public Map<Artist, List<String>> collectName(Stream<Album> alubms) {
return alubms.collect(groupingBy(Album::getMainMusician, mapping(Album::getName, toList())));
}
/** =====重構和定製收集器===== */
/**
* 使用除joining之外的方法拼接字串
*
* 獲取藝術家姓名 (初步)
*/
@Test
public void jointTest() {
StringBuilder reduced = artists.stream()
.map(Artist::getName)
.reduce(new StringBuilder(), (builder, name) -> {
if (builder.length() > 0) builder.append(", ");
return builder.append(name);
}, (left, right) -> left.append(right));
reduced.insert(0, "[");
reduced.append("]");
String result = reduced.toString();
System.out.println(result); // out -> [ace, each]
}
/**
* 優化(自定義StringCombiner)
*/
@Test
public void jointTest2() {
StringCombiner reduced = artists.stream()
.map(Artist::getName)
.reduce(new StringCombiner(", ", "[", "]"),
StringCombiner::add,
StringCombiner::merge); // 這裡實際沒有呼叫merge
String string = reduced.toString();
System.out.println(string); // out -> [ace, each]
}
/***
* 進一步優化(使用定製收集器StringCollector收集)
*/
@Test
public void jointTest3() {
String collect = artists.stream()
.map(Artist::getName)
.collect(new StringCollector(", ", "[", "]"));
System.out.println(collect); // out -> [ace, each]
}
/**
* 用reducing代替自定義收集器
*/
@Test
public void jointtTest4() {
// 這種方法的缺點是第二個引數每次都需要new一個物件
StringCombiner collect = artists.stream()
.map(Artist::getName)
.collect(Collectors.reducing(
new StringCombiner(", ", "[", "]"),
name -> new StringCombiner(", ", "[", "]").add(name),
StringCombiner::merge)); // 這裡會呼叫merge, 每次都是兩個StringCombiner物件合併, 所以修改StringCombiner.add即可得到正確格式[ace, each]
String s = collect.toString();
System.out.println(s); // out -> [ace[each]
}
}