This repository has been archived by the owner on Sep 13, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathjavafan-xing-zhong-list-listobject-listde-qu-bie.html
349 lines (305 loc) · 24.5 KB
/
javafan-xing-zhong-list-listobject-listde-qu-bie.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Java泛型中List、List<Object>、List<?>的区别</title>
<link href="/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="华科美团点评技术俱乐部 Full Atom Feed" />
<link href="/feeds/java.atom.xml" type="application/atom+xml" rel="alternate" title="华科美团点评技术俱乐部 Categories Atom Feed" />
<!-- Bootstrap Core CSS -->
<link href="/theme/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="/theme/css/clean-blog.min.css" rel="stylesheet">
<!-- Code highlight color scheme -->
<link href="/theme/css/code_blocks/darkly.css" rel="stylesheet">
<!-- Custom Fonts -->
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
<link href='https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<meta name="description" content="Java 1.5中引入了泛型的概念以增加代码的安全性与清晰度,同时为了提供对旧代码的兼容性,让旧代码不经过改动也可以在新版本中运行,Java提供了原生态类型(或称原始类型)。但是实际中在新的代码中已经不应该使用原生态类型。...">
<meta name="author" content="Di Wu">
<meta name="tags" content="java">
<meta property="og:locale" content="zh_CN.UTF-8">
<meta property="og:site_name" content="华科美团点评技术俱乐部">
<meta property="og:type" content="article">
<meta property="article:author" content="/author/di-wu.html">
<meta property="og:url" content="/javafan-xing-zhong-list-listobject-listde-qu-bie.html">
<meta property="og:title" content="Java泛型中List、List<Object>、List<?>的区别">
<meta property="article:published_time" content="2017-02-14 16:30:00+08:00">
<meta property="og:description" content="Java 1.5中引入了泛型的概念以增加代码的安全性与清晰度,同时为了提供对旧代码的兼容性,让旧代码不经过改动也可以在新版本中运行,Java提供了原生态类型(或称原始类型)。但是实际中在新的代码中已经不应该使用原生态类型。...">
<meta property="og:image" content="//images/bg.jpg">
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-default navbar-custom navbar-fixed-top">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header page-scroll">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">华科美团点评技术俱乐部</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li><a href="/categories.html">分类</a></li>
<li><a href="/archives.html">归档</a></li>
<li><a href="/authors.html">作者</a></li>
<li><a href="/tags.html">标签</a></li>
<li><a href="/pages/about/index.html">关于</a></li>
<li><a href="/pages/friendlinks/index.html">友链</a></li>
</ul>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container -->
</nav>
<!-- Page Header -->
<header class="intro-header" style="background-image: url('/images/bg.jpg')">
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<div class="post-heading">
<h1>Java泛型中List、List<Object>、List<?>的区别</h1>
<span class="meta">Posted by
<a href="/author/di-wu.html">Di Wu</a>
on 2017年 2月14日 周二
</span>
</div>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<!-- Post Content -->
<article>
<p>Java 1.5中引入了泛型的概念以增加代码的安全性与清晰度,同时为了提供对旧代码的兼容性,让旧代码不经过改动也可以在新版本中运行,Java提供了原生态类型(或称原始类型)。但是实际中在新的代码中已经不应该使用原生态类型。</p>
<p>原生态类型的含义是不带任何实际参数的泛型名称,例如Java 1.5后改为泛型实现的<code>List<E></code>,<code>List</code>就是它的原生态类型,与没有引入泛型之前的类型完全一致。</p>
<p>而在虚拟机层面上,是没有泛型这一概念的——所有对象都属于普通类。在编译时,所有的泛型类都会被视为原生态类型。</p>
<p>那么为什么不应该使用原生态类型呢?</p>
<blockquote>
<p>如果使用原生态类型,就失掉了泛型在安全性和表述性方面的所有优势。——Effective Java</p>
</blockquote>
<p>泛型的目的简单地说就是可以让一些运行时才能发现的错误可以在编译期间就可以被编译器所检测出,运行时出问题的代价与编译期出现问题的代价的差别可想而知。换句话说,泛型是编译器的一种及时发现错误的机制,同时也给用户带来了代码的清晰与简洁的附加好处(不必再写一些复杂而危险并且不直观的强制类型转换)。</p>
<p>下面就进入正题谈谈以<code>List</code>为例时<code>List</code>、<code>List<Object></code>、<code>List<?></code>的区别。</p>
<p>先下定义:</p>
<ul>
<li><code>List</code>:<strong>原生态类型</strong></li>
<li><code>List<Object></code>:<strong>参数化的类型</strong>,表明<code>List</code>中可以<strong>容纳</strong>任意类型的对象</li>
<li><code>List<?></code>:<strong>无限定通配符类型</strong>,表示<strong>只能包含某一种未知对象类型</strong></li>
</ul>
<p>下面看一段代码:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6
7
8</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DiffInGeneric</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="n">List</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">strings</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><>();</span>
<span class="n">List</span> <span class="n">list</span> <span class="o">=</span> <span class="n">strings</span><span class="o">;</span><span class="c1">//ok</span>
<span class="n">List</span><span class="o"><</span><span class="n">Object</span><span class="o">></span> <span class="n">objects</span> <span class="o">=</span> <span class="n">strings</span><span class="o">;</span>
<span class="c1">//Error: java: incompatible types: java.util.List<java.lang.String> cannot be converted to java.util.List<java.lang.Object></span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
</td></tr></table>
<p>我们创建了一个<code>List<String></code>类型的对象<code>strings</code>,再把它赋给原生态类型<code>List</code>,这是可以的。但是第5行中尝试把它传递给<code>List<Object></code>时,出现了一个类型不相容错误,注意,这是一个编译期错误。</p>
<p>这是因为泛型有子类型化的规则:</p>
<p><code>List<String></code>是原生态类型<code>List</code>的一个子类型。虽然<code>String</code>是<code>Object</code>的子类型,但是由于<strong>泛型是不可协变的</strong>,<code>List<String></code>并不是<code>List<Object></code>的子类型,所以这里的传递无法通过编译。</p>
<p>如果像上面那样使用原生态类型会有什么隐患呢?看下面一段代码:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre> 1
2
3
4
5
6
7
8
9
10
11</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DiffInGeneric</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="n">List</span><span class="o"><</span><span class="n">String</span><span class="o">></span> <span class="n">strings</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><>();</span>
<span class="n">unsafeAdd</span><span class="o">(</span><span class="n">strings</span><span class="o">,</span> <span class="o">(</span><span class="n">Integer</span><span class="o">)</span><span class="mi">1</span><span class="o">);</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">strings</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">));</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">unsafeAdd</span><span class="o">(</span><span class="n">List</span> <span class="n">list</span><span class="o">,</span> <span class="n">Object</span> <span class="n">object</span><span class="o">)</span> <span class="o">{</span>
<span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">object</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
</td></tr></table>
<p>编译器提示了两条警告:</p>
<p>第8行:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6</pre></div></td><td class="code"><div class="highlight"><pre><span></span>warning: <span class="o">[</span>rawtypes<span class="o">]</span> found raw type: List
private static void unsafeAdd<span class="o">(</span>List list, Object object<span class="o">)</span> <span class="o">{</span>
^
missing <span class="nb">type</span> arguments <span class="k">for</span> generic class List<E>
where E is a type-variable:
E extends Object declared in interface List
</pre></div>
</td></tr></table>
<p>警告发现了原生态类型<code>List</code>,同时还贴心地指出了<code>List<E></code>的形式以及<code>E</code>的来源。</p>
<p>第9行:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5</pre></div></td><td class="code"><div class="highlight"><pre><span></span>warning: <span class="o">[</span>unchecked<span class="o">]</span> unchecked call to add<span class="o">(</span>E<span class="o">)</span> as a member of the raw <span class="nb">type</span> List
list.add<span class="o">(</span>object<span class="o">)</span><span class="p">;</span>
^
where E is a type-variable:
E extends Object declared in interface List
</pre></div>
</td></tr></table>
<p>同样指出了我们正在把一个对象添加到<code>List</code>中,而这个添加过程由于我们使用了原生态类型而无法被检验。</p>
<p>如果忽略这两条警告并运行这个程序,显然会出现一条错误:</p>
<p>第5行: </p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">Exception</span> <span class="n">in</span> <span class="n">thread</span> <span class="s">"main"</span> <span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">ClassCastException</span><span class="o">:</span> <span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">Integer</span> <span class="n">cannot</span> <span class="n">be</span> <span class="n">cast</span> <span class="n">to</span> <span class="n">java</span><span class="o">.</span><span class="na">lang</span><span class="o">.</span><span class="na">String</span>
</pre></div>
</td></tr></table>
<p>我们试图把一个自动装箱后的<code>Integer</code>对象插入到了一个被声明为<code>List<String></code>的<code>List</code>中,由于我们在<code>unsafeAdd</code>方法中使用了原生态类型,从而使得编译器无法在编译期间检查<code>add</code>参数的合法性,从而没有产生编译错误而是产生了一条警告,运行后当试图把这个错误的<code>Integer</code>对象作为<code>String</code>取出时就会出现<code>ClassCaseException</code>异常,这是个运行时的异常,导致了程序中断。</p>
<p>如果我们把<code>unsafeAdd</code>方法的参数从<code>List</code>改为<code>List<Object></code>会发生什么呢?正如之前所说的那样,由于<code>List<String></code>并不是<code>List<Object></code>的子类型,所以在传递参数的时候就会出现第一段代码中出现的<strong>编译期错误</strong>。这体现了泛型所带来的安全性。</p>
<p>可以这么说,<code>List<Object></code>唯一特殊的地方只是<code>Object</code>是所有类型的超类,由于泛型的不可协变性,<strong>它只能表示<code>List</code>中可以容纳所有类型的对象,却不能表示任何参数类型的<code>List<E></code>。</strong></p>
<p>而<code>List<?></code>则是通配符类型中的一种特例,它并没有<code>extend</code>或<code>super</code>这样的限制,从而可以做到引用任意参数类型的<code>List<E></code>。但由于没有表示类型的符号(<code>E</code>),在方法中无法引用这个类型,所以它只用于无需使用具体类型的方法之中,如果不是这个情况,则需要使用泛型方法(只用<code>List<?></code>的<strong>不是</strong>一个泛型方法,它具有<code>List<?></code>这个固定的参数`)。</p>
<p>但是<code>List<?></code>还是不能用作上面的<code>unsafeAdd</code>的参数,修改后会出现一条奇怪的编译错误:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6
7
8
9</pre></div></td><td class="code"><div class="highlight"><pre><span></span>error: no suitable method found <span class="k">for</span> add<span class="o">(</span>Object<span class="o">)</span>
list.add<span class="o">(</span>object<span class="o">)</span><span class="p">;</span>
^
method Collection.add<span class="o">(</span>CAP#1<span class="o">)</span> is not applicable
<span class="o">(</span>argument mismatch<span class="p">;</span> Object cannot be converted to CAP#1<span class="o">)</span>
method List.add<span class="o">(</span>CAP#1<span class="o">)</span> is not applicable
<span class="o">(</span>argument mismatch<span class="p">;</span> Object cannot be converted to CAP#1<span class="o">)</span>
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
</pre></div>
</td></tr></table>
<p>这是因为无法将任何元素(<code>null</code>除外)放入<code>List<?></code>中。这又是为什么呢?先来看一个有限定通配符的例子:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6
7
8
9</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DiffInGeneric</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="n">List</span><span class="o"><?</span> <span class="kd">extends</span> <span class="n">Number</span><span class="o">></span> <span class="n">numbers</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">Integer</span><span class="o">>();</span>
<span class="n">numbers</span><span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">Double</span><span class="o">>();</span>
<span class="n">numbers</span><span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">Float</span><span class="o">>();</span>
<span class="n">numbers</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">Number</span><span class="o">>();</span>
<span class="n">numbers</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="n">Integer</span><span class="o">(</span><span class="mi">1</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
</td></tr></table>
<p>第7行报出了与之前相似的编译错误:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3
4
5
6
7
8
9</pre></div></td><td class="code"><div class="highlight"><pre><span></span>error: no suitable method found <span class="k">for</span> add<span class="o">(</span>Integer<span class="o">)</span>
numbers.add<span class="o">(</span>new Integer<span class="o">(</span><span class="m">1</span><span class="o">))</span><span class="p">;</span>
^
method Collection.add<span class="o">(</span>CAP#1<span class="o">)</span> is not applicable
<span class="o">(</span>argument mismatch<span class="p">;</span> Integer cannot be converted to CAP#1<span class="o">)</span>
method List.add<span class="o">(</span>CAP#1<span class="o">)</span> is not applicable
<span class="o">(</span>argument mismatch<span class="p">;</span> Integer cannot be converted to CAP#1<span class="o">)</span>
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Number
</pre></div>
</td></tr></table>
<p>这次我们可以看出错误的原因:可以将一个<code>List<Integer></code>传递给<code>List<? extends Number></code>,因为<code>Integer</code>是<code>Number</code>的子类,符合限定符的条件。同理,也可以将类似的对象传递给它,当然也可以把<code>List<Number></code>传递给它。</p>
<p>如果允许这个对象的<code>add</code>操作,我们无法知道这个参数是否与对象的泛型参数相同,因为我们只知道它是<code>Number</code>的一个子类。</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre>1
2
3</pre></div></td><td class="code"><div class="highlight"><pre><span></span><span class="n">List</span><span class="o"><?</span> <span class="kd">extends</span> <span class="n">Parent</span><span class="o">></span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">Child</span><span class="o">>();</span>
<span class="n">List</span><span class="o"><?</span> <span class="kd">extends</span> <span class="n">Parent</span><span class="o">></span> <span class="n">parents</span> <span class="o">=</span> <span class="n">list</span><span class="o">;</span>
<span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="n">Parent</span><span class="o">());</span>
</pre></div>
</td></tr></table>
<p>上面的1,2两行是完全合法的,如果允许第3行的<code>add</code>操作,那么会把一个<code>Parent</code>对象加入到一个实际类型是<code>Child</code>的<code>List</code>中,而<code>Parent</code>is-not-a <code>Child</code>,这破坏了Java的类型安全,是绝对不允许的。</p>
<p>上面是有限制通配符的情况,那么针对<code>List<?></code>这样的无限制通配符更是如此。因此,为了保证类型安全,不允许对<code>List<?></code>或<code>List<? extends E></code>这样的通配符类型进行类似<code>add</code>的操作。</p>
<p>使用泛型方法可以避免这个问题(重申通配符类型并不是泛型方法),使用无限制通配符类型可以取代其他需要表示<strong>包含某一种对象类型的泛型类型</strong>的情况而不是使用原生态类型<code>List</code>。</p>
</article>
<div class="tags">
<p>tags: <a href="/tag/java.html">java</a></p>
</div>
<hr>
</div>
</div>
</div>
<hr>
<!-- Footer -->
<footer>
<div class="container">
<div class="row">
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
<ul class="list-inline text-center">
<li>
<a href="https://github.com/HUSTMeituanClub">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-github fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
<li>
<a href="mailto:@hustmeituan.club">
<span class="fa-stack fa-lg">
<i class="fa fa-circle fa-stack-2x"></i>
<i class="fa fa-envelope fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
</ul>
<p class="copyright text-muted">
Blog powered by <a href="http://getpelican.com">Pelican</a>,
which takes great advantage of <a href="http://python.org">Python</a>.
</p> </div>
</div>
</div>
</footer>
<!-- jQuery -->
<script src="/theme/js/jquery.min.js"></script>
<!-- Bootstrap Core JavaScript -->
<script src="/theme/js/bootstrap.min.js"></script>
<!-- Custom Theme JavaScript -->
<script src="/theme/js/clean-blog.min.js"></script>
</body>
</html>