[{"data":1,"prerenderedAt":1697},["ShallowReactive",2],{"blog-\u002Fblog\u002Fjava-collectors-groupingby":3},{"id":4,"title":5,"body":6,"description":1683,"difficulty":1684,"extension":1685,"framework":1686,"frameworkSlug":113,"meta":1687,"navigation":588,"order":155,"path":1688,"qaPath":1689,"seo":1690,"stem":1691,"subtopic":1692,"topic":1693,"topicSlug":1694,"updated":1695,"__hash__":1696},"blog\u002Fblog\u002Fjava-collectors-groupingby.md","Java Collectors & groupingBy — collect(), Downstream Collectors & Custom Collectors",{"type":7,"value":8,"toc":1670},"minimark",[9,14,67,71,108,189,209,213,248,282,298,302,337,425,459,463,497,596,609,613,630,737,770,774,810,942,958,962,994,1151,1159,1163,1195,1307,1325,1329,1351,1437,1446,1570,1588,1592,1666],[10,11,13],"h2",{"id":12},"why-collectors-are-the-hard-part-of-streams","Why Collectors are the hard part of streams",[15,16,17,18,22,23,22,26,29,30,34,35,39,40,43,44,47,48,50,51,54,55,58,59,62,63,66],"p",{},"Most of the Stream API is easy to skim: ",[19,20,21],"code",{},"filter",", ",[19,24,25],{},"map",[19,27,28],{},"reduce"," read like a sentence.\n",[31,32,33],"strong",{},"Collectors"," are where interviewers separate people who ",[36,37,38],"em",{},"used"," streams from people who\n",[36,41,42],{},"understand"," them. The reason is that a ",[19,45,46],{},"Collector"," is not one operation but a small\nrecipe of cooperating functions, and the ",[19,49,33],{}," factory composes those recipes into\neach other — a ",[19,52,53],{},"groupingBy"," whose downstream is a ",[19,56,57],{},"mapping"," whose downstream is a\n",[19,60,61],{},"joining",". Once you see collectors as ",[31,64,65],{},"building blocks that nest",", the whole API stops\nfeeling like a grab-bag of static methods and starts feeling like a small language for\naggregation. This guide builds that mental model from the bottom up.",[10,68,70],{"id":69},"collect-is-a-mutable-reduction","collect() is a mutable reduction",[15,72,73,74,77,78,81,82,84,85,88,89,92,93,92,96,99,100,103,104,107],{},"The ",[19,75,76],{},"collect()"," terminal operation performs a ",[31,79,80],{},"mutable reduction",": instead of folding\nimmutable values together like ",[19,83,28],{},", it pours elements into a ",[31,86,87],{},"mutable result\ncontainer"," — an ",[19,90,91],{},"ArrayList",", a ",[19,94,95],{},"HashMap",[19,97,98],{},"StringBuilder"," — accumulating in place. That\nin-place mutation is why ",[19,101,102],{},"collect"," is the efficient way to build a collection: there is no\nallocation per step, just repeated ",[19,105,106],{},"add",".",[109,110,115],"pre",{"className":111,"code":112,"language":113,"meta":114,"style":114},"language-java shiki shiki-themes github-light github-dark","\u002F\u002F reduce combines immutable values; collect mutates one container\nList\u003CString> upper = names.stream()\n    .map(String::toUpperCase)\n    .collect(Collectors.toList());   \u002F\u002F pours into one ArrayList\n","java","",[19,116,117,126,153,170],{"__ignoreMap":114},[118,119,122],"span",{"class":120,"line":121},"line",1,[118,123,125],{"class":124},"sJ8bj","\u002F\u002F reduce combines immutable values; collect mutates one container\n",[118,127,129,133,137,140,143,146,150],{"class":120,"line":128},2,[118,130,132],{"class":131},"sVt8B","List\u003C",[118,134,136],{"class":135},"szBVR","String",[118,138,139],{"class":131},"> upper ",[118,141,142],{"class":135},"=",[118,144,145],{"class":131}," names.",[118,147,149],{"class":148},"sScJk","stream",[118,151,152],{"class":131},"()\n",[118,154,156,159,161,164,167],{"class":120,"line":155},3,[118,157,158],{"class":131},"    .",[118,160,25],{"class":148},[118,162,163],{"class":131},"(String",[118,165,166],{"class":135},"::",[118,168,169],{"class":131},"toUpperCase)\n",[118,171,173,175,177,180,183,186],{"class":120,"line":172},4,[118,174,158],{"class":131},[118,176,102],{"class":148},[118,178,179],{"class":131},"(Collectors.",[118,181,182],{"class":148},"toList",[118,184,185],{"class":131},"());   ",[118,187,188],{"class":124},"\u002F\u002F pours into one ArrayList\n",[15,190,191,192,194,195,197,198,201,202,205,206,208],{},"The argument to ",[19,193,102],{}," is a ",[19,196,46],{},", and ",[19,199,200],{},"java.util.stream.Collectors"," is a factory\nof ready-made ones. ",[31,203,204],{},"Rule of thumb:"," reach into the ",[19,207,33],{}," factory first; only\nhand-roll a collector when nothing there composes into what you need.",[10,210,212],{"id":211},"the-anatomy-of-a-collector","The anatomy of a Collector",[15,214,215,216,219,220,223,224,227,228,231,232,235,236,239,240,243,244,247],{},"A ",[19,217,218],{},"Collector\u003CT, A, R>"," is three type parameters and up to five functions. ",[19,221,222],{},"T"," is the input\nelement, ",[19,225,226],{},"A"," is the mutable accumulation type, ",[19,229,230],{},"R"," is the final result. The four functions\nthat do the work are the ",[31,233,234],{},"supplier"," (make an empty container), the ",[31,237,238],{},"accumulator"," (fold\none element in), the ",[31,241,242],{},"combiner"," (merge two partial containers), and the ",[31,245,246],{},"finisher","\n(turn the container into the result).",[109,249,251],{"className":111,"code":250,"language":113,"meta":114,"style":114},"\u002F\u002F toList, expressed as its four parts:\n\u002F\u002F   supplier    = ArrayList::new   -> a fresh empty list\n\u002F\u002F   accumulator = List::add        -> add one element\n\u002F\u002F   combiner    = (a, b) -> { a.addAll(b); return a; }  \u002F\u002F merge two lists\n\u002F\u002F   finisher    = identity         -> the list IS the result (IDENTITY_FINISH)\n",[19,252,253,258,263,268,276],{"__ignoreMap":114},[118,254,255],{"class":120,"line":121},[118,256,257],{"class":124},"\u002F\u002F toList, expressed as its four parts:\n",[118,259,260],{"class":120,"line":128},[118,261,262],{"class":124},"\u002F\u002F   supplier    = ArrayList::new   -> a fresh empty list\n",[118,264,265],{"class":120,"line":155},[118,266,267],{"class":124},"\u002F\u002F   accumulator = List::add        -> add one element\n",[118,269,270,273],{"class":120,"line":172},[118,271,272],{"class":124},"\u002F\u002F   combiner    = (a, b) -> { a.addAll(b); return a; }",[118,274,275],{"class":124},"  \u002F\u002F merge two lists\n",[118,277,279],{"class":120,"line":278},5,[118,280,281],{"class":124},"\u002F\u002F   finisher    = identity         -> the list IS the result (IDENTITY_FINISH)\n",[15,283,73,284,286,287,289,290,293,294,297],{},[31,285,242],{}," is the piece that makes a collector safe for parallel streams: split the\nwork, fill several containers, then merge them. The ",[31,288,246],{}," is skipped entirely when\nthe container already ",[36,291,292],{},"is"," the result — that optimization is flagged by the\n",[19,295,296],{},"IDENTITY_FINISH"," characteristic. Understanding these five parts is what lets you read,\ncompose, and eventually write collectors.",[10,299,301],{"id":300},"tolist-toset-and-tocollection","toList, toSet, and toCollection",[15,303,304,305,310,311,314,315,317,318,310,323,326,327,332,333,336],{},"The simplest collectors gather elements into a collection, differing only in the container\nthey fill. ",[31,306,307],{},[19,308,309],{},"toList()"," accumulates into a ",[19,312,313],{},"List"," (an ",[19,316,91],{}," in practice).\n",[31,319,320],{},[19,321,322],{},"toSet()",[19,324,325],{},"HashSet",", dropping duplicates and abandoning order.\n",[31,328,329],{},[19,330,331],{},"toCollection(supplier)"," accumulates into ",[36,334,335],{},"whatever"," collection you hand it, for when\nthe default won't do.",[109,338,340],{"className":111,"code":339,"language":113,"meta":114,"style":114},"List\u003CString>    list   = s.collect(Collectors.toList());\nSet\u003CString>     set    = s.collect(Collectors.toSet());          \u002F\u002F dedup, no order\nTreeSet\u003CString> sorted = s.collect(Collectors.toCollection(TreeSet::new)); \u002F\u002F sorted\n",[19,341,342,365,392],{"__ignoreMap":114},[118,343,344,346,348,351,353,356,358,360,362],{"class":120,"line":121},[118,345,132],{"class":131},[118,347,136],{"class":135},[118,349,350],{"class":131},">    list   ",[118,352,142],{"class":135},[118,354,355],{"class":131}," s.",[118,357,102],{"class":148},[118,359,179],{"class":131},[118,361,182],{"class":148},[118,363,364],{"class":131},"());\n",[118,366,367,370,372,375,377,379,381,383,386,389],{"class":120,"line":128},[118,368,369],{"class":131},"Set\u003C",[118,371,136],{"class":135},[118,373,374],{"class":131},">     set    ",[118,376,142],{"class":135},[118,378,355],{"class":131},[118,380,102],{"class":148},[118,382,179],{"class":131},[118,384,385],{"class":148},"toSet",[118,387,388],{"class":131},"());          ",[118,390,391],{"class":124},"\u002F\u002F dedup, no order\n",[118,393,394,397,399,402,404,406,408,410,413,416,419,422],{"class":120,"line":155},[118,395,396],{"class":131},"TreeSet\u003C",[118,398,136],{"class":135},[118,400,401],{"class":131},"> sorted ",[118,403,142],{"class":135},[118,405,355],{"class":131},[118,407,102],{"class":148},[118,409,179],{"class":131},[118,411,412],{"class":148},"toCollection",[118,414,415],{"class":131},"(TreeSet",[118,417,418],{"class":135},"::new",[118,420,421],{"class":131},")); ",[118,423,424],{"class":124},"\u002F\u002F sorted\n",[15,426,427,428,431,432,435,437,438,441,442,445,446,448,449,451,452,455,456,107],{},"One distinction interviewers love: ",[19,429,430],{},"Collectors.toList()"," returns a ",[31,433,434],{},"modifiable",[19,436,91],{},", while the newer ",[19,439,440],{},"stream().toList()"," (Java 16+) returns an ",[31,443,444],{},"unmodifiable","\nlist. Prefer ",[19,447,440],{}," for read-only results; keep ",[19,450,430],{}," for\nwhen you genuinely need to mutate afterward. Java 10's ",[19,453,454],{},"toUnmodifiableList\u002FSet\u002FMap"," give\nunmodifiable variants that also reject ",[19,457,458],{},"null",[10,460,462],{"id":461},"tomap-and-the-merge-function","toMap and the merge function",[15,464,465,468,469,472,473,476,477,480,481,483,484,486,487,490,491,493,494,107],{},[19,466,467],{},"toMap"," builds a ",[19,470,471],{},"Map"," from each element using a ",[31,474,475],{},"key mapper"," and a ",[31,478,479],{},"value mapper",". The\ndefault result is a ",[19,482,95],{},". The trap, and the most common ",[19,485,467],{}," bug, is ",[31,488,489],{},"duplicate\nkeys",": if two elements produce the same key, the two-argument ",[19,492,467],{}," throws\n",[19,495,496],{},"IllegalStateException",[109,498,500],{"className":111,"code":499,"language":113,"meta":114,"style":114},"\u002F\u002F Three-arg form: a merge function resolves key collisions\nMap\u003CString, Integer> totals = orders.stream()\n    .collect(Collectors.toMap(\n        Order::customer,     \u002F\u002F key mapper\n        Order::amount,       \u002F\u002F value mapper\n        Integer::sum));      \u002F\u002F merge: (existing, new) -> existing + new\n\n\u002F\u002F a fourth argument supplies the map type, e.g. TreeMap::new for ordering\n",[19,501,502,507,531,544,557,569,583,590],{"__ignoreMap":114},[118,503,504],{"class":120,"line":121},[118,505,506],{"class":124},"\u002F\u002F Three-arg form: a merge function resolves key collisions\n",[118,508,509,512,514,516,519,522,524,527,529],{"class":120,"line":128},[118,510,511],{"class":131},"Map\u003C",[118,513,136],{"class":135},[118,515,22],{"class":131},[118,517,518],{"class":135},"Integer",[118,520,521],{"class":131},"> totals ",[118,523,142],{"class":135},[118,525,526],{"class":131}," orders.",[118,528,149],{"class":148},[118,530,152],{"class":131},[118,532,533,535,537,539,541],{"class":120,"line":155},[118,534,158],{"class":131},[118,536,102],{"class":148},[118,538,179],{"class":131},[118,540,467],{"class":148},[118,542,543],{"class":131},"(\n",[118,545,546,549,551,554],{"class":120,"line":172},[118,547,548],{"class":131},"        Order",[118,550,166],{"class":135},[118,552,553],{"class":131},"customer,     ",[118,555,556],{"class":124},"\u002F\u002F key mapper\n",[118,558,559,561,563,566],{"class":120,"line":278},[118,560,548],{"class":131},[118,562,166],{"class":135},[118,564,565],{"class":131},"amount,       ",[118,567,568],{"class":124},"\u002F\u002F value mapper\n",[118,570,572,575,577,580],{"class":120,"line":571},6,[118,573,574],{"class":131},"        Integer",[118,576,166],{"class":135},[118,578,579],{"class":131},"sum));      ",[118,581,582],{"class":124},"\u002F\u002F merge: (existing, new) -> existing + new\n",[118,584,586],{"class":120,"line":585},7,[118,587,589],{"emptyLinePlaceholder":588},true,"\n",[118,591,593],{"class":120,"line":592},8,[118,594,595],{"class":124},"\u002F\u002F a fourth argument supplies the map type, e.g. TreeMap::new for ordering\n",[15,597,598,599,602,603,605,606,608],{},"The merge function ",[19,600,601],{},"(existing, new) -> result"," runs whenever two elements collide on a\nkey. ",[31,604,204],{}," unless the keys are provably unique, always supply a merge\nfunction — it is cheaper to write than to debug the ",[19,607,496],{}," in production.",[10,610,612],{"id":611},"groupingby-the-group-by-of-streams","groupingBy: the GROUP BY of streams",[15,614,615,617,618,621,622,625,626,629],{},[19,616,53],{}," takes a ",[31,619,620],{},"classifier function"," and partitions elements into a\n",[19,623,624],{},"Map\u003CK, List\u003CT>>",", where every element sharing a classifier result lands in the same list.\nThe single-argument form gives lists; the two-argument form replaces the list with a\n",[31,627,628],{},"downstream collector"," applied to each group — and that is where the power lives.",[109,631,633],{"className":111,"code":632,"language":113,"meta":114,"style":114},"\u002F\u002F single-arg: Map\u003CDept, List\u003CEmployee>>\nMap\u003CDept, List\u003CEmployee>> byDept = emps.stream()\n    .collect(Collectors.groupingBy(Employee::dept));\n\n\u002F\u002F two-arg: a downstream reshapes each bucket (here, count per dept)\nMap\u003CDept, Long> counts = emps.stream()\n    .collect(Collectors.groupingBy(Employee::dept, Collectors.counting()));\n",[19,634,635,640,665,683,687,692,714],{"__ignoreMap":114},[118,636,637],{"class":120,"line":121},[118,638,639],{"class":124},"\u002F\u002F single-arg: Map\u003CDept, List\u003CEmployee>>\n",[118,641,642,644,647,650,653,656,658,661,663],{"class":120,"line":128},[118,643,511],{"class":131},[118,645,646],{"class":135},"Dept",[118,648,649],{"class":131},", List\u003C",[118,651,652],{"class":135},"Employee",[118,654,655],{"class":131},">> byDept ",[118,657,142],{"class":135},[118,659,660],{"class":131}," emps.",[118,662,149],{"class":148},[118,664,152],{"class":131},[118,666,667,669,671,673,675,678,680],{"class":120,"line":155},[118,668,158],{"class":131},[118,670,102],{"class":148},[118,672,179],{"class":131},[118,674,53],{"class":148},[118,676,677],{"class":131},"(Employee",[118,679,166],{"class":135},[118,681,682],{"class":131},"dept));\n",[118,684,685],{"class":120,"line":172},[118,686,589],{"emptyLinePlaceholder":588},[118,688,689],{"class":120,"line":278},[118,690,691],{"class":124},"\u002F\u002F two-arg: a downstream reshapes each bucket (here, count per dept)\n",[118,693,694,696,698,700,703,706,708,710,712],{"class":120,"line":571},[118,695,511],{"class":131},[118,697,646],{"class":135},[118,699,22],{"class":131},[118,701,702],{"class":135},"Long",[118,704,705],{"class":131},"> counts ",[118,707,142],{"class":135},[118,709,660],{"class":131},[118,711,149],{"class":148},[118,713,152],{"class":131},[118,715,716,718,720,722,724,726,728,731,734],{"class":120,"line":585},[118,717,158],{"class":131},[118,719,102],{"class":148},[118,721,179],{"class":131},[118,723,53],{"class":148},[118,725,677],{"class":131},[118,727,166],{"class":135},[118,729,730],{"class":131},"dept, Collectors.",[118,732,733],{"class":148},"counting",[118,735,736],{"class":131},"()));\n",[15,738,739,740,22,742,22,745,748,749,22,751,22,753,22,755,758,759,197,762,765,766,769],{},"Common downstreams are ",[19,741,733],{},[19,743,744],{},"summingInt\u002FLong\u002FDouble",[19,746,747],{},"averagingInt\u002FDouble",",\n",[19,750,57],{},[19,752,385],{},[19,754,61],{},[19,756,757],{},"maxBy","\u002F",[19,760,761],{},"minBy",[19,763,764],{},"reducing",". Because the downstream is\n",[36,767,768],{},"itself a collector",", you can keep composing — which is the whole game.",[10,771,773],{"id":772},"reshaping-groups-mapping-filtering-nesting","Reshaping groups: mapping, filtering, nesting",[15,775,776,777,779,780,785,786,789,790,795,796,799,800,803,804,809],{},"Three patterns turn ",[19,778,53],{}," from \"bucket of objects\" into exactly the shape you want.\n",[31,781,782],{},[19,783,784],{},"mapping(mapper, downstream)"," transforms each element before it reaches the downstream\n— a ",[19,787,788],{},"map()"," embedded inside the collector. ",[31,791,792],{},[19,793,794],{},"filtering(predicate, downstream)"," (Java 9)\nkeeps only matching members but ",[31,797,798],{},"preserves empty group keys",", unlike an upstream\n",[19,801,802],{},"filter()"," which deletes the whole bucket. And because a downstream can be ",[31,805,806,807],{},"another\n",[19,808,53],{},", you nest them for multi-level maps.",[109,811,813],{"className":111,"code":812,"language":113,"meta":114,"style":114},"\u002F\u002F names per dept (mapping projects a field before collecting to a list)\nMap\u003CDept, List\u003CString>> names = emps.stream()\n    .collect(Collectors.groupingBy(Employee::dept,\n             Collectors.mapping(Employee::name, Collectors.toList())));\n\n\u002F\u002F two-level: by dept, then by city — like GROUP BY dept, city\nMap\u003CDept, Map\u003CString, List\u003CEmployee>>> nested = emps.stream()\n    .collect(Collectors.groupingBy(Employee::dept,\n             Collectors.groupingBy(Employee::city)));\n",[19,814,815,820,841,858,877,881,886,912,928],{"__ignoreMap":114},[118,816,817],{"class":120,"line":121},[118,818,819],{"class":124},"\u002F\u002F names per dept (mapping projects a field before collecting to a list)\n",[118,821,822,824,826,828,830,833,835,837,839],{"class":120,"line":128},[118,823,511],{"class":131},[118,825,646],{"class":135},[118,827,649],{"class":131},[118,829,136],{"class":135},[118,831,832],{"class":131},">> names ",[118,834,142],{"class":135},[118,836,660],{"class":131},[118,838,149],{"class":148},[118,840,152],{"class":131},[118,842,843,845,847,849,851,853,855],{"class":120,"line":155},[118,844,158],{"class":131},[118,846,102],{"class":148},[118,848,179],{"class":131},[118,850,53],{"class":148},[118,852,677],{"class":131},[118,854,166],{"class":135},[118,856,857],{"class":131},"dept,\n",[118,859,860,863,865,867,869,872,874],{"class":120,"line":172},[118,861,862],{"class":131},"             Collectors.",[118,864,57],{"class":148},[118,866,677],{"class":131},[118,868,166],{"class":135},[118,870,871],{"class":131},"name, Collectors.",[118,873,182],{"class":148},[118,875,876],{"class":131},"())));\n",[118,878,879],{"class":120,"line":278},[118,880,589],{"emptyLinePlaceholder":588},[118,882,883],{"class":120,"line":571},[118,884,885],{"class":124},"\u002F\u002F two-level: by dept, then by city — like GROUP BY dept, city\n",[118,887,888,890,892,895,897,899,901,904,906,908,910],{"class":120,"line":585},[118,889,511],{"class":131},[118,891,646],{"class":135},[118,893,894],{"class":131},", Map\u003C",[118,896,136],{"class":135},[118,898,649],{"class":131},[118,900,652],{"class":135},[118,902,903],{"class":131},">>> nested ",[118,905,142],{"class":135},[118,907,660],{"class":131},[118,909,149],{"class":148},[118,911,152],{"class":131},[118,913,914,916,918,920,922,924,926],{"class":120,"line":592},[118,915,158],{"class":131},[118,917,102],{"class":148},[118,919,179],{"class":131},[118,921,53],{"class":148},[118,923,677],{"class":131},[118,925,166],{"class":135},[118,927,857],{"class":131},[118,929,931,933,935,937,939],{"class":120,"line":930},9,[118,932,862],{"class":131},[118,934,53],{"class":148},[118,936,677],{"class":131},[118,938,166],{"class":135},[118,940,941],{"class":131},"city)));\n",[15,943,944,945,948,949,951,952,954,955,107],{},"Use ",[19,946,947],{},"filtering"," over a pre-",[19,950,21],{}," whenever every group key must appear even with no\nsurviving members; use ",[19,953,57],{}," to collect a field rather than the whole object; nest to\nmirror a multi-column ",[19,956,957],{},"GROUP BY",[10,959,961],{"id":960},"partitioningby-the-two-bucket-special-case","partitioningBy: the two-bucket special case",[15,963,964,967,968,970,971,974,975,978,979,982,983,986,987,990,991,993],{},[19,965,966],{},"partitioningBy"," is a specialized ",[19,969,53],{}," for a ",[31,972,973],{},"predicate",": it splits the stream\ninto exactly two groups, returning a ",[19,976,977],{},"Map\u003CBoolean, List\u003CT>>"," keyed by ",[19,980,981],{},"true"," and ",[19,984,985],{},"false",".\nIts defining difference is that the map ",[31,988,989],{},"always contains both keys",", even when one\npartition is empty — whereas ",[19,992,53],{}," simply omits empty groups.",[109,995,997],{"className":111,"code":996,"language":113,"meta":114,"style":114},"Map\u003CBoolean, List\u003CInteger>> parts = nums.stream()\n    .collect(Collectors.partitioningBy(n -> n % 2 == 0));\nparts.get(true);   \u002F\u002F evens\nparts.get(false);  \u002F\u002F odds  -- present even if empty\n\n\u002F\u002F it accepts a downstream too:\nMap\u003CBoolean, Long> howMany =\n    nums.stream().collect(Collectors.partitioningBy(n -> n > 0, Collectors.counting()));\n",[19,998,999,1022,1057,1076,1092,1096,1101,1117],{"__ignoreMap":114},[118,1000,1001,1003,1006,1008,1010,1013,1015,1018,1020],{"class":120,"line":121},[118,1002,511],{"class":131},[118,1004,1005],{"class":135},"Boolean",[118,1007,649],{"class":131},[118,1009,518],{"class":135},[118,1011,1012],{"class":131},">> parts ",[118,1014,142],{"class":135},[118,1016,1017],{"class":131}," nums.",[118,1019,149],{"class":148},[118,1021,152],{"class":131},[118,1023,1024,1026,1028,1030,1032,1035,1038,1041,1044,1048,1051,1054],{"class":120,"line":128},[118,1025,158],{"class":131},[118,1027,102],{"class":148},[118,1029,179],{"class":131},[118,1031,966],{"class":148},[118,1033,1034],{"class":131},"(n ",[118,1036,1037],{"class":135},"->",[118,1039,1040],{"class":131}," n ",[118,1042,1043],{"class":135},"%",[118,1045,1047],{"class":1046},"sj4cs"," 2",[118,1049,1050],{"class":135}," ==",[118,1052,1053],{"class":1046}," 0",[118,1055,1056],{"class":131},"));\n",[118,1058,1059,1062,1065,1068,1070,1073],{"class":120,"line":155},[118,1060,1061],{"class":131},"parts.",[118,1063,1064],{"class":148},"get",[118,1066,1067],{"class":131},"(",[118,1069,981],{"class":1046},[118,1071,1072],{"class":131},");   ",[118,1074,1075],{"class":124},"\u002F\u002F evens\n",[118,1077,1078,1080,1082,1084,1086,1089],{"class":120,"line":172},[118,1079,1061],{"class":131},[118,1081,1064],{"class":148},[118,1083,1067],{"class":131},[118,1085,985],{"class":1046},[118,1087,1088],{"class":131},");  ",[118,1090,1091],{"class":124},"\u002F\u002F odds  -- present even if empty\n",[118,1093,1094],{"class":120,"line":278},[118,1095,589],{"emptyLinePlaceholder":588},[118,1097,1098],{"class":120,"line":571},[118,1099,1100],{"class":124},"\u002F\u002F it accepts a downstream too:\n",[118,1102,1103,1105,1107,1109,1111,1114],{"class":120,"line":585},[118,1104,511],{"class":131},[118,1106,1005],{"class":135},[118,1108,22],{"class":131},[118,1110,702],{"class":135},[118,1112,1113],{"class":131},"> howMany ",[118,1115,1116],{"class":135},"=\n",[118,1118,1119,1122,1124,1127,1129,1131,1133,1135,1137,1139,1142,1144,1147,1149],{"class":120,"line":592},[118,1120,1121],{"class":131},"    nums.",[118,1123,149],{"class":148},[118,1125,1126],{"class":131},"().",[118,1128,102],{"class":148},[118,1130,179],{"class":131},[118,1132,966],{"class":148},[118,1134,1034],{"class":131},[118,1136,1037],{"class":135},[118,1138,1040],{"class":131},[118,1140,1141],{"class":135},">",[118,1143,1053],{"class":1046},[118,1145,1146],{"class":131},", Collectors.",[118,1148,733],{"class":148},[118,1150,736],{"class":131},[15,1152,1153,1154,1156,1157,107],{},"Reach for ",[19,1155,966],{}," whenever the grouping key is boolean and you want both buckets\nguaranteed present; it is also marginally faster than the equivalent ",[19,1158,53],{},[10,1160,1162],{"id":1161},"joining-and-the-statistics-collectors","joining and the statistics collectors",[15,1164,1165,1167,1168,1173,1174,1176,1177,1179,1180,1183,1184,22,1186,197,1188,1191,1192,107],{},[19,1166,61],{}," concatenates ",[31,1169,1170],{},[19,1171,1172],{},"CharSequence"," elements into one ",[19,1175,136],{},", using a\n",[19,1178,98],{}," internally so it beats reducing with ",[19,1181,1182],{},"+",". The numeric collectors —\n",[19,1185,744],{},[19,1187,747],{},[19,1189,1190],{},"summarizingInt\u002FLong\u002FDouble"," — extract\na value and aggregate it. The summarizing ones are the standout: they compute ",[31,1193,1194],{},"count,\nsum, min, max, and average in a single pass",[109,1196,1198],{"className":111,"code":1197,"language":113,"meta":114,"style":114},"String csv = names.stream()\n    .collect(Collectors.joining(\", \", \"[\", \"]\"));   \u002F\u002F \"[Ann, Bob, Cy]\"\n\nIntSummaryStatistics stats = emps.stream()\n    .collect(Collectors.summarizingInt(Employee::salary));\nstats.getCount();   stats.getSum();   stats.getMax();   stats.getAverage();\n",[19,1199,1200,1213,1245,1249,1262,1280],{"__ignoreMap":114},[118,1201,1202,1205,1207,1209,1211],{"class":120,"line":121},[118,1203,1204],{"class":131},"String csv ",[118,1206,142],{"class":135},[118,1208,145],{"class":131},[118,1210,149],{"class":148},[118,1212,152],{"class":131},[118,1214,1215,1217,1219,1221,1223,1225,1229,1231,1234,1236,1239,1242],{"class":120,"line":128},[118,1216,158],{"class":131},[118,1218,102],{"class":148},[118,1220,179],{"class":131},[118,1222,61],{"class":148},[118,1224,1067],{"class":131},[118,1226,1228],{"class":1227},"sZZnC","\", \"",[118,1230,22],{"class":131},[118,1232,1233],{"class":1227},"\"[\"",[118,1235,22],{"class":131},[118,1237,1238],{"class":1227},"\"]\"",[118,1240,1241],{"class":131},"));   ",[118,1243,1244],{"class":124},"\u002F\u002F \"[Ann, Bob, Cy]\"\n",[118,1246,1247],{"class":120,"line":155},[118,1248,589],{"emptyLinePlaceholder":588},[118,1250,1251,1254,1256,1258,1260],{"class":120,"line":172},[118,1252,1253],{"class":131},"IntSummaryStatistics stats ",[118,1255,142],{"class":135},[118,1257,660],{"class":131},[118,1259,149],{"class":148},[118,1261,152],{"class":131},[118,1263,1264,1266,1268,1270,1273,1275,1277],{"class":120,"line":278},[118,1265,158],{"class":131},[118,1267,102],{"class":148},[118,1269,179],{"class":131},[118,1271,1272],{"class":148},"summarizingInt",[118,1274,677],{"class":131},[118,1276,166],{"class":135},[118,1278,1279],{"class":131},"salary));\n",[118,1281,1282,1285,1288,1291,1294,1296,1299,1301,1304],{"class":120,"line":571},[118,1283,1284],{"class":131},"stats.",[118,1286,1287],{"class":148},"getCount",[118,1289,1290],{"class":131},"();   stats.",[118,1292,1293],{"class":148},"getSum",[118,1295,1290],{"class":131},[118,1297,1298],{"class":148},"getMax",[118,1300,1290],{"class":131},[118,1302,1303],{"class":148},"getAverage",[118,1305,1306],{"class":131},"();\n",[15,1308,1309,1310,1313,1314,1317,1318,1321,1322,1324],{},"Note that every ",[19,1311,1312],{},"averaging*"," variant returns a ",[19,1315,1316],{},"Double"," (a mean is rarely integral). When\nyou need several statistics at once, prefer ",[19,1319,1320],{},"summarizing*"," over running three separate\ncollectors — it traverses the stream only once. All of these shine as ",[19,1323,53],{},"\ndownstreams for per-group totals and stats.",[10,1326,1328],{"id":1327},"collectingandthen-and-custom-collectors","collectingAndThen and custom collectors",[15,1330,1331,1334,1335,1338,1339,1342,1343,758,1345,1347,1348,1350],{},[19,1332,1333],{},"collectingAndThen(downstream, finisher)"," runs a collector and then applies a ",[31,1336,1337],{},"finishing\ntransformation"," to its result — bolting a custom finisher onto an existing collector\nwithout writing one. Its two classic uses are wrapping a collection as unmodifiable and\nunwrapping the ",[19,1340,1341],{},"Optional"," that ",[19,1344,757],{},[19,1346,761],{}," produce as a ",[19,1349,53],{}," downstream.",[109,1352,1354],{"className":111,"code":1353,"language":113,"meta":114,"style":114},"\u002F\u002F highest-paid employee per dept, with the Optional unwrapped\nMap\u003CDept, Employee> top = emps.stream()\n    .collect(Collectors.groupingBy(Employee::dept,\n             Collectors.collectingAndThen(\n                 Collectors.maxBy(Comparator.comparingInt(Employee::salary)),\n                 Optional::get)));\n",[19,1355,1356,1361,1382,1398,1407,1427],{"__ignoreMap":114},[118,1357,1358],{"class":120,"line":121},[118,1359,1360],{"class":124},"\u002F\u002F highest-paid employee per dept, with the Optional unwrapped\n",[118,1362,1363,1365,1367,1369,1371,1374,1376,1378,1380],{"class":120,"line":128},[118,1364,511],{"class":131},[118,1366,646],{"class":135},[118,1368,22],{"class":131},[118,1370,652],{"class":135},[118,1372,1373],{"class":131},"> top ",[118,1375,142],{"class":135},[118,1377,660],{"class":131},[118,1379,149],{"class":148},[118,1381,152],{"class":131},[118,1383,1384,1386,1388,1390,1392,1394,1396],{"class":120,"line":155},[118,1385,158],{"class":131},[118,1387,102],{"class":148},[118,1389,179],{"class":131},[118,1391,53],{"class":148},[118,1393,677],{"class":131},[118,1395,166],{"class":135},[118,1397,857],{"class":131},[118,1399,1400,1402,1405],{"class":120,"line":172},[118,1401,862],{"class":131},[118,1403,1404],{"class":148},"collectingAndThen",[118,1406,543],{"class":131},[118,1408,1409,1412,1414,1417,1420,1422,1424],{"class":120,"line":278},[118,1410,1411],{"class":131},"                 Collectors.",[118,1413,757],{"class":148},[118,1415,1416],{"class":131},"(Comparator.",[118,1418,1419],{"class":148},"comparingInt",[118,1421,677],{"class":131},[118,1423,166],{"class":135},[118,1425,1426],{"class":131},"salary)),\n",[118,1428,1429,1432,1434],{"class":120,"line":571},[118,1430,1431],{"class":131},"                 Optional",[118,1433,166],{"class":135},[118,1435,1436],{"class":131},"get)));\n",[15,1438,1439,1440,1445],{},"When nothing composes, build one directly with ",[31,1441,1442],{},[19,1443,1444],{},"Collector.of(supplier, accumulator, combiner, [finisher], characteristics...)"," — the same four functions from the anatomy\nsection, passed by hand. The combiner is mandatory so the collector works in parallel.",[109,1447,1449],{"className":111,"code":1448,"language":113,"meta":114,"style":114},"Collector\u003CString, StringJoiner, String> upperCsv = Collector.of(\n    () -> new StringJoiner(\", \"),       \u002F\u002F supplier\n    (j, s) -> j.add(s.toUpperCase()),   \u002F\u002F accumulator\n    StringJoiner::merge,                \u002F\u002F combiner (parallel-safe)\n    StringJoiner::toString);            \u002F\u002F finisher\nString result = names.stream().collect(upperCsv);\n",[19,1450,1451,1480,1503,1527,1540,1552],{"__ignoreMap":114},[118,1452,1453,1456,1458,1460,1463,1465,1467,1470,1472,1475,1478],{"class":120,"line":121},[118,1454,1455],{"class":131},"Collector\u003C",[118,1457,136],{"class":135},[118,1459,22],{"class":131},[118,1461,1462],{"class":135},"StringJoiner",[118,1464,22],{"class":131},[118,1466,136],{"class":135},[118,1468,1469],{"class":131},"> upperCsv ",[118,1471,142],{"class":135},[118,1473,1474],{"class":131}," Collector.",[118,1476,1477],{"class":148},"of",[118,1479,543],{"class":131},[118,1481,1482,1485,1487,1490,1493,1495,1497,1500],{"class":120,"line":128},[118,1483,1484],{"class":131},"    () ",[118,1486,1037],{"class":135},[118,1488,1489],{"class":135}," new",[118,1491,1492],{"class":148}," StringJoiner",[118,1494,1067],{"class":131},[118,1496,1228],{"class":1227},[118,1498,1499],{"class":131},"),       ",[118,1501,1502],{"class":124},"\u002F\u002F supplier\n",[118,1504,1505,1508,1510,1513,1515,1518,1521,1524],{"class":120,"line":155},[118,1506,1507],{"class":131},"    (j, s) ",[118,1509,1037],{"class":135},[118,1511,1512],{"class":131}," j.",[118,1514,106],{"class":148},[118,1516,1517],{"class":131},"(s.",[118,1519,1520],{"class":148},"toUpperCase",[118,1522,1523],{"class":131},"()),   ",[118,1525,1526],{"class":124},"\u002F\u002F accumulator\n",[118,1528,1529,1532,1534,1537],{"class":120,"line":172},[118,1530,1531],{"class":131},"    StringJoiner",[118,1533,166],{"class":135},[118,1535,1536],{"class":131},"merge,                ",[118,1538,1539],{"class":124},"\u002F\u002F combiner (parallel-safe)\n",[118,1541,1542,1544,1546,1549],{"class":120,"line":278},[118,1543,1531],{"class":131},[118,1545,166],{"class":135},[118,1547,1548],{"class":131},"toString);            ",[118,1550,1551],{"class":124},"\u002F\u002F finisher\n",[118,1553,1554,1557,1559,1561,1563,1565,1567],{"class":120,"line":571},[118,1555,1556],{"class":131},"String result ",[118,1558,142],{"class":135},[118,1560,145],{"class":131},[118,1562,149],{"class":148},[118,1564,1126],{"class":131},[118,1566,102],{"class":148},[118,1568,1569],{"class":131},"(upperCsv);\n",[15,1571,1572,1574,1575,22,1577,1579,1580,1583,1584,1587],{},[31,1573,204],{}," compose existing collectors with ",[19,1576,57],{},[19,1578,1404],{},", and\n",[19,1581,1582],{},"teeing"," first; write a fully custom ",[19,1585,1586],{},"Collector.of"," only when no combination fits.",[10,1589,1591],{"id":1590},"recap","Recap",[15,1593,1594,194,1596,1598,1599,1601,1602,1605,1606,1608,1609,758,1611,758,1613,1615,1616,982,1618,1621,1622,1626,1627,1630,1631,1634,1635,982,1637,1639,1640,1642,1643,1645,1646,758,1649,758,1652,1655,1656,1658,1659,1661,1662,1665],{},[19,1595,76],{},[31,1597,80],{}," driven by a ",[19,1600,46],{},", whose four functions —\n",[31,1603,1604],{},"supplier, accumulator, combiner, finisher"," — explain everything else (the combiner\nmakes it parallel-safe, the finisher is skipped under ",[19,1607,296],{},"). Gather with\n",[19,1610,182],{},[19,1612,385],{},[19,1614,412],{},", build maps with ",[19,1617,467],{},[31,1619,1620],{},"always supply a merge\nfunction"," when keys can collide, and group with ",[31,1623,1624],{},[19,1625,53],{}," — single, with a\n",[31,1628,1629],{},"downstream",", or ",[31,1632,1633],{},"nested"," — reshaping buckets via ",[19,1636,57],{},[19,1638,947],{},". Use\n",[19,1641,966],{}," for the boolean two-bucket case (both keys always present), ",[19,1644,61],{}," for\nstrings, and the ",[19,1647,1648],{},"summing",[19,1650,1651],{},"averaging",[19,1653,1654],{},"summarizing"," collectors for per-group statistics.\nFinally, ",[19,1657,1404],{}," adapts a collector's output and ",[19,1660,1586],{}," lets you write\none from scratch — but the real skill is seeing collectors as ",[31,1663,1664],{},"composable blocks"," and\nreaching for the factory before the hand-rolled solution.",[1667,1668,1669],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":114,"searchDepth":128,"depth":128,"links":1671},[1672,1673,1674,1675,1676,1677,1678,1679,1680,1681,1682],{"id":12,"depth":128,"text":13},{"id":69,"depth":128,"text":70},{"id":211,"depth":128,"text":212},{"id":300,"depth":128,"text":301},{"id":461,"depth":128,"text":462},{"id":611,"depth":128,"text":612},{"id":772,"depth":128,"text":773},{"id":960,"depth":128,"text":961},{"id":1161,"depth":128,"text":1162},{"id":1327,"depth":128,"text":1328},{"id":1590,"depth":128,"text":1591},"A deep guide to Java Collectors — the collect() mutable reduction, the four-part Collector anatomy, toMap merge functions, groupingBy with downstream and nested collectors, partitioningBy, joining, the statistics collectors, collectingAndThen, and writing a custom Collector.of.","hard","md","Java",{},"\u002Fblog\u002Fjava-collectors-groupingby","\u002Fjava\u002Fstreams-functional\u002Fcollectors-grouping",{"title":5,"description":1683},"blog\u002Fjava-collectors-groupingby","Collectors & Grouping","Streams & Functional","streams-functional","2026-06-20","o0OMeswc5krIhzBd_jK5OPe-Tcn28fDv9lmJW3dAVvg",1782244090058]