[{"data":1,"prerenderedAt":1433},["ShallowReactive",2],{"blog-\u002Fblog\u002Fjava-generics-type-erasure":3},{"id":4,"title":5,"body":6,"description":1419,"difficulty":1420,"extension":1421,"framework":1422,"frameworkSlug":76,"meta":1423,"navigation":161,"order":124,"path":1424,"qaPath":1425,"seo":1426,"stem":1427,"subtopic":1428,"topic":1429,"topicSlug":1430,"updated":1431,"__hash__":1432},"blog\u002Fblog\u002Fjava-generics-type-erasure.md","Java Type Erasure Explained — How Generics Vanish at Runtime",{"type":7,"value":8,"toc":1404},"minimark",[9,14,40,44,71,199,214,218,237,349,377,381,396,468,478,489,520,639,653,657,687,804,811,815,826,923,941,945,960,1010,1013,1020,1037,1117,1146,1219,1230,1234,1269,1332,1347,1351,1400],[10,11,13],"h2",{"id":12},"why-generics-disappear-at-runtime","Why generics disappear at runtime",[15,16,17,18,22,23,26,27,31,32,35,36,39],"p",{},"Almost every confusing rule in Java generics — why you can't write ",[19,20,21],"code",{},"new T()",", why\n",[19,24,25],{},"List\u003CString>.class"," won't compile, why two overloads \"clash\" — traces back to a single\ndesign decision: ",[28,29,30],"strong",{},"type erasure",". Generics in Java are a ",[28,33,34],{},"compile-time fiction",". The\ncompiler uses type arguments to check your code, then ",[28,37,38],{},"throws them away",", emitting\nbytecode that looks essentially like pre-generics Java. Understanding erasure turns a pile\nof arbitrary-seeming restrictions into one coherent story, and that's exactly what a strong\ninterview answer demonstrates.",[10,41,43],{"id":42},"what-erasure-is-and-why-java-chose-it","What erasure is and why Java chose it",[15,45,46,49,50,53,54,57,58,61,62,66,67,70],{},[28,47,48],{},"Type erasure"," removes all generic type information during compilation. At runtime there\nis no ",[19,51,52],{},"T",", and a ",[19,55,56],{},"List\u003CString>"," and a ",[19,59,60],{},"List\u003CInteger>"," are the ",[63,64,65],"em",{},"same"," class. The driving\nreason was ",[28,68,69],{},"backward compatibility",": generics arrived in Java 5, long after millions of\nlines of raw-type code existed. Erasure let new generic code and old legacy code share the\nsame JVM and bytecode format without breaking anything.",[72,73,78],"pre",{"className":74,"code":75,"language":76,"meta":77,"style":77},"language-java shiki shiki-themes github-light github-dark","List\u003CString> a = new ArrayList\u003C>();\nList\u003CInteger> b = new ArrayList\u003C>();\nSystem.out.println(a.getClass() == b.getClass()); \u002F\u002F true — both are just ArrayList\n\nList raw = a;          \u002F\u002F legacy raw type still interoperates\nraw.add(\"ok\");         \u002F\u002F unchecked, but compiles and runs\n","java","",[19,79,80,105,122,156,163,177],{"__ignoreMap":77},[81,82,85,89,93,96,99,102],"span",{"class":83,"line":84},"line",1,[81,86,88],{"class":87},"sVt8B","List\u003C",[81,90,92],{"class":91},"szBVR","String",[81,94,95],{"class":87},"> a ",[81,97,98],{"class":91},"=",[81,100,101],{"class":91}," new",[81,103,104],{"class":87}," ArrayList\u003C>();\n",[81,106,108,110,113,116,118,120],{"class":83,"line":107},2,[81,109,88],{"class":87},[81,111,112],{"class":91},"Integer",[81,114,115],{"class":87},"> b ",[81,117,98],{"class":91},[81,119,101],{"class":91},[81,121,104],{"class":87},[81,123,125,128,132,135,138,141,144,147,149,152],{"class":83,"line":124},3,[81,126,127],{"class":87},"System.out.",[81,129,131],{"class":130},"sScJk","println",[81,133,134],{"class":87},"(a.",[81,136,137],{"class":130},"getClass",[81,139,140],{"class":87},"() ",[81,142,143],{"class":91},"==",[81,145,146],{"class":87}," b.",[81,148,137],{"class":130},[81,150,151],{"class":87},"()); ",[81,153,155],{"class":154},"sJ8bj","\u002F\u002F true — both are just ArrayList\n",[81,157,159],{"class":83,"line":158},4,[81,160,162],{"emptyLinePlaceholder":161},true,"\n",[81,164,166,169,171,174],{"class":83,"line":165},5,[81,167,168],{"class":87},"List raw ",[81,170,98],{"class":91},[81,172,173],{"class":87}," a;          ",[81,175,176],{"class":154},"\u002F\u002F legacy raw type still interoperates\n",[81,178,180,183,186,189,193,196],{"class":83,"line":179},6,[81,181,182],{"class":87},"raw.",[81,184,185],{"class":130},"add",[81,187,188],{"class":87},"(",[81,190,192],{"class":191},"sZZnC","\"ok\"",[81,194,195],{"class":87},");         ",[81,197,198],{"class":154},"\u002F\u002F unchecked, but compiles and runs\n",[15,200,201,202,205,206,209,210,213],{},"The alternative — ",[28,203,204],{},"reified"," generics (as in C#\u002F.NET), where ",[19,207,208],{},"List\u003Cint>"," and\n",[19,211,212],{},"List\u003Cstring>"," are distinct runtime types — would have forced a new bytecode format and\nbroken the existing ecosystem. Java traded runtime type knowledge for compatibility.",[10,215,217],{"id":216},"how-the-compiler-erases","How the compiler erases",[15,219,220,221,224,225,228,229,232,233,236],{},"Erasure is three concrete transformations. The compiler ",[28,222,223],{},"replaces each type parameter","\nwith its leftmost bound (or ",[19,226,227],{},"Object"," if unbounded), ",[28,230,231],{},"inserts casts"," wherever erased\nvalues are read out, and ",[28,234,235],{},"generates bridge methods"," to keep polymorphism intact. Because\nthe code was already type-checked, the inserted casts are guaranteed safe.",[72,238,240],{"className":74,"code":239,"language":76,"meta":77,"style":77},"\u002F\u002F You write:\nclass Box\u003CT> { T value; T get() { return value; } }\nString s = new Box\u003CString>().get();\n\n\u002F\u002F After erasure (conceptually):\nclass Box { Object value; Object get() { return value; } }\nString s = (String) new Box().get();   \u002F\u002F compiler inserts the cast for you\n",[19,241,242,247,275,297,301,306,323],{"__ignoreMap":77},[81,243,244],{"class":83,"line":84},[81,245,246],{"class":154},"\u002F\u002F You write:\n",[81,248,249,252,255,258,260,263,266,269,272],{"class":83,"line":107},[81,250,251],{"class":91},"class",[81,253,254],{"class":130}," Box",[81,256,257],{"class":87},"\u003C",[81,259,52],{"class":91},[81,261,262],{"class":87},"> { T value; T ",[81,264,265],{"class":130},"get",[81,267,268],{"class":87},"() { ",[81,270,271],{"class":91},"return",[81,273,274],{"class":87}," value; } }\n",[81,276,277,280,282,284,287,289,292,294],{"class":83,"line":124},[81,278,279],{"class":87},"String s ",[81,281,98],{"class":91},[81,283,101],{"class":91},[81,285,286],{"class":87}," Box\u003C",[81,288,92],{"class":91},[81,290,291],{"class":87},">().",[81,293,265],{"class":130},[81,295,296],{"class":87},"();\n",[81,298,299],{"class":83,"line":158},[81,300,162],{"emptyLinePlaceholder":161},[81,302,303],{"class":83,"line":165},[81,304,305],{"class":154},"\u002F\u002F After erasure (conceptually):\n",[81,307,308,310,312,315,317,319,321],{"class":83,"line":179},[81,309,251],{"class":91},[81,311,254],{"class":130},[81,313,314],{"class":87}," { Object value; Object ",[81,316,265],{"class":130},[81,318,268],{"class":87},[81,320,271],{"class":91},[81,322,274],{"class":87},[81,324,326,328,330,333,336,338,341,343,346],{"class":83,"line":325},7,[81,327,279],{"class":87},[81,329,98],{"class":91},[81,331,332],{"class":87}," (String) ",[81,334,335],{"class":91},"new",[81,337,254],{"class":130},[81,339,340],{"class":87},"().",[81,342,265],{"class":130},[81,344,345],{"class":87},"();   ",[81,347,348],{"class":154},"\u002F\u002F compiler inserts the cast for you\n",[15,350,351,352,355,356,359,360,362,363,365,366,368,369,372,373,376],{},"A bounded ",[19,353,354],{},"\u003CT extends Number>"," erases to ",[19,357,358],{},"Number",", not ",[19,361,227],{},", which is why you can call\n",[19,364,358],{}," methods on ",[19,367,52],{},". With multiple bounds (",[19,370,371],{},"\u003CT extends Comparable\u003CT> & Serializable>",")\nonly the ",[28,374,375],{},"first"," bound drives erasure, so put the most useful type first.",[10,378,380],{"id":379},"reifiable-vs-non-reifiable-types","Reifiable vs non-reifiable types",[15,382,383,384,387,388,391,392,395],{},"A ",[28,385,386],{},"reifiable"," type is one whose information is fully available at runtime — its\nrepresentation is complete after erasure. A ",[28,389,390],{},"non-reifiable"," type loses information to\nerasure, so the JVM can't reconstruct it. This single distinction governs where generics\nare ",[63,393,394],{},"allowed"," at runtime-sensitive operations.",[72,397,399],{"className":74,"code":398,"language":76,"meta":77,"style":77},"\u002F\u002F Reifiable (survive erasure):  String, Integer, int[], List (raw), List\u003C?>\n\u002F\u002F Non-reifiable (erased):       List\u003CString>, List\u003C? extends Number>, T, List\u003CString>[]\n\nObject o = new ArrayList\u003CString>();\nboolean p = o instanceof List\u003C?>;       \u002F\u002F OK — unbounded wildcard is reifiable\n\u002F\u002F boolean q = o instanceof List\u003CString>; \u002F\u002F ERROR — non-reifiable\n",[19,400,401,406,411,415,432,460],{"__ignoreMap":77},[81,402,403],{"class":83,"line":84},[81,404,405],{"class":154},"\u002F\u002F Reifiable (survive erasure):  String, Integer, int[], List (raw), List\u003C?>\n",[81,407,408],{"class":83,"line":107},[81,409,410],{"class":154},"\u002F\u002F Non-reifiable (erased):       List\u003CString>, List\u003C? extends Number>, T, List\u003CString>[]\n",[81,412,413],{"class":83,"line":124},[81,414,162],{"emptyLinePlaceholder":161},[81,416,417,420,422,424,427,429],{"class":83,"line":158},[81,418,419],{"class":87},"Object o ",[81,421,98],{"class":91},[81,423,101],{"class":91},[81,425,426],{"class":87}," ArrayList\u003C",[81,428,92],{"class":91},[81,430,431],{"class":87},">();\n",[81,433,434,437,440,442,445,448,451,454,457],{"class":83,"line":165},[81,435,436],{"class":91},"boolean",[81,438,439],{"class":87}," p ",[81,441,98],{"class":91},[81,443,444],{"class":87}," o ",[81,446,447],{"class":91},"instanceof",[81,449,450],{"class":87}," List",[81,452,453],{"class":91},"\u003C?>",[81,455,456],{"class":87},";       ",[81,458,459],{"class":154},"\u002F\u002F OK — unbounded wildcard is reifiable\n",[81,461,462,465],{"class":83,"line":179},[81,463,464],{"class":154},"\u002F\u002F boolean q = o instanceof List\u003CString>;",[81,466,467],{"class":154}," \u002F\u002F ERROR — non-reifiable\n",[15,469,470,471,474,475,477],{},"The rule of thumb: if a type still carries a generic argument other than ",[19,472,473],{},"?",", it's\nnon-reifiable, and you can't use it with ",[19,476,447],{},", array creation, or a class literal.",[10,479,481,482,484,485],{"id":480},"why-new-t-new-t-tclass-and-instanceof-list-are-banned","Why new T(), new T",[81,483],{},", T.class and instanceof List",[486,487,488],"string",{}," are banned",[15,490,491,492,499,500,503,504,506,507,509,510,512,513,515,516,519],{},"Each of these needs runtime type information that erasure removed. There is only ",[28,493,494,495,498],{},"one\n",[19,496,497],{},"Class"," object"," for ",[19,501,502],{},"List",", shared by all parameterizations, so ",[19,505,25],{}," would\npoint at nothing distinct. ",[19,508,21],{}," has no concrete class for the ",[19,511,335],{}," bytecode to\ninstantiate, and the compiler can't guarantee ",[19,514,52],{}," even has a no-arg constructor.\n",[19,517,518],{},"instanceof List\u003CString>"," is a runtime check against a type argument that no longer exists.",[72,521,523],{"className":74,"code":522,"language":76,"meta":77,"style":77},"class Factory\u003CT> {\n  \u002F\u002F T make() { return new T(); }              \u002F\u002F ERROR — cannot instantiate T\n  Class\u003C?> c = List.class;                      \u002F\u002F OK — the one List class object\n  \u002F\u002F Class\u003C?> d = List\u003CString>.class;           \u002F\u002F ERROR — no parameterized literal\n\n  T make(Class\u003CT> type) throws Exception {       \u002F\u002F workaround: a type token\n    return type.getDeclaredConstructor().newInstance();\n  }\n}\n",[19,524,525,539,547,565,573,577,609,627,633],{"__ignoreMap":77},[81,526,527,529,532,534,536],{"class":83,"line":84},[81,528,251],{"class":91},[81,530,531],{"class":130}," Factory",[81,533,257],{"class":87},[81,535,52],{"class":91},[81,537,538],{"class":87},"> {\n",[81,540,541,544],{"class":83,"line":107},[81,542,543],{"class":154},"  \u002F\u002F T make() { return new T(); }",[81,545,546],{"class":154},"              \u002F\u002F ERROR — cannot instantiate T\n",[81,548,549,552,554,557,559,562],{"class":83,"line":124},[81,550,551],{"class":87},"  Class\u003C",[81,553,473],{"class":91},[81,555,556],{"class":87},"> c ",[81,558,98],{"class":91},[81,560,561],{"class":87}," List.class;                      ",[81,563,564],{"class":154},"\u002F\u002F OK — the one List class object\n",[81,566,567,570],{"class":83,"line":158},[81,568,569],{"class":154},"  \u002F\u002F Class\u003C?> d = List\u003CString>.class;",[81,571,572],{"class":154},"           \u002F\u002F ERROR — no parameterized literal\n",[81,574,575],{"class":83,"line":165},[81,576,162],{"emptyLinePlaceholder":161},[81,578,579,582,585,588,590,593,597,600,603,606],{"class":83,"line":179},[81,580,581],{"class":87},"  T ",[81,583,584],{"class":130},"make",[81,586,587],{"class":87},"(Class\u003C",[81,589,52],{"class":91},[81,591,592],{"class":87},"> ",[81,594,596],{"class":595},"s4XuR","type",[81,598,599],{"class":87},") ",[81,601,602],{"class":91},"throws",[81,604,605],{"class":87}," Exception {       ",[81,607,608],{"class":154},"\u002F\u002F workaround: a type token\n",[81,610,611,614,617,620,622,625],{"class":83,"line":325},[81,612,613],{"class":91},"    return",[81,615,616],{"class":87}," type.",[81,618,619],{"class":130},"getDeclaredConstructor",[81,621,340],{"class":87},[81,623,624],{"class":130},"newInstance",[81,626,296],{"class":87},[81,628,630],{"class":83,"line":629},8,[81,631,632],{"class":87},"  }\n",[81,634,636],{"class":83,"line":635},9,[81,637,638],{"class":87},"}\n",[15,640,641,642,644,645,648,649,652],{},"The escape hatch is always the same shape: hand the code something ",[28,643,386],{}," (a\n",[19,646,647],{},"Class\u003CT>"," token or a ",[19,650,651],{},"Supplier\u003CT>",") so the missing type comes from somewhere.",[10,654,656],{"id":655},"generic-arrays-and-heap-pollution","Generic arrays and heap pollution",[15,658,659,660,663,664,666,667,670,671,674,675,678,679,682,683,686],{},"Arrays and generics have ",[28,661,662],{},"opposite"," runtime models. Arrays are ",[28,665,204],{}," — they\nremember their element type and throw ",[19,668,669],{},"ArrayStoreException"," if you store the wrong thing.\nGenerics are ",[28,672,673],{},"erased",". A generic array couldn't perform its store-check because its\nelement type was wiped, so ",[19,676,677],{},"new T[]"," and ",[19,680,681],{},"new List\u003CString>[]"," are forbidden. Allowing them\nwould make ",[28,684,685],{},"heap pollution"," — a parameterized variable pointing at an object of the wrong\ntype — trivially easy and silent.",[72,688,690],{"className":74,"code":689,"language":76,"meta":77,"style":77},"List\u003CString>[] arr = (List\u003CString>[]) new List[1]; \u002F\u002F unchecked cast — the only way in\nObject[] objs = arr;                  \u002F\u002F arrays are covariant\nobjs[0] = List.of(42);                \u002F\u002F stores a List\u003CInteger> — no runtime error!\nString s = arr[0].get(0);             \u002F\u002F ClassCastException far from the real cause\n",[19,691,692,733,748,778],{"__ignoreMap":77},[81,693,694,696,698,701,703,706,708,710,713,716,718,720,723,727,730],{"class":83,"line":84},[81,695,88],{"class":87},[81,697,92],{"class":91},[81,699,700],{"class":87},">[] arr ",[81,702,98],{"class":91},[81,704,705],{"class":87}," (List",[81,707,257],{"class":91},[81,709,92],{"class":87},[81,711,712],{"class":91},">",[81,714,715],{"class":87},"[]) ",[81,717,335],{"class":91},[81,719,450],{"class":91},[81,721,722],{"class":87},"[",[81,724,726],{"class":725},"sj4cs","1",[81,728,729],{"class":87},"]; ",[81,731,732],{"class":154},"\u002F\u002F unchecked cast — the only way in\n",[81,734,735,737,740,742,745],{"class":83,"line":107},[81,736,227],{"class":91},[81,738,739],{"class":87},"[] objs ",[81,741,98],{"class":91},[81,743,744],{"class":87}," arr;                  ",[81,746,747],{"class":154},"\u002F\u002F arrays are covariant\n",[81,749,750,753,756,759,761,764,767,769,772,775],{"class":83,"line":124},[81,751,752],{"class":87},"objs[",[81,754,755],{"class":725},"0",[81,757,758],{"class":87},"] ",[81,760,98],{"class":91},[81,762,763],{"class":87}," List.",[81,765,766],{"class":130},"of",[81,768,188],{"class":87},[81,770,771],{"class":725},"42",[81,773,774],{"class":87},");                ",[81,776,777],{"class":154},"\u002F\u002F stores a List\u003CInteger> — no runtime error!\n",[81,779,780,782,784,787,789,792,794,796,798,801],{"class":83,"line":158},[81,781,279],{"class":87},[81,783,98],{"class":91},[81,785,786],{"class":87}," arr[",[81,788,755],{"class":725},[81,790,791],{"class":87},"].",[81,793,265],{"class":130},[81,795,188],{"class":87},[81,797,755],{"class":725},[81,799,800],{"class":87},");             ",[81,802,803],{"class":154},"\u002F\u002F ClassCastException far from the real cause\n",[15,805,806,807,810],{},"The corruption stays hidden until a later read trips a compiler-inserted cast. Prefer a\n",[19,808,809],{},"List\u003CList\u003CString>>"," over an array of a non-reifiable type.",[10,812,814],{"id":813},"bridge-methods","Bridge methods",[15,816,817,818,821,822,825],{},"When a generic supertype's method erases to a ",[63,819,820],{},"different"," signature than the subtype's\noverride, the compiler emits a synthetic ",[28,823,824],{},"bridge method"," so dynamic dispatch still lands\non your real override. Without it, calling through the supertype reference would invoke the\ninherited erased method and silently skip your code.",[72,827,829],{"className":74,"code":828,"language":76,"meta":77,"style":77},"class Node\u003CT> { void set(T t) { } }            \u002F\u002F erases to set(Object)\nclass StringNode extends Node\u003CString> {\n  @Override void set(String s) { }             \u002F\u002F set(String)\n  \u002F\u002F compiler-generated bridge in StringNode:\n  \u002F\u002F void set(Object o) { set((String) o); }   \u002F\u002F forwards to the real override\n}\n",[19,830,831,863,881,906,911,919],{"__ignoreMap":77},[81,832,833,835,838,840,842,845,848,851,854,857,860],{"class":83,"line":84},[81,834,251],{"class":91},[81,836,837],{"class":130}," Node",[81,839,257],{"class":87},[81,841,52],{"class":91},[81,843,844],{"class":87},"> { ",[81,846,847],{"class":91},"void",[81,849,850],{"class":130}," set",[81,852,853],{"class":87},"(T ",[81,855,856],{"class":595},"t",[81,858,859],{"class":87},") { } }            ",[81,861,862],{"class":154},"\u002F\u002F erases to set(Object)\n",[81,864,865,867,870,873,875,877,879],{"class":83,"line":107},[81,866,251],{"class":91},[81,868,869],{"class":130}," StringNode",[81,871,872],{"class":91}," extends",[81,874,837],{"class":130},[81,876,257],{"class":87},[81,878,92],{"class":91},[81,880,538],{"class":87},[81,882,883,886,889,892,894,897,900,903],{"class":83,"line":124},[81,884,885],{"class":87},"  @",[81,887,888],{"class":91},"Override",[81,890,891],{"class":91}," void",[81,893,850],{"class":130},[81,895,896],{"class":87},"(String ",[81,898,899],{"class":595},"s",[81,901,902],{"class":87},") { }             ",[81,904,905],{"class":154},"\u002F\u002F set(String)\n",[81,907,908],{"class":83,"line":158},[81,909,910],{"class":154},"  \u002F\u002F compiler-generated bridge in StringNode:\n",[81,912,913,916],{"class":83,"line":165},[81,914,915],{"class":154},"  \u002F\u002F void set(Object o) { set((String) o); }",[81,917,918],{"class":154},"   \u002F\u002F forwards to the real override\n",[81,920,921],{"class":83,"line":179},[81,922,638],{"class":87},[15,924,925,926,929,930,933,934,209,937,940],{},"Bridge methods carry the ",[19,927,928],{},"ACC_BRIDGE"," flag and report ",[19,931,932],{},"true"," from ",[19,935,936],{},"Method.isBridge()",[19,938,939],{},"Method.isSynthetic()",". They're why reflective scans sometimes report a \"duplicate\" method\ndiffering only by parameter type — filter them out when introspecting generic classes.",[10,942,944],{"id":943},"name-clashes-from-shared-erasure","Name clashes from shared erasure",[15,946,947,948,951,952,955,956,959],{},"Because type arguments vanish, two methods that differ ",[63,949,950],{},"only"," by their generic parameter\nend up with the ",[28,953,954],{},"same erased signature",", and a class can't declare two methods with\nidentical signatures. The compiler reports a ",[28,957,958],{},"\"name clash\""," even though the source looks\nunambiguous.",[72,961,963],{"className":74,"code":962,"language":76,"meta":77,"style":77},"class Printer {\n  void print(List\u003CString> s) { }    \u002F\u002F erases to print(List)\n  \u002F\u002F void print(List\u003CInteger> i) { } \u002F\u002F ERROR — same erasure: name clash\n}\n",[19,964,965,975,998,1006],{"__ignoreMap":77},[81,966,967,969,972],{"class":83,"line":84},[81,968,251],{"class":91},[81,970,971],{"class":130}," Printer",[81,973,974],{"class":87}," {\n",[81,976,977,980,983,986,988,990,992,995],{"class":83,"line":107},[81,978,979],{"class":91},"  void",[81,981,982],{"class":130}," print",[81,984,985],{"class":87},"(List\u003C",[81,987,92],{"class":91},[81,989,592],{"class":87},[81,991,899],{"class":595},[81,993,994],{"class":87},") { }    ",[81,996,997],{"class":154},"\u002F\u002F erases to print(List)\n",[81,999,1000,1003],{"class":83,"line":124},[81,1001,1002],{"class":154},"  \u002F\u002F void print(List\u003CInteger> i) { }",[81,1004,1005],{"class":154}," \u002F\u002F ERROR — same erasure: name clash\n",[81,1007,1008],{"class":83,"line":158},[81,1009,638],{"class":87},[15,1011,1012],{},"The fix is to give the methods genuinely different erased signatures — different raw types\nor arity — or simply rename one. Distinct type arguments alone are never enough.",[10,1014,1016,1017],{"id":1015},"the-class-type-token-pattern","The Class",[856,1018,1019],{}," type-token pattern",[15,1021,1022,1023,1025,1026,1029,1030,1032,1033,1036],{},"When code genuinely needs the runtime type of ",[19,1024,52],{},", the standard solution is a ",[28,1027,1028],{},"type\ntoken",": pass in a ",[19,1031,647],{}," to recover what erasure removed. This is how\n",[19,1034,1035],{},"fromJson(json, User.class)"," works, and frameworks like Jackson and Spring lean on it\neverywhere.",[72,1038,1040],{"className":74,"code":1039,"language":76,"meta":77,"style":77},"\u003CT> T fromJson(String json, Class\u003CT> type) {\n  Object parsed = parse(json);\n  return type.cast(parsed);          \u002F\u002F typed cast via the token, no unchecked warning\n}\nUser u = fromJson(s, User.class);    \u002F\u002F T inferred from the token\n",[19,1041,1042,1068,1081,1097,1101],{"__ignoreMap":77},[81,1043,1044,1046,1048,1050,1053,1056,1059,1061,1063,1065],{"class":83,"line":84},[81,1045,257],{"class":91},[81,1047,52],{"class":87},[81,1049,712],{"class":91},[81,1051,1052],{"class":87}," T ",[81,1054,1055],{"class":130},"fromJson",[81,1057,1058],{"class":87},"(String json, Class",[81,1060,257],{"class":91},[81,1062,52],{"class":87},[81,1064,712],{"class":91},[81,1066,1067],{"class":87}," type) {\n",[81,1069,1070,1073,1075,1078],{"class":83,"line":107},[81,1071,1072],{"class":87},"  Object parsed ",[81,1074,98],{"class":91},[81,1076,1077],{"class":130}," parse",[81,1079,1080],{"class":87},"(json);\n",[81,1082,1083,1086,1088,1091,1094],{"class":83,"line":124},[81,1084,1085],{"class":91},"  return",[81,1087,616],{"class":87},[81,1089,1090],{"class":130},"cast",[81,1092,1093],{"class":87},"(parsed);          ",[81,1095,1096],{"class":154},"\u002F\u002F typed cast via the token, no unchecked warning\n",[81,1098,1099],{"class":83,"line":158},[81,1100,638],{"class":87},[81,1102,1103,1106,1108,1111,1114],{"class":83,"line":165},[81,1104,1105],{"class":87},"User u ",[81,1107,98],{"class":91},[81,1109,1110],{"class":130}," fromJson",[81,1112,1113],{"class":87},"(s, User.class);    ",[81,1115,1116],{"class":154},"\u002F\u002F T inferred from the token\n",[15,1118,1119,1120,1122,1123,1126,1127,1129,1130,1133,1134,1137,1138,1141,1142,1145],{},"A plain ",[19,1121,647],{}," can't represent a ",[63,1124,1125],{},"parameterized"," type like ",[19,1128,56],{}," (no such\nliteral exists). The ",[28,1131,1132],{},"super type token"," trick — Jackson's ",[19,1135,1136],{},"TypeReference",", Guava's\n",[19,1139,1140],{},"TypeToken"," — subclasses an abstract generic type so the argument is pinned in the class\nsignature, which ",[63,1143,1144],{},"does"," survive erasure as metadata, then reads it back via reflection:",[72,1147,1149],{"className":74,"code":1148,"language":76,"meta":77,"style":77},"TypeReference\u003CList\u003CString>> ref = new TypeReference\u003CList\u003CString>>() {};\nType t = ref.getClass().getGenericSuperclass();              \u002F\u002F TypeReference\u003CList\u003CString>>\nType arg = ((ParameterizedType) t).getActualTypeArguments()[0]; \u002F\u002F List\u003CString>\n",[19,1150,1151,1173,1196],{"__ignoreMap":77},[81,1152,1153,1156,1158,1161,1163,1165,1168,1170],{"class":83,"line":84},[81,1154,1155],{"class":87},"TypeReference\u003CList\u003C",[81,1157,92],{"class":91},[81,1159,1160],{"class":87},">> ref ",[81,1162,98],{"class":91},[81,1164,101],{"class":91},[81,1166,1167],{"class":87}," TypeReference\u003CList\u003C",[81,1169,92],{"class":91},[81,1171,1172],{"class":87},">>() {};\n",[81,1174,1175,1178,1180,1183,1185,1187,1190,1193],{"class":83,"line":107},[81,1176,1177],{"class":87},"Type t ",[81,1179,98],{"class":91},[81,1181,1182],{"class":87}," ref.",[81,1184,137],{"class":130},[81,1186,340],{"class":87},[81,1188,1189],{"class":130},"getGenericSuperclass",[81,1191,1192],{"class":87},"();              ",[81,1194,1195],{"class":154},"\u002F\u002F TypeReference\u003CList\u003CString>>\n",[81,1197,1198,1201,1203,1206,1209,1212,1214,1216],{"class":83,"line":124},[81,1199,1200],{"class":87},"Type arg ",[81,1202,98],{"class":91},[81,1204,1205],{"class":87}," ((ParameterizedType) t).",[81,1207,1208],{"class":130},"getActualTypeArguments",[81,1210,1211],{"class":87},"()[",[81,1213,755],{"class":725},[81,1215,729],{"class":87},[81,1217,1218],{"class":154},"\u002F\u002F List\u003CString>\n",[15,1220,1221,1222,1225,1226,1229],{},"Declaration-site generics (fields, method signatures, generic superclasses) live in\nmetadata and are readable via ",[19,1223,1224],{},"java.lang.reflect.Type","; it's the ",[63,1227,1228],{},"instance"," argument of a\nruntime object that's truly gone.",[10,1231,1233],{"id":1232},"safevarargs-and-unchecked-warnings","@SafeVarargs and unchecked warnings",[15,1235,1236,1237,1240,1241,1244,1245,1248,1249,1252,1253,1256,1257,1260,1261,1264,1265,1268],{},"A generic varargs parameter ",[19,1238,1239],{},"T..."," is compiled as a ",[19,1242,1243],{},"T[]"," — a non-reifiable generic array —\nso it triggers an unavoidable ",[28,1246,1247],{},"\"possible heap pollution\""," warning. ",[19,1250,1251],{},"@SafeVarargs"," is your\nsigned assertion that the method only ",[63,1254,1255],{},"reads"," the varargs and never pollutes the array; it\nsuppresses the warning at the declaration and every call site. It applies only to methods\nthat can't be overridden (",[19,1258,1259],{},"static",", ",[19,1262,1263],{},"final",", or ",[19,1266,1267],{},"private",").",[72,1270,1272],{"className":74,"code":1271,"language":76,"meta":77,"style":77},"@SafeVarargs\nstatic \u003CT> List\u003CT> listOf(T... items) {        \u002F\u002F T[] is a non-reifiable array\n  return new ArrayList\u003C>(Arrays.asList(items)); \u002F\u002F safe — read only, never stores into items\n}\n",[19,1273,1274,1282,1310,1328],{"__ignoreMap":77},[81,1275,1276,1279],{"class":83,"line":84},[81,1277,1278],{"class":87},"@",[81,1280,1281],{"class":91},"SafeVarargs\n",[81,1283,1284,1286,1289,1291,1293,1295,1297,1299,1301,1304,1307],{"class":83,"line":107},[81,1285,1259],{"class":91},[81,1287,1288],{"class":91}," \u003C",[81,1290,52],{"class":87},[81,1292,712],{"class":91},[81,1294,450],{"class":87},[81,1296,257],{"class":91},[81,1298,52],{"class":87},[81,1300,712],{"class":91},[81,1302,1303],{"class":130}," listOf",[81,1305,1306],{"class":87},"(T... items) {        ",[81,1308,1309],{"class":154},"\u002F\u002F T[] is a non-reifiable array\n",[81,1311,1312,1314,1316,1319,1322,1325],{"class":83,"line":124},[81,1313,1085],{"class":91},[81,1315,101],{"class":91},[81,1317,1318],{"class":87}," ArrayList\u003C>(Arrays.",[81,1320,1321],{"class":130},"asList",[81,1323,1324],{"class":87},"(items)); ",[81,1326,1327],{"class":154},"\u002F\u002F safe — read only, never stores into items\n",[81,1329,1330],{"class":83,"line":158},[81,1331,638],{"class":87},[15,1333,1334,1335,1338,1339,1342,1343,1346],{},"More broadly, an ",[28,1336,1337],{},"unchecked warning"," is erasure admitting it can't verify a cast to a\nparameterized type. Treat each one as a potential ",[19,1340,1341],{},"ClassCastException"," to justify, and only\n",[19,1344,1345],{},"@SuppressWarnings(\"unchecked\")"," at the narrowest scope, with a comment, once you've proven\nsafety by hand.",[10,1348,1350],{"id":1349},"recap","Recap",[15,1352,1353,1355,1356,1359,1360,1362,1363,1366,1367,1370,1371,1373,1374,1260,1376,1264,1379,1381,1382,1384,1385,1388,1389,1394,1395,1399],{},[28,1354,48],{}," is the single fact behind Java generics: the compiler uses type arguments\nto check your code, then ",[28,1357,1358],{},"replaces type parameters with their bound, inserts casts, and\nadds bridge methods",", leaving no ",[19,1361,52],{}," at runtime. Java chose it for ",[28,1364,1365],{},"backward\ncompatibility"," with pre-generics code, accepting the loss of runtime type identity. The\n",[28,1368,1369],{},"reifiable vs non-reifiable"," distinction explains every restriction — no ",[19,1372,21],{},",\n",[19,1375,677],{},[19,1377,1378],{},"T.class",[19,1380,518],{},", the generic-array ban that guards\nagainst ",[28,1383,685],{},", and the ",[28,1386,1387],{},"name clash"," when overloads share an erasure. When\nyou truly need the runtime type, reach for a ",[28,1390,1391,1393],{},[19,1392,647],{}," type token"," (or a super type\ntoken for parameterized types), and use ",[28,1396,1397],{},[19,1398,1251],{}," to vouch for read-only generic\nvarargs. Every generics quirk you hit is erasure's price for a compatible platform.",[1401,1402,1403],"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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .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}",{"title":77,"searchDepth":107,"depth":107,"links":1405},[1406,1407,1408,1409,1410,1412,1413,1414,1415,1417,1418],{"id":12,"depth":107,"text":13},{"id":42,"depth":107,"text":43},{"id":216,"depth":107,"text":217},{"id":379,"depth":107,"text":380},{"id":480,"depth":107,"text":1411},"Why new T(), new T, T.class and instanceof List are banned",{"id":655,"depth":107,"text":656},{"id":813,"depth":107,"text":814},{"id":943,"depth":107,"text":944},{"id":1015,"depth":107,"text":1416},"The Class type-token pattern",{"id":1232,"depth":107,"text":1233},{"id":1349,"depth":107,"text":1350},"How Java type erasure works — why generics are compile-time only, reifiable vs non-reifiable types, generic array and heap pollution restrictions, bridge methods, name clashes, and the Class\u003CT> type-token escape hatch.","hard","md","Java",{},"\u002Fblog\u002Fjava-generics-type-erasure","\u002Fjava\u002Fgenerics\u002Ftype-erasure",{"title":5,"description":1419},"blog\u002Fjava-generics-type-erasure","Type Erasure","Generics","generics","2026-06-20","wZkvr9e8sJ3a3DxbEYjluampRNLfbBmQ7s8P3E13Aag",1782244090132]