[{"data":1,"prerenderedAt":1547},["ShallowReactive",2],{"blog-\u002Fblog\u002Fjava-comparable-vs-comparator":3},{"id":4,"title":5,"body":6,"description":1533,"difficulty":1534,"extension":1535,"framework":1536,"frameworkSlug":99,"meta":1537,"navigation":918,"order":199,"path":1538,"qaPath":1539,"seo":1540,"stem":1541,"subtopic":1542,"topic":1543,"topicSlug":1544,"updated":1545,"__hash__":1546},"blog\u002Fblog\u002Fjava-comparable-vs-comparator.md","Java Comparable vs Comparator — Sorting, Chaining & the Pitfalls",{"type":7,"value":8,"toc":1522},"minimark",[9,14,45,49,94,252,285,289,315,361,364,455,467,471,509,546,611,627,631,685,783,808,812,851,957,965,969,988,1123,1189,1193,1238,1418,1450,1454,1518],[10,11,13],"h2",{"id":12},"two-ways-to-say-smaller","Two ways to say \"smaller\"",[15,16,17,18,25,26,30,31,36,37,40,41,44],"p",{},"Almost every Java program eventually has to put things in order, and the JDK gives you\ntwo complementary hooks for it. ",[19,20,21],"strong",{},[22,23,24],"code",{},"Comparable"," lets a type declare its ",[27,28,29],"em",{},"own"," ordering;\n",[19,32,33],{},[22,34,35],{},"Comparator"," lets you describe an ordering ",[27,38,39],{},"from the outside",", as many different ways\nas you like. Knowing which to reach for — and how the sorting machinery, the ",[22,42,43],{},"compareTo","\ncontract, and sorted collections all lean on them — is the difference between code that\nsorts correctly and code that quietly returns garbage. This guide ties the whole picture\ntogether.",[10,46,48],{"id":47},"natural-ordering-with-comparable","Natural ordering with Comparable",[15,50,51,52,57,58,61,62,65,66,70,71,74,75,78,79,82,83,86,87,89,90,93],{},"A type that implements ",[19,53,54],{},[22,55,56],{},"Comparable\u003CT>"," (from ",[22,59,60],{},"java.lang",") defines its ",[19,63,64],{},"natural\nordering"," by overriding a single method, ",[19,67,68],{},[22,69,43],{},". The contract is the same one\nevery sort relies on: return a ",[19,72,73],{},"negative"," int when ",[22,76,77],{},"this"," sorts before the argument,\n",[19,80,81],{},"zero"," when they tie, and ",[19,84,85],{},"positive"," when ",[22,88,77],{}," sorts after. Only the ",[27,91,92],{},"sign","\nmatters, never the magnitude.",[95,96,101],"pre",{"className":97,"code":98,"language":99,"meta":100,"style":100},"language-java shiki shiki-themes github-light github-dark","class Person implements Comparable\u003CPerson> {\n  String name; int age;\n  @Override public int compareTo(Person other) {\n    return Integer.compare(this.age, other.age); \u002F\u002F natural order = by age\n  }\n}\n\u002F\u002F Now Person works with no comparator anywhere:\nCollections.sort(people);            \u002F\u002F uses compareTo\nTreeSet\u003CPerson> ts = new TreeSet\u003C>(); \u002F\u002F uses compareTo\n","java","",[22,102,103,132,144,172,197,203,209,215,230],{"__ignoreMap":100},[104,105,108,112,116,119,122,126,129],"span",{"class":106,"line":107},"line",1,[104,109,111],{"class":110},"szBVR","class",[104,113,115],{"class":114},"sScJk"," Person",[104,117,118],{"class":110}," implements",[104,120,121],{"class":114}," Comparable",[104,123,125],{"class":124},"sVt8B","\u003C",[104,127,128],{"class":110},"Person",[104,130,131],{"class":124},"> {\n",[104,133,135,138,141],{"class":106,"line":134},2,[104,136,137],{"class":124},"  String name; ",[104,139,140],{"class":110},"int",[104,142,143],{"class":124}," age;\n",[104,145,147,150,153,156,159,162,165,169],{"class":106,"line":146},3,[104,148,149],{"class":124},"  @",[104,151,152],{"class":110},"Override",[104,154,155],{"class":110}," public",[104,157,158],{"class":110}," int",[104,160,161],{"class":114}," compareTo",[104,163,164],{"class":124},"(Person ",[104,166,168],{"class":167},"s4XuR","other",[104,170,171],{"class":124},") {\n",[104,173,175,178,181,184,187,190,193],{"class":106,"line":174},4,[104,176,177],{"class":110},"    return",[104,179,180],{"class":124}," Integer.",[104,182,183],{"class":114},"compare",[104,185,186],{"class":124},"(",[104,188,77],{"class":189},"sj4cs",[104,191,192],{"class":124},".age, other.age); ",[104,194,196],{"class":195},"sJ8bj","\u002F\u002F natural order = by age\n",[104,198,200],{"class":106,"line":199},5,[104,201,202],{"class":124},"  }\n",[104,204,206],{"class":106,"line":205},6,[104,207,208],{"class":124},"}\n",[104,210,212],{"class":106,"line":211},7,[104,213,214],{"class":195},"\u002F\u002F Now Person works with no comparator anywhere:\n",[104,216,218,221,224,227],{"class":106,"line":217},8,[104,219,220],{"class":124},"Collections.",[104,222,223],{"class":114},"sort",[104,225,226],{"class":124},"(people);            ",[104,228,229],{"class":195},"\u002F\u002F uses compareTo\n",[104,231,233,236,238,241,244,247,250],{"class":106,"line":232},9,[104,234,235],{"class":124},"TreeSet\u003C",[104,237,128],{"class":110},[104,239,240],{"class":124},"> ts ",[104,242,243],{"class":110},"=",[104,245,246],{"class":110}," new",[104,248,249],{"class":124}," TreeSet\u003C>(); ",[104,251,229],{"class":195},[15,253,254,255,257,258,261,262,265,266,269,270,273,274,273,277,280,281,284],{},"Implement ",[22,256,24],{}," for the ",[27,259,260],{},"one"," obvious ordering a type genuinely ",[27,263,264],{},"is"," — numbers by\nvalue, strings alphabetically, money by amount. Once it's there, ",[22,267,268],{},"Collections.sort",",\n",[22,271,272],{},"Arrays.sort",", ",[22,275,276],{},"TreeSet",[22,278,279],{},"TreeMap",", and ",[22,282,283],{},"Stream.sorted()"," all just work without an extra\nargument.",[10,286,288],{"id":287},"external-ordering-with-comparator","External ordering with Comparator",[15,290,291,292,57,297,300,301,304,305,310,311,314],{},"A ",[19,293,294],{},[22,295,296],{},"Comparator\u003CT>",[22,298,299],{},"java.util",") is a ",[27,302,303],{},"separate"," object that orders two elements\nvia ",[19,306,307],{},[22,308,309],{},"compare(a, b)",", using the identical sign convention. Because it lives outside the\nclass, you can define unlimited orderings, and you can sort types you can't edit (",[22,312,313],{},"String",",\nlibrary classes, your colleague's untouchable model).",[95,316,318],{"className":97,"code":317,"language":99,"meta":100,"style":100},"\u002F\u002F order Person by name without touching the Person class\nComparator\u003CPerson> byName = (a, b) -> a.name.compareTo(b.name);\npeople.sort(byName);\n",[22,319,320,325,351],{"__ignoreMap":100},[104,321,322],{"class":106,"line":107},[104,323,324],{"class":195},"\u002F\u002F order Person by name without touching the Person class\n",[104,326,327,330,332,335,337,340,343,346,348],{"class":106,"line":134},[104,328,329],{"class":124},"Comparator\u003C",[104,331,128],{"class":110},[104,333,334],{"class":124},"> byName ",[104,336,243],{"class":110},[104,338,339],{"class":124}," (a, b) ",[104,341,342],{"class":110},"->",[104,344,345],{"class":124}," a.name.",[104,347,43],{"class":114},[104,349,350],{"class":124},"(b.name);\n",[104,352,353,356,358],{"class":106,"line":146},[104,354,355],{"class":124},"people.",[104,357,223],{"class":114},[104,359,360],{"class":124},"(byName);\n",[15,362,363],{},"The two interfaces map onto each other cleanly:",[365,366,367,384],"table",{},[368,369,370],"thead",{},[371,372,373,376,380],"tr",{},[374,375],"th",{},[374,377,378],{},[22,379,56],{},[374,381,382],{},[22,383,296],{},[385,386,387,401,416,433,444],"tbody",{},[371,388,389,393,397],{},[390,391,392],"td",{},"Package",[390,394,395],{},[22,396,60],{},[390,398,399],{},[22,400,299],{},[371,402,403,406,411],{},[390,404,405],{},"Method",[390,407,408],{},[22,409,410],{},"int compareTo(T o)",[390,412,413],{},[22,414,415],{},"int compare(T a, T b)",[371,417,418,421,427],{},[390,419,420],{},"Lives",[390,422,423,426],{},[19,424,425],{},"inside"," the class",[390,428,429,430,432],{},"a ",[19,431,303],{}," object",[371,434,435,438,441],{},[390,436,437],{},"Orderings",[390,439,440],{},"one (the natural one)",[390,442,443],{},"as many as you want",[371,445,446,449,452],{},[390,447,448],{},"Edits the class?",[390,450,451],{},"yes",[390,453,454],{},"no",[15,456,457,460,461,463,464,466],{},[19,458,459],{},"Rule of thumb:"," ",[22,462,24],{}," for the single intrinsic order, ",[22,465,35],{}," for\neverything situational — especially for types you don't own.",[10,468,470],{"id":469},"the-contract-and-the-subtraction-trap","The contract — and the subtraction trap",[15,472,473,474,477,478,481,482,485,486,489,490,493,494,497,498,501,502,504,505,508],{},"The contract isn't a suggestion. A valid ordering must be ",[19,475,476],{},"antisymmetric","\n(",[22,479,480],{},"sgn(a.compareTo(b)) == -sgn(b.compareTo(a))","), ",[19,483,484],{},"transitive"," (if ",[22,487,488],{},"a > b"," and ",[22,491,492],{},"b > c","\nthen ",[22,495,496],{},"a > c","), and ",[19,499,500],{},"consistent"," (equal inputs always compare equally). Break\ntransitivity and modern ",[22,503,272],{}," will detect it and throw\n",[22,506,507],{},"IllegalArgumentException: Comparison method violates its general contract!",".",[15,510,511,512,515,516,519,520,523,524,527,528,531,532,535,536,538,539,542,543,508],{},"The most common way to break the contract by accident is the \"clever\" one-liner\n",[22,513,514],{},"return a - b;",". Integer subtraction can ",[19,517,518],{},"overflow",": when ",[22,521,522],{},"a"," is large positive and ",[22,525,526],{},"b","\nis large negative, ",[22,529,530],{},"a - b"," wraps past ",[22,533,534],{},"Integer.MAX_VALUE"," and comes back ",[19,537,73],{},", so\na value that should sort ",[27,540,541],{},"after"," sorts ",[27,544,545],{},"before",[95,547,549],{"className":97,"code":548,"language":99,"meta":100,"style":100},"int a = Integer.MAX_VALUE, b = -1;\nSystem.out.println(a - b);                 \u002F\u002F -2147483648  (wrong sign!)\nSystem.out.println(Integer.compare(a, b)); \u002F\u002F positive     (correct)\n",[22,550,551,574,594],{"__ignoreMap":100},[104,552,553,555,558,560,563,565,568,571],{"class":106,"line":107},[104,554,140],{"class":110},[104,556,557],{"class":124}," a ",[104,559,243],{"class":110},[104,561,562],{"class":124}," Integer.MAX_VALUE, b ",[104,564,243],{"class":110},[104,566,567],{"class":110}," -",[104,569,570],{"class":189},"1",[104,572,573],{"class":124},";\n",[104,575,576,579,582,585,588,591],{"class":106,"line":134},[104,577,578],{"class":124},"System.out.",[104,580,581],{"class":114},"println",[104,583,584],{"class":124},"(a ",[104,586,587],{"class":110},"-",[104,589,590],{"class":124}," b);                 ",[104,592,593],{"class":195},"\u002F\u002F -2147483648  (wrong sign!)\n",[104,595,596,598,600,603,605,608],{"class":106,"line":146},[104,597,578],{"class":124},[104,599,581],{"class":114},[104,601,602],{"class":124},"(Integer.",[104,604,183],{"class":114},[104,606,607],{"class":124},"(a, b)); ",[104,609,610],{"class":195},"\u002F\u002F positive     (correct)\n",[15,612,613,614,619,620,273,623,626],{},"The fix is ",[19,615,616],{},[22,617,618],{},"Integer.compare"," (or ",[22,621,622],{},"Long.compare",[22,624,625],{},"Double.compare","): overflow-safe,\nself-documenting, no cleverness required. Never subtract to compare.",[10,628,630],{"id":629},"building-comparators-by-composition","Building comparators by composition",[15,632,633,634,636,637,640,641,643,644,647,648,653,654,656,657,662,663,668,669,681,682,684],{},"You rarely hand-write ",[22,635,183],{}," anymore. The ",[19,638,639],{},"factory methods"," on ",[22,642,35],{}," build one\nfrom a ",[27,645,646],{},"key extractor"," and compose into multi-level orderings. ",[19,649,650],{},[22,651,652],{},"comparing"," pulls a\n",[22,655,24],{}," key out of each element; ",[19,658,659],{},[22,660,661],{},"thenComparing"," adds a tie-breaker that's only\nconsulted when the previous level returns zero; ",[19,664,665],{},[22,666,667],{},"reversed"," flips direction; and\n",[19,670,671,674,675,674,678],{},[22,672,673],{},"comparingInt","\u002F",[22,676,677],{},"comparingLong",[22,679,680],{},"comparingDouble"," take a primitive extractor to skip the\nautoboxing that plain ",[22,683,652],{}," would do on every comparison.",[95,686,688],{"className":97,"code":687,"language":99,"meta":100,"style":100},"people.sort(\n  Comparator.comparing(Person::getLastName)   \u002F\u002F primary key\n            .thenComparing(Person::getFirstName) \u002F\u002F tie-breaker\n            .thenComparingInt(Person::getAge)    \u002F\u002F primitive, no boxing\n);\npeople.sort(Comparator.comparingInt(Person::getAge).reversed()); \u002F\u002F oldest first\n",[22,689,690,699,718,735,752,757],{"__ignoreMap":100},[104,691,692,694,696],{"class":106,"line":107},[104,693,355],{"class":124},[104,695,223],{"class":114},[104,697,698],{"class":124},"(\n",[104,700,701,704,706,709,712,715],{"class":106,"line":134},[104,702,703],{"class":124},"  Comparator.",[104,705,652],{"class":114},[104,707,708],{"class":124},"(Person",[104,710,711],{"class":110},"::",[104,713,714],{"class":124},"getLastName)   ",[104,716,717],{"class":195},"\u002F\u002F primary key\n",[104,719,720,723,725,727,729,732],{"class":106,"line":146},[104,721,722],{"class":124},"            .",[104,724,661],{"class":114},[104,726,708],{"class":124},[104,728,711],{"class":110},[104,730,731],{"class":124},"getFirstName) ",[104,733,734],{"class":195},"\u002F\u002F tie-breaker\n",[104,736,737,739,742,744,746,749],{"class":106,"line":174},[104,738,722],{"class":124},[104,740,741],{"class":114},"thenComparingInt",[104,743,708],{"class":124},[104,745,711],{"class":110},[104,747,748],{"class":124},"getAge)    ",[104,750,751],{"class":195},"\u002F\u002F primitive, no boxing\n",[104,753,754],{"class":106,"line":199},[104,755,756],{"class":124},");\n",[104,758,759,761,763,766,768,770,772,775,777,780],{"class":106,"line":205},[104,760,355],{"class":124},[104,762,223],{"class":114},[104,764,765],{"class":124},"(Comparator.",[104,767,673],{"class":114},[104,769,708],{"class":124},[104,771,711],{"class":110},[104,773,774],{"class":124},"getAge).",[104,776,667],{"class":114},[104,778,779],{"class":124},"()); ",[104,781,782],{"class":195},"\u002F\u002F oldest first\n",[15,784,785,786,796,797,799,800,803,804,807],{},"Two placement gotchas worth internalizing: ",[19,787,788,791,792,795],{},[22,789,790],{},"reversed()"," flips the ",[27,793,794],{},"entire"," chain built so\nfar",", not just the last ",[22,798,661],{},", so parenthesize deliberately or reverse a single\nkey with ",[22,801,802],{},"thenComparing(Person::getAge, reverseOrder())",". And because comparators are\nstateless and thread-safe, build the ones you reuse as ",[22,805,806],{},"static final"," constants instead of\nrebuilding the chain in a hot loop.",[10,809,811],{"id":810},"sorting-null-fields-safely","Sorting null fields safely",[15,813,814,815,820,821,824,825,827,828,833,834,839,840,674,843,846,847,850],{},"A plain comparator throws ",[19,816,817],{},[22,818,819],{},"NullPointerException"," the moment it meets a ",[22,822,823],{},"null"," element\nor a ",[22,826,823],{}," key. Wrap it with ",[19,829,830],{},[22,831,832],{},"Comparator.nullsFirst(...)"," or ",[19,835,836],{},[22,837,838],{},"nullsLast(...)"," to\ndecide where nulls land, and combine it with ",[22,841,842],{},"naturalOrder()",[22,844,845],{},"reverseOrder()"," so you never\nwrite ",[22,848,849],{},"(a, b) -> a.compareTo(b)"," by hand.",[95,852,854],{"className":97,"code":853,"language":99,"meta":100,"style":100},"List\u003CString> s = Arrays.asList(\"b\", null, \"a\");\ns.sort(Comparator.nullsFirst(Comparator.naturalOrder())); \u002F\u002F [null, a, b]\n\n\u002F\u002F guard a possibly-null key inside a chain\npeople.sort(Comparator.comparing(Person::getName,\n                                 Comparator.nullsLast(Comparator.naturalOrder())));\n",[22,855,856,891,914,920,925,942],{"__ignoreMap":100},[104,857,858,861,863,866,868,871,874,876,880,882,884,886,889],{"class":106,"line":107},[104,859,860],{"class":124},"List\u003C",[104,862,313],{"class":110},[104,864,865],{"class":124},"> s ",[104,867,243],{"class":110},[104,869,870],{"class":124}," Arrays.",[104,872,873],{"class":114},"asList",[104,875,186],{"class":124},[104,877,879],{"class":878},"sZZnC","\"b\"",[104,881,273],{"class":124},[104,883,823],{"class":189},[104,885,273],{"class":124},[104,887,888],{"class":878},"\"a\"",[104,890,756],{"class":124},[104,892,893,896,898,900,903,905,908,911],{"class":106,"line":134},[104,894,895],{"class":124},"s.",[104,897,223],{"class":114},[104,899,765],{"class":124},[104,901,902],{"class":114},"nullsFirst",[104,904,765],{"class":124},[104,906,907],{"class":114},"naturalOrder",[104,909,910],{"class":124},"())); ",[104,912,913],{"class":195},"\u002F\u002F [null, a, b]\n",[104,915,916],{"class":106,"line":146},[104,917,919],{"emptyLinePlaceholder":918},true,"\n",[104,921,922],{"class":106,"line":174},[104,923,924],{"class":195},"\u002F\u002F guard a possibly-null key inside a chain\n",[104,926,927,929,931,933,935,937,939],{"class":106,"line":199},[104,928,355],{"class":124},[104,930,223],{"class":114},[104,932,765],{"class":124},[104,934,652],{"class":114},[104,936,708],{"class":124},[104,938,711],{"class":110},[104,940,941],{"class":124},"getName,\n",[104,943,944,947,950,952,954],{"class":106,"line":205},[104,945,946],{"class":124},"                                 Comparator.",[104,948,949],{"class":114},"nullsLast",[104,951,765],{"class":124},[104,953,907],{"class":114},[104,955,956],{"class":124},"())));\n",[15,958,959,961,962,964],{},[19,960,459],{}," if a field can be ",[22,963,823],{},", wrap its comparator rather than hoping the\ndata is clean.",[10,966,968],{"id":967},"one-ordering-every-sort","One ordering, every sort",[15,970,971,972,674,974,976,977,980,981,984,985,508],{},"The payoff of having ",[22,973,24],{},[22,975,35],{}," as a shared currency is that ",[27,978,979],{},"every"," sorting\nentry point in the JDK accepts the same comparators. They split into two families: those\nthat ",[19,982,983],{},"mutate in place"," and those that ",[19,986,987],{},"produce a new ordering",[95,989,991],{"className":97,"code":990,"language":99,"meta":100,"style":100},"Collections.sort(list);                                  \u002F\u002F in place, natural order\nlist.sort(Comparator.comparing(Person::getName));        \u002F\u002F in place, modern idiom\nArrays.sort(boxed, Comparator.reverseOrder());           \u002F\u002F object array, in place\nList\u003CPerson> byAge = people.stream()                     \u002F\u002F NEW list, source untouched\n    .sorted(Comparator.comparingInt(Person::getAge))\n    .toList();\nnew TreeSet\u003C>(String.CASE_INSENSITIVE_ORDER);            \u002F\u002F kept sorted on every insert\nnew PriorityQueue\u003C>(Comparator.reverseOrder());          \u002F\u002F max-heap; head only\n",[22,992,993,1005,1026,1045,1068,1087,1097,1108],{"__ignoreMap":100},[104,994,995,997,999,1002],{"class":106,"line":107},[104,996,220],{"class":124},[104,998,223],{"class":114},[104,1000,1001],{"class":124},"(list);                                  ",[104,1003,1004],{"class":195},"\u002F\u002F in place, natural order\n",[104,1006,1007,1010,1012,1014,1016,1018,1020,1023],{"class":106,"line":134},[104,1008,1009],{"class":124},"list.",[104,1011,223],{"class":114},[104,1013,765],{"class":124},[104,1015,652],{"class":114},[104,1017,708],{"class":124},[104,1019,711],{"class":110},[104,1021,1022],{"class":124},"getName));        ",[104,1024,1025],{"class":195},"\u002F\u002F in place, modern idiom\n",[104,1027,1028,1031,1033,1036,1039,1042],{"class":106,"line":146},[104,1029,1030],{"class":124},"Arrays.",[104,1032,223],{"class":114},[104,1034,1035],{"class":124},"(boxed, Comparator.",[104,1037,1038],{"class":114},"reverseOrder",[104,1040,1041],{"class":124},"());           ",[104,1043,1044],{"class":195},"\u002F\u002F object array, in place\n",[104,1046,1047,1049,1051,1054,1056,1059,1062,1065],{"class":106,"line":174},[104,1048,860],{"class":124},[104,1050,128],{"class":110},[104,1052,1053],{"class":124},"> byAge ",[104,1055,243],{"class":110},[104,1057,1058],{"class":124}," people.",[104,1060,1061],{"class":114},"stream",[104,1063,1064],{"class":124},"()                     ",[104,1066,1067],{"class":195},"\u002F\u002F NEW list, source untouched\n",[104,1069,1070,1073,1076,1078,1080,1082,1084],{"class":106,"line":199},[104,1071,1072],{"class":124},"    .",[104,1074,1075],{"class":114},"sorted",[104,1077,765],{"class":124},[104,1079,673],{"class":114},[104,1081,708],{"class":124},[104,1083,711],{"class":110},[104,1085,1086],{"class":124},"getAge))\n",[104,1088,1089,1091,1094],{"class":106,"line":205},[104,1090,1072],{"class":124},[104,1092,1093],{"class":114},"toList",[104,1095,1096],{"class":124},"();\n",[104,1098,1099,1102,1105],{"class":106,"line":211},[104,1100,1101],{"class":110},"new",[104,1103,1104],{"class":124}," TreeSet\u003C>(String.CASE_INSENSITIVE_ORDER);            ",[104,1106,1107],{"class":195},"\u002F\u002F kept sorted on every insert\n",[104,1109,1110,1112,1115,1117,1120],{"class":106,"line":217},[104,1111,1101],{"class":110},[104,1113,1114],{"class":124}," PriorityQueue\u003C>(Comparator.",[104,1116,1038],{"class":114},[104,1118,1119],{"class":124},"());          ",[104,1121,1122],{"class":195},"\u002F\u002F max-heap; head only\n",[15,1124,1125,1126,1128,1129,1132,1133,1135,1136,1139,1140,1143,1144,1147,1148,1151,1152,1155,1156,1159,1160,1163,1164,1167,1168,1171,1172,1174,1175,674,1178,1181,1182,1185,1186,1188],{},"A few distinctions matter under questioning. ",[22,1127,268],{}," simply delegates to\n",[22,1130,1131],{},"list.sort",", so prefer the method on the object you already hold. ",[22,1134,272],{}," accepts a\ncomparator only for ",[19,1137,1138],{},"object"," arrays — ",[19,1141,1142],{},"primitive"," arrays sort natural-ascending only,\nso to sort ",[22,1145,1146],{},"int[]"," descending you box to ",[22,1149,1150],{},"Integer[]"," first. ",[22,1153,1154],{},"Stream.sorted"," does ",[19,1157,1158],{},"not","\nmutate the source and is ",[19,1161,1162],{},"stateful"," (it buffers everything before emitting). And a\n",[22,1165,1166],{},"PriorityQueue"," only guarantees the ",[19,1169,1170],{},"head"," is the min\u002Fmax — iterating it does ",[27,1173,1158],{}," yield\nsorted order, so trust ",[22,1176,1177],{},"peek",[22,1179,1180],{},"poll"," and nothing else. Object sorts (TimSort) are\n",[19,1183,1184],{},"stable","; primitive ",[22,1187,272],{}," (dual-pivot quicksort) is not.",[10,1190,1192],{"id":1191},"consistency-with-equals-and-the-treeset-dedup-trap","Consistency with equals and the TreeSet dedup trap",[15,1194,1195,1196,86,1199,460,1202,86,1205,1208,1209,1212,1213,1219,1220,674,1222,1224,1225,1227,1228,1231,1232,489,1234,1237],{},"Here's the subtlety that trips up even experienced developers. An ordering is ",[19,1197,1198],{},"consistent\nwith equals",[22,1200,1201],{},"a.compareTo(b) == 0",[27,1203,1204],{},"exactly",[22,1206,1207],{},"a.equals(b)",". It's only\n",[27,1210,1211],{},"recommended",", not enforced — but ",[19,1214,1215,1216],{},"sorted collections judge equality by comparison, not by\n",[22,1217,1218],{},"equals",". A ",[22,1221,276],{},[22,1223,279],{}," decides two elements are duplicates purely because their\n",[22,1226,183],{}," returns ",[22,1229,1230],{},"0","; ",[22,1233,1218],{},[22,1235,1236],{},"hashCode"," are never consulted.",[95,1239,1241],{"className":97,"code":1240,"language":99,"meta":100,"style":100},"\u002F\u002F comparator looks only at age:\nTreeSet\u003CPerson> set = new TreeSet\u003C>(Comparator.comparingInt(Person::getAge));\nset.add(new Person(\"Ada\", 30));\nset.add(new Person(\"Bob\", 30));   \u002F\u002F NOT added — same age -> compare == 0\nSystem.out.println(set.size());   \u002F\u002F 1, even though Ada != Bob\n\n\u002F\u002F the classic JDK inconsistency:\nnew BigDecimal(\"1.0\").equals(new BigDecimal(\"1.00\"));    \u002F\u002F false\nnew BigDecimal(\"1.0\").compareTo(new BigDecimal(\"1.00\")); \u002F\u002F 0  -> \"equal\" to a TreeSet\n",[22,1242,1243,1248,1273,1300,1327,1345,1349,1354,1388],{"__ignoreMap":100},[104,1244,1245],{"class":106,"line":107},[104,1246,1247],{"class":195},"\u002F\u002F comparator looks only at age:\n",[104,1249,1250,1252,1254,1257,1259,1261,1264,1266,1268,1270],{"class":106,"line":134},[104,1251,235],{"class":124},[104,1253,128],{"class":110},[104,1255,1256],{"class":124},"> set ",[104,1258,243],{"class":110},[104,1260,246],{"class":110},[104,1262,1263],{"class":124}," TreeSet\u003C>(Comparator.",[104,1265,673],{"class":114},[104,1267,708],{"class":124},[104,1269,711],{"class":110},[104,1271,1272],{"class":124},"getAge));\n",[104,1274,1275,1278,1281,1283,1285,1287,1289,1292,1294,1297],{"class":106,"line":146},[104,1276,1277],{"class":124},"set.",[104,1279,1280],{"class":114},"add",[104,1282,186],{"class":124},[104,1284,1101],{"class":110},[104,1286,115],{"class":114},[104,1288,186],{"class":124},[104,1290,1291],{"class":878},"\"Ada\"",[104,1293,273],{"class":124},[104,1295,1296],{"class":189},"30",[104,1298,1299],{"class":124},"));\n",[104,1301,1302,1304,1306,1308,1310,1312,1314,1317,1319,1321,1324],{"class":106,"line":174},[104,1303,1277],{"class":124},[104,1305,1280],{"class":114},[104,1307,186],{"class":124},[104,1309,1101],{"class":110},[104,1311,115],{"class":114},[104,1313,186],{"class":124},[104,1315,1316],{"class":878},"\"Bob\"",[104,1318,273],{"class":124},[104,1320,1296],{"class":189},[104,1322,1323],{"class":124},"));   ",[104,1325,1326],{"class":195},"\u002F\u002F NOT added — same age -> compare == 0\n",[104,1328,1329,1331,1333,1336,1339,1342],{"class":106,"line":199},[104,1330,578],{"class":124},[104,1332,581],{"class":114},[104,1334,1335],{"class":124},"(set.",[104,1337,1338],{"class":114},"size",[104,1340,1341],{"class":124},"());   ",[104,1343,1344],{"class":195},"\u002F\u002F 1, even though Ada != Bob\n",[104,1346,1347],{"class":106,"line":205},[104,1348,919],{"emptyLinePlaceholder":918},[104,1350,1351],{"class":106,"line":211},[104,1352,1353],{"class":195},"\u002F\u002F the classic JDK inconsistency:\n",[104,1355,1356,1358,1361,1363,1366,1369,1371,1373,1375,1377,1379,1382,1385],{"class":106,"line":217},[104,1357,1101],{"class":110},[104,1359,1360],{"class":114}," BigDecimal",[104,1362,186],{"class":124},[104,1364,1365],{"class":878},"\"1.0\"",[104,1367,1368],{"class":124},").",[104,1370,1218],{"class":114},[104,1372,186],{"class":124},[104,1374,1101],{"class":110},[104,1376,1360],{"class":114},[104,1378,186],{"class":124},[104,1380,1381],{"class":878},"\"1.00\"",[104,1383,1384],{"class":124},"));    ",[104,1386,1387],{"class":195},"\u002F\u002F false\n",[104,1389,1390,1392,1394,1396,1398,1400,1402,1404,1406,1408,1410,1412,1415],{"class":106,"line":232},[104,1391,1101],{"class":110},[104,1393,1360],{"class":114},[104,1395,186],{"class":124},[104,1397,1365],{"class":878},[104,1399,1368],{"class":124},[104,1401,43],{"class":114},[104,1403,186],{"class":124},[104,1405,1101],{"class":110},[104,1407,1360],{"class":114},[104,1409,186],{"class":124},[104,1411,1381],{"class":878},[104,1413,1414],{"class":124},")); ",[104,1416,1417],{"class":195},"\u002F\u002F 0  -> \"equal\" to a TreeSet\n",[15,1419,1420,1421,1424,1425,1427,1428,1431,1432,1434,1435,674,1437,1439,1440,1443,1444,1446,1447,1449],{},"So a coarse comparator silently swallows distinct objects, and ",[22,1422,1423],{},"BigDecimal"," lands as one\nelement in a ",[22,1426,276],{}," but two in a ",[22,1429,1430],{},"HashSet",". ",[19,1433,459],{}," make any\n",[22,1436,276],{},[22,1438,279],{}," comparator ",[27,1441,1442],{},"total enough"," that only truly-equal elements compare to 0,\nand keep ",[22,1445,43],{}," consistent with ",[22,1448,1218],{}," unless you have a documented reason not to.",[10,1451,1453],{"id":1452},"recap","Recap",[15,1455,1456,1458,1459,1462,1463,573,1465,1467,1468,1471,1472,1474,1475,1478,1479,1481,1482,1496,1497,1499,1500,269,1502,273,1505,273,1507,273,1509,280,1511,1513,1514,1517],{},[22,1457,24],{}," defines a type's ",[19,1460,1461],{},"one natural ordering"," from the inside via ",[22,1464,43],{},[22,1466,35],{}," defines ",[19,1469,1470],{},"any number of external orderings"," via ",[22,1473,183],{}," — both governed by\nthe same sign-based, antisymmetric, transitive, consistent ",[19,1476,1477],{},"contract",". Never subtract to\ncompare (use ",[22,1480,618],{}," to dodge overflow); build comparators with ",[19,1483,1484,1486,1487,1489,1490,1489,1492,1489,1494],{},[22,1485,652],{}," \u002F\n",[22,1488,661],{}," \u002F ",[22,1491,667],{},[22,1493,902],{},[22,1495,673],{}," instead of hand-rolling\n",[22,1498,183],{},"; and remember that one comparator feeds every sort — ",[22,1501,268],{},[22,1503,1504],{},"List.sort",[22,1506,272],{},[22,1508,1154],{},[22,1510,276],{},[22,1512,1166],{}," alike. Above\nall, keep ",[19,1515,1516],{},"consistency with equals"," in mind: sorted collections de-duplicate by\ncomparison, so a too-loose comparator will quietly drop elements you meant to keep.",[1519,1520,1521],"style",{},"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 pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":100,"searchDepth":134,"depth":134,"links":1523},[1524,1525,1526,1527,1528,1529,1530,1531,1532],{"id":12,"depth":134,"text":13},{"id":47,"depth":134,"text":48},{"id":287,"depth":134,"text":288},{"id":469,"depth":134,"text":470},{"id":629,"depth":134,"text":630},{"id":810,"depth":134,"text":811},{"id":967,"depth":134,"text":968},{"id":1191,"depth":134,"text":1192},{"id":1452,"depth":134,"text":1453},"Java Comparable vs Comparator explained — natural vs custom ordering, the compareTo contract and the a-b overflow trap, comparator factory chaining, and how every Java sort (Collections, List, Arrays, Stream, TreeSet, heaps) picks its order.","medium","md","Java",{},"\u002Fblog\u002Fjava-comparable-vs-comparator","\u002Fjava\u002Fcollections\u002Fcomparable-comparator",{"title":5,"description":1533},"blog\u002Fjava-comparable-vs-comparator","Comparable & Comparator","Collections","collections","2026-06-20","JAg8WWPoufyMXZBYh4XoYekrnMQElAM9o0QbF1aJ1XI",1782244091113]