[{"data":1,"prerenderedAt":3643},["ShallowReactive",2],{"post:\u002Fposts\u002Fjava\u002Fjava-generics-notes":3,"posts:ordered":3361},{"id":4,"title":5,"body":6,"category":3345,"cover":3346,"date":3347,"description":3348,"extension":3349,"featured":3350,"meta":3351,"navigation":142,"path":3352,"seo":3353,"series":3354,"seriesOrder":86,"stem":3355,"tags":3356,"__hash__":3360},"posts\u002Fposts\u002Fjava\u002Fjava-generics-notes.md","Java 泛型笔记：泛型方法、有界类型与泛型类",{"type":7,"value":8,"toc":3324},"minimark",[9,16,21,26,29,289,292,300,306,371,374,382,402,417,420,424,428,443,450,454,490,494,498,505,613,635,638,665,678,681,791,797,801,822,825,859,862,866,889,902,909,992,998,1009,1013,1019,1101,1124,1163,1169,1172,1203,1207,1220,1239,1245,1260,1264,1278,1281,1366,1406,1412,1428,1432,1436,1461,1538,1551,1555,1622,1648,1664,1725,1731,1734,1741,1746,1807,1824,1827,1940,2022,2028,2031,2078,2082,2088,2151,2178,2184,2187,2214,2218,2229,2312,2440,2446,2478,2490,2563,2583,2587,2591,2598,2735,2745,2818,2824,2827,2868,2872,2882,2964,2988,3054,3060,3071,3084,3163,3169,3187,3191,3320],[10,11,12],"blockquote",{},[13,14,15],"p",{},"这是一份个人学习笔记：讲解部分是我按自己的理解写的，示例代码也都是自己写的，输出结果是本地跑出来的。参考主要是 Oracle 的 Java 官方文档与语言规范，理解有偏差的地方欢迎指出。",[17,18,20],"h1",{"id":19},"_01-重复代码与多态polymorphism尝试","0.1 重复代码与多态（Polymorphism）尝试",[22,23,25],"h2",{"id":24},"_011-靠重载overloading堆出来的重复代码","0.1.1 靠重载（Overloading）堆出来的重复代码",[13,27,28],{},"要给不同类型的数组求和，用重载（Overloading）写出来就是三个几乎一样的方法：",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-java shiki shiki-themes github-light github-dark","static double sum(int[] arr) {\n    double total = 0;\n    for (int e : arr) {\n        total += e;\n    }\n    return total;\n}\n\nstatic double sum(float[] arr) {\n    double total = 0;\n    for (float e : arr) {\n        total += e;\n    }\n    return total;\n}\n\nstatic double sum(double[] arr) {\n    double total = 0;\n    for (double e : arr) {\n        total += e;\n    }\n    return total;\n}\n","java","",[37,38,39,65,84,104,116,122,131,137,144,160,173,188,197,202,209,214,219,235,248,263,272,277,284],"code",{"__ignoreMap":35},[40,41,44,48,51,55,59,62],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"szBVR","static",[40,49,50],{"class":46}," double",[40,52,54],{"class":53},"sScJk"," sum",[40,56,58],{"class":57},"sVt8B","(",[40,60,61],{"class":46},"int",[40,63,64],{"class":57},"[] arr) {\n",[40,66,68,71,74,77,81],{"class":42,"line":67},2,[40,69,70],{"class":46},"    double",[40,72,73],{"class":57}," total ",[40,75,76],{"class":46},"=",[40,78,80],{"class":79},"sj4cs"," 0",[40,82,83],{"class":57},";\n",[40,85,87,90,93,95,98,101],{"class":42,"line":86},3,[40,88,89],{"class":46},"    for",[40,91,92],{"class":57}," (",[40,94,61],{"class":46},[40,96,97],{"class":57}," e ",[40,99,100],{"class":46},":",[40,102,103],{"class":57}," arr) {\n",[40,105,107,110,113],{"class":42,"line":106},4,[40,108,109],{"class":57},"        total ",[40,111,112],{"class":46},"+=",[40,114,115],{"class":57}," e;\n",[40,117,119],{"class":42,"line":118},5,[40,120,121],{"class":57},"    }\n",[40,123,125,128],{"class":42,"line":124},6,[40,126,127],{"class":46},"    return",[40,129,130],{"class":57}," total;\n",[40,132,134],{"class":42,"line":133},7,[40,135,136],{"class":57},"}\n",[40,138,140],{"class":42,"line":139},8,[40,141,143],{"emptyLinePlaceholder":142},true,"\n",[40,145,147,149,151,153,155,158],{"class":42,"line":146},9,[40,148,47],{"class":46},[40,150,50],{"class":46},[40,152,54],{"class":53},[40,154,58],{"class":57},[40,156,157],{"class":46},"float",[40,159,64],{"class":57},[40,161,163,165,167,169,171],{"class":42,"line":162},10,[40,164,70],{"class":46},[40,166,73],{"class":57},[40,168,76],{"class":46},[40,170,80],{"class":79},[40,172,83],{"class":57},[40,174,176,178,180,182,184,186],{"class":42,"line":175},11,[40,177,89],{"class":46},[40,179,92],{"class":57},[40,181,157],{"class":46},[40,183,97],{"class":57},[40,185,100],{"class":46},[40,187,103],{"class":57},[40,189,191,193,195],{"class":42,"line":190},12,[40,192,109],{"class":57},[40,194,112],{"class":46},[40,196,115],{"class":57},[40,198,200],{"class":42,"line":199},13,[40,201,121],{"class":57},[40,203,205,207],{"class":42,"line":204},14,[40,206,127],{"class":46},[40,208,130],{"class":57},[40,210,212],{"class":42,"line":211},15,[40,213,136],{"class":57},[40,215,217],{"class":42,"line":216},16,[40,218,143],{"emptyLinePlaceholder":142},[40,220,222,224,226,228,230,233],{"class":42,"line":221},17,[40,223,47],{"class":46},[40,225,50],{"class":46},[40,227,54],{"class":53},[40,229,58],{"class":57},[40,231,232],{"class":46},"double",[40,234,64],{"class":57},[40,236,238,240,242,244,246],{"class":42,"line":237},18,[40,239,70],{"class":46},[40,241,73],{"class":57},[40,243,76],{"class":46},[40,245,80],{"class":79},[40,247,83],{"class":57},[40,249,251,253,255,257,259,261],{"class":42,"line":250},19,[40,252,89],{"class":46},[40,254,92],{"class":57},[40,256,232],{"class":46},[40,258,97],{"class":57},[40,260,100],{"class":46},[40,262,103],{"class":57},[40,264,266,268,270],{"class":42,"line":265},20,[40,267,109],{"class":57},[40,269,112],{"class":46},[40,271,115],{"class":57},[40,273,275],{"class":42,"line":274},21,[40,276,121],{"class":57},[40,278,280,282],{"class":42,"line":279},22,[40,281,127],{"class":46},[40,283,130],{"class":57},[40,285,287],{"class":42,"line":286},23,[40,288,136],{"class":57},[13,290,291],{},"三个方法只有数组类型不同，循环体和累加逻辑完全一样。类型越多，复制粘贴的次数就越多，改一处逻辑要改好几份——这就是泛型（Generics）想解决的问题。",[22,293,295,296,299],{"id":294},"_012-换成-object-行不行","0.1.2 换成 ",[37,297,298],{},"Object[]"," 行不行？",[13,301,302,303,305],{},"一个自然的想法是「用多态（Polymorphism）把参数提升为 ",[37,304,298],{},"」，这样只需要一个方法：",[30,307,309],{"className":32,"code":308,"language":34,"meta":35,"style":35},"static double sum(Object[] arr) {\n    double total = 0;\n    for (Object e : arr) {\n        total += e;\n    }\n    return total;\n}\n",[37,310,311,326,338,349,357,361,367],{"__ignoreMap":35},[40,312,313,315,317,319,321,324],{"class":42,"line":43},[40,314,47],{"class":46},[40,316,50],{"class":46},[40,318,54],{"class":53},[40,320,58],{"class":57},[40,322,323],{"class":46},"Object",[40,325,64],{"class":57},[40,327,328,330,332,334,336],{"class":42,"line":67},[40,329,70],{"class":46},[40,331,73],{"class":57},[40,333,76],{"class":46},[40,335,80],{"class":79},[40,337,83],{"class":57},[40,339,340,342,345,347],{"class":42,"line":86},[40,341,89],{"class":46},[40,343,344],{"class":57}," (Object e ",[40,346,100],{"class":46},[40,348,103],{"class":57},[40,350,351,353,355],{"class":42,"line":106},[40,352,109],{"class":57},[40,354,112],{"class":46},[40,356,115],{"class":57},[40,358,359],{"class":42,"line":118},[40,360,121],{"class":57},[40,362,363,365],{"class":42,"line":124},[40,364,127],{"class":46},[40,366,130],{"class":57},[40,368,369],{"class":42,"line":133},[40,370,136],{"class":57},[13,372,373],{},"编译这段代码，编译器直接报错：",[30,375,380],{"className":376,"code":378,"language":379,"meta":35},[377],"language-text","PolymorphismAttempt.java:5: error: bad operand types for binary operator '+'\n            total += e;\n                  ^\n  first type:  double\n  second type: Object\n1 error\n","text",[37,381,378],{"__ignoreMap":35},[13,383,384,385,388,389,391,392,395,396,398,399,401],{},"报错的原因是变量 ",[37,386,387],{},"e"," 的编译期类型是 ",[37,390,323],{},"，而 ",[37,393,394],{},"+"," 运算符知道怎么处理 ",[37,397,232],{},"，却不知道该怎么处理一个 ",[37,400,323],{},"。想把值取出来就得显式做强制类型转换（Explicit Type Casting），而且转错了要到运行期才炸。",[13,403,404,405,407,408,411,412,416],{},"就算转换的问题绕过去了，还有第二个隐患：",[37,406,298],{}," 什么都能装，",[37,409,410],{},"{1, 2, \"three\"}"," 这样的数组能顺利通过编译，求和时才在运行期出错。也就是说，",[413,414,415],"strong",{},"类型安全（Type Safety）在编译期就丢掉了","。",[13,418,419],{},"两类问题的共同点是：写方法的时候我就已经知道数组里装的是什么类型了，可编译器不知道。要解决它，需要一种「类型先留空、调用时再填」的写法。",[17,421,423],{"id":422},"_02-泛型generics是什么","0.2 泛型（Generics）是什么",[22,425,427],{"id":426},"_021-参数化类型parameterized-types","0.2.1 参数化类型（Parameterized Types）",[13,429,430,431,434,435,438,439,442],{},"泛型（Generics）允许在定义类或方法时使用泛型类型（Generic Types），也就是",[413,432,433],{},"参数化类型（Parameterized Types）","：先写一个类型占位符（例如 ",[37,436,437],{},"T","），具体是什么类型留到使用的时候再指定，用尖括号 ",[37,440,441],{},"\u003C>"," 写出来。",[13,444,445,446,449],{},"用一句话概括：",[413,447,448],{},"把「类型」变成可以传入的参数","。方法参数传的是值，泛型参数传的是类型。",[22,451,453],{"id":452},"_022-两个核心好处","0.2.2 两个核心好处",[455,456,457,470],"table",{},[458,459,460],"thead",{},[461,462,463,467],"tr",{},[464,465,466],"th",{},"好处",[464,468,469],{},"含义",[471,472,473,482],"tbody",{},[461,474,475,479],{},[476,477,478],"td",{},"类型安全（Type Safety）",[476,480,481],{},"编译器（Compiler）在编译期就检查类型用得对不对，错误提前暴露，而不是拖到运行期",[461,483,484,487],{},[476,485,486],{},"复用（Reusability）",[476,488,489],{},"一份泛型算法或数据结构就能处理多种类型，调用方也不需要写显式类型转换（Explicit Type Casting）",[17,491,493],{"id":492},"_03-泛型方法generic-methods","0.3 泛型方法（Generic Methods）",[22,495,497],{"id":496},"_031-声明位置与命名习惯","0.3.1 声明位置与命名习惯",[13,499,500,501,504],{},"泛型类型声明在",[413,502,503],{},"修饰符（Modifier）与返回类型之间","，用尖括号包起来：",[30,506,508],{"className":32,"code":507,"language":34,"meta":35,"style":35},"static \u003CT> void printAll(T[] arr) {\n    for (int i = 0; i \u003C arr.length; i++) {\n        T cur = arr[i];\n        System.out.print(cur + \" \");\n    }\n    System.out.println();\n}\n",[37,509,510,534,564,574,594,598,609],{"__ignoreMap":35},[40,511,512,514,517,519,522,525,528,530,532],{"class":42,"line":43},[40,513,47],{"class":46},[40,515,516],{"class":46}," \u003C",[40,518,437],{"class":57},[40,520,521],{"class":46},">",[40,523,524],{"class":46}," void",[40,526,527],{"class":53}," printAll",[40,529,58],{"class":57},[40,531,437],{"class":46},[40,533,64],{"class":57},[40,535,536,538,540,542,545,547,549,552,555,558,561],{"class":42,"line":67},[40,537,89],{"class":46},[40,539,92],{"class":57},[40,541,61],{"class":46},[40,543,544],{"class":57}," i ",[40,546,76],{"class":46},[40,548,80],{"class":79},[40,550,551],{"class":57},"; i ",[40,553,554],{"class":46},"\u003C",[40,556,557],{"class":57}," arr.length; i",[40,559,560],{"class":46},"++",[40,562,563],{"class":57},") {\n",[40,565,566,569,571],{"class":42,"line":86},[40,567,568],{"class":57},"        T cur ",[40,570,76],{"class":46},[40,572,573],{"class":57}," arr[i];\n",[40,575,576,579,582,585,587,591],{"class":42,"line":106},[40,577,578],{"class":57},"        System.out.",[40,580,581],{"class":53},"print",[40,583,584],{"class":57},"(cur ",[40,586,394],{"class":46},[40,588,590],{"class":589},"sZZnC"," \" \"",[40,592,593],{"class":57},");\n",[40,595,596],{"class":42,"line":118},[40,597,121],{"class":57},[40,599,600,603,606],{"class":42,"line":124},[40,601,602],{"class":57},"    System.out.",[40,604,605],{"class":53},"println",[40,607,608],{"class":57},"();\n",[40,610,611],{"class":42,"line":133},[40,612,136],{"class":57},[13,614,615,616,618,619,622,623,626,627,630,631,634],{},"拆开看这行声明：",[37,617,47],{}," 是修饰符，",[37,620,621],{},"\u003CT>"," 是声明的泛型类型，",[37,624,625],{},"void"," 是返回类型，",[37,628,629],{},"printAll"," 是方法名，",[37,632,633],{},"T[] arr"," 是使用了泛型类型的参数。",[13,636,637],{},"占位符本身的名字随意，但社区有约定俗成的习惯：",[639,640,641,647,653,659],"ul",{},[642,643,644,646],"li",{},[37,645,437],{}," — Type（类型）",[642,648,649,652],{},[37,650,651],{},"E"," — Element（元素）",[642,654,655,658],{},[37,656,657],{},"K"," — Key（键）",[642,660,661,664],{},[37,662,663],{},"V"," — Value（值）",[13,666,667,668,670,671,673,674,677],{},"一旦声明了 ",[37,669,437],{},"，它就能用在三个地方：参数类型（",[37,672,633],{},"）、局部变量类型（",[37,675,676],{},"T cur","），以及后面会讲的返回类型。",[13,679,680],{},"调用同一份方法，传入三种不同类型的数组：",[30,682,684],{"className":32,"code":683,"language":34,"meta":35,"style":35},"Integer[] intArr = {5, 8, 1};\nDouble[] doubleArr = {2.5, 0.5, 3.5};\nString[] wordArr = {\"kiwi\", \"banana\", \"pear\"};\nprintAll(intArr);\nprintAll(doubleArr);\nprintAll(wordArr);\n",[37,685,686,716,743,770,777,784],{"__ignoreMap":35},[40,687,688,691,694,696,699,702,705,708,710,713],{"class":42,"line":43},[40,689,690],{"class":46},"Integer",[40,692,693],{"class":57},"[] intArr ",[40,695,76],{"class":46},[40,697,698],{"class":57}," {",[40,700,701],{"class":79},"5",[40,703,704],{"class":57},", ",[40,706,707],{"class":79},"8",[40,709,704],{"class":57},[40,711,712],{"class":79},"1",[40,714,715],{"class":57},"};\n",[40,717,718,721,724,726,728,731,733,736,738,741],{"class":42,"line":67},[40,719,720],{"class":46},"Double",[40,722,723],{"class":57},"[] doubleArr ",[40,725,76],{"class":46},[40,727,698],{"class":57},[40,729,730],{"class":79},"2.5",[40,732,704],{"class":57},[40,734,735],{"class":79},"0.5",[40,737,704],{"class":57},[40,739,740],{"class":79},"3.5",[40,742,715],{"class":57},[40,744,745,748,751,753,755,758,760,763,765,768],{"class":42,"line":86},[40,746,747],{"class":46},"String",[40,749,750],{"class":57},"[] wordArr ",[40,752,76],{"class":46},[40,754,698],{"class":57},[40,756,757],{"class":589},"\"kiwi\"",[40,759,704],{"class":57},[40,761,762],{"class":589},"\"banana\"",[40,764,704],{"class":57},[40,766,767],{"class":589},"\"pear\"",[40,769,715],{"class":57},[40,771,772,774],{"class":42,"line":106},[40,773,629],{"class":53},[40,775,776],{"class":57},"(intArr);\n",[40,778,779,781],{"class":42,"line":118},[40,780,629],{"class":53},[40,782,783],{"class":57},"(doubleArr);\n",[40,785,786,788],{"class":42,"line":124},[40,787,629],{"class":53},[40,789,790],{"class":57},"(wordArr);\n",[30,792,795],{"className":793,"code":794,"language":379,"meta":35},[377],"5 8 1 \n2.5 0.5 3.5 \nkiwi banana pear \n",[37,796,794],{"__ignoreMap":35},[22,798,800],{"id":799},"_032-调用时的类型推断type-inference","0.3.2 调用时的类型推断（Type Inference）",[13,802,803,804,807,808,811,812,815,816,818,819,821],{},"调用 ",[37,805,806],{},"printAll(intArr)"," 时并没有写 ",[37,809,810],{},"\u003CInteger>","，编译器是从",[413,813,814],{},"实参的类型","推断（Type Inference）出 ",[37,817,437],{}," 就是 ",[37,820,690],{}," 的。",[13,823,824],{},"推断依据是实参，所以同一份泛型方法也不排斥显式指定：",[30,826,828],{"className":32,"code":827,"language":34,"meta":35,"style":35},"GenericsDemo.\u003CString>printAll(wordArr);\nGenericsDemo.\u003CInteger>printAll(intArr);\n",[37,829,830,845],{"__ignoreMap":35},[40,831,832,835,837,839,841,843],{"class":42,"line":43},[40,833,834],{"class":57},"GenericsDemo.",[40,836,554],{"class":46},[40,838,747],{"class":57},[40,840,521],{"class":46},[40,842,629],{"class":53},[40,844,790],{"class":57},[40,846,847,849,851,853,855,857],{"class":42,"line":67},[40,848,834],{"class":57},[40,850,554],{"class":46},[40,852,690],{"class":57},[40,854,521],{"class":46},[40,856,629],{"class":53},[40,858,776],{"class":57},[13,860,861],{},"这种写法用得很少，一般只在编译器没法从实参推断出类型时才需要。",[22,863,865],{"id":864},"_033-编译器背后做了什么类型擦除type-erasure","0.3.3 编译器背后做了什么：类型擦除（Type Erasure）",[13,867,868,869,872,873,875,876,818,878,880,881,875,884,818,886,888],{},"有一种常见的说法是「编译器会为每种用到的类型各生成一个版本，类似重载」。从",[413,870,871],{},"调用点的行为","看确实像：",[37,874,806],{}," 里的 ",[37,877,437],{},[37,879,690],{},"，",[37,882,883],{},"printAll(wordArr)",[37,885,437],{},[37,887,747],{},"，各自都受编译期检查。",[13,890,891,892,895,896,898,899,901],{},"但 Java 的实现方式并不是复制多份方法体，而是",[413,893,894],{},"类型擦除（Type Erasure）","：泛型只存在于编译期，编译出来的字节码里 ",[37,897,437],{}," 会被擦除成它的边界（没有边界时擦除成 ",[37,900,323],{},"），编译器在需要的地方插入强制类型转换，必要时还会生成桥方法（Bridge Method）。",[13,903,904,905,908],{},"可以亲手验证一下擦除的结果——两个泛型参数不同、元素类型也不同的 ",[37,906,907],{},"ArrayList","，运行期是同一个类：",[30,910,912],{"className":32,"code":911,"language":34,"meta":35,"style":35},"List\u003CString> names = new ArrayList\u003C>();\nList\u003CInteger> numbers = new ArrayList\u003C>();\nSystem.out.println(names.getClass() == numbers.getClass());\nSystem.out.println(names.getClass().getName());\n",[37,913,914,932,947,974],{"__ignoreMap":35},[40,915,916,919,921,924,926,929],{"class":42,"line":43},[40,917,918],{"class":57},"List\u003C",[40,920,747],{"class":46},[40,922,923],{"class":57},"> names ",[40,925,76],{"class":46},[40,927,928],{"class":46}," new",[40,930,931],{"class":57}," ArrayList\u003C>();\n",[40,933,934,936,938,941,943,945],{"class":42,"line":67},[40,935,918],{"class":57},[40,937,690],{"class":46},[40,939,940],{"class":57},"> numbers ",[40,942,76],{"class":46},[40,944,928],{"class":46},[40,946,931],{"class":57},[40,948,949,952,954,957,960,963,966,969,971],{"class":42,"line":86},[40,950,951],{"class":57},"System.out.",[40,953,605],{"class":53},[40,955,956],{"class":57},"(names.",[40,958,959],{"class":53},"getClass",[40,961,962],{"class":57},"() ",[40,964,965],{"class":46},"==",[40,967,968],{"class":57}," numbers.",[40,970,959],{"class":53},[40,972,973],{"class":57},"());\n",[40,975,976,978,980,982,984,987,990],{"class":42,"line":106},[40,977,951],{"class":57},[40,979,605],{"class":53},[40,981,956],{"class":57},[40,983,959],{"class":53},[40,985,986],{"class":57},"().",[40,988,989],{"class":53},"getName",[40,991,973],{"class":57},[30,993,996],{"className":994,"code":995,"language":379,"meta":35},[377],"true\njava.util.ArrayList\n",[37,997,995],{"__ignoreMap":35},[13,999,1000,1001,1004,1005,1008],{},"这也解释了两件事：为什么泛型参数只能是引用类型（下一节），以及为什么",[413,1002,1003],{},"运行时拿不到泛型的实际类型参数","（",[37,1006,1007],{},"List\u003CString>"," 在运行期没法问出「你的元素是 String」）。",[22,1010,1012],{"id":1011},"_034-泛型可以作返回类型","0.3.4 泛型可以作返回类型",[13,1014,1015,1016,1018],{},"把 ",[37,1017,437],{}," 写在返回类型的位置，方法就返回一个「调用时确定的类型」：",[30,1020,1022],{"className":32,"code":1021,"language":34,"meta":35,"style":35},"static \u003CT> T elementAt(T[] arr, int index) {\n    if (index >= 0 && index \u003C arr.length) {\n        return arr[index];\n    }\n    return null;\n}\n",[37,1023,1024,1052,1076,1084,1088,1097],{"__ignoreMap":35},[40,1025,1026,1028,1030,1032,1034,1037,1040,1042,1044,1047,1049],{"class":42,"line":43},[40,1027,47],{"class":46},[40,1029,516],{"class":46},[40,1031,437],{"class":57},[40,1033,521],{"class":46},[40,1035,1036],{"class":57}," T ",[40,1038,1039],{"class":53},"elementAt",[40,1041,58],{"class":57},[40,1043,437],{"class":46},[40,1045,1046],{"class":57},"[] arr, ",[40,1048,61],{"class":46},[40,1050,1051],{"class":57}," index) {\n",[40,1053,1054,1057,1060,1063,1065,1068,1071,1073],{"class":42,"line":67},[40,1055,1056],{"class":46},"    if",[40,1058,1059],{"class":57}," (index ",[40,1061,1062],{"class":46},">=",[40,1064,80],{"class":79},[40,1066,1067],{"class":46}," &&",[40,1069,1070],{"class":57}," index ",[40,1072,554],{"class":46},[40,1074,1075],{"class":57}," arr.length) {\n",[40,1077,1078,1081],{"class":42,"line":86},[40,1079,1080],{"class":46},"        return",[40,1082,1083],{"class":57}," arr[index];\n",[40,1085,1086],{"class":42,"line":106},[40,1087,121],{"class":57},[40,1089,1090,1092,1095],{"class":42,"line":118},[40,1091,127],{"class":46},[40,1093,1094],{"class":79}," null",[40,1096,83],{"class":57},[40,1098,1099],{"class":42,"line":124},[40,1100,136],{"class":57},[13,1102,803,1103,1106,1107,1110,1111,1114,1115,1117,1118,1120,1121,1123],{},[37,1104,1105],{},"elementAt(wordArr, 1)"," 时，编译器根据 ",[37,1108,1109],{},"wordArr"," 是 ",[37,1112,1113],{},"String[]"," 推断出 ",[37,1116,437],{}," 为 ",[37,1119,747],{},"，所以返回值可以直接赋给 ",[37,1122,747],{}," 变量，不需要强制转换：",[30,1125,1127],{"className":32,"code":1126,"language":34,"meta":35,"style":35},"System.out.println(elementAt(wordArr, 1));\nSystem.out.println(elementAt(wordArr, 5));\n",[37,1128,1129,1147],{"__ignoreMap":35},[40,1130,1131,1133,1135,1137,1139,1142,1144],{"class":42,"line":43},[40,1132,951],{"class":57},[40,1134,605],{"class":53},[40,1136,58],{"class":57},[40,1138,1039],{"class":53},[40,1140,1141],{"class":57},"(wordArr, ",[40,1143,712],{"class":79},[40,1145,1146],{"class":57},"));\n",[40,1148,1149,1151,1153,1155,1157,1159,1161],{"class":42,"line":67},[40,1150,951],{"class":57},[40,1152,605],{"class":53},[40,1154,58],{"class":57},[40,1156,1039],{"class":53},[40,1158,1141],{"class":57},[40,1160,701],{"class":79},[40,1162,1146],{"class":57},[30,1164,1167],{"className":1165,"code":1166,"language":379,"meta":35},[377],"banana\nnull\n",[37,1168,1166],{"__ignoreMap":35},[13,1170,1171],{},"两个约束值得注意：",[639,1173,1174,1187],{},[642,1175,1176,1186],{},[413,1177,1178,1179,1182,1183,1185],{},"所有 ",[37,1180,1181],{},"return"," 语句返回的都必须是 ",[37,1184,437],{}," 类型的引用","，返回别的东西编译不过。",[642,1188,1189,1190,1193,1194,1196,1197,1199,1200,1202],{},"越界时返回 ",[37,1191,1192],{},"null"," 是可行的写法，因为 ",[37,1195,437],{}," 一定是引用类型，可以是 ",[37,1198,1192],{},"。调用方拿到 ",[37,1201,1192],{}," 之后要自己处理。",[22,1204,1206],{"id":1205},"_035-泛型只能代表引用类型reference-type","0.3.5 泛型只能代表引用类型（Reference Type）",[13,1208,1209,1212,1213,1216,1217,1219],{},[37,1210,1211],{},"static \u003CT> void printAll(T[] arr)"," 只能接收 ",[37,1214,1215],{},"Integer[]","、",[37,1218,1113],{}," 这类引用类型的数组。把泛型参数写成基本类型，编译器会直接拒绝：",[30,1221,1223],{"className":32,"code":1222,"language":34,"meta":35,"style":35},"List\u003Cint> numbers = new ArrayList\u003C>();\n",[37,1224,1225],{"__ignoreMap":35},[40,1226,1227,1229,1231,1233,1235,1237],{"class":42,"line":43},[40,1228,918],{"class":57},[40,1230,61],{"class":46},[40,1232,940],{"class":57},[40,1234,76],{"class":46},[40,1236,928],{"class":46},[40,1238,931],{"class":57},[30,1240,1243],{"className":1241,"code":1242,"language":379,"meta":35},[377],"PrimitiveTypeArg.java:6: error: unexpected type\n        List\u003Cint> numbers = new ArrayList\u003C>();\n             ^\n  required: reference\n  found:    int\n1 error\n",[37,1244,1242],{"__ignoreMap":35},[13,1246,1247,1248,1216,1250,1216,1252,1255,1256,1259],{},"需要装基本类型时用对应的包装类（Wrapper Class）：",[37,1249,690],{},[37,1251,720],{},[37,1253,1254],{},"Character","……自动装箱（Autoboxing）会让 ",[37,1257,1258],{},"list.add(1)"," 这样的写法照常工作。",[22,1261,1263],{"id":1262},"_036-练习判断数组是否包含某个元素","0.3.6 练习：判断数组是否包含某个元素",[13,1265,1266,1269,1270,1216,1272,1216,1275,1277],{},[413,1267,1268],{},"题目","：写一个方法，判断数组中是否包含指定元素，要求同时支持 ",[37,1271,1215],{},[37,1273,1274],{},"Double[]",[37,1276,1113],{}," 等多种数组类型。",[13,1279,1280],{},"用泛型方法只需要一份实现：",[30,1282,1284],{"className":32,"code":1283,"language":34,"meta":35,"style":35},"static \u003CT> boolean contains(T[] arr, T target) {\n    for (T cur : arr) {\n        if (cur.equals(target)) {\n            return true;\n        }\n    }\n    return false;\n}\n",[37,1285,1286,1309,1320,1334,1344,1349,1353,1362],{"__ignoreMap":35},[40,1287,1288,1290,1292,1294,1296,1299,1302,1304,1306],{"class":42,"line":43},[40,1289,47],{"class":46},[40,1291,516],{"class":46},[40,1293,437],{"class":57},[40,1295,521],{"class":46},[40,1297,1298],{"class":46}," boolean",[40,1300,1301],{"class":53}," contains",[40,1303,58],{"class":57},[40,1305,437],{"class":46},[40,1307,1308],{"class":57},"[] arr, T target) {\n",[40,1310,1311,1313,1316,1318],{"class":42,"line":67},[40,1312,89],{"class":46},[40,1314,1315],{"class":57}," (T cur ",[40,1317,100],{"class":46},[40,1319,103],{"class":57},[40,1321,1322,1325,1328,1331],{"class":42,"line":86},[40,1323,1324],{"class":46},"        if",[40,1326,1327],{"class":57}," (cur.",[40,1329,1330],{"class":53},"equals",[40,1332,1333],{"class":57},"(target)) {\n",[40,1335,1336,1339,1342],{"class":42,"line":106},[40,1337,1338],{"class":46},"            return",[40,1340,1341],{"class":79}," true",[40,1343,83],{"class":57},[40,1345,1346],{"class":42,"line":118},[40,1347,1348],{"class":57},"        }\n",[40,1350,1351],{"class":42,"line":124},[40,1352,121],{"class":57},[40,1354,1355,1357,1360],{"class":42,"line":133},[40,1356,127],{"class":46},[40,1358,1359],{"class":79}," false",[40,1361,83],{"class":57},[40,1363,1364],{"class":42,"line":139},[40,1365,136],{"class":57},[30,1367,1369],{"className":32,"code":1368,"language":34,"meta":35,"style":35},"System.out.println(contains(intArr, 8));\nSystem.out.println(contains(wordArr, \"mango\"));\n",[37,1370,1371,1389],{"__ignoreMap":35},[40,1372,1373,1375,1377,1379,1382,1385,1387],{"class":42,"line":43},[40,1374,951],{"class":57},[40,1376,605],{"class":53},[40,1378,58],{"class":57},[40,1380,1381],{"class":53},"contains",[40,1383,1384],{"class":57},"(intArr, ",[40,1386,707],{"class":79},[40,1388,1146],{"class":57},[40,1390,1391,1393,1395,1397,1399,1401,1404],{"class":42,"line":67},[40,1392,951],{"class":57},[40,1394,605],{"class":53},[40,1396,58],{"class":57},[40,1398,1381],{"class":53},[40,1400,1141],{"class":57},[40,1402,1403],{"class":589},"\"mango\"",[40,1405,1146],{"class":57},[30,1407,1410],{"className":1408,"code":1409,"language":379,"meta":35},[377],"true\nfalse\n",[37,1411,1409],{"__ignoreMap":35},[13,1413,1414,1415,1418,1419,1421,1422,1424,1425,1427],{},"这里用的是 ",[37,1416,1417],{},"equals()"," 而不是 ",[37,1420,965],{},"：",[37,1423,965],{}," 比较的是两个引用是否指向同一个对象，判断「值是否相等」要用 ",[37,1426,1417],{},"，这也是泛型代码里判断元素相等的标准做法。",[17,1429,1431],{"id":1430},"_04-有界泛型类型bounded-generic-types","0.4 有界泛型类型（Bounded Generic Types）",[22,1433,1435],{"id":1434},"_041-无界泛型能做的事很有限","0.4.1 无界泛型能做的事很有限",[13,1437,1438,875,1440,1442,1443,1445,1446,1448,1449,1451,1452,1216,1454,1216,1457,1460],{},[37,1439,621],{},[37,1441,437],{}," 没有边界，编译器就只把它当成 ",[37,1444,323],{}," 看待（擦除也擦到 ",[37,1447,323],{},"），因此方法体里只能调用 ",[37,1450,323],{}," 上有的方法：",[37,1453,1417],{},[37,1455,1456],{},"toString()",[37,1458,1459],{},"hashCode()","。想调用别的就编译不过：",[30,1462,1464],{"className":32,"code":1463,"language":34,"meta":35,"style":35},"\u002F\u002F 编译错误：找不到符号 makeSound()\nstatic \u003CT> void makeNoiseNTimes(T animal, int n) {\n    for (int i = 0; i \u003C n; i++) {\n        animal.makeSound();\n    }\n}\n",[37,1465,1466,1472,1495,1520,1530,1534],{"__ignoreMap":35},[40,1467,1468],{"class":42,"line":43},[40,1469,1471],{"class":1470},"sJ8bj","\u002F\u002F 编译错误：找不到符号 makeSound()\n",[40,1473,1474,1476,1478,1480,1482,1484,1487,1490,1492],{"class":42,"line":67},[40,1475,47],{"class":46},[40,1477,516],{"class":46},[40,1479,437],{"class":57},[40,1481,521],{"class":46},[40,1483,524],{"class":46},[40,1485,1486],{"class":53}," makeNoiseNTimes",[40,1488,1489],{"class":57},"(T animal, ",[40,1491,61],{"class":46},[40,1493,1494],{"class":57}," n) {\n",[40,1496,1497,1499,1501,1503,1505,1507,1509,1511,1513,1516,1518],{"class":42,"line":86},[40,1498,89],{"class":46},[40,1500,92],{"class":57},[40,1502,61],{"class":46},[40,1504,544],{"class":57},[40,1506,76],{"class":46},[40,1508,80],{"class":79},[40,1510,551],{"class":57},[40,1512,554],{"class":46},[40,1514,1515],{"class":57}," n; i",[40,1517,560],{"class":46},[40,1519,563],{"class":57},[40,1521,1522,1525,1528],{"class":42,"line":106},[40,1523,1524],{"class":57},"        animal.",[40,1526,1527],{"class":53},"makeSound",[40,1529,608],{"class":57},[40,1531,1532],{"class":42,"line":118},[40,1533,121],{"class":57},[40,1535,1536],{"class":42,"line":124},[40,1537,136],{"class":57},[13,1539,1540,1541,1544,1545,1547,1548,1550],{},"可是方法本身可能明明只想处理「动物」。把这种限制说出来，就是",[413,1542,1543],{},"有界泛型类型（Bounded Generic Types）","：声明 ",[37,1546,437],{}," 时顺便规定它必须是某个类型的子类型。一旦有了边界，编译器就知道 ",[37,1549,437],{}," 至少具备哪些方法，方法体里就能放心调用。",[22,1552,1554],{"id":1553},"_042-上界是一个类","0.4.2 上界是一个类",[30,1556,1558],{"className":32,"code":1557,"language":34,"meta":35,"style":35},"static \u003CT extends Animal> void makeSoundNTimes(T animal, int n) {\n    for (int i = 0; i \u003C n; i++) {\n        animal.makeSound();\n    }\n}\n",[37,1559,1560,1582,1606,1614,1618],{"__ignoreMap":35},[40,1561,1562,1564,1566,1569,1571,1573,1576,1578,1580],{"class":42,"line":43},[40,1563,47],{"class":46},[40,1565,516],{"class":46},[40,1567,1568],{"class":57},"T extends Animal",[40,1570,521],{"class":46},[40,1572,524],{"class":46},[40,1574,1575],{"class":53}," makeSoundNTimes",[40,1577,1489],{"class":57},[40,1579,61],{"class":46},[40,1581,1494],{"class":57},[40,1583,1584,1586,1588,1590,1592,1594,1596,1598,1600,1602,1604],{"class":42,"line":67},[40,1585,89],{"class":46},[40,1587,92],{"class":57},[40,1589,61],{"class":46},[40,1591,544],{"class":57},[40,1593,76],{"class":46},[40,1595,80],{"class":79},[40,1597,551],{"class":57},[40,1599,554],{"class":46},[40,1601,1515],{"class":57},[40,1603,560],{"class":46},[40,1605,563],{"class":57},[40,1607,1608,1610,1612],{"class":42,"line":86},[40,1609,1524],{"class":57},[40,1611,1527],{"class":53},[40,1613,608],{"class":57},[40,1615,1616],{"class":42,"line":106},[40,1617,121],{"class":57},[40,1619,1620],{"class":42,"line":118},[40,1621,136],{"class":57},[13,1623,1624,1627,1628,1110,1630,1633,1634,1636,1637,1640,1641,1643,1644,1647],{},[37,1625,1626],{},"\u003CT extends Animal>"," 读作「",[37,1629,437],{},[37,1631,1632],{},"Animal"," 或 ",[37,1635,1632],{}," 的子类型」，于是 ",[37,1638,1639],{},"animal.makeSound()"," 合法——",[37,1642,1632],{}," 上声明的 ",[37,1645,1646],{},"makeSound()","，所有子类型都继承到了。",[13,1649,1650,1651,1653,1654,1657,1658,1661,1662,1421],{},"哪些实参可以传进来？",[37,1652,1632],{}," 自己、子类 ",[37,1655,1656],{},"Cat","、子类 ",[37,1659,1660],{},"Baby"," 都满足 ",[37,1663,1568],{},[30,1665,1667],{"className":32,"code":1666,"language":34,"meta":35,"style":35},"Cat cat = new Cat(\"Mimi\");\nmakeSoundNTimes(cat, 2);\nmakeSoundNTimes(new Animal(\"Generic\"), 1);\n",[37,1668,1669,1688,1701],{"__ignoreMap":35},[40,1670,1671,1674,1676,1678,1681,1683,1686],{"class":42,"line":43},[40,1672,1673],{"class":57},"Cat cat ",[40,1675,76],{"class":46},[40,1677,928],{"class":46},[40,1679,1680],{"class":53}," Cat",[40,1682,58],{"class":57},[40,1684,1685],{"class":589},"\"Mimi\"",[40,1687,593],{"class":57},[40,1689,1690,1693,1696,1699],{"class":42,"line":67},[40,1691,1692],{"class":53},"makeSoundNTimes",[40,1694,1695],{"class":57},"(cat, ",[40,1697,1698],{"class":79},"2",[40,1700,593],{"class":57},[40,1702,1703,1705,1707,1710,1713,1715,1718,1721,1723],{"class":42,"line":86},[40,1704,1692],{"class":53},[40,1706,58],{"class":57},[40,1708,1709],{"class":46},"new",[40,1711,1712],{"class":53}," Animal",[40,1714,58],{"class":57},[40,1716,1717],{"class":589},"\"Generic\"",[40,1719,1720],{"class":57},"), ",[40,1722,712],{"class":79},[40,1724,593],{"class":57},[30,1726,1729],{"className":1727,"code":1728,"language":379,"meta":35},[377],"Mimi says meow\nMimi says meow\nGeneric makes a sound\n",[37,1730,1728],{"__ignoreMap":35},[13,1732,1733],{},"注意这里的「子类型」不只有继承一种来源：实现某个接口（Implements）的类型同样算子类型，所以下面这种把接口当边界的写法也成立。",[22,1735,1737,1738],{"id":1736},"_043-上界是一个接口comparablet","0.4.3 上界是一个接口：",[37,1739,1740],{},"Comparable\u003CT>",[13,1742,1743,1745],{},[37,1744,1740],{}," 是一个泛型接口（Generic Interface），它规定实现类的对象怎么互相比较大小：",[30,1747,1749],{"className":32,"code":1748,"language":34,"meta":35,"style":35},"interface Comparable\u003CT> {\n    \u002F\u002F 比较 this 与 another 的顺序：\n    \u002F\u002F this \u003C another 返回负数\n    \u002F\u002F this > another 返回正数\n    \u002F\u002F 两者相等返回 0\n    int compareTo(T another);\n}\n",[37,1750,1751,1766,1771,1776,1781,1786,1803],{"__ignoreMap":35},[40,1752,1753,1756,1759,1761,1763],{"class":42,"line":43},[40,1754,1755],{"class":46},"interface",[40,1757,1758],{"class":53}," Comparable",[40,1760,554],{"class":57},[40,1762,437],{"class":46},[40,1764,1765],{"class":57},"> {\n",[40,1767,1768],{"class":42,"line":67},[40,1769,1770],{"class":1470},"    \u002F\u002F 比较 this 与 another 的顺序：\n",[40,1772,1773],{"class":42,"line":86},[40,1774,1775],{"class":1470},"    \u002F\u002F this \u003C another 返回负数\n",[40,1777,1778],{"class":42,"line":106},[40,1779,1780],{"class":1470},"    \u002F\u002F this > another 返回正数\n",[40,1782,1783],{"class":42,"line":118},[40,1784,1785],{"class":1470},"    \u002F\u002F 两者相等返回 0\n",[40,1787,1788,1791,1794,1797,1801],{"class":42,"line":124},[40,1789,1790],{"class":46},"    int",[40,1792,1793],{"class":53}," compareTo",[40,1795,1796],{"class":57},"(T ",[40,1798,1800],{"class":1799},"s4XuR","another",[40,1802,593],{"class":57},[40,1804,1805],{"class":42,"line":133},[40,1806,136],{"class":57},[13,1808,1809,1810,1216,1812,1216,1815,1817,1818,1820,1821,416],{},"Java 里所有数字包装类（",[37,1811,690],{},[37,1813,1814],{},"Float",[37,1816,720],{},"……）和 ",[37,1819,747],{}," 都已经实现了这个接口，所以它们自带 ",[37,1822,1823],{},"compareTo()",[13,1825,1826],{},"有了这个边界，就能写出「比较三个同类型对象取最大」的泛型方法：",[30,1828,1830],{"className":32,"code":1829,"language":34,"meta":35,"style":35},"static \u003CT extends Comparable\u003CT>> T max3(T a, T b, T c) {\n    T max = a;\n    if (b.compareTo(max) > 0) {\n        max = b;\n    }\n    if (c.compareTo(max) > 0) {\n        max = c;\n    }\n    return max;\n}\n",[37,1831,1832,1856,1866,1885,1895,1899,1916,1925,1929,1936],{"__ignoreMap":35},[40,1833,1834,1836,1838,1841,1843,1845,1848,1850,1853],{"class":42,"line":43},[40,1835,47],{"class":46},[40,1837,516],{"class":46},[40,1839,1840],{"class":57},"T extends Comparable",[40,1842,554],{"class":46},[40,1844,437],{"class":57},[40,1846,1847],{"class":46},">>",[40,1849,1036],{"class":57},[40,1851,1852],{"class":53},"max3",[40,1854,1855],{"class":57},"(T a, T b, T c) {\n",[40,1857,1858,1861,1863],{"class":42,"line":67},[40,1859,1860],{"class":57},"    T max ",[40,1862,76],{"class":46},[40,1864,1865],{"class":57}," a;\n",[40,1867,1868,1870,1873,1876,1879,1881,1883],{"class":42,"line":86},[40,1869,1056],{"class":46},[40,1871,1872],{"class":57}," (b.",[40,1874,1875],{"class":53},"compareTo",[40,1877,1878],{"class":57},"(max) ",[40,1880,521],{"class":46},[40,1882,80],{"class":79},[40,1884,563],{"class":57},[40,1886,1887,1890,1892],{"class":42,"line":106},[40,1888,1889],{"class":57},"        max ",[40,1891,76],{"class":46},[40,1893,1894],{"class":57}," b;\n",[40,1896,1897],{"class":42,"line":118},[40,1898,121],{"class":57},[40,1900,1901,1903,1906,1908,1910,1912,1914],{"class":42,"line":124},[40,1902,1056],{"class":46},[40,1904,1905],{"class":57}," (c.",[40,1907,1875],{"class":53},[40,1909,1878],{"class":57},[40,1911,521],{"class":46},[40,1913,80],{"class":79},[40,1915,563],{"class":57},[40,1917,1918,1920,1922],{"class":42,"line":133},[40,1919,1889],{"class":57},[40,1921,76],{"class":46},[40,1923,1924],{"class":57}," c;\n",[40,1926,1927],{"class":42,"line":139},[40,1928,121],{"class":57},[40,1930,1931,1933],{"class":42,"line":146},[40,1932,127],{"class":46},[40,1934,1935],{"class":57}," max;\n",[40,1937,1938],{"class":42,"line":162},[40,1939,136],{"class":57},[30,1941,1943],{"className":32,"code":1942,"language":34,"meta":35,"style":35},"System.out.println(max3(4, 9, 2));\nSystem.out.println(max3(1.5, 3.5, 2.5));\nSystem.out.println(max3(\"pear\", \"apple\", \"plum\"));\n",[37,1944,1945,1971,1996],{"__ignoreMap":35},[40,1946,1947,1949,1951,1953,1955,1957,1960,1962,1965,1967,1969],{"class":42,"line":43},[40,1948,951],{"class":57},[40,1950,605],{"class":53},[40,1952,58],{"class":57},[40,1954,1852],{"class":53},[40,1956,58],{"class":57},[40,1958,1959],{"class":79},"4",[40,1961,704],{"class":57},[40,1963,1964],{"class":79},"9",[40,1966,704],{"class":57},[40,1968,1698],{"class":79},[40,1970,1146],{"class":57},[40,1972,1973,1975,1977,1979,1981,1983,1986,1988,1990,1992,1994],{"class":42,"line":67},[40,1974,951],{"class":57},[40,1976,605],{"class":53},[40,1978,58],{"class":57},[40,1980,1852],{"class":53},[40,1982,58],{"class":57},[40,1984,1985],{"class":79},"1.5",[40,1987,704],{"class":57},[40,1989,740],{"class":79},[40,1991,704],{"class":57},[40,1993,730],{"class":79},[40,1995,1146],{"class":57},[40,1997,1998,2000,2002,2004,2006,2008,2010,2012,2015,2017,2020],{"class":42,"line":86},[40,1999,951],{"class":57},[40,2001,605],{"class":53},[40,2003,58],{"class":57},[40,2005,1852],{"class":53},[40,2007,58],{"class":57},[40,2009,767],{"class":589},[40,2011,704],{"class":57},[40,2013,2014],{"class":589},"\"apple\"",[40,2016,704],{"class":57},[40,2018,2019],{"class":589},"\"plum\"",[40,2021,1146],{"class":57},[30,2023,2026],{"className":2024,"code":2025,"language":379,"meta":35},[377],"9\n3.5\nplum\n",[37,2027,2025],{"__ignoreMap":35},[13,2029,2030],{},"三点说明：",[639,2032,2033,2048,2069],{},[642,2034,2035,2036,2038,2039,2041,2042,2044,2045,2047],{},"因为 ",[37,2037,437],{}," 实现了 ",[37,2040,1740],{},"，方法体里才能调用 ",[37,2043,1823],{},"。如果没有这个边界，",[37,2046,1852],{}," 连比较都没法写。",[642,2049,2050,416,2060,2063,2064,1110,2066,2068],{},[413,2051,2052,2053,2056,2057],{},"接口在泛型里也用 ",[37,2054,2055],{},"extends","，不用 ",[37,2058,2059],{},"implements",[37,2061,2062],{},"\u003CT extends Comparable\u003CT>>"," 的意思是「",[37,2065,437],{},[37,2067,1740],{}," 的子类型」，可能是继承来的，也可能是实现来的。",[642,2070,2071,2072,1418,2075,2077],{},"边界的写法是 ",[37,2073,2074],{},"T extends Comparable\u003CT>",[37,2076,1840],{},"：既然现在有泛型，就顺手把「和谁比较」也说清楚，编译器检查得更严。",[22,2079,2081],{"id":2080},"_044-多重边界multiple-bounds","0.4.4 多重边界（Multiple Bounds）",[13,2083,2084,2085,1421],{},"一个泛型类型可以同时满足多个上界，语法是 ",[37,2086,2087],{},"\u003CT extends SuperClass & Interface1 & Interface2>",[30,2089,2091],{"className":32,"code":2090,"language":34,"meta":35,"style":35},"static \u003CT extends Number & Comparable\u003CT>> T maxOfNumber(T a, T b) {\n    return a.compareTo(b) >= 0 ? a : b;\n}\n",[37,2092,2093,2121,2147],{"__ignoreMap":35},[40,2094,2095,2097,2099,2102,2105,2107,2109,2111,2113,2115,2118],{"class":42,"line":43},[40,2096,47],{"class":46},[40,2098,516],{"class":46},[40,2100,2101],{"class":57},"T extends Number ",[40,2103,2104],{"class":46},"&",[40,2106,1758],{"class":57},[40,2108,554],{"class":46},[40,2110,437],{"class":57},[40,2112,1847],{"class":46},[40,2114,1036],{"class":57},[40,2116,2117],{"class":53},"maxOfNumber",[40,2119,2120],{"class":57},"(T a, T b) {\n",[40,2122,2123,2125,2128,2130,2133,2135,2137,2140,2143,2145],{"class":42,"line":67},[40,2124,127],{"class":46},[40,2126,2127],{"class":57}," a.",[40,2129,1875],{"class":53},[40,2131,2132],{"class":57},"(b) ",[40,2134,1062],{"class":46},[40,2136,80],{"class":79},[40,2138,2139],{"class":46}," ?",[40,2141,2142],{"class":57}," a ",[40,2144,100],{"class":46},[40,2146,1894],{"class":57},[40,2148,2149],{"class":42,"line":86},[40,2150,136],{"class":57},[30,2152,2154],{"className":32,"code":2153,"language":34,"meta":35,"style":35},"System.out.println(maxOfNumber(12, 7));\n",[37,2155,2156],{"__ignoreMap":35},[40,2157,2158,2160,2162,2164,2166,2168,2171,2173,2176],{"class":42,"line":43},[40,2159,951],{"class":57},[40,2161,605],{"class":53},[40,2163,58],{"class":57},[40,2165,2117],{"class":53},[40,2167,58],{"class":57},[40,2169,2170],{"class":79},"12",[40,2172,704],{"class":57},[40,2174,2175],{"class":79},"7",[40,2177,1146],{"class":57},[30,2179,2182],{"className":2180,"code":2181,"language":379,"meta":35},[377],"12\n",[37,2183,2181],{"__ignoreMap":35},[13,2185,2186],{},"两个细节：",[639,2188,2189,2198],{},[642,2190,2191,2194,2195,2197],{},[413,2192,2193],{},"类必须写在第一位","，接口跟在后面，",[37,2196,2104],{}," 连接；多个上界里最多只有一个类。",[642,2199,2200,2201,2204,2205,2208,2209,2211,2212,416],{},"有了 ",[37,2202,2203],{},"Number"," 边界，方法体里可以调用 ",[37,2206,2207],{},"doubleValue()"," 之类的方法；有了 ",[37,2210,1740],{}," 边界，还能调 ",[37,2213,1823],{},[22,2215,2217],{"id":2216},"_045-借助多态把方法体写简单","0.4.5 借助多态把方法体写简单",[13,2219,2220,2222,2223,2225,2226,2228],{},[37,2221,2203],{}," 是所有数字包装类的父类，它定义了 ",[37,2224,2207],{},"，把值以 ",[37,2227,232],{}," 返回。于是「求一串数字的和」可以这样写：",[30,2230,2232],{"className":32,"code":2231,"language":34,"meta":35,"style":35},"static \u003CT extends Number> double sum(List\u003CT> list) {\n    double total = 0;\n    for (Number e : list) {\n        total += e.doubleValue();\n    }\n    return total;\n}\n",[37,2233,2234,2261,2273,2284,2298,2302,2308],{"__ignoreMap":35},[40,2235,2236,2238,2240,2243,2245,2247,2249,2252,2254,2256,2258],{"class":42,"line":43},[40,2237,47],{"class":46},[40,2239,516],{"class":46},[40,2241,2242],{"class":57},"T extends Number",[40,2244,521],{"class":46},[40,2246,50],{"class":46},[40,2248,54],{"class":53},[40,2250,2251],{"class":57},"(List",[40,2253,554],{"class":46},[40,2255,437],{"class":57},[40,2257,521],{"class":46},[40,2259,2260],{"class":57}," list) {\n",[40,2262,2263,2265,2267,2269,2271],{"class":42,"line":67},[40,2264,70],{"class":46},[40,2266,73],{"class":57},[40,2268,76],{"class":46},[40,2270,80],{"class":79},[40,2272,83],{"class":57},[40,2274,2275,2277,2280,2282],{"class":42,"line":86},[40,2276,89],{"class":46},[40,2278,2279],{"class":57}," (Number e ",[40,2281,100],{"class":46},[40,2283,2260],{"class":57},[40,2285,2286,2288,2290,2293,2296],{"class":42,"line":106},[40,2287,109],{"class":57},[40,2289,112],{"class":46},[40,2291,2292],{"class":57}," e.",[40,2294,2295],{"class":53},"doubleValue",[40,2297,608],{"class":57},[40,2299,2300],{"class":42,"line":118},[40,2301,121],{"class":57},[40,2303,2304,2306],{"class":42,"line":124},[40,2305,127],{"class":46},[40,2307,130],{"class":57},[40,2309,2310],{"class":42,"line":133},[40,2311,136],{"class":57},[30,2313,2315],{"className":32,"code":2314,"language":34,"meta":35,"style":35},"List\u003CInteger> intList = new ArrayList\u003C>();\nintList.add(1);\nintList.add(2);\nintList.add(3);\nList\u003CDouble> doubleList = new ArrayList\u003C>();\ndoubleList.add(1.1);\ndoubleList.add(2.2);\nSystem.out.println(sum(intList));\nSystem.out.println(sum(doubleList));\n",[37,2316,2317,2332,2346,2358,2371,2386,2400,2413,2427],{"__ignoreMap":35},[40,2318,2319,2321,2323,2326,2328,2330],{"class":42,"line":43},[40,2320,918],{"class":57},[40,2322,690],{"class":46},[40,2324,2325],{"class":57},"> intList ",[40,2327,76],{"class":46},[40,2329,928],{"class":46},[40,2331,931],{"class":57},[40,2333,2334,2337,2340,2342,2344],{"class":42,"line":67},[40,2335,2336],{"class":57},"intList.",[40,2338,2339],{"class":53},"add",[40,2341,58],{"class":57},[40,2343,712],{"class":79},[40,2345,593],{"class":57},[40,2347,2348,2350,2352,2354,2356],{"class":42,"line":86},[40,2349,2336],{"class":57},[40,2351,2339],{"class":53},[40,2353,58],{"class":57},[40,2355,1698],{"class":79},[40,2357,593],{"class":57},[40,2359,2360,2362,2364,2366,2369],{"class":42,"line":106},[40,2361,2336],{"class":57},[40,2363,2339],{"class":53},[40,2365,58],{"class":57},[40,2367,2368],{"class":79},"3",[40,2370,593],{"class":57},[40,2372,2373,2375,2377,2380,2382,2384],{"class":42,"line":118},[40,2374,918],{"class":57},[40,2376,720],{"class":46},[40,2378,2379],{"class":57},"> doubleList ",[40,2381,76],{"class":46},[40,2383,928],{"class":46},[40,2385,931],{"class":57},[40,2387,2388,2391,2393,2395,2398],{"class":42,"line":124},[40,2389,2390],{"class":57},"doubleList.",[40,2392,2339],{"class":53},[40,2394,58],{"class":57},[40,2396,2397],{"class":79},"1.1",[40,2399,593],{"class":57},[40,2401,2402,2404,2406,2408,2411],{"class":42,"line":133},[40,2403,2390],{"class":57},[40,2405,2339],{"class":53},[40,2407,58],{"class":57},[40,2409,2410],{"class":79},"2.2",[40,2412,593],{"class":57},[40,2414,2415,2417,2419,2421,2424],{"class":42,"line":139},[40,2416,951],{"class":57},[40,2418,605],{"class":53},[40,2420,58],{"class":57},[40,2422,2423],{"class":53},"sum",[40,2425,2426],{"class":57},"(intList));\n",[40,2428,2429,2431,2433,2435,2437],{"class":42,"line":146},[40,2430,951],{"class":57},[40,2432,605],{"class":53},[40,2434,58],{"class":57},[40,2436,2423],{"class":53},[40,2438,2439],{"class":57},"(doubleList));\n",[30,2441,2444],{"className":2442,"code":2443,"language":379,"meta":35},[377],"6.0\n3.3000000000000003\n",[37,2445,2443],{"__ignoreMap":35},[13,2447,2448,2451,2452,1418,2455,2458,2459,2461,2462,2464,2465,2467,2468,2471,2472,2474,2475,416],{},[37,2449,2450],{},"for"," 循环里声明的是 ",[37,2453,2454],{},"Number e",[37,2456,2457],{},"T e","：既然 ",[37,2460,437],{}," 的上界已经是 ",[37,2463,2203],{},"，直接用父类型接收更省事，",[37,2466,2207],{}," 照样能调——这是多态在泛型方法体里的用法。第二个输出也顺便暴露了浮点数精度问题：",[37,2469,2470],{},"1.1 + 2.2"," 在 ",[37,2473,232],{}," 下并不精确等于 ",[37,2476,2477],{},"3.3",[13,2479,2480,2481,2483,2484,2486,2487,2489],{},"那这里还需要 ",[37,2482,437],{}," 吗？方法体里其实没用到 ",[37,2485,437],{},"，只用到「元素是 ",[37,2488,2203],{}," 的子类型」。这种情况下更好的写法是用通配符（Wildcard）表达同样的约束：",[30,2491,2493],{"className":32,"code":2492,"language":34,"meta":35,"style":35},"static double sum(List\u003C? extends Number> list) {\n    double total = 0;\n    for (Number e : list) {\n        total += e.doubleValue();\n    }\n    return total;\n}\n",[37,2494,2495,2515,2527,2537,2549,2553,2559],{"__ignoreMap":35},[40,2496,2497,2499,2501,2503,2505,2508,2511,2513],{"class":42,"line":43},[40,2498,47],{"class":46},[40,2500,50],{"class":46},[40,2502,54],{"class":53},[40,2504,2251],{"class":57},[40,2506,2507],{"class":46},"\u003C?",[40,2509,2510],{"class":57}," extends Number",[40,2512,521],{"class":46},[40,2514,2260],{"class":57},[40,2516,2517,2519,2521,2523,2525],{"class":42,"line":67},[40,2518,70],{"class":46},[40,2520,73],{"class":57},[40,2522,76],{"class":46},[40,2524,80],{"class":79},[40,2526,83],{"class":57},[40,2528,2529,2531,2533,2535],{"class":42,"line":86},[40,2530,89],{"class":46},[40,2532,2279],{"class":57},[40,2534,100],{"class":46},[40,2536,2260],{"class":57},[40,2538,2539,2541,2543,2545,2547],{"class":42,"line":106},[40,2540,109],{"class":57},[40,2542,112],{"class":46},[40,2544,2292],{"class":57},[40,2546,2295],{"class":53},[40,2548,608],{"class":57},[40,2550,2551],{"class":42,"line":118},[40,2552,121],{"class":57},[40,2554,2555,2557],{"class":42,"line":124},[40,2556,127],{"class":46},[40,2558,130],{"class":57},[40,2560,2561],{"class":42,"line":133},[40,2562,136],{"class":57},[13,2564,2565,2568,2569,2571,2572,2575,2576,1216,2579,2582],{},[37,2566,2567],{},"List\u003C? extends Number>"," 读作「元素类型是 ",[37,2570,2203],{}," 或其子类型的任意 ",[37,2573,2574],{},"List","」，",[37,2577,2578],{},"List\u003CInteger>",[37,2580,2581],{},"List\u003CDouble>"," 都能传进来，而且不需要在方法名前面再声明一个只用一次的类型参数。通配符是泛型里另一个独立话题，这里先知道「约束参数、而不是约束自己声明的类型变量」时它更合适就够了。",[17,2584,2586],{"id":2585},"_05-泛型类generic-classes","0.5 泛型类（Generic Classes）",[22,2588,2590],{"id":2589},"_051-定义与实例化","0.5.1 定义与实例化",[13,2592,2593,2594,2597],{},"泛型类型同样可以先写在",[413,2595,2596],{},"类名后面","，然后在类里到处使用：",[30,2599,2601],{"className":32,"code":2600,"language":34,"meta":35,"style":35},"class Box\u003CT> {\n    private T value;\n\n    Box() {\n    }\n\n    Box(T value) {\n        this.value = value;\n    }\n\n    void set(T newValue) {\n        this.value = newValue;\n    }\n\n    T get() {\n        return value;\n    }\n}\n",[37,2602,2603,2617,2625,2629,2637,2641,2645,2656,2669,2673,2677,2692,2703,2707,2711,2721,2727,2731],{"__ignoreMap":35},[40,2604,2605,2608,2611,2613,2615],{"class":42,"line":43},[40,2606,2607],{"class":46},"class",[40,2609,2610],{"class":53}," Box",[40,2612,554],{"class":57},[40,2614,437],{"class":46},[40,2616,1765],{"class":57},[40,2618,2619,2622],{"class":42,"line":67},[40,2620,2621],{"class":46},"    private",[40,2623,2624],{"class":57}," T value;\n",[40,2626,2627],{"class":42,"line":86},[40,2628,143],{"emptyLinePlaceholder":142},[40,2630,2631,2634],{"class":42,"line":106},[40,2632,2633],{"class":53},"    Box",[40,2635,2636],{"class":57},"() {\n",[40,2638,2639],{"class":42,"line":118},[40,2640,121],{"class":57},[40,2642,2643],{"class":42,"line":124},[40,2644,143],{"emptyLinePlaceholder":142},[40,2646,2647,2649,2651,2654],{"class":42,"line":133},[40,2648,2633],{"class":53},[40,2650,1796],{"class":57},[40,2652,2653],{"class":1799},"value",[40,2655,563],{"class":57},[40,2657,2658,2661,2664,2666],{"class":42,"line":139},[40,2659,2660],{"class":79},"        this",[40,2662,2663],{"class":57},".value ",[40,2665,76],{"class":46},[40,2667,2668],{"class":57}," value;\n",[40,2670,2671],{"class":42,"line":146},[40,2672,121],{"class":57},[40,2674,2675],{"class":42,"line":162},[40,2676,143],{"emptyLinePlaceholder":142},[40,2678,2679,2682,2685,2687,2690],{"class":42,"line":175},[40,2680,2681],{"class":46},"    void",[40,2683,2684],{"class":53}," set",[40,2686,1796],{"class":57},[40,2688,2689],{"class":1799},"newValue",[40,2691,563],{"class":57},[40,2693,2694,2696,2698,2700],{"class":42,"line":190},[40,2695,2660],{"class":79},[40,2697,2663],{"class":57},[40,2699,76],{"class":46},[40,2701,2702],{"class":57}," newValue;\n",[40,2704,2705],{"class":42,"line":199},[40,2706,121],{"class":57},[40,2708,2709],{"class":42,"line":204},[40,2710,143],{"emptyLinePlaceholder":142},[40,2712,2713,2716,2719],{"class":42,"line":211},[40,2714,2715],{"class":57},"    T ",[40,2717,2718],{"class":53},"get",[40,2720,2636],{"class":57},[40,2722,2723,2725],{"class":42,"line":216},[40,2724,1080],{"class":46},[40,2726,2668],{"class":57},[40,2728,2729],{"class":42,"line":221},[40,2730,121],{"class":57},[40,2732,2733],{"class":42,"line":237},[40,2734,136],{"class":57},[13,2736,2737,2738,2741,2742,2744],{},"跟泛型方法不同的是，泛型类的类型参数在",[413,2739,2740],{},"创建对象时","指定，之后类里所有的 ",[37,2743,437],{}," 都被替换成那个类型：",[30,2746,2748],{"className":32,"code":2747,"language":34,"meta":35,"style":35},"Box\u003CString> strBox = new Box\u003C>(\"a string\");\nBox\u003CInteger> intBox = new Box\u003C>(10);\nSystem.out.println(strBox.get());\nSystem.out.println(intBox.get());\n",[37,2749,2750,2772,2792,2805],{"__ignoreMap":35},[40,2751,2752,2755,2757,2760,2762,2764,2767,2770],{"class":42,"line":43},[40,2753,2754],{"class":57},"Box\u003C",[40,2756,747],{"class":46},[40,2758,2759],{"class":57},"> strBox ",[40,2761,76],{"class":46},[40,2763,928],{"class":46},[40,2765,2766],{"class":57}," Box\u003C>(",[40,2768,2769],{"class":589},"\"a string\"",[40,2771,593],{"class":57},[40,2773,2774,2776,2778,2781,2783,2785,2787,2790],{"class":42,"line":67},[40,2775,2754],{"class":57},[40,2777,690],{"class":46},[40,2779,2780],{"class":57},"> intBox ",[40,2782,76],{"class":46},[40,2784,928],{"class":46},[40,2786,2766],{"class":57},[40,2788,2789],{"class":79},"10",[40,2791,593],{"class":57},[40,2793,2794,2796,2798,2801,2803],{"class":42,"line":86},[40,2795,951],{"class":57},[40,2797,605],{"class":53},[40,2799,2800],{"class":57},"(strBox.",[40,2802,2718],{"class":53},[40,2804,973],{"class":57},[40,2806,2807,2809,2811,2814,2816],{"class":42,"line":106},[40,2808,951],{"class":57},[40,2810,605],{"class":53},[40,2812,2813],{"class":57},"(intBox.",[40,2815,2718],{"class":53},[40,2817,973],{"class":57},[30,2819,2822],{"className":2820,"code":2821,"language":379,"meta":35},[377],"a string\n10\n",[37,2823,2821],{"__ignoreMap":35},[13,2825,2826],{},"几点：",[639,2828,2829,2848,2862],{},[642,2830,2831,875,2834,2836,2837,2839,2840,2843,2844,2847],{},[37,2832,2833],{},"Box\u003CString>",[37,2835,437],{}," 变成 ",[37,2838,747],{}," 之后，",[37,2841,2842],{},"set(T)"," 就等价于 ",[37,2845,2846],{},"set(String)","，往里塞别的类型编译不过。",[642,2849,2850,2851,2854,2855,2857,2858,2861],{},"等号右边的 ",[37,2852,2853],{},"new Box\u003C>()"," 用了菱形运算符（Diamond Operator），尖括号里可以不写类型，编译器从左边 ",[37,2856,2833],{}," 推断。写成 ",[37,2859,2860],{},"new Box\u003CString>()"," 也完全合法。",[642,2863,2864,2865,2867],{},"泛型类里 ",[37,2866,437],{}," 能出现的位置和泛型方法一样：实例属性（Instance Attribute）的类型、方法参数（Method Arguments）的类型、方法返回值（Return Type）的类型。",[22,2869,2871],{"id":2870},"_052-集合框架collections-framework中的泛型类","0.5.2 集合框架（Collections Framework）中的泛型类",[13,2873,2874,2875,1216,2878,2881],{},"Java 集合框架里到处是泛型类，",[37,2876,2877],{},"ArrayList\u003CE>",[37,2879,2880],{},"HashMap\u003CK,V>"," 的类名后面就带着类型参数。它们的声明大致是：",[30,2883,2885],{"className":32,"code":2884,"language":34,"meta":35,"style":35},"class ArrayList\u003CE> {\n    ArrayList() {}\n    \u002F\u002F 其余代码省略\n    void add(int index, E element) { \u002F* 方法体省略 *\u002F }\n    boolean add(E e) { \u002F* 方法体省略 *\u002F }\n}\n",[37,2886,2887,2900,2908,2913,2942,2960],{"__ignoreMap":35},[40,2888,2889,2891,2894,2896,2898],{"class":42,"line":43},[40,2890,2607],{"class":46},[40,2892,2893],{"class":53}," ArrayList",[40,2895,554],{"class":57},[40,2897,651],{"class":46},[40,2899,1765],{"class":57},[40,2901,2902,2905],{"class":42,"line":67},[40,2903,2904],{"class":53},"    ArrayList",[40,2906,2907],{"class":57},"() {}\n",[40,2909,2910],{"class":42,"line":86},[40,2911,2912],{"class":1470},"    \u002F\u002F 其余代码省略\n",[40,2914,2915,2917,2920,2922,2924,2927,2930,2933,2936,2939],{"class":42,"line":106},[40,2916,2681],{"class":46},[40,2918,2919],{"class":53}," add",[40,2921,58],{"class":57},[40,2923,61],{"class":46},[40,2925,2926],{"class":1799}," index",[40,2928,2929],{"class":57},", E ",[40,2931,2932],{"class":1799},"element",[40,2934,2935],{"class":57},") { ",[40,2937,2938],{"class":1470},"\u002F* 方法体省略 *\u002F",[40,2940,2941],{"class":57}," }\n",[40,2943,2944,2947,2949,2952,2954,2956,2958],{"class":42,"line":118},[40,2945,2946],{"class":46},"    boolean",[40,2948,2919],{"class":53},[40,2950,2951],{"class":57},"(E ",[40,2953,387],{"class":1799},[40,2955,2935],{"class":57},[40,2957,2938],{"class":1470},[40,2959,2941],{"class":57},[40,2961,2962],{"class":42,"line":124},[40,2963,136],{"class":57},[13,2965,2966,2967,2970,2971,875,2974,818,2976,2978,2979,2981,2982,2985,2986,1421],{},"读这种声明的方法是「",[413,2968,2969],{},"先看类名后面有几个类型参数，再去找方法签名里它们出现在哪","」。",[37,2972,2973],{},"add(int index, E element)",[37,2975,651],{},[37,2977,2877],{}," 的 ",[37,2980,651],{},"，所以创建 ",[37,2983,2984],{},"ArrayList\u003CString>"," 之后，第二个实参必须是 ",[37,2987,747],{},[30,2989,2991],{"className":32,"code":2990,"language":34,"meta":35,"style":35},"ArrayList\u003CString> nameList = new ArrayList\u003C>();\nnameList.add(0, \"Grace Hopper\");\nnameList.add(0, \"Alan Turing\");\nSystem.out.println(nameList);\n",[37,2992,2993,3009,3028,3045],{"__ignoreMap":35},[40,2994,2995,2998,3000,3003,3005,3007],{"class":42,"line":43},[40,2996,2997],{"class":57},"ArrayList\u003C",[40,2999,747],{"class":46},[40,3001,3002],{"class":57},"> nameList ",[40,3004,76],{"class":46},[40,3006,928],{"class":46},[40,3008,931],{"class":57},[40,3010,3011,3014,3016,3018,3021,3023,3026],{"class":42,"line":67},[40,3012,3013],{"class":57},"nameList.",[40,3015,2339],{"class":53},[40,3017,58],{"class":57},[40,3019,3020],{"class":79},"0",[40,3022,704],{"class":57},[40,3024,3025],{"class":589},"\"Grace Hopper\"",[40,3027,593],{"class":57},[40,3029,3030,3032,3034,3036,3038,3040,3043],{"class":42,"line":86},[40,3031,3013],{"class":57},[40,3033,2339],{"class":53},[40,3035,58],{"class":57},[40,3037,3020],{"class":79},[40,3039,704],{"class":57},[40,3041,3042],{"class":589},"\"Alan Turing\"",[40,3044,593],{"class":57},[40,3046,3047,3049,3051],{"class":42,"line":106},[40,3048,951],{"class":57},[40,3050,605],{"class":53},[40,3052,3053],{"class":57},"(nameList);\n",[30,3055,3058],{"className":3056,"code":3057,"language":379,"meta":35},[377],"[Alan Turing, Grace Hopper]\n",[37,3059,3057],{"__ignoreMap":35},[13,3061,3062,1421,3065,2978,3067,3070],{},[413,3063,3064],{},"练习",[37,3066,2880],{},[37,3068,3069],{},"put(K key, V value)"," 里，键和值分别是哪个类型参数？用「名字 → 名字长度」建一个映射。",[13,3072,3073,3074,3076,3077,3079,3080,3083],{},"对上面这份签名来说，",[37,3075,657],{}," 决定键、",[37,3078,663],{}," 决定值，所以声明成 ",[37,3081,3082],{},"HashMap\u003CString, Integer>"," 即可：",[30,3085,3087],{"className":32,"code":3086,"language":34,"meta":35,"style":35},"Map\u003CString, Integer> nameToLength = new HashMap\u003C>();\nnameToLength.put(\"Grace Hopper\", \"Grace Hopper\".length());\nnameToLength.put(\"Alan Turing\", \"Alan Turing\".length());\nSystem.out.println(nameToLength);\n",[37,3088,3089,3110,3134,3154],{"__ignoreMap":35},[40,3090,3091,3094,3096,3098,3100,3103,3105,3107],{"class":42,"line":43},[40,3092,3093],{"class":57},"Map\u003C",[40,3095,747],{"class":46},[40,3097,704],{"class":57},[40,3099,690],{"class":46},[40,3101,3102],{"class":57},"> nameToLength ",[40,3104,76],{"class":46},[40,3106,928],{"class":46},[40,3108,3109],{"class":57}," HashMap\u003C>();\n",[40,3111,3112,3115,3118,3120,3122,3124,3126,3129,3132],{"class":42,"line":67},[40,3113,3114],{"class":57},"nameToLength.",[40,3116,3117],{"class":53},"put",[40,3119,58],{"class":57},[40,3121,3025],{"class":589},[40,3123,704],{"class":57},[40,3125,3025],{"class":589},[40,3127,3128],{"class":57},".",[40,3130,3131],{"class":53},"length",[40,3133,973],{"class":57},[40,3135,3136,3138,3140,3142,3144,3146,3148,3150,3152],{"class":42,"line":86},[40,3137,3114],{"class":57},[40,3139,3117],{"class":53},[40,3141,58],{"class":57},[40,3143,3042],{"class":589},[40,3145,704],{"class":57},[40,3147,3042],{"class":589},[40,3149,3128],{"class":57},[40,3151,3131],{"class":53},[40,3153,973],{"class":57},[40,3155,3156,3158,3160],{"class":42,"line":106},[40,3157,951],{"class":57},[40,3159,605],{"class":53},[40,3161,3162],{"class":57},"(nameToLength);\n",[30,3164,3167],{"className":3165,"code":3166,"language":379,"meta":35},[377],"{Alan Turing=11, Grace Hopper=12}\n",[37,3168,3166],{"__ignoreMap":35},[13,3170,3171,3174,3175,3178,3179,3181,3182,3184,3185,416],{},[37,3172,3173],{},"put(\"Grace Hopper\", ...)"," 能编译通过是因为第二个实参 ",[37,3176,3177],{},"\"Grace Hopper\".length()"," 的类型是 ",[37,3180,61],{},"，自动装箱成 ",[37,3183,690],{}," 之后正好匹配 ",[37,3186,663],{},[17,3188,3190],{"id":3189},"_06-小结","0.6 小结",[455,3192,3193,3206],{},[458,3194,3195],{},[461,3196,3197,3200,3203],{},[464,3198,3199],{},"概念",[464,3201,3202],{},"写法",[464,3204,3205],{},"关键点",[471,3207,3208,3224,3240,3251,3267,3282,3295,3307],{},[461,3209,3210,3213,3218],{},[476,3211,3212],{},"泛型方法",[476,3214,3215],{},[37,3216,3217],{},"static \u003CT> void m(T[] arr)",[476,3219,3220,3221,3223],{},"类型参数写在修饰符与返回类型之间，",[37,3222,437],{}," 能用作参数、变量、返回类型",[461,3225,3226,3229,3234],{},[476,3227,3228],{},"类型推断",[476,3230,3231],{},[37,3232,3233],{},"m(intArr)",[476,3235,3236,3237,3239],{},"从实参推断 ",[37,3238,437],{},"，一般不用手写尖括号",[461,3241,3242,3245,3248],{},[476,3243,3244],{},"实现机制",[476,3246,3247],{},"—",[476,3249,3250],{},"编译期检查 + 类型擦除（Type Erasure），运行期拿不到实际类型参数",[461,3252,3253,3256,3260],{},[476,3254,3255],{},"泛型只能装引用类型",[476,3257,3258],{},[37,3259,2578],{},[476,3261,3262,3263,3266],{},"基本类型要装箱，",[37,3264,3265],{},"List\u003Cint>"," 编译不过",[461,3268,3269,3272,3276],{},[476,3270,3271],{},"有界泛型",[476,3273,3274],{},[37,3275,1626],{},[476,3277,3278,3279,3281],{},"接口也用 ",[37,3280,2055],{},"；有边界才能调用边界上的方法",[461,3283,3284,3287,3292],{},[476,3285,3286],{},"多重边界",[476,3288,3289],{},[37,3290,3291],{},"\u003CT extends Number & Comparable\u003CT>>",[476,3293,3294],{},"类写在第一位，多个上界最多一个类",[461,3296,3297,3300,3304],{},[476,3298,3299],{},"通配符",[476,3301,3302],{},[37,3303,2567],{},[476,3305,3306],{},"只约束「传进来的参数」时，比额外声明一个类型参数更合适",[461,3308,3309,3312,3317],{},[476,3310,3311],{},"泛型类",[476,3313,3314],{},[37,3315,3316],{},"class Box\u003CT>",[476,3318,3319],{},"类型参数在创建对象时指定，集合框架普遍这么用",[3321,3322,3323],"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 .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":35,"searchDepth":86,"depth":86,"links":3325},[3326,3327,3329,3330,3331,3332,3333,3334,3335,3336,3337,3338,3339,3341,3342,3343,3344],{"id":24,"depth":67,"text":25},{"id":294,"depth":67,"text":3328},"0.1.2 换成 Object[] 行不行？",{"id":426,"depth":67,"text":427},{"id":452,"depth":67,"text":453},{"id":496,"depth":67,"text":497},{"id":799,"depth":67,"text":800},{"id":864,"depth":67,"text":865},{"id":1011,"depth":67,"text":1012},{"id":1205,"depth":67,"text":1206},{"id":1262,"depth":67,"text":1263},{"id":1434,"depth":67,"text":1435},{"id":1553,"depth":67,"text":1554},{"id":1736,"depth":67,"text":3340},"0.4.3 上界是一个接口：Comparable\u003CT>",{"id":2080,"depth":67,"text":2081},{"id":2216,"depth":67,"text":2217},{"id":2589,"depth":67,"text":2590},{"id":2870,"depth":67,"text":2871},"学习",null,"2026-10-13","学习 Java 泛型时的个人笔记：从重复的重载方法与多态尝试讲起，再到泛型方法与类型推断、有界泛型类型、Comparable 与 Number 边界，以及泛型类在集合中的用法。示例代码均为自写，运行结果来自本地实测。","md",false,{},"\u002Fposts\u002Fjava\u002Fjava-generics-notes",{"title":5,"description":3348},"Java 学习笔记","posts\u002Fjava\u002Fjava-generics-notes",[3357,3358,3359],"Java","泛型","学习笔记","rPNsjjxlr5pE12LTOgnGefQJfL7wA7J0nKLqpVo-1Pw",[3362,3369,3375,3381,3387,3393,3398,3403,3408,3413,3418,3423,3428,3433,3438,3443,3448,3453,3458,3463,3468,3473,3478,3483,3488,3493,3498,3503,3508,3514,3518,3522,3526,3530,3534,3538,3542,3546,3550,3554,3558,3562,3563,3569,3573,3577,3581,3585,3589,3593,3597,3602,3606,3610,3615,3620,3626,3630,3635,3639],{"path":3363,"title":3364,"description":3365,"date":3366,"series":3367,"seriesOrder":3368},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-28","软件分析与设计笔记 28：draw.io 作图手册：五张图的符号位置、落笔顺序与自检","不模拟软件界面，只讲清三件事：每个 UML 符号在 draw.io 里搜什么词、一张图该按什么顺序落笔、画完拿什么标准自查。覆盖用例图、活动图、顺序图、类图、界面草图五种图纸，末尾附「最容易画反的十二笔」总检表。","2026-11-19","软件分析与设计笔记",28,{"path":3370,"title":3371,"description":3372,"date":3373,"series":3367,"seriesOrder":3374},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-27","软件分析与设计笔记 27：Paper 1 应试：从读案例到交卷的一次完整演算","拿一份完整的案例，从头到尾走一遍：怎么读案例列角色、怎么在四个小问之间分配时间、每一张图从哪一句话推出来、两张表怎么填、交卷前拿什么清单自查。附 C25 真题与样卷的题型分值对照。","2026-11-18",27,{"path":3376,"title":3377,"description":3378,"date":3379,"series":3367,"seriesOrder":3380},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-26","软件分析与设计笔记 26：总结：把整条链路串起来","系列收尾：从需求到代码的完整链路回顾、每一环的产出与验收方式、六条贯穿全系列的原则，以及一份可以直接拿去做项目自查的清单。","2026-11-17",26,{"path":3382,"title":3383,"description":3384,"date":3385,"series":3367,"seriesOrder":3386},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-25","软件分析与设计笔记 25：代码重构：坏味道与手法","重构不改变外部行为、只改善内部结构：四种常见坏味道与对应手法、为什么动手之前必须先有测试、什么时候该重构，以及重构和前面那些设计模型是什么关系。","2026-11-16",25,{"path":3388,"title":3389,"description":3390,"date":3391,"series":3367,"seriesOrder":3392},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-24","软件分析与设计笔记 24：设计原则：内聚、耦合与多态","好设计怎么衡量：内聚与耦合分别在说什么、一次从「差设计」到「多态重设计」的完整对照、抽象类与接口的选择依据，以及复用为什么会从优势变成风险。","2026-11-15",24,{"path":3394,"title":3395,"description":3396,"date":3397,"series":3367,"seriesOrder":286},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-23","软件分析与设计笔记 23：设计建模：从对象模型到能实现的方案","分析模型能用但还不能直接开工：对象模型怎么映射到关系模型、继承映射的三种方案怎么选、数据访问层与 Data Broker 解决什么问题，以及三层架构与 MVC 的分工。","2026-11-14",{"path":3399,"title":3400,"description":3401,"date":3402,"series":3367,"seriesOrder":279},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-22","软件分析与设计笔记 22：分析建模：边界、实体与控制对象","不经过分析直接写代码会踩什么坑、三种分析对象各管一段、怎么用顺序图完成一次用例实现，以及从顺序图导出类的属性、操作与关系的方法。","2026-11-13",{"path":3404,"title":3405,"description":3406,"date":3407,"series":3367,"seriesOrder":274},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-21","软件分析与设计笔记 21：UML：类图与顺序图","为什么需要一种共同的画法：类符号三格怎么画、五种关系的符号与设计含义、每种关系落到 Java 是什么写法，顺序图的三要素怎么摆；以及这三种最容易找不到的符号在 draw.io 里各搜什么词。","2026-11-12",{"path":3409,"title":3410,"description":3411,"date":3412,"series":3367,"seriesOrder":265},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-20","软件分析与设计笔记 20：面向对象概念：对象、关系与多态","面向对象不是「用某个语言写代码」：对象与类的区别、封装真正在保护什么、五种关系（依赖、关联、聚合、组合、继承）各自何时用，以及多态为什么能减少条件判断。","2026-11-11",{"path":3414,"title":3415,"description":3416,"date":3417,"series":3367,"seriesOrder":250},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-19","软件分析与设计笔记 19：规范化与逻辑数据模型","一次改地址引发的数据不一致，讲清规范化的真正动机；UNF 到 3NF 每一步在解决什么问题，派生字段为什么不能存，优化为什么和规范化方向相反，以及逻辑数据模型在 3NF 之上补了什么。","2026-11-10",{"path":3419,"title":3420,"description":3421,"date":3422,"series":3367,"seriesOrder":237},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-18","软件分析与设计笔记 18：数据库分析：实体、关系与数据字典","为什么不能拿到需求就动手建表：实体与关系怎么判定、基数为什么必须由业务方确认、数据命名要遵守什么规则，以及数据字典四列里最容易被空着的那一列。","2026-11-09",{"path":3424,"title":3425,"description":3426,"date":3427,"series":3367,"seriesOrder":221},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-17","软件分析与设计笔记 17：用生成式 AI 做设计与开发：能用的场景与要防的坑","生成式 AI 在工作流里真正站得住的三个场景 —— 造示例数据、解释陌生代码、起草初稿；它为什么会复现社会偏见、为什么会编得像真的，以及哪些内容绝不能直接采用。","2026-11-08",{"path":3429,"title":3430,"description":3431,"date":3432,"series":3367,"seriesOrder":216},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-16","软件分析与设计笔记 16：从 persona 到 URS：一份交付物的完整骨架","把前面十几篇串成一份能交出去的东西：用户需求规格说明书的九个部分、每一章来自哪个环节、由谁确认，以及面向决策者的提案该讲清哪三件事。","2026-11-07",{"path":3434,"title":3435,"description":3436,"date":3437,"series":3367,"seriesOrder":211},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-15","软件分析与设计笔记 15：非功能需求：把「快、稳、安全」变成数字","为什么「系统要快」不是一条需求：非功能需求怎么量化，决定性能的用户量、交易量、数据量三张表怎么估，安全需求要写清什么，以及可靠性的「不可用」该怎么定义。","2026-11-06",{"path":3439,"title":3440,"description":3441,"date":3442,"series":3367,"seriesOrder":204},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-14","软件分析与设计笔记 14：软件测试：层级、方法与等价类划分","「我们测过了」这句话问题在哪：为什么静态评审和动态测试缺一不可，五个测试层级各自验证什么，白盒与黑盒的差别，以及用等价类划分和边界值把无限输入变成有限用例的方法。","2026-11-05",{"path":3444,"title":3445,"description":3446,"date":3447,"series":3367,"seriesOrder":199},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-13","软件分析与设计笔记 13：需求获取：五种调查方法与一场访谈","需求从哪里来：五种事实调查方法各自适合回答什么、代价是什么，访谈从准备到记录的全过程，问卷设计里最容易犯的错，以及访谈记录必须写下来的那几项信息。","2026-11-04",{"path":3449,"title":3450,"description":3451,"date":3452,"series":3367,"seriesOrder":190},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-12","软件分析与设计笔记 12：前端设计：把流程变成屏幕","界面设计不是「画得好看」：从活动图走到屏幕的三步，CRUD 任务流怎么当检查表，界面必须回答的三个问题，信息架构与五种导航类型，视觉一致性与留白，以及移动端与 Web 端的差别。","2026-11-03",{"path":3454,"title":3455,"description":3456,"date":3457,"series":3367,"seriesOrder":175},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-11","软件分析与设计笔记 11：Agentic AI 与业务流程自动化","AI agent 到底是什么：五项能力与四个核心组件，什么场景该用定时任务、什么该用程序、什么才值得用 agent，两种实现路径的差别，必须提前防住的四类失败模式，以及设计方法从提示词到编排的演进。","2026-11-02",{"path":3459,"title":3460,"description":3461,"date":3462,"series":3367,"seriesOrder":162},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-10","软件分析与设计笔记 10：精炼用例模型：include、extend 与泛化","三种精炼用例关系的判断标准：include 什么时候用、它和 extend 的区别到底在哪、参与者泛化与用例泛化各解决什么问题，以及精炼到什么程度就该停手。","2026-11-01",{"path":3464,"title":3465,"description":3466,"date":3467,"series":3367,"seriesOrder":146},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-09","软件分析与设计笔记 09：功能需求（二）：从流程到业务实体","活动图画完之后，怎么从流程里筛出真正的业务实体：三条判断标准、哪些名词其实不该建实体、属性从哪里来，以及领域模型和后面的数据模型分别负责什么。","2026-10-31",{"path":3469,"title":3470,"description":3471,"date":3472,"series":3367,"seriesOrder":139},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-08","软件分析与设计笔记 08：功能需求（一）：用例模型与活动图","从用户故事到用例的翻译过程：什么才算「好」的参与者，系统边界怎么划，用例描述必须写清哪四件事，活动图怎么把分支、并行与泳道画清楚，以及这两个图在 draw.io 里各用哪个形状、最容易画错哪一笔。","2026-10-30",{"path":3474,"title":3475,"description":3476,"date":3477,"series":3367,"seriesOrder":133},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-07","软件分析与设计笔记 07：用户故事：把需求写成可验收的一句话","用户故事的三段结构，怎么从用户画像和数据地图里把它提取出来，一条故事多大算合适、时序为什么也是需求的一部分，以及验收标准怎么写才算真的可验收。","2026-10-29",{"path":3479,"title":3480,"description":3481,"date":3482,"series":3367,"seriesOrder":124},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-06","软件分析与设计笔记 06：范围界定（数据侧）：用数据校验你的判断","访谈结论和数字对不上时该怎么办：分析的四个层次分别在回答什么问题、为什么不能跳级、数据准备与搜集怎么做成一张应用地图，以及数据最容易在哪几个地方骗你。","2026-10-28",{"path":3484,"title":3485,"description":3486,"date":3487,"series":3367,"seriesOrder":118},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-05","软件分析与设计笔记 05：范围界定（用户侧）：用户画像与客户旅程图","为什么「所有用户」是一个陷阱，项目边界该怎么划，用户画像要写哪五个要素、最容易写成什么废纸，客户旅程图怎么把阶段、想法和情绪画在同一张纸上，以及怎么从这两样东西一步步推到可验证的需求。","2026-10-27",{"path":3489,"title":3490,"description":3491,"date":3492,"series":3367,"seriesOrder":106},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-04","软件分析与设计笔记 04：接手陌生案例的第一步：三问与一张表","拿到一个完全陌生的业务案例，怎么从零开始不跑偏：只问 Who、What、Why 三个问题，把「问题—事实—假设—理由」记成一张四列表，说清为什么假设必须写明理由，以及带着问题进访谈为什么比空手去有效得多。","2026-10-26",{"path":3494,"title":3495,"description":3496,"date":3497,"series":3367,"seriesOrder":86},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-03","软件分析与设计笔记 03：Agile 与 Scrum：迭代、角色与产出物","从「三周做完交付，客户说这不是我要的」讲起，说清瀑布式与迭代式的根本差别，Scrum 的三个角色和三种产出物，一个 sprint 里四次固定活动各自在解决什么问题，以及敏捷里最常见的几个误解。","2026-10-25",{"path":3499,"title":3500,"description":3501,"date":3502,"series":3367,"seriesOrder":67},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-02","软件分析与设计笔记 02：设计思维、用户驱动与数据驱动","从「客服要一个能一次看完所有排期的界面」这个翻车案例讲起，说清设计思维到底是什么、用户驱动与数据驱动各自能看见什么又看不见什么、为什么真正的落点是把两者合起来的 data-informed design，以及访谈中怎么问出有用的信息。","2026-10-24",{"path":3504,"title":3505,"description":3506,"date":3507,"series":3367,"seriesOrder":43},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-01","软件分析与设计笔记 01：课程地图与从需求到设计的主干","这一篇不写具体方法，先把整条链路铺开：软件分析与设计到底要解决什么问题，从需求到设计分成哪两段，中间有哪十几个环节，一份完整的交付物里应该有什么，以及后面 27 篇笔记怎么排、新人该按什么顺序读。","2026-10-23",{"path":3509,"title":3510,"description":3511,"date":3512,"series":3513,"seriesOrder":43},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-01","Spring 学习笔记 01：IoC、依赖注入与第一个 Spring Boot 应用","从「手写 new 出来的三层结构」的痛点讲起，说清 IoC 容器、Bean 与依赖注入是怎么回事，再用 Maven 与 Spring Initializr 跑起第一个能返回 JSON 的应用。","2026-10-15","Spring 学习笔记",{"path":3515,"title":3516,"description":3517,"date":3512,"series":3513,"seriesOrder":67},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-02","Spring 学习笔记 02：Spring Data JPA 基础——实体映射与 Repository","用 ORM 把类映射到表：@Entity、@Id、@GeneratedValue 怎么用，CrudRepository 三层接口怎么选，H2 与 MySQL 怎么配，以及 ddl-auto 到底该写哪个值。",{"path":3519,"title":3520,"description":3521,"date":3512,"series":3513,"seriesOrder":86},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-03","Spring 学习笔记 03：JPA 查询——派生方法、JPQL 与分页排序","Repository 自带的方法只够按主键查，这篇讲三种自己造查询的方式：方法名派生、@Query 写 JPQL、原生 SQL，以及 Pageable 分页排序和投影返回。",{"path":3523,"title":3524,"description":3525,"date":3512,"series":3513,"seriesOrder":106},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-04","Spring 学习笔记 04：JPA 关联映射与仓储测试","一对多、多对多怎么映射，谁负责维护外键，FetchType 与 CascadeType 该怎么选，顺带用 @DataJpaTest 与 AssertJ 把仓储层测起来。",{"path":3527,"title":3528,"description":3529,"date":3512,"series":3513,"seriesOrder":118},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-05","Spring 学习笔记 05：Spring MVC 与 Thymeleaf 入门","一次页面请求在 Spring MVC 里走了哪几步，@Controller 怎么把数据交给模板，@PathVariable 与 @RequestParam 怎么取参数，以及控制器为什么必须是无状态的。",{"path":3531,"title":3532,"description":3533,"date":3512,"series":3513,"seriesOrder":124},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-06","Spring 学习笔记 06：表单绑定与服务端校验","把表单字段绑到对象上，用 Bean Validation 注解做服务端校验，把错误信息显示回用户刚填的那一页，再补上自定义校验注解与全局异常处理。",{"path":3535,"title":3536,"description":3537,"date":3512,"series":3513,"seriesOrder":133},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-07","Spring 学习笔记 07：Thymeleaf 片段复用与国际化","用 th:fragment 与 th:insert\u002Fth:replace 把导航栏、页脚抽成片段，页面套用统一布局，再用 messages.properties 与 LocaleResolver 做中英文切换。",{"path":3539,"title":3540,"description":3541,"date":3512,"series":3513,"seriesOrder":139},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-08","Spring 学习笔记 08：HTTP Session 与会话状态管理","HTTP 无状态带来的问题，Cookie 与 Session 怎么分工，Spring 里读写会话的几种方式，以及会话固定、超时、多实例共享这些必须想清楚的事。",{"path":3543,"title":3544,"description":3545,"date":3512,"series":3513,"seriesOrder":146},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-09","Spring 学习笔记 09：拦截器 HandlerInterceptor","登录检查、权限判断、请求耗时统计这类横切逻辑该放哪：HandlerInterceptor 的三个回调各在什么时机执行，怎么注册与排除路径，以及它和过滤器的分工。",{"path":3547,"title":3548,"description":3549,"date":3512,"series":3513,"seriesOrder":162},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-10","Spring 学习笔记 10：Service 层与事务","一组数据库操作要么全成功要么全失败，靠 @Transactional 怎么做到；传播行为、隔离级别、回滚规则怎么选，以及自调用失效、乐观锁悲观锁这些实战里的坑。",{"path":3551,"title":3552,"description":3553,"date":3512,"series":3513,"seriesOrder":175},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-11","Spring 学习笔记 11：REST API 基础——把 Service 暴露成接口","同一套 Service 怎么同时服务页面和客户端：@RestController 的路由与方法语义、状态码怎么给、DTO 怎么定，以及 MockMvc 端到端和切片测试的两种写法与各自的坑。",{"path":3555,"title":3556,"description":3557,"date":3512,"series":3513,"seriesOrder":190},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-12","Spring 学习笔记 12：REST 进阶——版本、更新、跨域与文档化","接口能跑通之后要处理的几件事：URL 与响应结构怎么定、版本怎么演进、更新怎么做到可安全重试、CORS 怎么配、HATEOAS 与 Spring Data REST 该不该用。",{"path":3559,"title":3560,"description":3561,"date":3347,"series":3354,"seriesOrder":106},"\u002Fposts\u002Fjava\u002Fjava-functionalprogramming-notes","Java 函数式编程笔记：Lambda、函数式接口与 Streams","学习 Java 函数式编程时的个人笔记：Lambda 表达式的五步简写与方法引用、常见函数式接口、Stream 流水线与惰性求值，以及创建流、中间操作、终止操作与收集器的用法。示例代码均为自写，运行结果来自本地实测。",{"path":3352,"title":5,"description":3348,"date":3347,"series":3354,"seriesOrder":86},{"path":3564,"title":3565,"description":3566,"date":3567,"series":3568,"seriesOrder":106},"\u002Fposts\u002Fmysql\u002Fmysql-learning-04","MySQL 学习笔记 04：数据操纵（增删改）","MySQL 学习笔记第四篇：INSERT 插入（含批量插入与 INSERT ... SELECT）、UPDATE 更新、DELETE 删除，以及各自的坑。","2026-10-10","SQL 学习笔记",{"path":3570,"title":3571,"description":3572,"date":3567,"series":3568,"seriesOrder":118},"\u002Fposts\u002Fmysql\u002Fmysql-learning-05","MySQL 学习笔记 05：DDL 概览","MySQL 学习笔记第五篇：数据定义语言概览——Schema、Table、View、Index、Constraints 这几类对象分别对应哪些 CREATE \u002F ALTER \u002F DROP 命令。",{"path":3574,"title":3575,"description":3576,"date":3567,"series":3568,"seriesOrder":124},"\u002Fposts\u002Fmysql\u002Fmysql-learning-06","MySQL 学习笔记 06：CREATE TABLE 与数据类型","MySQL 学习笔记第六篇：CREATE TABLE 的完整语法、常用数据类型的选型建议，以及外键的两种定义方式。",{"path":3578,"title":3579,"description":3580,"date":3567,"series":3568,"seriesOrder":133},"\u002Fposts\u002Fmysql\u002Fmysql-learning-07","MySQL 学习笔记 07：索引","MySQL 学习笔记第七篇：索引的作用与 B+ 树结构、单列与复合索引的创建、索引的代价与适用场景。",{"path":3582,"title":3583,"description":3584,"date":3567,"series":3568,"seriesOrder":139},"\u002Fposts\u002Fmysql\u002Fmysql-learning-08","MySQL 学习笔记 08：DROP 与 ALTER","MySQL 学习笔记第八篇：DROP 删除数据库对象、ALTER 修改表结构，以及常见的坑。",{"path":3586,"title":3587,"description":3588,"date":3567,"series":3568,"seriesOrder":146},"\u002Fposts\u002Fmysql\u002Fmysql-learning-09","MySQL 学习笔记 09：约束与数据完整性","MySQL 学习笔记第九篇：SQL 约束——NOT NULL、CHECK、PRIMARY KEY、FOREIGN KEY 等如何保证数据完整性，以及它们的对比。",{"path":3590,"title":3591,"description":3592,"date":3567,"series":3568,"seriesOrder":162},"\u002Fposts\u002Fmysql\u002Fmysql-learning-10","MySQL 学习笔记 10：视图","MySQL 学习笔记第十篇：视图（Views）的定义与特性、和表的对比、创建与使用，以及可更新视图的限制。",{"path":3594,"title":3595,"description":3596,"date":3567,"series":3568,"seriesOrder":175},"\u002Fposts\u002Fmysql\u002Fmysql-learning-11","MySQL 学习笔记 11：存储过程","MySQL 学习笔记第十一篇：存储过程（Stored Procedures）的概念、创建与调用、带参数的写法，以及优缺点。",{"path":3598,"title":3599,"description":3600,"date":3601,"series":3568,"seriesOrder":43},"\u002Fposts\u002Fmysql\u002Fmysql-learning-01","MySQL 学习笔记 01：MySQL 基础知识","MySQL 学习笔记第一篇：MySQL Server 与 Workbench 的安装，数据库与 DBMS 的基本概念，关系模型、ACID 事务、常见数据类型，以及后续的学习路线。","2026-10-09",{"path":3603,"title":3604,"description":3605,"date":3601,"series":3568,"seriesOrder":67},"\u002Fposts\u002Fmysql\u002Fmysql-learning-02","MySQL 学习笔记 02：SELECT 查询与过滤","MySQL 学习笔记第二篇：SELECT 的基本语法与逻辑执行顺序，WHERE 过滤（比较、LIKE、IN、BETWEEN、NULL）、各种 JOIN、UNION 与子查询。",{"path":3607,"title":3608,"description":3609,"date":3601,"series":3568,"seriesOrder":86},"\u002Fposts\u002Fmysql\u002Fmysql-learning-03","MySQL 学习笔记 03：聚合、分组与输出控制","MySQL 学习笔记第三篇：COUNT\u002FSUM\u002FAVG 等聚合函数、GROUP BY 分组、HAVING 过滤、ORDER BY 与 LIMIT，以及一条 SELECT 的完整逻辑执行顺序。",{"path":3611,"title":3612,"description":3613,"date":3614,"series":3354,"seriesOrder":67},"\u002Fposts\u002Fjava\u002Fjava-oop-notes","Java 面向对象笔记：类、继承、多态与异常","学习 Java 面向对象编程时的个人笔记：类与对象、封装与访问修饰符、继承与多态、集合、抽象类与接口、异常处理。示例代码均为自写，运行结果来自本地实测。","2026-10-06",{"path":3616,"title":3617,"description":3618,"date":3619,"series":3354,"seriesOrder":43},"\u002Fposts\u002Fjava\u002Fjava-basics-notes","Java 基础语法笔记：从环境安装到方法与数组","学习 Java 基础语法时的个人笔记：JDK 安装、输入输出、变量与运算符、条件与循环、字符串、数组与方法。示例代码均为自写，运行结果来自本地实测。","2026-10-05",{"path":3621,"title":3622,"description":3623,"date":3624,"series":3625,"seriesOrder":43},"\u002Fposts\u002Fgit\u002Fgit-install-and-basics","Git 安装与基本使用","从装好 Git 到跑通日常提交流程，把最常用的几条命令和它们背后的三个区域讲清楚。","2026-10-04","Git 入门",{"path":3627,"title":3628,"description":3629,"date":3624,"series":3625,"seriesOrder":67},"\u002Fposts\u002Fgit\u002Flocal-remote-repository","本地远程仓库的部署","用自己的机器当 Git 服务器：建一个裸仓库、走 SSH 连上去，把本地提交推上去。",{"path":3631,"title":3632,"description":3633,"date":3624,"series":3634,"seriesOrder":43},"\u002Fposts\u002Fguide\u002Fguide-file-layout","文件都放在哪","文章、图片、封面分别放在仓库的什么位置，哪些目录可以放手改、哪些碰了会出问题。","博客使用指南",{"path":3636,"title":3637,"description":3638,"date":3624,"series":3634,"seriesOrder":67},"\u002Fposts\u002Fguide\u002Fguide-frontmatter","frontmatter 每个字段是什么意思","文章开头那段被横线包住的字段，逐个说明作用、默认行为和写错的后果。",{"path":3640,"title":3641,"description":3642,"date":3624,"series":3634,"seriesOrder":86},"\u002Fposts\u002Fguide\u002Fguide-markdown","正文怎么写 —— Markdown 与排版效果","标题、链接、图片、代码、表格分别怎么写，以及本站对它们做了哪些自定义处理。",1793268152050]