1.意思
jdk中的K,V,T,E等泛型名称很多人以为是固定写法,其实这些名称是可以改的,比如改成zhangsan,lisi都可以,jdk为了容易看懂,所以用K表示键,V表示值,T表示type类型,E表示enum枚举,
其实这四个都只是符号,都是表示泛型名称,下面的例子的T全部可以换成E,也可以换成K,V,zhangsan,都没关系。
? 表示不确定的类型
Object java中所有类的父类。
2.使用方法
1. import
2. import
3. import
4. import
5. //T1,T2都是随便定义的东西,注意1:他们不会关联到其他类,只是在本类中通用,只是告诉我们new的时候要加入泛型
6. public class
7. public static void
8. new
9. new Test<String, String> ().getbb("");
10. new Test().getcc(Test.class);
11. //注意下6:面这个HashMap的括号里面不能是T,E,T1,T2等不确定的东西,但可以是?
12. new
13. new
14. }
15.
16. T2 getaa() {
17. //注意2:T2将自动转型为String,这个不需要去担心
18. return (T2) "few";
19.
20. }
21.
22. public <T> void
23. //注意3:Class<T>前面缺少<T>将编译错误
24. System.out.println(x.getClass().getName());
25. }
26.
27. public
28. //getcc前面的Class<T>前面缺少<T>将编译错误,注意4:Class<?>里面的问号可以换成T
29. System.out.println(a.getClass().getName());
30. //注意5:参数里面的Class<T>最大的好处是如果方法里面定义了泛型,可以自动获取类型值,比如如下的List<T>可以自动获取到a的类型,不必强调死
31. new
32. System.out.println(aa);
33. return
34. }
35. }
1. 1)基本概念:
2. 1.5里面使用泛型最多的地方。Java语言引入泛型是一个 较大的功能增强,不仅语言、类型系统和编译器有了大变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为了泛型化的 了,使用泛型的优点为:
3.
4. * 类型安全:
5. 泛 型的主要目标是提高Java程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就 只存在于程序员的脑海中。Java程序中的一种流行技术是定义这样的集合,即它的元素或键是功能类型的,比如“_列表”。通过在变量声明中捕获这一附加的 类型信息,泛型允许编译器实施这些附加的约束,类型错误现在就可以在编译时被捕获了,而不是在运行时才来进行检测操作。
6. * 消除强制类型转换:
7. 泛型的一个附带的好处是,消除源代码中的许多强制类型转换,这使得代码更加可读,而且减少了出错的机会。比较两段代码:
8. 不使用泛型的代码段:
9. new
10. new Integer(3));
11. 0);
12. 使用泛型:
13. new
14. new Integer(3));
15. 0);
16. * 潜在的性能收获:
17. 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将类型转换插入到各种字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的JVM也带来了优化可能。
18.
19. 泛型本质上是提供类型的“类型参数【外露参数】”,它们也被成为参数化类型(Parameterized Type)或参量多态(Parametric Polymorphism), 用GJ(Generic Java)编写的程序看起来和普通的Java基本相同,只不过多了一些参数化的类型同时少了一些类型转换。实际上,GJ程序也是首先被转化秤一般的不带泛 型的Java程序后再进行处理的,编译器自动完成从GenericJava到普通Java的翻译,具体转化过程分为以下几个阶段:
20.
21. * 将参数化类型中的类型参数“擦除”(erasure)掉;
22. * 将类型变量用“上限(Upper bound)”取代,通常情况下这些上限是Object。这里的类型变量是指实例域,本地方法域,方法参数以方法返回值用来标记类型信息的“变量”。
23. * 添加类型转换并插入“桥方法”(bridge method),以覆盖可以正常的工作
24.
25. 转化后的程序和没有引入的程序程序员不得不手工完成转换的程序是非常一致的,下边针对GJ的特点做一个简要的总结:
26.
27. * 类型安全:泛型的一个主要目标是提高Java的类型安全,使用泛型可以使编译器知道变量的类型限制,进而可以在更高程度上验证类型假设。如果没有泛型,那么类型的安全性主要由程序员来宝物,这显然不如带有泛型的安全性高。
28. * 消除强制类型转换:泛型可以消除源代码中的许多强制类型转换,这样使得代码更加可读,并且减少出错的机会
29. * 向后兼容:支持泛型的Java编译器可以用来编译经过泛型扩充的Java程序(GJ程序),但是现有的没有使用泛型扩充的Java程序仍然可以用这些编译器来编译
30. * 层次清晰,比较规范:无论被编译的源程序是否使用泛型扩充,编译生成的字节码均可以被虚拟机接受执行。就是说不论编译器输入的GJ程序还是一般的Java程序,经过编译后的字节码都遵循了Java虚拟机规范里面针对字节码的要求,也可以说:泛型对Java虚拟机是透明的
31. * 性能收益:目前来讲GJ编写的代码和一般的Java代码在效率上是很接近的,但是泛型可以进一步优化
32.
33. API中的类型定义:
34. K——键、比如映射的键
35. V——值,比如_和_的内容,或者 _中的值
36. E——异常类型
37. T——泛型
38. 1]简单的泛型例子——
39. package
40.
41. class
42. T ob;
43. GenClass(T o){
44. this.ob = o;
45. }
46. T getOb(){
47. return this.ob;
48. }
49. void
50. "Type of T is "
51. }
52. }
53.
54. public class
55. public static void
56. new GenClass<Integer>(88);
57. iObject.showType();
58. }
59. }
60. 这段代码的输出为:
61. Type of T is java.lang.Integer
62. 【*:上边定义了一个带泛型的类,在使用的时候T可以替换为所有我们需要的类型,这种定义方式类似C++里面的模板定义。】
63. 2)深入理解泛型:
64. 【*:这里仅仅提供一般初学者和一些开发人员能够理解的泛型知识】
65. 1]数据类型转换:
66. 1.4到JDK 1.5的升级过程泛型是一个大的跨度,这种跨度使得编写程序更加规范,其实在前边讲解集合的时候已经使用了很多泛型的编程格式了,提供的很多代码Demo 都是泛型的。这里再提供一段简单的泛型使用代码:
67. ——[$]使用泛型的 List——
68. package
69.
70. import
71. import
72. import
73. /**
74. *泛型List的使用,结合了JDK 1.4和JDK 1.5的两种用法,但是在JDK 1.4编译器中下边的方式编不通
75. **/
76. public class
77. public static void
78. new
79. new
80. "List 1");
81. "List 2");
82. "Generic List 1");
83. "Generic List 2");
84. "No Generic:");
85. Iterator iterator = list.iterator();
86. while(iterator.hasNext()){
87. "[" + (String)iterator.next() + "],");
88. }
89. "\nGeneric:");
90. Iterator<String> genIterator = genList.iterator();
91. while(genIterator.hasNext()){
92. "[" + genIterator.next() + "],");
93. }
94. }
95. }
96. 上边这段代码的输出为:
97. No Generic:
98. [List 1],[List 2],
99. Generic:
100. [Generic List 1],[Generic List 2],
101. 这里使用了两种不同的方式【*: 在最原始的定义的List参数里面,不使用泛型的时候都是直接使用Object类型,在传入String的时候会进行向下转型,一般情况不会出现转型失败 的情况,但是使用了泛型过后,就上边的genList代码段里面,只能添加String对象,如果使用了其他类型的元素这里编译器就会直接报错,这种做法消除了JVM本身的类型转换。】
102. 2]基本类型限制
103. 5.0的版本号】中 的类型变量的限制之一就是:必须使用引用类型进行实例化,基本类型不起作用,就是说不能使用:List<int> list = new ArrayList<int>();这种定义方式。也就是说在泛型使用的过程里面,如果要针对基本类型进行泛型使用,必须要进行包装,就是 Boxing操作,比如把int包装成为Integer。
104. 14中:
105.
106. * 不应在静态成员中引用封闭类型参数
107. * 不能用基本类型实例化泛型类型参数
108. instanceof操作中使用“外露”类型参数
109. new操作符中使用“外露”的类型参数
110. implements或extends字句中使用 “外露”类型参数
111.
112. 这里简单透露一点JVM的原理:JVM 本身不支持泛型,在编译器进行泛型代码的编译的时候,其实是使用了“擦除”功能,就是JVM在编译带泛型的代码的时候,实际上对带泛型的代码进行了类型检 查,然后“擦除”泛型代码中的类型支持,转换为普通类型进行编译。这里有一个新概念成为“外露”类型——单独出现而不是位于某个类型中的类型参数如 (List<T>中的T)针对T类型而言,T的上界就是Object。这一项技术的功能极其强大,我们可以使几乎所有泛型类型的精度增强,但 是与JVM兼容。
113. ——[$]静态成员中的封闭类型参数——
114. package
115.
116. public class
117. static void
118. //T t;
119. }
120. static class
121. //StaticListGenericDemo<T> t;
122. }
123. }
124. 这里被注释掉的代码是不能通过JVM编译的,因为编译器完全禁止在静态方法和静态内部类中引用封闭类型参数,下边几种情况这里就不做解释了,T在整个过程里面是不应该作为外露类型来使用。
125. instanceof操作中的“外露参数”——
126. package
127.
128. import
129.
130. interface
131. public void
132. }
133.
134. class C<T> implements
135. int counter = 0;
136. Hashtable<Integer,T> values;
137. public
138. new
139. }
140. public void
141. new
142. counter++;
143. }
144. }
145. "test") ,会发出 ClassCastException 。但并非如此;计算将继续,就仿佛数据类型转换成功了一样,然后在进一步进行计算时发出错误,或者更糟:用遭破坏的数据完成计算,但不向外发出任何错误信号。同样,对“外露”类型参数的 instanceof
146. 3]泛型的构造函数:
147. 在定义泛型的构造函数的时候,要解决这一个问题,需要一定的操作:
148.
149. * 要求类型参数的所有实例化都包括不带参数的构造函数
150. * 只要泛型类的运行时实例化没有包括所需要的构造函数,就抛异常
151. * 修改语言的语法以包括更加详细的类型参数界限
152.
153. ——[$]定义一个带泛型构造函数——
154. package
155.
156. class
157. private double
158. extends
159. val = arg.doubleValue();
160. }
161. void
162. "Val: "
163. }
164. }
165.
166. public class
167. public static void
168. new GenGons(100);
169. new GenGons(123.5F);
170. genOne.showVal();
171. genTwo.showVal();
172. }
173. }
174. 这段程序输出为:
175. Val: 100.0
176. Val: 123.5
177. 4]泛型中通配符的使用:
178. 通配符——使 用一个?标识类型参数,是一种表示未知类型的约束方法。通配符并不包含在最初的泛型设计中,从形成JSR14到发布其最终版本之间的五年时间内完成了设计 添加到泛型中。通配符在泛型的使用中具有重要的意义,它们为一个泛型类所指定的类型集合提供了一个有用的类型范围。对泛型的ArrayList而言,对于 任意类型T,ArrayList<?>类型是ArrayList<T>的超类型,但是这些超类型在执行类型推断方面是不起作用的。通配符类型List<?>、原始List和具体List<Object>都不相同。如果说变量X具有List<?>类型,标识存在一些T类型,其中x是List<T>类型,x具有相同的结构,尽管我们不知道其元素的具体类型。这并不代表它具有任意内容,而是指我们并不了解内容的类型限制是什么 — 但我们知道存在 某种限制。另一方面,原始类型 List 是异构的,我们不能对其元素有任何类型限制,具体类型 List<Object> 表示我们明确地知道它能包含任何对象(当然,泛型的类型系统没有 “列表内容” 的概念,但可以从 List 之类的集合类型轻松地理解泛型)。 通配符在类型系统中的作用部分来自其不会发生协变(covariant)这一特性。数组是协变的,因为 Integer 是 Number 的子类型,数组类型 Integer[] 是 Number[] 的子类型,因此在任何需要 Number[] 值的地方都可以提供一个 Integer[] 值。另一方面,泛型不是协变的, List<Integer> 不是 List<Number> 的子类型,试图在要求 List<Number> 的位置提供 List<Integer> 是一个类型错误。这不算很严重的问题,也不是所有人都认为的错误,但泛型和数组的不同行为的确引起了许多混乱。
179. ——[$]通配符的使用 ——
180. package
181.
182. class Status<T extends
183. T[] nums;
184. Status(T[] o){
185. nums = o;
186. }
187. double
188. double sum = 0.0;
189. for( int i = 0; i < nums.length; i++)
190. sum += nums[i].doubleValue();
191. return
192. }
193. boolean
194. if( average() == obj.average())
195. return true;
196. return false;
197. }
198. }
199.
200. public class
201. public static void
202. 1,2,3,4,5};
203. new
204. "iob average is "+ iobj.average());
205. 1.1,2.2,3.3,4.4,5.5};
206. new
207. "dob average is "+ dobj.average());
208. 1.1F,2.2F,3.3F,4.4F,5.5F};
209. new
210. "fob average is "+ fobj.average());
211. }
212. }
213. 这段程序的输出为:
214. iob average is 3.0
215. dob average is 3.3
216. fob average is 3.300000023841858
217. ——[$]返回泛型值的方法——
218. package
219.
220. import
221.
222. class
223. class SubClass extends Base implements
224. class SubClassTwo extends Base implements
225.
226. public class
227. public static <T extends
228. return null;
229. }
230. public static void
231. new SubClass(), new
232. new SubClass(), new
233. }
234. }
235. 注意上边返回值的写法
236. ——[$]?通配符——
237. package
238.
239. import
240. import
241. /**
242. *问号通配符
243. **/
244. public class
245. private static void testMethod(List<? extends
246. public static void
247. new
248. new
249. new
250. //testMethod(oList); //这里会出现编译错误
251. testMethod(iList);
252. testMethod(nList);
253. }
254. }
255. extends以及super关键字进行使用,其含义在于传入泛型的类型定义为extends后边的类型的子类,所以上边的注释掉的代码是没有办法通过编译的。
256. 【*:泛型真正在开发过程程序员需要掌握的是用法,上边讲到的四点都比较深入,而且都是讨论的与JVM处理泛型原理相关的内容,如果没有弄懂没有关系,下边讲“泛型类型捕获”的时候主要提及应用层的相关内容。】
257. ——[$]定义一个泛型接口——
258. package
259.
260. interface MinMan<T extends
261. T min();
262. T max();
263. }
264. class MyDemo<T extends Comparable<T>> implements
265. T[] tValues;
266. MyDemo(T[] o){ tValues = o;}
267. public
268. 0];
269. for( int i = 1; i < tValues.length; i++ )
270. if(tValues[i].compareTo(vT) < 0)
271. vT = tValues[i];
272. return
273. }
274. public
275. 0];
276. for( int i = 1; i < tValues.length; i++ )
277. if(tValues[i].compareTo(vT) > 0)
278. vT = tValues[i];
279. return
280. }
281. }
282. public class
283. public static void
284. 3,6,13,11,45,22,33,21};
285. new
286. "iob Max:"
287. "iob Min:"
288. }
289. }
290. 该输出为:
291. iob Max:45
292. iob Min:3
293. ——[$]泛型的方法重载——
294. package
295. /**
296. *一段错误的代码段
297. **/
298. class
299. T ob1;
300. V ob2;
301. void
302. this.ob1 = o;
303. }
304. void
305. this.ob2 = o;
306. }
307. }
308. class
309. T ob;
310. GenDemo(){
311. this.ob = new
312. }
313. }
314. class
315. static
316. static
317. return
318. }
319. }
320. 分析上边的代码段就可以发现很多问题:
321.
322. * 首先set方法并不能通过这种方式重载,原因很简单,虽然这里使用了通配符T、V,但是这两个“外露” 类型在编译器里面为默认为相同的,因为这种情况下两个都属于占位符类型,按照这样的方式就会使得set方法的方法签名对JVM而言是一模一样的
323. this.ob = new
324. static的方法或者类定义里面,这就是上边代码段的代码。
325.
326. ——[$]泛型Java 类——
327. package
328.
329. class
330. T ob;
331. GenType(T ob){
332. this.ob = ob;
333. }
334. T getOb(){
335. return this.ob;
336. }
337. }
338.
339. class GenStr<T extends
340. T str;
341. GenStr(T o){
342. this.str = o;
343. }
344. T getStr(){
345. return this.str;
346. }
347. }
348.
349. public class
350. public static void
351. new GenType<Integer>(99);
352. new GenStr<Float>(102.2F);
353. System.out.println(iObGenType.getClass().getName());
354. System.out.println(fOb.getClass().getName());
355. }
356. }
357. 根据输出结果可以知道该类型就为我们定义的类型:
358. org.susan.java.generic.GenType
359. org.susan.java.generic.GenStr
360. 3)泛型“类型捕获”
361. extends 和super】
362. extends T这 种格式,编译器遇到一个这样带有通配符的变量的时候,它如何来进行识别操作呢,它会觉得遇到T了过后,一定会有T定义的变量,而对这些T而言一定有一个 Class<T>类型。但是它不知道T代表什么,但是JVM会为T定制一个占位符来代替T的类型。占位符被称为特殊通配符的捕获。这种情况 下,编译器会为通配符提供一个名字,每个变量声明中出现的一个通配符都会活得JVM的一个捕获,,因此在泛型声明中如果用了public void
363. 1.5无疑是最复杂的部分,而且Java编译器产生的一些令人困惑的错误以及很多消息都可能和通配符有关。】
364. extends
365. 注意:泛型本身不是协变的
366. 比 如有类型Integer和Number,因为Integer类是Number类的子类,也就是说Integer是一个Number,Integer[]也 是一个Number[],按照这样的逻辑,我们也许会觉得泛型也是遵循了这样原理List<Integer>也会是 List<Number>的子类,但是这种做法在泛型里面是错误的。这种做法在传入的时候会被编译器定义为类型错误。看一段程序:
367. ——[$]协变的概念代码——
368. package
369.
370. import
371. import
372. /**
373. *关于泛型协变的概念代码
374. **/
375. public class
376. private static void
377. "One:"
378. }
379. private static void
380. "Two:"
381. }
382. private static void methodThree(List<? extends
383. "Three:"
384. }
385. private static void methodFour(List<? super
386. "Four:"
387. }
388. public static void
389. new
390. new
391. new
392. methodOne(nList);
393. //这里编译报错,因为nList是List<Number>,但是方法接受参数是 List<Integer>,它们不存在继承关系,这里也证明了协变的简单
394. //methodTwo(nList);
395. methodThree(nList);
396. methodFour(nList);
397.
398. //这里编译报错,iList是List<Integer>,不是 List<Number>
399. //methodOne(iList);
400. methodTwo(iList);
401. methodThree(iList);
402. //这里编译报错,iList不满足条件List<? super Number>,因为List<Integer>中外露类型Integer不满足Integer super Number
403. //methodFour(iList);
404.
405. //最后三个编译错误留给读者自己去分析
406. //methodOne(oList);
407. //methodTwo(oList);
408. //methodThree(oList);
409. methodFour(oList);
410. }
411. }
412. 1.5的JDK环境里面进行1.4的写法,如果不去掉类型检测可能会报警告:
413. extends和super关键字的使用了。关于通配符的使用可能还需要写更多的代码,最好办法是直接去参考集合部分的源代码,是一个不错的学习途径。】
414. 针对Java泛型做一个简单的总结:
415.
416. * 泛型的“外露”类型可以用在类、接口和方法中,一般称为泛型类、泛型接口、泛型方法
417. * 泛型的“外露”类型只可以是类类型(包括自定义类),不能是简单类型
418. extends和super关键字,该关键字标识了“外露”类型的界限
419. * 泛型不支持直接的类型协变,在协变过程注意泛型的“外露”类型满足的界限的条件
420. * 泛型还可以使用通配符进行操作,这种情况可以用来作为类模版