[{"data":1,"prerenderedAt":1604},["ShallowReactive",2],{"blog-\u002Fblog\u002Fjava-equals-hashcode-contract":3},{"id":4,"title":5,"body":6,"description":1590,"difficulty":1591,"extension":1592,"framework":1593,"frameworkSlug":108,"meta":1594,"navigation":194,"order":191,"path":1595,"qaPath":1596,"seo":1597,"stem":1598,"subtopic":1599,"topic":1600,"topicSlug":1601,"updated":1602,"__hash__":1603},"blog\u002Fblog\u002Fjava-equals-hashcode-contract.md","Java equals & hashCode Contract — Identity, Equality, and Hash Collections",{"type":7,"value":8,"toc":1577},"minimark",[9,14,64,68,103,231,254,258,264,334,450,466,470,482,514,556,575,579,611,799,812,816,837,926,975,979,991,1116,1140,1144,1163,1261,1277,1281,1309,1444,1457,1461,1506,1510,1573],[10,11,13],"h2",{"id":12},"why-equals-and-hashcode-matter","Why equals and hashCode matter",[15,16,17,18,22,23,22,26,29,30,34,35,38,39,42,43,47,48,51,52,55,56,59,60,63],"p",{},"Almost every Java value class — ",[19,20,21],"code",{},"String",", ",[19,24,25],{},"Integer",[19,27,28],{},"LocalDate",", your own domain\nobjects — has to answer one deceptively hard question: ",[31,32,33],"em",{},"when are two of these \"the same\"?","\nGet the answer right and your objects drop cleanly into a ",[19,36,37],{},"HashMap"," or ",[19,40,41],{},"HashSet",". Get it\nwrong and your collections ",[44,45,46],"strong",{},"silently lose data"," — no exception, no warning, just a\n",[19,49,50],{},"contains"," that returns ",[19,53,54],{},"false"," for an object you swear you put in. This guide walks\nthrough the ",[19,57,58],{},"equals","\u002F",[19,61,62],{},"hashCode"," contract, the design judgment behind it, and the traps\nthat turn up in interviews.",[10,65,67],{"id":66},"vs-equals-identity-vs-equality","== vs equals: identity vs equality",[15,69,70,71,76,77,80,81,84,85,76,89,92,93,96,97,99,100,102],{},"The first distinction is the one most beginners blur. ",[44,72,73],{},[19,74,75],{},"=="," compares ",[44,78,79],{},"identity"," — for\nobjects it asks \"are these the ",[31,82,83],{},"same object"," in memory?\" ",[44,86,87],{},[19,88,58],{},[44,90,91],{},"logical\nvalue",", as the class defines it. The ",[19,94,95],{},"Object"," default ",[19,98,58],{}," is just ",[19,101,75],{},", so it means\nnothing useful until you override it.",[104,105,110],"pre",{"className":106,"code":107,"language":108,"meta":109,"style":109},"language-java shiki shiki-themes github-light github-dark","String a = new String(\"x\");\nString b = new String(\"x\");\na == b;          \u002F\u002F false — two distinct objects on the heap (identity)\na.equals(b);     \u002F\u002F true  — same characters (value equality)\n\nString c = \"x\", d = \"x\";\nc == d;          \u002F\u002F true  — literals are interned, so == happens to work\n","java","",[19,111,112,142,160,175,189,196,217],{"__ignoreMap":109},[113,114,117,121,125,128,132,135,139],"span",{"class":115,"line":116},"line",1,[113,118,120],{"class":119},"sVt8B","String a ",[113,122,124],{"class":123},"szBVR","=",[113,126,127],{"class":123}," new",[113,129,131],{"class":130},"sScJk"," String",[113,133,134],{"class":119},"(",[113,136,138],{"class":137},"sZZnC","\"x\"",[113,140,141],{"class":119},");\n",[113,143,145,148,150,152,154,156,158],{"class":115,"line":144},2,[113,146,147],{"class":119},"String b ",[113,149,124],{"class":123},[113,151,127],{"class":123},[113,153,131],{"class":130},[113,155,134],{"class":119},[113,157,138],{"class":137},[113,159,141],{"class":119},[113,161,163,166,168,171],{"class":115,"line":162},3,[113,164,165],{"class":119},"a ",[113,167,75],{"class":123},[113,169,170],{"class":119}," b;          ",[113,172,174],{"class":173},"sJ8bj","\u002F\u002F false — two distinct objects on the heap (identity)\n",[113,176,178,181,183,186],{"class":115,"line":177},4,[113,179,180],{"class":119},"a.",[113,182,58],{"class":130},[113,184,185],{"class":119},"(b);     ",[113,187,188],{"class":173},"\u002F\u002F true  — same characters (value equality)\n",[113,190,192],{"class":115,"line":191},5,[113,193,195],{"emptyLinePlaceholder":194},true,"\n",[113,197,199,202,204,207,210,212,214],{"class":115,"line":198},6,[113,200,201],{"class":119},"String c ",[113,203,124],{"class":123},[113,205,206],{"class":137}," \"x\"",[113,208,209],{"class":119},", d ",[113,211,124],{"class":123},[113,213,206],{"class":137},[113,215,216],{"class":119},";\n",[113,218,220,223,225,228],{"class":115,"line":219},7,[113,221,222],{"class":119},"c ",[113,224,75],{"class":123},[113,226,227],{"class":119}," d;          ",[113,229,230],{"class":173},"\u002F\u002F true  — literals are interned, so == happens to work\n",[15,232,233,234,236,237,239,240,243,244,247,248,250,251,253],{},"Using ",[19,235,75],{}," on objects when you meant value equality is the classic ",[19,238,21],{},"-comparison\nbug: it \"works\" on interned literals, then fails the moment text arrives from input,\nconcatenation, or ",[19,241,242],{},"new",". ",[44,245,246],{},"Rule of thumb:"," ",[19,249,75],{}," for primitives and identity, ",[19,252,58],{},"\nfor value.",[10,255,257],{"id":256},"the-equals-contract-five-properties","The equals contract: five properties",[15,259,260,261,263],{},"A correct ",[19,262,58],{}," is not just \"compare some fields.\" It must satisfy five properties, and\nsorted\u002Fhashed collections rely on every one of them:",[265,266,267,282,300,316,322],"ul",{},[268,269,270,273,274,277,278,281],"li",{},[44,271,272],{},"Reflexive"," — ",[19,275,276],{},"x.equals(x)"," is always ",[19,279,280],{},"true",".",[268,283,284,273,287,290,291,247,293,247,296,299],{},[44,285,286],{},"Symmetric",[19,288,289],{},"x.equals(y)"," is ",[19,292,280],{},[31,294,295],{},"if and only if",[19,297,298],{},"y.equals(x)"," is.",[268,301,302,305,306,308,309,312,313,281],{},[44,303,304],{},"Transitive"," — if ",[19,307,289],{}," and ",[19,310,311],{},"y.equals(z)",", then ",[19,314,315],{},"x.equals(z)",[268,317,318,321],{},[44,319,320],{},"Consistent"," — repeated calls return the same result (no random or time-based inputs).",[268,323,324,273,327,330,331,333],{},[44,325,326],{},"Non-null",[19,328,329],{},"x.equals(null)"," returns ",[19,332,54],{}," and never throws.",[104,335,337],{"className":106,"code":336,"language":108,"meta":109,"style":109},"@Override public boolean equals(Object o) {\n  if (this == o) return true;             \u002F\u002F reflexive fast-path\n  if (!(o instanceof Point p)) return false; \u002F\u002F handles null AND wrong type, non-throwing\n  return x == p.x && y == p.y;            \u002F\u002F symmetric & transitive by construction\n}\n",[19,338,339,359,389,418,445],{"__ignoreMap":109},[113,340,341,344,347,350,353,356],{"class":115,"line":116},[113,342,343],{"class":119},"@",[113,345,346],{"class":123},"Override",[113,348,349],{"class":123}," public",[113,351,352],{"class":123}," boolean",[113,354,355],{"class":130}," equals",[113,357,358],{"class":119},"(Object o) {\n",[113,360,361,364,367,371,374,377,380,383,386],{"class":115,"line":144},[113,362,363],{"class":123},"  if",[113,365,366],{"class":119}," (",[113,368,370],{"class":369},"sj4cs","this",[113,372,373],{"class":123}," ==",[113,375,376],{"class":119}," o) ",[113,378,379],{"class":123},"return",[113,381,382],{"class":369}," true",[113,384,385],{"class":119},";             ",[113,387,388],{"class":173},"\u002F\u002F reflexive fast-path\n",[113,390,391,393,395,398,401,404,407,409,412,415],{"class":115,"line":162},[113,392,363],{"class":123},[113,394,366],{"class":119},[113,396,397],{"class":123},"!",[113,399,400],{"class":119},"(o ",[113,402,403],{"class":123},"instanceof",[113,405,406],{"class":119}," Point p)) ",[113,408,379],{"class":123},[113,410,411],{"class":369}," false",[113,413,414],{"class":119},"; ",[113,416,417],{"class":173},"\u002F\u002F handles null AND wrong type, non-throwing\n",[113,419,420,423,426,428,431,434,437,439,442],{"class":115,"line":177},[113,421,422],{"class":123},"  return",[113,424,425],{"class":119}," x ",[113,427,75],{"class":123},[113,429,430],{"class":119}," p.x ",[113,432,433],{"class":123},"&&",[113,435,436],{"class":119}," y ",[113,438,75],{"class":123},[113,440,441],{"class":119}," p.y;            ",[113,443,444],{"class":173},"\u002F\u002F symmetric & transitive by construction\n",[113,446,447],{"class":115,"line":191},[113,448,449],{"class":119},"}\n",[15,451,452,453,455,456,290,459,461,462,465],{},"The ",[19,454,403],{}," check does double duty: ",[19,457,458],{},"null instanceof Point",[19,460,54],{},", so it\nsatisfies the non-null rule, and a wrong type is rejected up front. Compare the ",[31,463,464],{},"same\nfields in the same way"," on both sides and symmetry and transitivity fall out naturally.",[10,467,469],{"id":468},"the-hashcode-contract","The hashCode contract",[15,471,472,474,475,478,479,481],{},[19,473,62],{}," returns an ",[19,476,477],{},"int"," used to bucket objects in hash-based collections. The contract\nbinds it tightly to ",[19,480,58],{},":",[483,484,485,497,504],"ol",{},[268,486,487,305,490,493,494,281],{},[44,488,489],{},"Equal objects must have equal hash codes",[19,491,492],{},"a.equals(b)",", then\n",[19,495,496],{},"a.hashCode() == b.hashCode()",[268,498,499,500,503],{},"The result must be ",[44,501,502],{},"consistent"," across calls (given unchanged state).",[268,505,506,513],{},[44,507,508,509,512],{},"Unequal objects ",[31,510,511],{},"may"," share a hash code"," — collisions are legal, just undesirable.",[104,515,517],{"className":106,"code":516,"language":108,"meta":109,"style":109},"@Override public int hashCode() {\n  return Objects.hash(x, y);   \u002F\u002F same fields as equals -> consistent\n}\n",[19,518,519,536,552],{"__ignoreMap":109},[113,520,521,523,525,527,530,533],{"class":115,"line":116},[113,522,343],{"class":119},[113,524,346],{"class":123},[113,526,349],{"class":123},[113,528,529],{"class":123}," int",[113,531,532],{"class":130}," hashCode",[113,534,535],{"class":119},"() {\n",[113,537,538,540,543,546,549],{"class":115,"line":144},[113,539,422],{"class":123},[113,541,542],{"class":119}," Objects.",[113,544,545],{"class":130},"hash",[113,547,548],{"class":119},"(x, y);   ",[113,550,551],{"class":173},"\u002F\u002F same fields as equals -> consistent\n",[113,553,554],{"class":115,"line":162},[113,555,449],{"class":119},[15,557,558,559,562,563,566,567,569,570,572,573,281],{},"Note the asymmetry: equal objects ",[31,560,561],{},"must"," collide on hash, but unequal objects are ",[31,564,565],{},"allowed","\nto. That is why ",[19,568,62],{}," is a fast pre-filter, not a substitute for ",[19,571,58],{}," — the\ncollection narrows to a bucket by hash, then confirms with ",[19,574,58],{},[10,576,578],{"id":577},"what-breaks-in-hashmap-and-hashset","What breaks in HashMap and HashSet",[15,580,581,582,59,584,586,587,247,589,592,593,595,596,598,599,601,602,605,606,608,609,281],{},"Here is the failure that the contract exists to prevent. A ",[19,583,37],{},[19,585,41],{}," finds a\nbucket by ",[19,588,62],{},[44,590,591],{},"first",", then walks that bucket calling ",[19,594,58],{},". Override ",[19,597,58],{},"\nbut forget ",[19,600,62],{},", and two \"equal\" objects compute ",[31,603,604],{},"different"," identity hashes, land in\n",[31,607,604],{}," buckets, and the lookup never even reaches your ",[19,610,58],{},[104,612,614],{"className":106,"code":613,"language":108,"meta":109,"style":109},"class Key {\n  final int id;\n  Key(int id) { this.id = id; }\n  @Override public boolean equals(Object o) {\n    return o instanceof Key k && k.id == id;   \u002F\u002F value equality...\n  }\n  \u002F\u002F ...but NO hashCode override -> inherits Object's identity hash!\n}\n\nvar set = new HashSet\u003CKey>();\nset.add(new Key(1));\nset.contains(new Key(1));   \u002F\u002F false! equal by equals, different hashCode -> wrong bucket\n",[19,615,616,627,637,663,685,711,716,721,726,731,753,776],{"__ignoreMap":109},[113,617,618,621,624],{"class":115,"line":116},[113,619,620],{"class":123},"class",[113,622,623],{"class":130}," Key",[113,625,626],{"class":119}," {\n",[113,628,629,632,634],{"class":115,"line":144},[113,630,631],{"class":123},"  final",[113,633,529],{"class":123},[113,635,636],{"class":119}," id;\n",[113,638,639,642,644,646,650,653,655,658,660],{"class":115,"line":162},[113,640,641],{"class":130},"  Key",[113,643,134],{"class":119},[113,645,477],{"class":123},[113,647,649],{"class":648},"s4XuR"," id",[113,651,652],{"class":119},") { ",[113,654,370],{"class":369},[113,656,657],{"class":119},".id ",[113,659,124],{"class":123},[113,661,662],{"class":119}," id; }\n",[113,664,665,668,670,672,674,676,679,682],{"class":115,"line":177},[113,666,667],{"class":119},"  @",[113,669,346],{"class":123},[113,671,349],{"class":123},[113,673,352],{"class":123},[113,675,355],{"class":130},[113,677,678],{"class":119},"(Object ",[113,680,681],{"class":648},"o",[113,683,684],{"class":119},") {\n",[113,686,687,690,693,695,698,700,703,705,708],{"class":115,"line":191},[113,688,689],{"class":123},"    return",[113,691,692],{"class":119}," o ",[113,694,403],{"class":123},[113,696,697],{"class":119}," Key k ",[113,699,433],{"class":123},[113,701,702],{"class":119}," k.id ",[113,704,75],{"class":123},[113,706,707],{"class":119}," id;   ",[113,709,710],{"class":173},"\u002F\u002F value equality...\n",[113,712,713],{"class":115,"line":198},[113,714,715],{"class":119},"  }\n",[113,717,718],{"class":115,"line":219},[113,719,720],{"class":173},"  \u002F\u002F ...but NO hashCode override -> inherits Object's identity hash!\n",[113,722,724],{"class":115,"line":723},8,[113,725,449],{"class":119},[113,727,729],{"class":115,"line":728},9,[113,730,195],{"emptyLinePlaceholder":194},[113,732,734,737,740,742,744,747,750],{"class":115,"line":733},10,[113,735,736],{"class":123},"var",[113,738,739],{"class":119}," set ",[113,741,124],{"class":123},[113,743,127],{"class":123},[113,745,746],{"class":119}," HashSet\u003C",[113,748,749],{"class":123},"Key",[113,751,752],{"class":119},">();\n",[113,754,756,759,762,764,766,768,770,773],{"class":115,"line":755},11,[113,757,758],{"class":119},"set.",[113,760,761],{"class":130},"add",[113,763,134],{"class":119},[113,765,242],{"class":123},[113,767,623],{"class":130},[113,769,134],{"class":119},[113,771,772],{"class":369},"1",[113,774,775],{"class":119},"));\n",[113,777,779,781,783,785,787,789,791,793,796],{"class":115,"line":778},12,[113,780,758],{"class":119},[113,782,50],{"class":130},[113,784,134],{"class":119},[113,786,242],{"class":123},[113,788,623],{"class":130},[113,790,134],{"class":119},[113,792,772],{"class":369},[113,794,795],{"class":119},"));   ",[113,797,798],{"class":173},"\u002F\u002F false! equal by equals, different hashCode -> wrong bucket\n",[15,800,801,802,804,308,806,808,809,281],{},"The collection isn't broken — it's doing exactly what you told it. ",[44,803,246],{},[19,805,58],{},[19,807,62],{}," are a package deal. Override neither or both, and derive both from\nthe ",[44,810,811],{},"same fields",[10,813,815],{"id":814},"instanceof-vs-getclass-the-symmetry-trap","instanceof vs getClass: the symmetry trap",[15,817,818,819,822,823,825,826,828,829,832,833,836],{},"How you check the argument's type is a real design decision, because it interacts with\ninheritance and the ",[44,820,821],{},"symmetry"," rule. ",[19,824,403],{}," lets a subclass be ",[19,827,58],{}," to its\nparent; ",[19,830,831],{},"getClass()"," demands the ",[31,834,835],{},"exact"," same class.",[104,838,840],{"className":106,"code":839,"language":108,"meta":109,"style":109},"\u002F\u002F instanceof — flexible, but if a subclass adds state, base.equals(sub) and\n\u002F\u002F sub.equals(base) can disagree -> SYMMETRY BROKEN\nif (!(o instanceof Point p)) return false;\n\n\u002F\u002F getClass — strict: a subclass instance is never equal to a base instance,\n\u002F\u002F which preserves symmetry but breaks Liskov substitution for equality\nif (o == null || getClass() != o.getClass()) return false;\n",[19,841,842,847,852,873,877,882,887],{"__ignoreMap":109},[113,843,844],{"class":115,"line":116},[113,845,846],{"class":173},"\u002F\u002F instanceof — flexible, but if a subclass adds state, base.equals(sub) and\n",[113,848,849],{"class":115,"line":144},[113,850,851],{"class":173},"\u002F\u002F sub.equals(base) can disagree -> SYMMETRY BROKEN\n",[113,853,854,857,859,861,863,865,867,869,871],{"class":115,"line":162},[113,855,856],{"class":123},"if",[113,858,366],{"class":119},[113,860,397],{"class":123},[113,862,400],{"class":119},[113,864,403],{"class":123},[113,866,406],{"class":119},[113,868,379],{"class":123},[113,870,411],{"class":369},[113,872,216],{"class":119},[113,874,875],{"class":115,"line":177},[113,876,195],{"emptyLinePlaceholder":194},[113,878,879],{"class":115,"line":191},[113,880,881],{"class":173},"\u002F\u002F getClass — strict: a subclass instance is never equal to a base instance,\n",[113,883,884],{"class":115,"line":198},[113,885,886],{"class":173},"\u002F\u002F which preserves symmetry but breaks Liskov substitution for equality\n",[113,888,889,891,894,896,899,902,905,908,911,914,917,920,922,924],{"class":115,"line":219},[113,890,856],{"class":123},[113,892,893],{"class":119}," (o ",[113,895,75],{"class":123},[113,897,898],{"class":369}," null",[113,900,901],{"class":123}," ||",[113,903,904],{"class":130}," getClass",[113,906,907],{"class":119},"() ",[113,909,910],{"class":123},"!=",[113,912,913],{"class":119}," o.",[113,915,916],{"class":130},"getClass",[113,918,919],{"class":119},"()) ",[113,921,379],{"class":123},[113,923,411],{"class":369},[113,925,216],{"class":119},[15,927,928,929,932,933,935,936,939,940,943,944,946,947,949,950,953,954,247,956,962,963,965,966,968,969,38,971,974],{},"Consider a ",[19,930,931],{},"ColorPoint extends Point"," that adds a color. With ",[19,934,403],{},", a ",[19,937,938],{},"Point"," would\nconsider a ",[19,941,942],{},"ColorPoint"," equal (ignoring color) while the ",[19,945,942],{}," would not consider\nthe ",[19,948,938],{}," equal (color differs) — asymmetric, which corrupts hash collections. ",[31,951,952],{},"Effective\nJava"," recommends ",[19,955,403],{},[44,957,958,959],{},"plus making value classes ",[19,960,961],{},"final"," (or favoring\ncomposition over inheritance), which sidesteps the whole problem. ",[44,964,246],{}," prefer\n",[19,967,403],{}," on a ",[19,970,961],{},[19,972,973],{},"record"," value type.",[10,976,978],{"id":977},"objectsequals-and-objectshash","Objects.equals and Objects.hash",[15,980,981,982,985,986,308,988,990],{},"You almost never need to hand-roll the null checks and bit-mixing. ",[19,983,984],{},"java.util.Objects","\ngives you null-safe building blocks that keep ",[19,987,58],{},[19,989,62],{}," consistent with each\nother by construction.",[104,992,994],{"className":106,"code":993,"language":108,"meta":109,"style":109},"@Override public boolean equals(Object o) {\n  if (this == o) return true;\n  if (!(o instanceof User u)) return false;\n  return age == u.age\n      && Objects.equals(name, u.name);   \u002F\u002F null-safe: no NPE if name is null\n}\n\n@Override public int hashCode() {\n  return Objects.hash(name, age);        \u002F\u002F combines fields, order-sensitive\n}\n",[19,995,996,1010,1028,1049,1061,1076,1080,1084,1098,1112],{"__ignoreMap":109},[113,997,998,1000,1002,1004,1006,1008],{"class":115,"line":116},[113,999,343],{"class":119},[113,1001,346],{"class":123},[113,1003,349],{"class":123},[113,1005,352],{"class":123},[113,1007,355],{"class":130},[113,1009,358],{"class":119},[113,1011,1012,1014,1016,1018,1020,1022,1024,1026],{"class":115,"line":144},[113,1013,363],{"class":123},[113,1015,366],{"class":119},[113,1017,370],{"class":369},[113,1019,373],{"class":123},[113,1021,376],{"class":119},[113,1023,379],{"class":123},[113,1025,382],{"class":369},[113,1027,216],{"class":119},[113,1029,1030,1032,1034,1036,1038,1040,1043,1045,1047],{"class":115,"line":162},[113,1031,363],{"class":123},[113,1033,366],{"class":119},[113,1035,397],{"class":123},[113,1037,400],{"class":119},[113,1039,403],{"class":123},[113,1041,1042],{"class":119}," User u)) ",[113,1044,379],{"class":123},[113,1046,411],{"class":369},[113,1048,216],{"class":119},[113,1050,1051,1053,1056,1058],{"class":115,"line":177},[113,1052,422],{"class":123},[113,1054,1055],{"class":119}," age ",[113,1057,75],{"class":123},[113,1059,1060],{"class":119}," u.age\n",[113,1062,1063,1066,1068,1070,1073],{"class":115,"line":191},[113,1064,1065],{"class":123},"      &&",[113,1067,542],{"class":119},[113,1069,58],{"class":130},[113,1071,1072],{"class":119},"(name, u.name);   ",[113,1074,1075],{"class":173},"\u002F\u002F null-safe: no NPE if name is null\n",[113,1077,1078],{"class":115,"line":198},[113,1079,449],{"class":119},[113,1081,1082],{"class":115,"line":219},[113,1083,195],{"emptyLinePlaceholder":194},[113,1085,1086,1088,1090,1092,1094,1096],{"class":115,"line":723},[113,1087,343],{"class":119},[113,1089,346],{"class":123},[113,1091,349],{"class":123},[113,1093,529],{"class":123},[113,1095,532],{"class":130},[113,1097,535],{"class":119},[113,1099,1100,1102,1104,1106,1109],{"class":115,"line":728},[113,1101,422],{"class":123},[113,1103,542],{"class":119},[113,1105,545],{"class":130},[113,1107,1108],{"class":119},"(name, age);        ",[113,1110,1111],{"class":173},"\u002F\u002F combines fields, order-sensitive\n",[113,1113,1114],{"class":115,"line":733},[113,1115,449],{"class":119},[15,1117,1118,1121,1122,1125,1126,1129,1130,1132,1133,59,1136,1139],{},[19,1119,1120],{},"Objects.equals(a, b)"," treats two nulls as equal and never throws; ",[19,1123,1124],{},"Objects.hash(...)","\napplies the proven ",[19,1127,1128],{},"31 * result + field"," mixing across your fields. ",[44,1131,246],{}," reach\nfor ",[19,1134,1135],{},"Objects.equals",[19,1137,1138],{},"Objects.hash"," over manual boilerplate — concise, null-safe, and\nmutually consistent.",[10,1141,1143],{"id":1142},"base-both-on-the-same-immutable-fields","Base both on the same immutable fields",[15,1145,1146,1147,1154,1155,1158,1159,1162],{},"The single most important implementation rule: ",[44,1148,1149,308,1151,1153],{},[19,1150,58],{},[19,1152,62],{}," must read the\nexact same fields",", and those fields should ideally be ",[44,1156,1157],{},"immutable",". If they diverge, the\ncontract breaks. If they're mutable, the object's hash can change ",[31,1160,1161],{},"while it sits in a map"," —\nstranding it in the wrong bucket.",[104,1164,1166],{"className":106,"code":1165,"language":108,"meta":109,"style":109},"var map = new HashMap\u003CList\u003CInteger>, String>();\nvar key = new ArrayList\u003C>(List.of(1));\nmap.put(key, \"v\");\nkey.add(2);          \u002F\u002F mutated a field equals\u002FhashCode depend on -> hash changed\nmap.get(key);        \u002F\u002F null! the entry is stranded in its original bucket\n",[19,1167,1168,1191,1214,1230,1248],{"__ignoreMap":109},[113,1169,1170,1172,1175,1177,1179,1182,1184,1187,1189],{"class":115,"line":116},[113,1171,736],{"class":123},[113,1173,1174],{"class":119}," map ",[113,1176,124],{"class":123},[113,1178,127],{"class":123},[113,1180,1181],{"class":119}," HashMap\u003CList\u003C",[113,1183,25],{"class":123},[113,1185,1186],{"class":119},">, ",[113,1188,21],{"class":123},[113,1190,752],{"class":119},[113,1192,1193,1195,1198,1200,1202,1205,1208,1210,1212],{"class":115,"line":144},[113,1194,736],{"class":123},[113,1196,1197],{"class":119}," key ",[113,1199,124],{"class":123},[113,1201,127],{"class":123},[113,1203,1204],{"class":119}," ArrayList\u003C>(List.",[113,1206,1207],{"class":130},"of",[113,1209,134],{"class":119},[113,1211,772],{"class":369},[113,1213,775],{"class":119},[113,1215,1216,1219,1222,1225,1228],{"class":115,"line":162},[113,1217,1218],{"class":119},"map.",[113,1220,1221],{"class":130},"put",[113,1223,1224],{"class":119},"(key, ",[113,1226,1227],{"class":137},"\"v\"",[113,1229,141],{"class":119},[113,1231,1232,1235,1237,1239,1242,1245],{"class":115,"line":177},[113,1233,1234],{"class":119},"key.",[113,1236,761],{"class":130},[113,1238,134],{"class":119},[113,1240,1241],{"class":369},"2",[113,1243,1244],{"class":119},");          ",[113,1246,1247],{"class":173},"\u002F\u002F mutated a field equals\u002FhashCode depend on -> hash changed\n",[113,1249,1250,1252,1255,1258],{"class":115,"line":191},[113,1251,1218],{"class":119},[113,1253,1254],{"class":130},"get",[113,1256,1257],{"class":119},"(key);        ",[113,1259,1260],{"class":173},"\u002F\u002F null! the entry is stranded in its original bucket\n",[15,1262,1263,1264,22,1266,1268,1269,1271,1272,59,1274,1276],{},"This is why ",[19,1265,21],{},[19,1267,25],{},", records, and enums make ideal keys — they're immutable, so\ntheir hash is fixed for life. ",[44,1270,246],{}," use immutable keys; if a key must be\nmutable, never touch the ",[19,1273,58],{},[19,1275,62],{}," fields while it's in a collection.",[10,1278,1280],{"id":1279},"records-equals-and-hashcode-for-free","Records: equals and hashCode for free",[15,1282,1283,1284,59,1286,1288,1289,1292,1293,1297,1298,22,1300,1302,1303,1306,1307,281],{},"Since Java 16, the safest way to get a correct ",[19,1285,58],{},[19,1287,62],{}," is to ",[31,1290,1291],{},"not write them at\nall",". A ",[44,1294,1295],{},[19,1296,973],{}," auto-generates a canonical constructor, accessors, and value-based\n",[19,1299,58],{},[19,1301,62],{},", and ",[19,1304,1305],{},"toString"," from its components — no boilerplate, and no way to\nforget ",[19,1308,62],{},[104,1310,1312],{"className":106,"code":1311,"language":108,"meta":109,"style":109},"record Point(int x, int y) { }\n\nnew Point(1, 2).equals(new Point(1, 2));                      \u002F\u002F true  — generated equals\nnew Point(1, 2).hashCode() == new Point(1, 2).hashCode();     \u002F\u002F true  — consistent\nnew Point(1, 2).toString();                                   \u002F\u002F \"Point[x=1, y=2]\"\n",[19,1313,1314,1333,1337,1376,1420],{"__ignoreMap":109},[113,1315,1316,1318,1321,1323,1325,1328,1330],{"class":115,"line":116},[113,1317,973],{"class":123},[113,1319,1320],{"class":130}," Point",[113,1322,134],{"class":119},[113,1324,477],{"class":123},[113,1326,1327],{"class":119}," x, ",[113,1329,477],{"class":123},[113,1331,1332],{"class":119}," y) { }\n",[113,1334,1335],{"class":115,"line":144},[113,1336,195],{"emptyLinePlaceholder":194},[113,1338,1339,1341,1343,1345,1347,1349,1351,1354,1356,1358,1360,1362,1364,1366,1368,1370,1373],{"class":115,"line":162},[113,1340,242],{"class":123},[113,1342,1320],{"class":130},[113,1344,134],{"class":119},[113,1346,772],{"class":369},[113,1348,22],{"class":119},[113,1350,1241],{"class":369},[113,1352,1353],{"class":119},").",[113,1355,58],{"class":130},[113,1357,134],{"class":119},[113,1359,242],{"class":123},[113,1361,1320],{"class":130},[113,1363,134],{"class":119},[113,1365,772],{"class":369},[113,1367,22],{"class":119},[113,1369,1241],{"class":369},[113,1371,1372],{"class":119},"));                      ",[113,1374,1375],{"class":173},"\u002F\u002F true  — generated equals\n",[113,1377,1378,1380,1382,1384,1386,1388,1390,1392,1394,1396,1398,1400,1402,1404,1406,1408,1410,1412,1414,1417],{"class":115,"line":177},[113,1379,242],{"class":123},[113,1381,1320],{"class":130},[113,1383,134],{"class":119},[113,1385,772],{"class":369},[113,1387,22],{"class":119},[113,1389,1241],{"class":369},[113,1391,1353],{"class":119},[113,1393,62],{"class":130},[113,1395,907],{"class":119},[113,1397,75],{"class":123},[113,1399,127],{"class":123},[113,1401,1320],{"class":130},[113,1403,134],{"class":119},[113,1405,772],{"class":369},[113,1407,22],{"class":119},[113,1409,1241],{"class":369},[113,1411,1353],{"class":119},[113,1413,62],{"class":130},[113,1415,1416],{"class":119},"();     ",[113,1418,1419],{"class":173},"\u002F\u002F true  — consistent\n",[113,1421,1422,1424,1426,1428,1430,1432,1434,1436,1438,1441],{"class":115,"line":191},[113,1423,242],{"class":123},[113,1425,1320],{"class":130},[113,1427,134],{"class":119},[113,1429,772],{"class":369},[113,1431,22],{"class":119},[113,1433,1241],{"class":369},[113,1435,1353],{"class":119},[113,1437,1305],{"class":130},[113,1439,1440],{"class":119},"();                                   ",[113,1442,1443],{"class":173},"\u002F\u002F \"Point[x=1, y=2]\"\n",[15,1445,1446,1447,1450,1451,1453,1454,1456],{},"Two records are equal exactly when all their components are equal. You ",[31,1448,1449],{},"can"," override the\ngenerated methods, but you almost never should. ",[44,1452,246],{}," for an immutable value\ntype, reach for a ",[19,1455,973],{}," before hand-writing equality — it gets the contract right by\ndefault.",[10,1458,1460],{"id":1459},"a-note-on-compareto-and-treemap","A note on compareTo and TreeMap",[15,1462,1463,1464,247,1467,1470,1471,1474,1475,289,1478,1481,1482,1484,1485,58,1488,1491,1492,1495,1496,58,1499,1466,1502,1505],{},"One last consistency rule worth knowing: ",[19,1465,1466],{},"compareTo",[31,1468,1469],{},"should"," agree with ",[19,1472,1473],{},"equals* (","x.compareTo(y) == 0",[19,1476,1477],{},"iff",[19,1479,1480],{},"). Sorted collections like ","TreeSet",[19,1483,59],{},"TreeMap",[19,1486,1487],{},"use **ordering, not",[19,1489,1490],{},"**, so a mismatch produces surprises — ","BigDecimal(\"1.0\")",[19,1493,1494],{},"and","BigDecimal(\"1.00\")",[19,1497,1498],{},"are unequal by",[19,1500,1501],{},"(scale differs) but",[19,1503,1504],{},"-equal, so a ","TreeSet` treats them as duplicates. Keep them consistent unless you have a documented\nreason not to.",[10,1507,1509],{"id":1508},"recap","Recap",[15,1511,452,1512,59,1514,1516,1517,1525,1526,290,1528,1531,1532,1534,1535,1543,1544,1547,1548,38,1551,59,1553,1555,1556,1558,1559,1561,1562,1564,1565,1567,1568,1572],{},[19,1513,58],{},[19,1515,62],{}," contract is where Java's object model meets its collections. Use\n",[44,1518,1519,1521,1522,1524],{},[19,1520,75],{}," for identity, ",[19,1523,58],{}," for value",". A correct ",[19,1527,58],{},[44,1529,1530],{},"reflexive, symmetric,\ntransitive, consistent, and non-null"," — properties that flow naturally from an\n",[19,1533,403],{}," guard and field-by-field comparison. ",[44,1536,1537,1538,1540,1541],{},"Override ",[19,1539,62],{}," whenever you\noverride ",[19,1542,58],{},", base both on the ",[44,1545,1546],{},"same immutable fields",", and remember that ",[44,1549,1550],{},"equal\nobjects must share a hash code",[19,1552,37],{},[19,1554,41],{}," will silently lose them. Prefer\n",[19,1557,403],{}," on ",[19,1560,961],{}," value types to dodge the symmetry trap, lean on ",[19,1563,1135],{},"\nand ",[19,1566,1138],{},", and — best of all — let a ",[44,1569,1570],{},[19,1571,973],{}," generate the whole contract for\nyou.",[1574,1575,1576],"style",{},"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 pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":109,"searchDepth":144,"depth":144,"links":1578},[1579,1580,1581,1582,1583,1584,1585,1586,1587,1588,1589],{"id":12,"depth":144,"text":13},{"id":66,"depth":144,"text":67},{"id":256,"depth":144,"text":257},{"id":468,"depth":144,"text":469},{"id":577,"depth":144,"text":578},{"id":814,"depth":144,"text":815},{"id":977,"depth":144,"text":978},{"id":1142,"depth":144,"text":1143},{"id":1279,"depth":144,"text":1280},{"id":1459,"depth":144,"text":1460},{"id":1508,"depth":144,"text":1509},"Master Java's equals\u002FhashCode contract — == vs equals, identity vs equality, the five equals properties, why equal objects must share a hash, how HashMap breaks when you get it wrong, instanceof vs getClass, Objects helpers, and records.","hard","md","Java",{},"\u002Fblog\u002Fjava-equals-hashcode-contract","\u002Fjava\u002Foop\u002Fequals-hashcode",{"title":5,"description":1590},"blog\u002Fjava-equals-hashcode-contract","equals & hashCode","Object-Oriented Programming","oop","2026-06-20","NnWnq_2H0GhBtkxic6qeE5xfpOvJvcCiapJe7WHXKYY",1782244091150]