<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Web分享]]></title><description><![CDATA[分享互联网。]]></description><link>https://zhoujin7.com/</link><image><url>http://zhoujin7.com/favicon.png</url><title>Web分享</title><link>https://zhoujin7.com/</link></image><generator>Ghost 1.25</generator><lastBuildDate>Sat, 18 Apr 2026 00:09:32 GMT</lastBuildDate><atom:link href="https://zhoujin7.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[【翻译】Java泛型教程]]></title><description><![CDATA[<div class="kg-card-markdown"><h2 id="">前言</h2>
<p>本文译自<a href="https://docs.oracle.com/javase/tutorial/java/generics/index.html">The Java™ Tutorials - Generics</a>。<br>
翻译之前，我只找到了两份机翻的版本，机翻的很多语句都不通顺，几乎没有经过人工修订，实在看不下去，索性自己翻译了一遍。翻译过程中，才发现已有人翻译过了，不过我还是坚持翻译完了。我英语水平很一般，有的句子我也琢磨了半天，不能保证100%没有错误或纰漏，如有问题请提交反馈。其他一些备注放在本文的<a href="#%E5%A4%87%E6%B3%A8">最后面</a>。<br>
其他人翻译的版本：</p>
<ul>
<li>精简版：<a href="http://zuoqy.com/2015/05/19/java-generic/">深入Java泛型 | Q.Y Zuo Believes</a></li>
<li>完整版：<a href="https://www.zybuluo.com/yishuailuo/note/328647#%E6%B3%9B%E5%9E%8B%E7%B1%BB%E4%B8%8E%E5%AD%90%E7%B1%BB%E5%8C%96">Java 泛型教程 - 作业部落 Cmd Markdown 编辑阅读器</a></li>
</ul>
<h2 id="">泛型概述</h2>
<p>任何不简单的软件项目中都会存在bug。虽然仔细的计划、编程和测试可以降低bug率，但无法避免bug。随着新特性的引入，以及代码库的规模和复杂性的增加，这一点变得尤为明显。</p>
<p>幸运的是，有些bug比其他bug更容易被发现。例如，编译时bug可以在早期被检测到；</p></div>]]></description><link>https://zhoujin7.com/java-generics-tutorial/</link><guid isPermaLink="false">5f88128cb230b90001638840</guid><category><![CDATA[翻译]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[周蒙牛]]></dc:creator><pubDate>Wed, 28 Aug 2019 09:14:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><h2 id="">前言</h2>
<p>本文译自<a href="https://docs.oracle.com/javase/tutorial/java/generics/index.html">The Java™ Tutorials - Generics</a>。<br>
翻译之前，我只找到了两份机翻的版本，机翻的很多语句都不通顺，几乎没有经过人工修订，实在看不下去，索性自己翻译了一遍。翻译过程中，才发现已有人翻译过了，不过我还是坚持翻译完了。我英语水平很一般，有的句子我也琢磨了半天，不能保证100%没有错误或纰漏，如有问题请提交反馈。其他一些备注放在本文的<a href="#%E5%A4%87%E6%B3%A8">最后面</a>。<br>
其他人翻译的版本：</p>
<ul>
<li>精简版：<a href="http://zuoqy.com/2015/05/19/java-generic/">深入Java泛型 | Q.Y Zuo Believes</a></li>
<li>完整版：<a href="https://www.zybuluo.com/yishuailuo/note/328647#%E6%B3%9B%E5%9E%8B%E7%B1%BB%E4%B8%8E%E5%AD%90%E7%B1%BB%E5%8C%96">Java 泛型教程 - 作业部落 Cmd Markdown 编辑阅读器</a></li>
</ul>
<h2 id="">泛型概述</h2>
<p>任何不简单的软件项目中都会存在bug。虽然仔细的计划、编程和测试可以降低bug率，但无法避免bug。随着新特性的引入，以及代码库的规模和复杂性的增加，这一点变得尤为明显。</p>
<p>幸运的是，有些bug比其他bug更容易被发现。例如，编译时bug可以在早期被检测到；你可以使用编译器的错误消息来找出问题所在并及时修复。然而，运行时bug可能更成问题；它们并不总是立即出现，当它们出现时，可能是在程序中与问题的实际原因相去甚远的一个点上。</p>
<p>泛型使更多的bug在编译时可检测，从而增加了代码的稳定性。学完本课程后，你可能希望继续学习由Gilad Bracha编写的<a href="https://docs.oracle.com/javase/tutorial/extra/generics/index.html">“泛型”</a>课程。</p>
<h2 id="">为什么要使用泛型？</h2>
<p>简而言之，泛型使类型（类和接口）成为定义类、接口和方法时的参数（parameters）。就像方法声明中使用的更为常见的形式参数（formal parameters）一样，形式类型参数（type parameters）为你提供了一种在不同输入下重用相同代码的办法。不同之处在于形式参数的输入是值，而形式类型参数的输入是类型。</p>
<p>使用泛型的代码比非泛型代码更有优势：</p>
<ul>
<li>
<p>在编译时进行更强的类型检查。<br>
Java编译器对泛型代码应用强类型检查，并在代码违反类型安全性时发出错误。修复编译时错误比修复运行时错误更容易，运行时错误很难被发现。</p>
</li>
<li>
<p>消除强制类型转换。<br>
以下不使用泛型的代码片段需要强制转换:</p>
<pre><code class="language-java">List list = new ArrayList();
list.add(&quot;hello&quot;);
String s = (String) list.get(0);
</code></pre>
<p>当重新使用泛型编写后，代码不需要强制转换：</p>
<pre><code class="language-java">List&lt;String&gt; list = new ArrayList&lt;String&gt;();
list.add(&quot;hello&quot;);
String s = list.get(0);   // no cast
</code></pre>
</li>
<li>
<p>使程序员能够实现泛型算法。<br>
通过使用泛型，程序员可以实现可定制的能够处理不同类型集合的泛型算法，实现的算法类型安全且易于阅读。</p>
</li>
</ul>
<h2 id="">泛型类型</h2>
<p>泛型类型是对类型进行了参数化（parameterized）的泛型类或接口。下面的Box类将被修改来演示这个概念。</p>
<h3 id="box">一个简单的Box类</h3>
<p>首先看看能存取任何类型对象的非泛型Box类。它有两个方法：set和get，前者将对象添加到box（Box的实例）中，后者从box中取出对象:</p>
<pre><code class="language-java">public class Box {
    private Object object;

    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}
</code></pre>
<p>由于它的set方法接收Object类型的对象，get方法返回Object类型的对象，你可以向set方法传入任何非基本类型的对象。没有办法在编译时验证类的使用方式。代码的一部分可能会在box中放置一个Integer对象，并期望从box中取得Integer对象，而代码的另一部分可能会错误地传入一个String对象，从而导致运行时错误。</p>
<h3 id="box">Box类的泛型版本</h3>
<p>泛型类的定义格式如下：</p>
<pre><code class="language-java">class name&lt;T1, T2, ..., Tn&gt; { /* ... */ }
</code></pre>
<p>形式类型参数部分位于类名后面的尖括号（&lt;&gt;）中。它指定了形式类型参数（也称为类型变量）T1，T2，……和Tn。</p>
<p>要修改Box类以使用泛型，可以通过将代码“public class Box”更改为“public class Box&lt;T&gt;”来创建泛型类型声明。这引入了类型变量T，它可以在类中的任何位置使用。</p>
<p>通过这一更改，Box类变为:</p>
<pre><code class="language-java">/**
 * Generic version of the Box class.
 * @param &lt;T&gt; the type of the value being boxed
 */
public class Box&lt;T&gt; {
    // T stands for &quot;Type&quot;
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}
</code></pre>
<p>如你所见，之前Box类中的Object都被T替换。类型变量可以是你指定的任何非基本类型：任何类类型、任何接口类型、任何数组类型，甚至是另一个类型变量。</p>
<p>同样的方式也可以用于创建泛型接口。</p>
<h3 id="">形式类型参数命名惯例</h3>
<p>根据惯例，形式类型参数名称是单个大写字母。这与你已经知道的变量<a href="https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html#naming">命名</a>惯例形成鲜明对比，这样做是有充分理由的: 如果没有这个惯例，就很难区分类型变量与普通类名以及接口名之间的区别。</p>
<p>最常用的形式类型参数名称有：</p>
<pre><code class="language-txt">E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types
</code></pre>
<p>在整个Java SE API以及本课程的其余部分中都用到了这些形式类型参数名称。</p>
<h3 id="invokinginstantiating">调用（Invoking）和实例化（Instantiating）泛型类型</h3>
<p>要从代码中引用泛型Box类，必须执行泛型类型调用（perform a generic type invocation），它将T替换为某些具体值，例如Integer：</p>
<pre><code class="language-java">Box&lt;Integer&gt; integerBox;
</code></pre>
<p>你可以认为泛型类型调用类似于普通的方法调用，方法调用是将实际参数（argument）传递给方法，泛型类型调用是将实际类型参数（type argument）传递给Box类本身，在本例中是将Integer传递给Box类本身。</p>
<p><strong>形式类型参数（Type Parameter）和实际类型参数（Type Argument）术语：</strong> 许多开发人员互换地使用术语“形式类型参数”和“实际类型参数”，但这两个术语并不相同。在编写代码时，提供的实际类型参数可以用来创建参数化类型（parameterized type）。因此，Foo&lt;T&gt;中的T是形式类型参数，Foo&lt;String&gt; f中的String是实际类型参数。本课程在使用这些术语时会遵循此定义。<br>
像任何其他变量声明一样，这段代码实际上并不创建新的Box对象。它只是声明integerBox将保存对“Box of Integer”的引用，Box&lt;Integer&gt;读作“Box of Integer”。</p>
<p>泛型类型的调用通常称为参数化类型。</p>
<p>要实例化此类，像往常一样使用new关键字，但要将&lt;Integer&gt;放在类名和括号之间：</p>
<pre><code class="language-java">Box&lt;Integer&gt; integerBox = new Box&lt;Integer&gt;();
</code></pre>
<h3 id="">菱形操作符</h3>
<p>在Java SE 7及更高版本中，只要编译器能够从上下文中确定或推断出实际类型参数，就可以用一组空的实际类型参数(&lt;&gt;)替换掉调用泛型类的构造方法所需的实际类型参数。这对尖括号&lt;&gt;被通俗地称为菱形操作符。例如，你可以使用以下语句创建Box&lt;Integer&gt;的实例：</p>
<pre><code class="language-java">Box&lt;Integer&gt; integerBox = new Box&lt;&gt;();
</code></pre>
<p>有关菱形符号和类型推断的更多信息，请参阅“类型推断”一节。</p>
<h3 id="">多个形式类型参数</h3>
<p>如前面所述，泛型类可以有多个形式类型参数。例如，实现了泛型接口Pair的泛型类OrderedPair：</p>
<pre><code class="language-java">public interface Pair&lt;K, V&gt; {
    public K getKey();
    public V getValue();
}

public class OrderedPair&lt;K, V&gt; implements Pair&lt;K, V&gt; {

    private K key;
    private V value;

    public OrderedPair(K key, V value) {
	this.key = key;
	this.value = value;
    }

    public K getKey()	{ return key; }
    public V getValue() { return value; }
}
</code></pre>
<p>以下语句创建了OrderedPair类的两个实例：</p>
<pre><code class="language-java">Pair&lt;String, Integer&gt; p1 = new OrderedPair&lt;String, Integer&gt;(&quot;Even&quot;, 8);
Pair&lt;String, String&gt;  p2 = new OrderedPair&lt;String, String&gt;(&quot;hello&quot;, &quot;world&quot;);
</code></pre>
<p>代码new OrderedPair&lt;String，Integer&gt;将K实例化为String，将V实例化为Integer。因此，OrderedPair的构造方法的形式参数类型分别是String和Integer。由于<a href="https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html">自动装箱</a>，将String和int传递给该类是有效的。</p>
<p>正如“菱形操作符”一节中所述，因为Java编译器可以从声明OrderedPair&lt;String，Integer&gt;中推断出K和V的类型，所以可以使用菱形符号缩短这些语句：</p>
<pre><code class="language-java">OrderedPair&lt;String, Integer&gt; p1 = new OrderedPair&lt;&gt;(&quot;Even&quot;, 8);
OrderedPair&lt;String, String&gt;  p2 = new OrderedPair&lt;&gt;(&quot;hello&quot;, &quot;world&quot;);
</code></pre>
<p>若要创建泛型接口，请遵循与创建泛型类相同的惯例。</p>
<h3 id="">参数化类型</h3>
<p>你还可以使用参数化类型（即List&lt;String&gt;）替换掉形式类型参数（即K或V）。例如，使用OrderedPair&lt;K，V&gt;的示例：</p>
<pre><code class="language-java">OrderedPair&lt;String, Box&lt;Integer&gt;&gt; p = new OrderedPair&lt;&gt;(&quot;primes&quot;, new Box&lt;Integer&gt;(...));
</code></pre>
<h2 id="">原始类型</h2>
<p>没有任何实际类型参数的泛型类或接口被称为原始类型（raw type）。例如，给定的泛型Box类：</p>
<pre><code class="language-java">public class Box&lt;T&gt; {
    public void set(T t) { /* ... */ }
    // ...
}
</code></pre>
<p>要创建Box&lt;T&gt;的参数化类型，你需要为形式类型参数T提供一个实际类型参数：</p>
<pre><code class="language-java">Box&lt;Integer&gt; intBox = new Box&lt;&gt;();
</code></pre>
<p>如果省略了实际类型参数，则创建Box&lt;T&gt;的原始类型：</p>
<pre><code class="language-java">Box rawBox = new Box();
</code></pre>
<p>因此，Box是泛型类型Box&lt;T&gt;的原始类型。但是，非泛型类类型或非泛型接口类型不是原始类型。</p>
<p>原始类型出现在遗留代码中，因为许多API类（如Collections类）在JDK 5.0之前并不是泛型的。当使用原始类型时，你实际上得到了JDK引入泛型之前的行为——一个提供Object对象的Box类。为了向后兼容，允许将参数化类型赋值给其原始类型：</p>
<pre><code class="language-java">Box&lt;String&gt; stringBox = new Box&lt;&gt;();
Box rawBox = stringBox;               // OK
</code></pre>
<p>但是，如果将原始类型赋值给参数化类型，则会收到警告：</p>
<pre><code class="language-java">Box rawBox = new Box();           // rawBox is a raw type of Box&lt;T&gt;
Box&lt;Integer&gt; intBox = rawBox;     // warning: unchecked conversion
</code></pre>
<p>如果使用原始类型调用相应泛型类型中定义的泛型方法，也会收到警告：</p>
<pre><code class="language-java">Box&lt;String&gt; stringBox = new Box&lt;&gt;();
Box rawBox = stringBox;
rawBox.set(8);  // warning: unchecked invocation to set(T)
</code></pre>
<p>警告显示，原始类型绕过了泛型类型检查，对不安全代码的捕获（catch）被推迟到了运行时。因此，你应该避免使用原始类型。</p>
<p>“类型擦除”一节有更多关于Java编译器如何使用原始类型的信息。</p>
<h3 id="unchecked">未经检查的（Unchecked）错误消息</h3>
<p>如前面所述，将遗留代码与泛型代码混合时，可能会遇到类似于以下内容的警告消息：</p>
<pre><code class="language-txt">Note: Example.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
</code></pre>
<p>当使用对原始类型进行操作的旧API时，可能会发生这种情况，如下面的示例所示：</p>
<pre><code class="language-java">public class WarningDemo {
    public static void main(String[] args){
        Box&lt;Integer&gt; bi;
        bi = createBox();
    }

    static Box createBox(){
        return new Box();
    }
}
</code></pre>
<p>术语“未经检查的”意味着编译器没有足够的类型信息来执行确保类型安全所必需的所有类型检查。默认情况下，“未经检查的”警告被禁用，尽管编译器提供了提示。若要查看所有“未经检查的”警告，请使用-Xlint:unchecked重新编译。</p>
<p>使用-Xlint:unchecked重新编译上一个示例，会显示以下附加信息：</p>
<pre><code class="language-txt">WarningDemo.java:4: warning: [unchecked] unchecked conversion
found   : Box
required: Box&lt;java.lang.Integer&gt;
        bi = createBox();
                      ^
1 warning
</code></pre>
<p>要完全禁用未经检查的警告，请使用-Xlint:-unchecked标志。@SuppressWarnings(&quot;unchecked&quot;) 注解会抑制未经检查的警告。如果你不熟悉@SuppressWarnings这个语法，请参阅<a href="https://docs.oracle.com/javase/tutorial/java/annotations/index.html">“注解”</a>一节。</p>
<h2 id="">泛型方法</h2>
<p>泛型方法是引入了它们自己的形式类型参数的方法。这类似于声明泛型类型，但形式类型参数的作用域仅限于声明它的方法。允许使用静态泛型方法和非静态泛型方法以及泛型类构造方法。</p>
<p>泛型方法的语法包括一个位于尖括号内，出现在方法的返回值类型之前的形式类型参数列表。对于静态泛型方法，形式类型参数部分必须出现在方法的返回值类型之前。</p>
<p>Util类包含一个比较两个Pair对象的泛型方法compare：</p>
<pre><code class="language-java">public class Util {
    public static &lt;K, V&gt; boolean compare(Pair&lt;K, V&gt; p1, Pair&lt;K, V&gt; p2) {
        return p1.getKey().equals(p2.getKey()) &amp;&amp;
               p1.getValue().equals(p2.getValue());
    }
}

public class Pair&lt;K, V&gt; {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey()   { return key; }
    public V getValue() { return value; }
}
</code></pre>
<p>调用此方法的完整语法如下：</p>
<pre><code class="language-java">Pair&lt;Integer, String&gt; p1 = new Pair&lt;&gt;(1, &quot;apple&quot;);
Pair&lt;Integer, String&gt; p2 = new Pair&lt;&gt;(2, &quot;pear&quot;);
boolean same = Util.&lt;Integer, String&gt;compare(p1, p2);
</code></pre>
<p>已明确提供类型，如粗体所示。通常，这可以省略，编译器将推断出所需的类型：</p>
<pre><code class="language-java">Pair&lt;Integer, String&gt; p1 = new Pair&lt;&gt;(1, &quot;apple&quot;);
Pair&lt;Integer, String&gt; p2 = new Pair&lt;&gt;(2, &quot;pear&quot;);
boolean same = Util.compare(p1, p2);
</code></pre>
<p>这个特性被称为类型推断，它允许你像调用普通方法一样调用泛型方法，而无需在尖括号之间指定类型。这个话题将在下一节“类型推断”中作进一步讨论。</p>
<h2 id="">有界类型形式参数</h2>
<p>有时，你可能希望限制可用作参数化类型中的实际类型参数的类型。例如，处理数字的方法可能只想接受Number或其子类的实例。这就是有界形式类型参数的用途。</p>
<p>若要声明有界形式类型参数，可以依次列出形式类型参数的名称，随后是extends关键字，最后是它的上界，在本例中它的上界为Number。注意，在这个上下文中，extends在一般意义上是指“extends”（继承类时使用的extends）或“implements”（实现接口时使用的implements）。</p>
<pre><code class="language-java">public class Box&lt;T&gt; {

    private T t;        

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }

    public &lt;U extends Number&gt; void inspect(U u){
        System.out.println(&quot;T: &quot; + t.getClass().getName());
        System.out.println(&quot;U: &quot; + u.getClass().getName());
    }

    public static void main(String[] args) {
        Box&lt;Integer&gt; integerBox = new Box&lt;Integer&gt;();
        integerBox.set(new Integer(10));
        integerBox.inspect(&quot;some text&quot;); // error: this is still String!
    }
}
</code></pre>
<p>将这个有界形式类型参数加入我们的泛型方法后，编译将会失败，因为我们将String类型的实际参数传递给了inspect方法：</p>
<pre><code class="language-txt">Box.java:21: &lt;U&gt;inspect(U) in Box&lt;java.lang.Integer&gt; cannot
  be applied to (java.lang.String)
                        integerBox.inspect(&quot;10&quot;);
                                  ^
1 error
</code></pre>
<p>除了限制可用于实例化泛型类型的类型之外，有界形式类型参数还允许你调用在边界中定义的方法：</p>
<pre><code class="language-java">public class NaturalNumber&lt;T extends Integer&gt; {

    private T n;

    public NaturalNumber(T n)  { this.n = n; }

    public boolean isEven() {
        return n.intValue() % 2 == 0;
    }

    // ...
}
</code></pre>
<p>isEven方法通过n调用Integer类中定义的intValue方法。</p>
<h3 id="">多个边界</h3>
<p>前面的示例演示了如何使用带有单个边界的形式类型参数，但是形式类型参数可以有多个边界：</p>
<pre><code class="language-java">&lt;T extends B1 &amp; B2 &amp; B3&gt;
</code></pre>
<p>被多个边界约束的类型变量是这些边界的子类型。如果其中一个边界是类，则必须首先指定它。例如：</p>
<pre><code class="language-java">Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }
</code></pre>
<pre><code class="language-java">class D &lt;T extends A &amp; B &amp; C&gt; { /* ... */ }
</code></pre>
<p>如果没有首先指定边界A，则会出现编译时错误：</p>
<pre><code class="language-java">class D &lt;T extends B &amp; A &amp; C&gt; { /* ... */ }  // compile-time error
</code></pre>
<h2 id="">泛型方法和有界类型形式参数</h2>
<p>有时，你可能希望限制可用作参数化类型中的实际类型参数的类型。例如，处理数字的方法可能只想接受Number或其子类的实例。这就是有界形式类型参数的用途。</p>
<p>若要声明有界形式类型参数，可以依次列出形式类型参数的名称，随后是extends关键字，最后是它的上界，在本例中它的上界为Number。注意，在这个上下文中，extends在一般意义上是指“extends”（继承类时使用的extends）或“implements”（实现接口时使用的implements）。</p>
<pre><code class="language-java">public class Box&lt;T&gt; {

    private T t;        

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }

    public &lt;U extends Number&gt; void inspect(U u){
        System.out.println(&quot;T: &quot; + t.getClass().getName());
        System.out.println(&quot;U: &quot; + u.getClass().getName());
    }

    public static void main(String[] args) {
        Box&lt;Integer&gt; integerBox = new Box&lt;Integer&gt;();
        integerBox.set(new Integer(10));
        integerBox.inspect(&quot;some text&quot;); // error: this is still String!
    }
}
</code></pre>
<p>将这个有界形式类型参数加入我们的泛型方法后，编译将会失败，因为我们将String类型的实际参数传递给了inspect方法：</p>
<pre><code class="language-txt">Box.java:21: &lt;U&gt;inspect(U) in Box&lt;java.lang.Integer&gt; cannot
  be applied to (java.lang.String)
                        integerBox.inspect(&quot;10&quot;);
                                  ^
1 error
</code></pre>
<p>除了限制可用于实例化泛型类型的类型之外，有界形式类型参数还允许你调用在边界中定义的方法：</p>
<pre><code class="language-java">public class NaturalNumber&lt;T extends Integer&gt; {

    private T n;

    public NaturalNumber(T n)  { this.n = n; }

    public boolean isEven() {
        return n.intValue() % 2 == 0;
    }

    // ...
}
</code></pre>
<p>isEven方法通过n调用Integer类中定义的intValue方法。</p>
<p>多个边界<br>
前面的示例演示了如何使用带有单个边界的形式类型参数，但是形式类型参数可以有多个边界：</p>
<pre><code class="language-java">&lt;T extends B1 &amp; B2 &amp; B3&gt;
</code></pre>
<p>被多个边界约束的类型变量是这些边界的子类型。如果其中一个边界是类，则必须首先指定它。例如：</p>
<pre><code class="language-java">Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }
</code></pre>
<pre><code class="language-java">class D &lt;T extends A &amp; B &amp; C&gt; { /* ... */ }
</code></pre>
<p>如果没有首先指定边界A，则会出现编译时错误：</p>
<pre><code class="language-java">class D &lt;T extends B &amp; A &amp; C&gt; { /* ... */ }  // compile-time error
</code></pre>
<h2 id="">泛型、继承和子类型</h2>
<p>你已经知道，只要类型兼容，就可以将一个类型的对象赋值给另一个类型的对象。例如，你可以将Integer对象赋值给Object对象，因为Object是Integer的父类型之一：</p>
<pre><code class="language-java">Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger;   // OK
</code></pre>
<p>在面向对象的术语中，这被称为“is a”关系。因为Integer是一种Object，因此允许赋值。但是Integer也是一种Number，所以下面的代码也是有效的：</p>
<pre><code class="language-java">public void someMethod(Number n) { /* ... */ }

someMethod(new Integer(10));   // OK
someMethod(new Double(10.1));   // OK
</code></pre>
<p>泛型也是如此。你可以执行泛型类型调用，将Number作为其实际类型参数传递，如果参数与Number兼容，则允许任何后续的add调用：</p>
<pre><code class="language-java">Box&lt;Number&gt; box = new Box&lt;Number&gt;();
box.add(new Integer(10));   // OK
box.add(new Double(10.1));  // OK
</code></pre>
<p>现在请考虑以下方法：</p>
<pre><code class="language-java">public void boxTest(Box&lt;Number&gt; n) { /* ... */ }
</code></pre>
<p>它接受什么样的实际类型参数？通过查看它的签名，可以看到它接受一个类型为Box&lt;Number&gt;的参数。但这是什么意思？你是否可以像预期的那样传入Box&lt;Integer&gt;或Box&lt;Double&gt;？答案是否定的，因为Box&lt;Integer&gt;和Box&lt;Double&gt;不是Box&lt;Number&gt;的子类型。</p>
<p>在使用泛型编程时，这是一个常见的误解，但这是一个需要学习的重要概念。</p>
<p><img src="https://img.hacpai.com/file/2019/08/genericssubtypeRelationship-5c7f0564.gif" alt="genericssubtypeRelationship.gif"><br>
图表显示Box&lt;Integer&gt;不是Box&lt;Number&gt;的子类型<br>
Box&lt;Integer&gt;不是Box&lt;Number&gt;的子类型，即使Integer是Number的子类型。<br>
<strong>注意：</strong> 给定的两个具体类型A和B（例如：Number和Integer），无论A和B是否相关，MyClass&lt;A&gt;与MyClass&lt;B&gt;都没有任何关系。MyClass&lt;A&gt;和MyClass&lt;B&gt;的公共父类是Object。</p>
<p>有关当形式类型参数相关时如何在两个泛型类之间创建类似子类型的关系的信息，请参阅“通配符和子类型多态”一节。</p>
<h3 id="">泛型类和子类型多态</h3>
<p>你可以通过继承泛型类或实现泛型接口来得到它的子类型。一个类或接口的形式类型参数与另一个类或接口的形式类型参数之间的关系由extends和implements子句确定。</p>
<p><a href="#%E5%A4%87%E6%B3%A81">备注1</a>。</p>
<p>以集合类为例，ArrayList&lt;E&gt;实现了List&lt;E&gt;，List&lt;E&gt;继承了Collection&lt;E&gt;。所以ArrayList&lt;String&gt;是List&lt;String&gt;的子类型，List&lt;String&gt;是Collection&lt;String&gt;的子类型。只要不改变实际类型参数，类型之间的子类型关系就会保持不变。</p>
<p>展示集合层次结构示例的图表：所以ArrayList&lt;String&gt;是List&lt;String&gt;的子类型，List&lt;String&gt;是Collection&lt;String&gt;的子类型。</p>
<p><img src="https://img.hacpai.com/file/2019/08/genericssampleHierarchy-91c43990.gif" alt="genericssampleHierarchy.gif"><br>
集合层次结构的示例<br>
现在假设我们想要定义我们自己的列表接口PayloadList，它将泛型类型P的可选值与每个元素相关联。它可能是这样声明的：</p>
<pre><code class="language-java">interface PayloadList&lt;E,P&gt; extends List&lt;E&gt; {
  void setPayload(int index, P val);
  ...
}
</code></pre>
<p>PayloadList的以下参数化类型是List&lt;String&gt;的子类型：</p>
<pre><code class="language-java">PayloadList&lt;String,String&gt;
PayloadList&lt;String,Integer&gt;
PayloadList&lt;String,Exception&gt;
</code></pre>
<p>展示PayLoadList层次结构示例的图表：所以ArrayList&lt;String&gt;是List&lt;String&gt;的子类型，List&lt;String&gt;是Collection&lt;String&gt;的子类型。与PayloadList&lt;String，String&gt;在同一级别上是PayloadList&lt;String，Integer&gt;和PayloadList&lt;String，Exceptions&gt;。</p>
<p><img src="https://img.hacpai.com/file/2019/08/genericspayloadListHierarchy-5fc422c2.gif" alt="genericspayloadListHierarchy.gif"><br>
PayloadList层次结构的示例</p>
<h2 id="">类型推断</h2>
<p>类型推断指的是Java编译器能够检查每个方法调用和相应的声明，以此来确定使调用有效的实际类型参数（或实际参数）。推断算法先确定实际参数的类型，以及赋值操作等号左边的类型或将要被返回的值的类型。最后，推断算法会尝试找到适用于所有实际参数的最具体类型。</p>
<p>为了说明最后一点，在下面的示例中，推断算法确定了传递给pick方法的第二个参数是Serializable类型：</p>
<pre><code class="language-java">static &lt;T&gt; T pick(T a1, T a2) { return a2; }
Serializable s = pick(&quot;d&quot;, new ArrayList&lt;String&gt;());
</code></pre>
<p><a href="#%E5%A4%87%E6%B3%A82">备注2</a>。</p>
<h3 id="">类型推断和泛型方法</h3>
<p>“泛型方法”一节向你介绍了类型推断，这使你能够像调用普通方法一样调用泛型方法，而无需在尖括号之间指定类型。考虑下面的例子，需要<a href="https://docs.oracle.com/javase/tutorial/java/generics/examples/Box.java">Box</a>类的BoxDemo：</p>
<pre><code class="language-java">public class BoxDemo {

  public static &lt;U&gt; void addBox(U u, 
      java.util.List&lt;Box&lt;U&gt;&gt; boxes) {
    Box&lt;U&gt; box = new Box&lt;&gt;();
    box.set(u);
    boxes.add(box);
  }

  public static &lt;U&gt; void outputBoxes(java.util.List&lt;Box&lt;U&gt;&gt; boxes) {
    int counter = 0;
    for (Box&lt;U&gt; box: boxes) {
      U boxContents = box.get();
      System.out.println(&quot;Box #&quot; + counter + &quot; contains [&quot; +
             boxContents.toString() + &quot;]&quot;);
      counter++;
    }
  }

  public static void main(String[] args) {
    java.util.ArrayList&lt;Box&lt;Integer&gt;&gt; listOfIntegerBoxes =
      new java.util.ArrayList&lt;&gt;();
    BoxDemo.&lt;Integer&gt;addBox(Integer.valueOf(10), listOfIntegerBoxes);
    BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
    BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes);
    BoxDemo.outputBoxes(listOfIntegerBoxes);
  }
}
</code></pre>
<p>以下是该示例的输出：</p>
<pre><code class="language-txt">Box #0 contains [10]
Box #1 contains [20]
Box #2 contains [30]
</code></pre>
<p>泛型方法addBox定义了一个名为U的形式类型参数。通常，Java编译器可以推断出泛型方法调用的实际类型参数。因此，在大多数情况下，你不必指定它们。例如，要调用泛型方法addBox，可以使用类型见证（type witness）指定实际类型参数，如下所示：</p>
<pre><code class="language-java">BoxDemo.&lt;Integer&gt;addBox(Integer.valueOf(10), listOfIntegerBoxes);
</code></pre>
<p>或者，如果省略了类型见证，Java编译器会（从方法的实际参数）自动推断出实际类型参数是Integer：</p>
<pre><code class="language-java">BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
</code></pre>
<h3 id="">泛型类的类型推断和实例化</h3>
<p>只要编译器可以从上下文中推断出实际类型参数，就可以用一组空的实际类型参数（&lt;&gt;）替换调用泛型类的构造方法所需的实际类型参数。这对尖括号被非正式地称为菱形操作符。</p>
<p>例如，考虑以下变量声明：</p>
<pre><code class="language-java">Map&lt;String, List&lt;String&gt;&gt; myMap = new HashMap&lt;String, List&lt;String&gt;&gt;();
</code></pre>
<p>你可以用一组空的实际类型参数(&lt;&gt;)替换掉构造方法的参数化类型：</p>
<pre><code class="language-java">Map&lt;String, List&lt;String&gt;&gt; myMap = new HashMap&lt;&gt;();
</code></pre>
<p>请注意，要在泛型类实例化期间利用类型推断，你必须使用菱形操作符。在以下示例中，编译器产生了未经检查的转换警告，因为HashMap()构造方法引用了HashMap的原始类型，而不是Map&lt;String, List&lt;String&gt;&gt;类型：</p>
<pre><code class="language-java">Map&lt;String, List&lt;String&gt;&gt; myMap = new HashMap(); // unchecked conversion warning
</code></pre>
<h3 id="">泛型类和非泛型类的类型推断和泛型构造方法</h3>
<p>请注意，构造方法在泛型类和非泛型类中都可以是泛型的（换句话说，可以声明它们自己的形式类型参数）。请考虑以下示例：</p>
<pre><code class="language-java">class MyClass&lt;X&gt; {
  &lt;T&gt; MyClass(T t) {
    // ...
  }
}
</code></pre>
<p>考虑以下MyClass类的实例化：</p>
<pre><code class="language-java">new MyClass&lt;Integer&gt;(&quot;&quot;)
</code></pre>
<p>该语句创建参数化类型MyClass&lt;Integer&gt;的实例；该语句显式地指定泛型类MyClass&lt;X&gt;的形式类型参数X的类型为Integer。请注意，此泛型类的构造方法包含一个形式类型参数T。编译器推断出此泛型类的构造方法的形式类型参数T的类型是String（因为此构造方法的实际参数是一个String对象）。</p>
<p>Java SE 7之前版本的编译器能够推断出泛型构造方法的形式类型参数的实际类型，这类似于泛型方法。然而，Java SE 7及更高版本中的编译器可以推断出要实例化的泛型类的形式类型参数的实际类型（需要使用菱形操作符&lt;&gt;）。请考虑以下示例：</p>
<pre><code class="language-java">MyClass&lt;Integer&gt; myObject = new MyClass&lt;&gt;(&quot;&quot;);
</code></pre>
<p>在这个例子中，编译器推断出泛型类MyClass&lt;X&gt;的形式类型参数X的类型是Integer。它推断出此泛型类的构造方法的形式类型参数T的类型是String。</p>
<p><strong>注意：</strong> 值得注意的是，推断算法仅使用实际调用参数、目标类型，以及显然的预期返回值类型来推断类型。推断算法不使用程序后续的结果做类型推断。</p>
<h3 id="">目标类型</h3>
<p>Java编译器利用目标类型来推断泛型方法调用的形式类型参数的实际类型。表达式的目标类型是Java编译器根据表达式出现的位置所期望的数据类型。考虑方法Collections.emptyList，声明如下：</p>
<pre><code class="language-java">static &lt;T&gt; List&lt;T&gt; emptyList();
</code></pre>
<p>考虑以下赋值语句：</p>
<pre><code class="language-java">List&lt;String&gt; listOne = Collections.emptyList();
</code></pre>
<p>该语句期待List&lt;String&gt;的实例；此数据类型是目标类型。因为方法emptyList返回List&lt;T&gt;类型的值，所以编译器推断出实际类型参数T的值必须是String。这在JavaSE 7和8中都适用。或者，你可以使用类型见证并指定T的值，如下所示：</p>
<pre><code class="language-java">List&lt;String&gt; listOne = Collections.&lt;String&gt;emptyList();
</code></pre>
<p>然而，在这种情况下，这是没有必要的。不过，在其他情况下，这是必要的。考虑以下方法：</p>
<pre><code class="language-java">void processStringList(List&lt;String&gt; stringList) {
    // process stringList
}
</code></pre>
<p>假设你要使用一个空列表调用方法processStringList。在Java SE 7中，以下语句不能编译通过：</p>
<pre><code class="language-java">processStringList(Collections.emptyList());
</code></pre>
<p>Java SE 7编译器生成类似于以下内容的错误消息：</p>
<pre><code class="language-java">List&lt;Object&gt; cannot be converted to List&lt;String&gt;
</code></pre>
<p>编译器要求提供T的实际类型参数，该值默认为Object。因此，方法Collections.emptyList的调用返回一个List&lt;Object&gt;类型的值，该值与方法processStringList不兼容。因此，在Java SE 7中，你必须按如下方式指定实际类型参数的值：</p>
<pre><code class="language-java">processStringList(Collections.&lt;String&gt;emptyList());
</code></pre>
<p>这在Java SE 8中不再是必需的。目标类型的概念已经扩展到包括方法的实际参数，例如方法processStringList的实际参数。在这种情况下，方法processStringList需要一个List&lt;String&gt;类型的参数。方法Collections.emptyList返回List&lt;T&gt;的值，因此使用List&lt;String&gt;的目标类型时，编译器推断出实际类型参数T的值为String。因此，在Java SE 8中，以下语句能通过编译：</p>
<pre><code class="language-java">processStringList(Collections.emptyList());
</code></pre>
<p>有关更多信息，请参阅<a href="https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html">“Lambda表达式”</a>中的<a href="https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#target-typing">“目标类型”</a>一节。</p>
<h2 id="">通配符</h2>
<p>在泛型代码中，问号（?），称为通配符，表示未知类型。通配符可用于多种情况：作为参数、字段或局部变量的类型；有时作为返回值类型(尽管使用更具体的类型的编程实践更好)。通配符从不用作泛型方法调用、泛型类实例创建或父类型的实际类型参数。</p>
<p>下面几节将更详细地讨论通配符，包括上界通配符、下界通配符和通配符捕获。</p>
<h2 id="">上界通配符</h2>
<p>你可以使用上界通配符来放宽对变量的限制。例如，假设你要编写一个适用于List&lt;Integer&gt;、List&lt;Double&gt;和List&lt;Number&gt;的方法；你可以通过使用上界通配符来实现这一点。</p>
<p>若要声明上界通配符，请使用通配符符号（?），它后面跟着extends关键字，然后是其上界。注意，在这个上下文中，extends在一般意义上是指“extends”（继承类时使用的extends）或“implements”（实现接口时使用的implements）。</p>
<p>要编写适用于Number列表的方法以及Number子类型列表的方法，例如Integer列表、Double列表和Float列表，你可以指定List&lt;？extends Number&gt;。术语List&lt;Number&gt;比List&lt;? extends Number&gt;更具限制性，因为前者只匹配Number类型的列表，而后者匹配Number类型或其任意子类的列表。</p>
<p>考虑以下方法process：</p>
<pre><code class="language-java">public static void process(List&lt;? extends Foo&gt; list) { /* ... */ }
</code></pre>
<p>上界通配符，&lt;? extends Foo&gt;，其中Foo是任意类型，匹配Foo和Foo的任何子类型。process方法可以像Foo类型一样访问列表元素：</p>
<pre><code class="language-java">public static void process(List&lt;? extends Foo&gt; list) {
    for (Foo elem : list) {
        // ...
    }
}
</code></pre>
<p>在foreach子句中，elem变量迭代列表中的每个元素。Foo类中定义的任何方法现在都可以在elem上使用。</p>
<p>sumOfList方法返回列表中数字的总和：</p>
<pre><code class="language-java">public static double sumOfList(List&lt;? extends Number&gt; list) {
    double s = 0.0;
    for (Number n : list)
        s += n.doubleValue();
    return s;
}
</code></pre>
<p>以下使用Integer对象的列表的代码输出结果是sum = 6.0：</p>
<pre><code class="language-java">List&lt;Integer&gt; li = Arrays.asList(1, 2, 3);
System.out.println(&quot;sum = &quot; + sumOfList(li));
</code></pre>
<p>Double值的列表可以使用相同的sumOfList方法。以下代码输出结果是sum = 7.0：</p>
<pre><code class="language-java">List&lt;Double&gt; ld = Arrays.asList(1.2, 2.3, 3.5);
System.out.println(&quot;sum = &quot; + sumOfList(ld));
</code></pre>
<h2 id="">无界通配符</h2>
<p>无界通配符类型是使用通配符（?）指定的，例如List&lt;?&gt;。这被称为未知类型的列表。在两种情况下，无界通配符是一种有用的办法：</p>
<ul>
<li>如果你正在编写可以使用Object类中提供的功能来实现的方法。</li>
<li>当代码使用泛型类中不依赖于形式类型参数的方法时。例如，List.size或List.clear。实际上，之所以经常使用Class&lt;?&gt;，是因为Class&lt;T&gt;中的大多数方法都不依赖于T。</li>
</ul>
<p>考虑以下方法，printList：</p>
<pre><code class="language-java">public static void printList(List&lt;Object&gt; list) {
    for (Object elem : list)
        System.out.println(elem + &quot; &quot;);
    System.out.println();
}
</code></pre>
<p>printList方法的目标是输出任何类型的列表，但它无法实现该目标——它只输出一个Object实例的列表；它不能输出List&lt;Integer&gt;，List&lt;String&gt;，List&lt;Double&gt;等，因为它们不是List&lt;Object&gt;的子类型。要编写泛型的printList方法，请使用List&lt;?&gt;：</p>
<pre><code class="language-java">public static void printList(List&lt;?&gt; list) {
    for (Object elem: list)
        System.out.print(elem + &quot; &quot;);
    System.out.println();
}
</code></pre>
<p>因为对于任何具体类型A，List&lt;A&gt;是List&lt;?&gt;的子类型，你可以使用printList方法输出任何类型的列表：</p>
<pre><code class="language-java">List&lt;Integer&gt; li = Arrays.asList(1, 2, 3);
List&lt;String&gt;  ls = Arrays.asList(&quot;one&quot;, &quot;two&quot;, &quot;three&quot;);
printList(li);
printList(ls);
</code></pre>
<p><strong>注意：</strong> 在本课程的示例中使用了<a href="https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList-T...-">Arrays.asList</a>方法。这个静态工厂方法转换指定的数组并返回固定大小的列表。<br>
值得注意的是List&lt;Object&gt;和List&lt;?&gt;是不同的。你可以将Object或Object的任何子类型插入到List&lt;Object&gt;中。但是你只能在List&lt;?&gt;中插入null。“通配符使用指南”一节提供了有关如何确定在给定情况下应该使用哪种通配符（如果有的话）的更多信息。</p>
<h2 id="">下界通配符</h2>
<p>在“上界通配符”一节中，上界通配符将未知类型限制为特定类型或该类型的子类型，并使用extends关键字表示。以类似的方式，下界通配符将未知类型限制为特定类型或该类型的父类型。</p>
<p>下界通配符使用通配符（?）表示，它后面跟着super关键字，然后是其下界：&lt;? super A&gt;。</p>
<p><strong>注意：</strong> 你可以指定通配符的上界，也可以指定通配符的下界，但不能同时指定两者。<br>
假设你要编写一个将Integer对象放入列表的方法。为了最大限度地提高灵活性，你希望该方法可以处理List&lt;Integer&gt;、List&lt;Number&gt;和List&lt;Object&gt;——任何可以保存Integer值的方法。</p>
<p>要编写适用于Integer列表和Integer父类型的列表的方法，例如Integer列表、Number列表和Object列表，你可以指定List&lt;? super Integer&gt;。术语Lis&lt;Integer&gt;比List&lt;? super Integer&gt;更具限制性，因为前者只匹配Integer类型的列表，而后者匹配Integer类型或其任意父类型的列表。</p>
<p>以下代码将数字1到10添加到列表的末尾：</p>
<pre><code class="language-java">public static void addNumbers(List&lt;? super Integer&gt; list) {
    for (int i = 1; i &lt;= 10; i++) {
        list.add(i);
    }
}
</code></pre>
<p>“通配符使用指南”一节提供了有关何时使用上界通配符以及何时使用下界通配符的指导。</p>
<h2 id="">通配符和子类型多态</h2>
<p>正如“泛型、继承和子类型”一节中所述，泛型类或泛型接口并不仅仅因为它们的类型之间有关系就相互关联。但是，你可以使用通配符在泛型类或泛型接口之间创建关系。</p>
<p>给定以下两个常规（非泛型）类：</p>
<pre><code class="language-java">class A { /* ... */ }
class B extends A { /* ... */ }
</code></pre>
<p>编写以下代码是合理的：</p>
<pre><code class="language-java">B b = new B();
A a = b;
</code></pre>
<p>该示例演示了常规类的继承遵循这个子类型规则：如果B继承了A，则B类是A类的子类型。此规则不适用于泛型类型：</p>
<pre><code class="language-java">List&lt;B&gt; lb = new ArrayList&lt;&gt;();
List&lt;A&gt; la = lb;   // compile-time error
</code></pre>
<p>鉴于Integer是Number的子类型，List&lt;Integer&gt;和List&lt;Number&gt;之间的关系是什么？</p>
<p>显示List&lt;Number&gt;和List&lt;Integer&gt;的公共父级是未知类型列表的关系图<br>
<img src="https://img.hacpai.com/file/2019/08/genericslistParent-31e3c8d3.gif" alt="genericslistParent.gif"><br>
公共父类是List&lt;?&gt;。<br>
尽管Integer是Number的子类型，但List&lt;Integer&gt;不是List&lt;Number&gt;的子类型，实际上，这两种类型并不相关。List&lt;Number&gt;和List&lt;Integer&gt;的公共父类是List&lt;?&gt;。</p>
<p>为了在这些类之间创建关系，以便代码可以通过List&lt;Integer&gt;的元素访问Number的方法，请使用上界通配符：</p>
<pre><code class="language-java">List&lt;? extends Integer&gt; intList = new ArrayList&lt;&gt;();
List&lt;? extends Number&gt;  numList = intList;  // OK. List&lt;? extends Integer&gt; is a subtype of List&lt;? extends Number&gt;
</code></pre>
<p>因为Integer是Number的子类型，而numList是Number对象的列表，所以intList（Integer对象的列表）和numList之间现在存在关系。下图显示了使用上界通配符和下界通配符声明的多个List类之间的关系。</p>
<p><img src="https://img.hacpai.com/file/2019/08/genericswildcardSubtyping-81d88ba5.gif" alt="genericswildcardSubtyping.gif"><br>
几个泛型列表类声明的层次结构。<br>
“通配符使用指南”一节提供了有关上界通配符和下界通配符使用上的更多信息。</p>
<h2 id="">通配符捕获和助手函数</h2>
<p>在某些情况下，编译器会推断出通配符的类型。例如，列表可以定义为List&lt;?&gt;，但是，在对表达式求值时，编译器会从代码中推断出特定的类型。这种情况称为通配符捕获。</p>
<p>在大多数情况下，你不必担心通配符捕获，除非你看到包含短语“capture of”的错误消息。</p>
<p>示例<a href="https://docs.oracle.com/javase/tutorial/java/generics/examples/WildcardError.java">WildcharError</a>在编译时产生一个捕获错误：</p>
<pre><code class="language-java">import java.util.List;

public class WildcardError {

    void foo(List&lt;?&gt; i) {
        i.set(0, i.get(0));
    }
}
</code></pre>
<p>在本例中，编译器将输入参数i处理为Object类型。当foo方法调用<a href="https://docs.oracle.com/javase/8/docs/api/java/util/List.html#set-int-E-">List.set(int, E)</a>时，编译器无法确认插入到列表中的对象的类型，从而产生错误。当这种类型的错误发生时，通常意味着编译器认为你给变量赋予了错误的类型。由于这个原因，Java语言中添加了泛型——以确保编译时的类型安全。</p>
<p>WildcarError示例在由Oracle的JDK 7 javac编译时生成以下错误：</p>
<pre><code class="language-txt">WildcardError.java:6: error: method set in interface List&lt;E&gt; cannot be applied to given types;
    i.set(0, i.get(0));
     ^
  required: int,CAP#1
  found: int,Object
  reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
  where E is a type-variable:
    E extends Object declared in interface List
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
1 error
</code></pre>
<p>在这个例子中，代码试图执行一个安全的操作，那么你如何处理这个编译器错误？你可以通过编写捕获通配符的私有助手方法来修复它。在这种情况下，你可以通过创建私有助手方法fooHelper来解决这个问题，如<a href="https://docs.oracle.com/javase/tutorial/java/generics/examples/WildcardFixed.java">WildcardFixed</a>中所示：</p>
<pre><code class="language-java">public class WildcardFixed {

    void foo(List&lt;?&gt; i) {
        fooHelper(i);
    }


    // Helper method created so that the wildcard can be captured
    // through type inference.
    private &lt;T&gt; void fooHelper(List&lt;T&gt; l) {
        l.set(0, l.get(0));
    }

}
</code></pre>
<p>多亏了助手方法，编译器使用推断算法确定了T在调用中是CAP#1，即捕获变量。这个示例现在编译成功。</p>
<p>按照惯例，助手方法通常被命名为originalMethodNameHelper。</p>
<p>现在考虑一个更复杂的例子，<a href="https://docs.oracle.com/javase/tutorial/java/generics/examples/WildcardErrorBad.java">WildhardErrorBad</a>：</p>
<pre><code class="language-java">import java.util.List;

public class WildcardErrorBad {

    void swapFirst(List&lt;? extends Number&gt; l1, List&lt;? extends Number&gt; l2) {
      Number temp = l1.get(0);
      l1.set(0, l2.get(0)); // expected a CAP#1 extends Number,
                            // got a CAP#2 extends Number;
                            // same bound, but different types
      l2.set(0, temp);	    // expected a CAP#1 extends Number,
                            // got a Number
    }
}
</code></pre>
<p>在此示例中，代码试图执行不安全的操作。例如，考虑以下对swapFirst方法的调用：</p>
<pre><code class="language-java">List&lt;Integer&gt; li = Arrays.asList(1, 2, 3);
List&lt;Double&gt;  ld = Arrays.asList(10.10, 20.20, 30.30);
swapFirst(li, ld);
</code></pre>
<p>List&lt;Integer&gt;和List&lt;Double&gt;都符合List&lt;？extends Number&gt;，从Integer值列表中取一个条目并尝试将其放入Double值列表中显然是不正确的。</p>
<p>使用Oracle的JDK javac编译器编译代码会产生以下错误：</p>
<pre><code class="language-txt">WildcardErrorBad.java:7: error: method set in interface List&lt;E&gt; cannot be applied to given types;
      l1.set(0, l2.get(0)); // expected a CAP#1 extends Number,
        ^
  required: int,CAP#1
  found: int,Number
  reason: actual argument Number cannot be converted to CAP#1 by method invocation conversion
  where E is a type-variable:
    E extends Object declared in interface List
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Number from capture of ? extends Number
WildcardErrorBad.java:10: error: method set in interface List&lt;E&gt; cannot be applied to given types;
      l2.set(0, temp);      // expected a CAP#1 extends Number,
        ^
  required: int,CAP#1
  found: int,Number
  reason: actual argument Number cannot be converted to CAP#1 by method invocation conversion
  where E is a type-variable:
    E extends Object declared in interface List
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Number from capture of ? extends Number
WildcardErrorBad.java:15: error: method set in interface List&lt;E&gt; cannot be applied to given types;
        i.set(0, i.get(0));
         ^
  required: int,CAP#1
  found: int,Object
  reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
  where E is a type-variable:
    E extends Object declared in interface List
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
3 errors
</code></pre>
<p>没有助手方法可以解决这个问题，因为代码根本就是错误的。</p>
<h2 id="">通配符使用指南</h2>
<p>学习使用泛型编程时，一个更令人困惑的地方是确定何时使用上界通配符以及何时使用下界通配符。本页提供了设计代码时要遵循的一些准则。</p>
<p>为了便于讨论，将变量视为提供两种功能之一是有帮助的：</p>
<p><strong>“In”变量</strong><br>
“ in”变量为代码提供数据。假设一个具有两个参数的copy方法：copy(src，dest)。src的实际参数提供要复制的数据，因此它是“in”形式参数。<br>
<strong>“Out”变量</strong><br>
“out”变量保存以供其他地方使用的数据。在复制示例中，copy(src, dest)，dest的实际参数接受数据，因此它是“out”形式参数。<br>
当然，有些变量同时用于“in”和“out”的目的——这种情况在指南中也有说明。</p>
<p>可以根据“in”和“out”原则来确定是否使用通配符以及使用哪种类型的通配符更合适。以下清单提供了要遵循的指导原则：</p>
<p><strong>通配符指南：</strong></p>
<ul>
<li>使用extends关键字定义带有上界通配符的“in”变量。</li>
<li>使用super关键字定义带有下界通配符的“out”变量。</li>
<li>如果可以使用Object类中定义的方法访问“in”变量，请使用无界通配符。</li>
<li>在代码需要同时作为“in”和“out”变量访问变量的情况下，不要使用通配符。</li>
</ul>
<p>这些指南不适用于方法的返回值类型。应该避免使用通配符作为返回值类型，因为它强制程序员使用代码来处理通配符。</p>
<p>List&lt;? extends ...&gt;可以被非正式地认为是只读的，但这不是严格的保证。假设你有以下两个类：</p>
<pre><code class="language-java">class NaturalNumber {

    private int i;

    public NaturalNumber(int i) { this.i = i; }
    // ...
}

class EvenNumber extends NaturalNumber {

    public EvenNumber(int i) { super(i); }
    // ...
}
</code></pre>
<p>请考虑以下代码：</p>
<pre><code class="language-java">List&lt;EvenNumber&gt; le = new ArrayList&lt;&gt;();
List&lt;? extends NaturalNumber&gt; ln = le;
ln.add(new NaturalNumber(35));  // compile-time error
</code></pre>
<p>因为List&lt;EvenNumber&gt;是List&lt;? extends NaturalNumber&gt;的子类型，你可以将le赋值给ln。但是你不能使用ln将自然数添加到偶数列表中。以下清单上的操作是可能的：</p>
<ul>
<li>你可以添加null。</li>
<li>你可以调用clear方法。</li>
<li>你可以获取迭代器并调用remove方法。</li>
<li>你可以捕获通配符并写入从列表中读取的元素。</li>
</ul>
<p>你可以看到由List&lt;? extends NaturalNumber&gt;定义的列表在严格意义上不是只读的，但你可能会这样想，因为你无法存储新元素或更改列表中的现有元素。</p>
<h2 id="">类型擦除</h2>
<p>Java语言引入了泛型，以便在编译时提供更严格的类型检查，并支持泛型编程。为了实现泛型，Java编译器将类型擦除应用于：</p>
<ul>
<li>将泛型类型中的所有形式类型参数替换为它的边界，如果形式类型参数是无界的，则替换为Object。因此，生成的字节码只包含普通的类、普通的接口和普通的方法。</li>
<li>必要时插入强制类型转换以保证类型安全。</li>
<li>生成桥接方法用来在继承的泛型类型中保留多态性。</li>
</ul>
<p>类型擦除确保不会为参数化类型创建新的类；因此，泛型不会产生运行时开销。</p>
<h2 id="">泛型类型的擦除</h2>
<p>在类型擦除过程中，Java编译器会删除所有形式类型参数，对于每一个形式类型参数，如果形式类型参数是有界的，则用它的第一个边界来代替，或者如果形式类型参数是无界的，则用Object来代替。</p>
<p>考虑以下表示单链表中节点的泛型类：</p>
<pre><code class="language-java">public class Node&lt;T&gt; {

    private T data;
    private Node&lt;T&gt; next;

    public Node(T data, Node&lt;T&gt; next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}
</code></pre>
<p>因为形式类型参数T是无界的，所以Java编译器将其替换为Object：</p>
<pre><code class="language-java">public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() { return data; }
    // ...
}
</code></pre>
<p>在以下示例中，泛型节点类使用有界形式类型参数：</p>
<pre><code class="language-java">public class Node&lt;T extends Comparable&lt;T&gt;&gt; {

    private T data;
    private Node&lt;T&gt; next;

    public Node(T data, Node&lt;T&gt; next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}
</code></pre>
<p>Java编译器将有界形式类型参数T替换为它的第一个边界类Comparable：</p>
<pre><code class="language-java">public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Comparable getData() { return data; }
    // ...
}
</code></pre>
<h2 id="">泛型方法的擦除</h2>
<p>Java编译器还会擦除泛型方法实际参数中的形式类型参数。考虑以下泛型方法：</p>
<pre><code class="language-java">// Counts the number of occurrences of elem in anArray.
//
public static &lt;T&gt; int count(T[] anArray, T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}
</code></pre>
<p>因为T是无界的，所以Java编译器将它替换为Object：</p>
<pre><code class="language-java">public static int count(Object[] anArray, Object elem) {
    int cnt = 0;
    for (Object e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}
</code></pre>
<p>假设定义了以下类：</p>
<pre><code class="language-java">class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }
</code></pre>
<p>你可以编写一个泛型方法来绘制不同的形状：</p>
<pre><code class="language-java">public static &lt;T extends Shape&gt; void draw(T shape) { /* ... */ }
</code></pre>
<p>Java编译器将T替换为Shape：</p>
<pre><code class="language-java">public static void draw(Shape shape) { /* ... */ }
</code></pre>
<h2 id="">类型擦除的影响和桥接方法</h2>
<p>有时候类型擦除会导致你可能没有预料到的情况。下面的示例演示了这种情况是如何发生的。示例（在Bridge方法中描述的）演示了编译器有时如何创建一个称为桥接方法的合成方法，作为类型擦除过程的一部分。</p>
<p>给出以下两个类：</p>
<pre><code class="language-java">public class Node&lt;T&gt; {

    public T data;

    public Node(T data) { this.data = data; }

    public void setData(T data) {
        System.out.println(&quot;Node.setData&quot;);
        this.data = data;
    }
}

public class MyNode extends Node&lt;Integer&gt; {
    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println(&quot;MyNode.setData&quot;);
        super.setData(data);
    }
}
</code></pre>
<p>考虑以下代码：</p>
<pre><code class="language-java">MyNode mn = new MyNode(5);
Node n = mn;            // A raw type - compiler throws an unchecked warning
n.setData(&quot;Hello&quot;);   
Integer x = mn.data;    // Causes a ClassCastException to be thrown.
</code></pre>
<p>类型擦除后，该代码变为：</p>
<pre><code class="language-java">MyNode mn = new MyNode(5);
Node n = (MyNode)mn;         // A raw type - compiler throws an unchecked warning
n.setData(&quot;Hello&quot;);
Integer x = (String)mn.data; // Causes a ClassCastException to be thrown.
</code></pre>
<p>下面是代码执行时发生的情况：</p>
<ul>
<li>n.setData(&quot;Hello&quot;);导致在MyNode类的对象上setData(Object)方法被执行。（MyNode类从Node类继承了setData(Object)方法）</li>
<li>在setData(Object)方法的主体中，n引用的对象的data字段被赋值为一个String对象。</li>
<li>通过mn引用的同一对象的data字段可以被访问，并且期待它是一个Integer对象（因为mn是一个MyNode对象，而一个MyNode对象是一个Node&lt;Integer&gt;对象）。</li>
<li>试图将String对象赋值给Integer对象会导致Java编译器在赋值时插入强制转换从而产生ClassCastException。</li>
</ul>
<h3 id="">桥接方法</h3>
<p>编译继承了参数化类的类时，或编译实现了参数化接口的类时，编译器可能需要创建一个称为桥接方法的合成方法，作为类型擦除过程的一部分。你通常不需要担心桥接方法，但是如果在堆栈跟踪信息中出现了桥接方法，你可能会感到困惑。</p>
<p>在类型擦除之后，Node和MyNode类变为：</p>
<pre><code class="language-java">public class Node {

    public Object data;

    public Node(Object data) { this.data = data; }

    public void setData(Object data) {
        System.out.println(&quot;Node.setData&quot;);
        this.data = data;
    }
}

public class MyNode extends Node {

    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println(&quot;MyNode.setData&quot;);
        super.setData(data);
    }
}
</code></pre>
<p>在类型擦除之后，方法签名不匹配。Node中的方法变为setData(Object)，MyNode中的方法变为setData(Integer)。因此，MyNode中的setData方法不会重写Node中的setData方法。</p>
<p>为了解决这个问题，在类型擦除后保留泛型类型的<a href="https://docs.oracle.com/javase/tutorial/java/IandI/polymorphism.html">多态性</a>，Java编译器将生成一个桥接方法，以确保子类型多态按预期工作。对于MyNode类，编译器为setData生成以下桥接方法：</p>
<pre><code class="language-java">class MyNode extends Node {

    // Bridge method generated by the compiler
    //
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println(&quot;MyNode.setData&quot;);
        super.setData(data);
    }

    // ...
}
</code></pre>
<p>如你所见，桥接方法在类型擦除之后与Node类的setData方法具有相同的方法签名，并把真正的实现委托给了原有的setData方法。</p>
<h2 id="">非具体化类型</h2>
<p>“类型擦除”一节讨论了编译器删除与形式类型参数和实际类型参数的相关信息的过程。类型擦除的后果与varargs形式参数是非具体化类型的可变参数(也称为varargs)方法有关。有关可变参数方法的更多信息，请参阅<a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html">“将信息传递给方法或构造方法”</a>中的<a href="https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html#varargs">“任意数量的参数”</a>一节。</p>
<p>此页面包含以下主题：</p>
<ul>
<li>非具体化类型（Non-Reifiable Types）</li>
<li>堆污染（Heap Pollution）</li>
<li>具有非具体化类型的形式参数的可变参数方法的潜在漏洞</li>
<li>阻止从使用了非具体化类型的形式参数的可变参数方法产生警告</li>
</ul>
<h3 id="">非具体化类型</h3>
<p>具体化类型（reifiable type）是在运行时可以得到其全部类型信息的类型。这包括基本类型，非泛型类型，原始类型和无界通配符的调用。</p>
<p>非具体化类型是类型信息在编译时被擦除了的类型，即除了无界通配符的调用之外的其他泛型类型的调用。非具体化类型的类型信息在运行时不是全部可用的。非具体化类型的示例是list&lt;string&gt;和list&lt;number&gt;；JVM无法在运行时区分这些类型之间的差异。如“泛型的限制”一节中所示，在某些情况下，不能使用非具体化类型：例如，在instanceof表达式中不能使用非具体化类型，以及不能使用非具体化类型作为数组中的元素。</p>
<p><a href="#%E5%A4%87%E6%B3%A83">备注3</a>。</p>
<h3 id="">堆污染</h3>
<p>当参数化类型的变量引用非该参数化类型的对象时，就会发生堆污染。如果程序执行了一些在编译时引起未经检查的警告的操作，就会发生这种情况。如果在编译时（在编译时类型检查规则的限制内）或在运行时，无法验证涉及参数化类型的操作（例如，强制转换或方法调用）的正确性，则会生成未经检查的警告。例如，当混合原始类型和参数化类型时，或者当执行未经检的强制转换时，就会发生堆污染。</p>
<p>在正常情况下，当所有代码同时编译时，编译器会发出未经检查的警告，以提醒你注意潜在的堆污染。如果单独编译部分代码，则很难检测到堆污染的潜在风险。如果确保代码编译时没有警告产生，则不会发生堆污染。</p>
<h3 id="">具有非具体化类型的形式参数的可变参数方法的潜在漏洞</h3>
<p>包含varargs形式参数的泛型方法可能会导致堆污染。</p>
<p>考虑以下ArrayBuilder类：</p>
<pre><code class="language-java">public class ArrayBuilder {

  public static &lt;T&gt; void addToList (List&lt;T&gt; listArg, T... elements) {
    for (T x : elements) {
      listArg.add(x);
    }
  }

  public static void faultyMethod(List&lt;String&gt;... l) {
    Object[] objectArray = l;     // Valid
    objectArray[0] = Arrays.asList(42);
    String s = l[0].get(0);       // ClassCastException thrown here
  }

}
</code></pre>
<p>以下示例HeapPollutionExample中用到了ArrayBuiler类：</p>
<pre><code class="language-java">public class HeapPollutionExample {

  public static void main(String[] args) {

    List&lt;String&gt; stringListA = new ArrayList&lt;String&gt;();
    List&lt;String&gt; stringListB = new ArrayList&lt;String&gt;();

    ArrayBuilder.addToList(stringListA, &quot;Seven&quot;, &quot;Eight&quot;, &quot;Nine&quot;);
    ArrayBuilder.addToList(stringListB, &quot;Ten&quot;, &quot;Eleven&quot;, &quot;Twelve&quot;);
    List&lt;List&lt;String&gt;&gt; listOfStringLists =
      new ArrayList&lt;List&lt;String&gt;&gt;();
    ArrayBuilder.addToList(listOfStringLists,
      stringListA, stringListB);

    ArrayBuilder.faultyMethod(Arrays.asList(&quot;Hello!&quot;), Arrays.asList(&quot;World!&quot;));
  }
}
</code></pre>
<p>编译时，ArrayBuilder.addToList方法的定义会产生以下警告：</p>
<pre><code class="language-txt">warning: [varargs] Possible heap pollution from parameterized vararg type T
</code></pre>
<p>当编译器遇到可变参数方法时，它会将varargs形式参数转换为数组。然而，Java编程语言不允许创建参数化类型的数组。在方法ArrayBuilder.addToList中，编译器将varargs形式参数T... elements转换为形式参数T[] elements，即数组。但是，由于类型擦除，编译器会将varargs形式参数转换为Object[]元素。因此，存在堆污染的可能性。</p>
<p>以下语句将varargs形式参数l赋值给Object数组objectArgs：</p>
<pre><code class="language-java">Object[] objectArray = l;
</code></pre>
<p>该语句可能会引入堆污染。与varargs形式参数l的参数化类型匹配的值可以赋值给变量objectArray，因此这些值可以赋值给l。但是，编译器不会在此语句中生成未经检查的警告。编译器在将varargs形式参数List&lt;String&gt;... l转换为形式参数List[] l时已生成警告。这个语句是有效的；变量l的类型为Object[]的子类型List[]。</p>
<p>因此，如果将任意类型的List对象赋值给objectArray数组的任意数组组件（array component），编译器不会发出警告或错误，如下所示：</p>
<pre><code class="language-java">objectArray[0] = Arrays.asList(42);
</code></pre>
<p>此语句将只包含一个Integer对象的List对象赋值给objectArray数组的第一个数组组件。</p>
<p>假设你使用以下语句调用ArrayBuilder.faultyMethod方法：</p>
<pre><code class="language-java">ArrayBuilder.faultyMethod(Arrays.asList(&quot;Hello!&quot;), Arrays.asList(&quot;World!&quot;));
</code></pre>
<p>在运行时，JVM会在以下语句中抛出ClassCastException：</p>
<pre><code class="language-java">// ClassCastException thrown here
String s = l[0].get(0);
</code></pre>
<p>存储在变量l的第一个数组组件中的对象具有List&lt;Integer&gt;类型，但此语句需要一个List&lt;String&gt;类型的对象。</p>
<h3 id="">阻止从使用了非具体化类型的形式参数的可变参数方法产生警告</h3>
<p>如果你声明了一个具有参数化类型参数的可变参数方法，并且确保该方法的主体不会因为不正确地处理varargs形式参数而引发ClassCastException或其他类似的异常，则可以通过向静态方法、final方法和构造方法的方法声明添加以下注解来阻止编译器为这类可变参数方法生成警告：</p>
<pre><code class="language-java">@SafeVarargs
</code></pre>
<p><a href="#%E5%A4%87%E6%B3%A83">备注3</a>。</p>
<p>@SafeVarargs注解作为方法契约（method's contract）的一部分陈述；此注解断言方法的实现不会不正确地处理varargs形式参数。</p>
<p>也可以通过在方法声明中添加以下内容来抑制这种警告，尽管这种做法不太可取：</p>
<pre><code class="language-java">@SuppressWarnings({&quot;unchecked&quot;, &quot;varargs&quot;})
</code></pre>
<p>但是，此方法不会抑制从方法的调用位置（call site）生成的警告。如果你不熟悉@SuppressWarnings这个语法，请参阅<a href="https://docs.oracle.com/javase/tutorial/java/annotations/index.html">“注解”</a>一节。</p>
<h2 id="">泛型的限制</h2>
<p>要有效地使用Java泛型，必须考虑以下限制：</p>
<ul>
<li>不能用基本类型实例化泛型类型</li>
<li>不能创建形式类型参数的实例</li>
<li>不能声明类型为形式类型参数的静态字段</li>
<li>不能对参数化类型使用强制类型转换或instanceof</li>
<li>不能创建参数化类型的数组</li>
<li>不能创建、捕获或抛出参数化类型的对象</li>
<li>不能重载类型擦除后形式参数类型为相同原始类型的方法</li>
</ul>
<h3 id="">不能用基本类型实例化泛型类型</h3>
<p>考虑以下参数化类型：</p>
<pre><code class="language-java">class Pair&lt;K, V&gt; {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    // ...
}
</code></pre>
<p>创建Pair对象时，不能用基本类型替换掉形式类型参数K或V：</p>
<pre><code class="language-java">Pair&lt;int, char&gt; p = new Pair&lt;&gt;(8, 'a');  // compile-time error
</code></pre>
<p>只能用非基本类型替换掉形式类型参数K和V：</p>
<pre><code class="language-java">Pair&lt;Integer, Character&gt; p = new Pair&lt;&gt;(8, 'a');
</code></pre>
<p>请注意，Java编译器将8自动装箱到Integer.valueOf(8)，将'a'自动装箱到Character('a')：</p>
<pre><code class="language-java">Pair&lt;Integer, Character&gt; p = new Pair&lt;&gt;(Integer.valueOf(8), new Character('a'));
</code></pre>
<p>有关自动装箱的详细信息，请参阅<a href="https://docs.oracle.com/javase/tutorial/java/data/index.html">“Numbers和Strings”</a>课程中的<a href="https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html">“自动装箱和自动拆箱”</a>一节。</p>
<h3 id="">不能创建形式类型参数的实例</h3>
<p>你不能创建形式类型参数的实例例如，以下代码会导致编译时错误：</p>
<pre><code class="language-java">public static &lt;E&gt; void append(List&lt;E&gt; list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}
</code></pre>
<p>作为变通方法，你可以通过反射创建形式类型参数的对象：</p>
<pre><code class="language-java">public static &lt;E&gt; void append(List&lt;E&gt; list, Class&lt;E&gt; cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}
</code></pre>
<p>你可以按以下方式调用append方法：</p>
<pre><code class="language-java">List&lt;String&gt; ls = new ArrayList&lt;&gt;();
append(ls, String.class);
</code></pre>
<h3 id="">不能声明类型为形式类型参数的静态字段</h3>
<p>类的静态字段是类的所有非静态对象共享的类级变量。因此，声明类型为形式类型参数的静态字段是不允许的。考虑下面的类：</p>
<pre><code class="language-java">public class MobileDevice&lt;T&gt; {
    private static T os;

    // ...
}
</code></pre>
<p>如果允许声明类型为形式类型参数的静态字段，则以下代码将混淆：</p>
<pre><code class="language-java">MobileDevice&lt;Smartphone&gt; phone = new MobileDevice&lt;&gt;();
MobileDevice&lt;Pager&gt; pager = new MobileDevice&lt;&gt;();
MobileDevice&lt;TabletPC&gt; pc = new MobileDevice&lt;&gt;();
</code></pre>
<p>因为静态字段os是由phone、pager和pc这三个对象共享的，那么os的实际类型是什么？它不能同时是Smartphone，Pager和TabletPC。因此，你无法创建声明类型为形式类型参数的静态字段。</p>
<h3 id="instanceof">不能对参数化类型使用强制类型转换或instanceof</h3>
<p>因为Java编译器会擦除泛型代码中的所有形式类型参数，所以无法验证运行时使用的泛型类型的参数化类型：</p>
<pre><code class="language-java">public static &lt;E&gt; void rtti(List&lt;E&gt; list) {
    if (list instanceof ArrayList&lt;Integer&gt;) {  // compile-time error
        // ...
    }
}
</code></pre>
<p>传递给rtti方法的参数化类型的集合是：</p>
<pre><code class="language-java">S = { ArrayList&lt;Integer&gt;, ArrayList&lt;String&gt; LinkedList&lt;Character&gt;, ... }
</code></pre>
<p>运行时不跟踪形式类型参数，因此无法区分ArrayList&lt;Integer&gt;和ArrayList&lt;String&gt;之间的区别。你最多可以做的是使用无界通配符来验证list是否为ArrayList：</p>
<pre><code class="language-java">public static void rtti(List&lt;?&gt; list) {
    if (list instanceof ArrayList&lt;?&gt;) {  // OK; instanceof requires a reifiable type
        // ...
    }
}
</code></pre>
<p>通常，除非参数化类型被无界通配符参数化，否则不能将其强制转换为参数化类型。例如：</p>
<pre><code class="language-java">List&lt;Integer&gt; li = new ArrayList&lt;&gt;();
List&lt;Number&gt;  ln = (List&lt;Number&gt;) li;  // compile-time error
</code></pre>
<p>但是，在某些情况下，编译器知道形式类型参数总是有效的，并且允许强制转换。例如：</p>
<pre><code class="language-java">List&lt;String&gt; l1 = ...;
ArrayList&lt;String&gt; l2 = (ArrayList&lt;String&gt;)l1;  // OK
</code></pre>
<h3 id="">不能创建参数化类型的数组</h3>
<p>你不能创建参数化类型的数组例如，以下代码不能通过编译：</p>
<pre><code class="language-java">List&lt;Integer&gt;[] arrayOfLists = new List&lt;Integer&gt;[2];  // compile-time error
</code></pre>
<p>以下代码说明了将不同类型插入数组时发生的情况：</p>
<pre><code class="language-java">Object[] strings = new String[2];
strings[0] = &quot;hi&quot;;   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.
</code></pre>
<p>如果你使用泛型列表尝试相同的操作，则会出现问题：</p>
<pre><code class="language-java">Object[] stringLists = new List&lt;String&gt;[];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList&lt;String&gt;();   // OK
stringLists[1] = new ArrayList&lt;Integer&gt;();  // An ArrayStoreException should be thrown,
                                            // but the runtime can't detect it.
</code></pre>
<p>如果允许参数化类型的数组，则前面的代码将无法抛出所需的ArrayStoreException。</p>
<h3 id="">不能创建、捕获或抛出参数化类型的对象</h3>
<p>泛型类不能直接或间接继承Throwable类。例如，以下类将无法编译：</p>
<pre><code class="language-java">// Extends Throwable indirectly
class MathException&lt;T&gt; extends Exception { /* ... */ }    // compile-time error

// Extends Throwable directly
class QueueFullException&lt;T&gt; extends Throwable { /* ... */ // compile-time error
</code></pre>
<p>无法捕获形式类型参数的实例的方法：</p>
<pre><code class="language-java">public static &lt;T extends Exception, J&gt; void execute(List&lt;J&gt; jobs) {
    try {
        for (J job : jobs)
            // ...
    } catch (T e) {   // compile-time error
        // ...
    }
}
</code></pre>
<p>不过，你可以在throws子句中使用形式类型参数：</p>
<pre><code class="language-java">class Parser&lt;T extends Exception&gt; {
    public void parse(File file) throws T {     // OK
        // ...
    }
}
</code></pre>
<h3 id="">不能重载类型擦除后形式参数类型为相同原始类型的方法</h3>
<p>类不能有两个在类型擦除后具有相同的签名的重载方法。</p>
<pre><code class="language-java">public class Example {
    public void print(Set&lt;String&gt; strSet) { }
    public void print(Set&lt;Integer&gt; intSet) { }
}
</code></pre>
<p>重载将共享相同的类文件表示法（classfile representation），并将生成编译时错误。</p>
<h2 id="">问题和练习</h2>
<ol>
<li>
<p>编写一个泛型方法来计算集合中具有特定属性的元素数目（例如，奇数，素数，回文数）。</p>
</li>
<li>
<p>下面的类能编译通过吗？如果不能编译通过，为什么呢？</p>
<pre><code class="language-java">public final class Algorithm {
    public static &lt;T&gt; T max(T x, T y) {
        return x &gt; y ? x : y;
    }
}
</code></pre>
</li>
<li>
<p>编写一个泛型方法来交换数组中两个不同元素的位置。</p>
</li>
<li>
<p>如果编译器在编译时擦除了所有形式类型参数，为什么要使用泛型？</p>
</li>
<li>
<p>类型擦除后，以下类转换成什么样子？</p>
<pre><code class="language-java">public class Pair&lt;K, V&gt; {

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey(); { return key; }
    public V getValue(); { return value; }

    public void setKey(K key)     { this.key = key; }
    public void setValue(V value) { this.value = value; }

    private K key;
    private V value;
}
</code></pre>
</li>
<li>
<p>类型擦除后，以下方法转换成什么样子？</p>
<pre><code class="language-java">public static &lt;T extends Comparable&lt;T&gt;&gt;
    int findFirstGreaterThan(T[] at, T elem) {
    // ...
}
</code></pre>
</li>
<li>
<p>下面的方法能编译通过吗？如果不能编译通过，为什么呢？</p>
<pre><code class="language-java">public static void print(List&lt;? extends Number&gt; list) {
    for (Number n : list)
        System.out.print(n + &quot; &quot;);
    System.out.println();
}
</code></pre>
</li>
<li>
<p>编写一个泛型方法，在列表的范围[begin, end)中找到最大元素。</p>
</li>
<li>
<p>下面的类能编译通过吗？如果不能编译通过，为什么呢？</p>
<pre><code class="language-java">public class Singleton&lt;T&gt; {

    public static T getInstance() {
        if (instance == null)
            instance = new Singleton&lt;T&gt;();

        return instance;
    }

    private static T instance = null;
}
</code></pre>
</li>
<li>
<p>给出以下类：</p>
<pre><code class="language-java">class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }

class Node&lt;T&gt; { /* ... */ }
</code></pre>
<p>下面的代码能编译通过吗？如果不能编译通过，为什么呢？</p>
<pre><code class="language-java">Node&lt;Circle&gt; nc = new Node&lt;&gt;();
Node&lt;Shape&gt;  ns = nc;
</code></pre>
</li>
<li>
<p>考虑这个类：</p>
<pre><code class="language-java">class Node&lt;T&gt; implements Comparable&lt;T&gt; {
    public int compareTo(T obj) { /* ... */ }
    // ...
}
</code></pre>
<p>下面的代码能编译通过吗？如果不能编译通过，为什么呢？</p>
<pre><code class="language-java">Node&lt;String&gt; node = new Node&lt;&gt;();
Comparable&lt;String&gt; comp = node;
</code></pre>
</li>
<li>
<p>如何调用以下方法来查找列表中的第一个和指定列表中的整数互质的整数？</p>
<pre><code class="language-java">public static &lt;T&gt; int findFirst(List&lt;T&gt; list, int begin, int end, UnaryPredicate&lt;T&gt; p)
</code></pre>
<p>注意，如果gcd(a, b) = 1，则两个整数a和b互质，其中gcd是最大公因子的缩写。</p>
</li>
</ol>
<p>检查你的答案。</p>
<h2 id="">问题和练习的答案</h2>
<ol>
<li>
<p>编写一个泛型方法来计算集合中具有特定属性的元素数目（例如，奇数，素数，回文数）。</p>
<p><strong>答案：</strong></p>
<pre><code class="language-java">public final class Algorithm {
    public static &lt;T&gt; int countIf(Collection&lt;T&gt; c, UnaryPredicate&lt;T&gt; p) {

        int count = 0;
        for (T elem : c)
            if (p.test(elem))
                ++count;
        return count;
    }
}
</code></pre>
<p>其中，泛型UnaryPredicate接口定义如下：</p>
<pre><code class="language-java">public interface UnaryPredicate&lt;T&gt; {
    public boolean test(T obj);
}
</code></pre>
<p>例如，下面的程序计算一个整数列表中奇数的个数：</p>
<pre><code class="language-java">import java.util.*;

class OddPredicate implements UnaryPredicate&lt;Integer&gt; {
    public boolean test(Integer i) { return i % 2 != 0; }
}

public class Test {
    public static void main(String[] args) {
        Collection&lt;Integer&gt; ci = Arrays.asList(1, 2, 3, 4);
        int count = Algorithm.countIf(ci, new OddPredicate());
        System.out.println(&quot;Number of odd integers = &quot; + count);
    }
}
</code></pre>
<p>该程序输出如下：</p>
<pre><code class="language-txt">Number of odd integers = 2
</code></pre>
</li>
<li>
<p>下面的类能编译通过吗？如果不能编译通过，为什么呢？</p>
<pre><code class="language-java">public final class Algorithm {
    public static &lt;T&gt; T max(T x, T y) {
        return x &gt; y ? x : y;
    }
}
</code></pre>
<p><strong>答案：</strong> 不能。大于(&gt;)运算符仅适用于基本数字类型。</p>
</li>
<li>
<p>编写一个泛型方法来交换数组中两个不同元素的位置。</p>
<p><strong>答案：</strong></p>
<pre><code class="language-java">public final class Algorithm {
    public static &lt;T&gt; void swap(T[] a, int i, int j) {
        T temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}
</code></pre>
</li>
<li>
<p>如果编译器在编译时擦除了所有形式类型参数，为什么要使用泛型？</p>
<p><strong>答案：</strong> 你应该使用泛型，因为：<br>
Java 编译器在编译时对泛型代码强制执行更严格的类型检查。<br>
泛型支持类型作为参数的编程。<br>
泛型使你能够实现泛型算法。</p>
</li>
<li>
<p>类型擦除后，以下类转换成什么样子？</p>
<pre><code class="language-java">public class Pair&lt;K, V&gt; {

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey(); { return key; }
    public V getValue(); { return value; }

    public void setKey(K key)     { this.key = key; }
    public void setValue(V value) { this.value = value; }

    private K key;
    private V value;
}
</code></pre>
<p><strong>答案：</strong></p>
<pre><code class="language-java">public class Pair {

    public Pair(Object key, Object value) {
        this.key = key;
        this.value = value;
    }

    public Object getKey()   { return key; }
    public Object getValue() { return value; }

    public void setKey(Object key)     { this.key = key; }
    public void setValue(Object value) { this.value = value; }

    private Object key;
    private Object value;
}
</code></pre>
</li>
<li>
<p>类型擦除后，以下方法转换成什么样子？</p>
<pre><code class="language-java">public static &lt;T extends Comparable&lt;T&gt;&gt;
    int findFirstGreaterThan(T[] at, T elem) {
    // ...
}
</code></pre>
<p><strong>答案：</strong></p>
<pre><code class="language-java">public static int findFirstGreaterThan(Comparable[] at, Comparable elem) {
    // ...
}
</code></pre>
</li>
<li>
<p>下面的方法能编译通过吗？如果不能编译通过，为什么呢？</p>
<pre><code class="language-java">public static void print(List&lt;? extends Number&gt; list) {
    for (Number n : list)
        System.out.print(n + &quot; &quot;);
    System.out.println();
}
</code></pre>
<p><strong>答案：</strong> 能。</p>
</li>
<li>
<p>编写一个泛型方法，在列表的范围[begin, end)中找到最大元素。</p>
<p><strong>答案：</strong></p>
<pre><code class="language-java">import java.util.*;

public final class Algorithm {
    public static &lt;T extends Object &amp; Comparable&lt;? super T&gt;&gt;
        T max(List&lt;? extends T&gt; list, int begin, int end) {

        T maxElem = list.get(begin);

        for (++begin; begin &lt; end; ++begin)
            if (maxElem.compareTo(list.get(begin)) &lt; 0)
                maxElem = list.get(begin);
        return maxElem;
    }
}
</code></pre>
</li>
<li>
<p>下面的类能编译通过吗？如果不能编译通过，为什么呢？</p>
<pre><code class="language-java">public class Singleton&lt;T&gt; {

    public static T getInstance() {
        if (instance == null)
            instance = new Singleton&lt;T&gt;();

        return instance;
    }

    private static T instance = null;
}
</code></pre>
<p><strong>答案：</strong> 不能。无法创建声明类型为形式类型参数T的静态字段。</p>
</li>
<li>
<p>给出以下类：</p>
<pre><code class="language-java">class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }

class Node&lt;T&gt; { /* ... */ }
</code></pre>
<p>下面的代码能编译通过吗？如果不能编译通过，为什么呢？</p>
<pre><code class="language-java">Node&lt;Circle&gt; nc = new Node&lt;&gt;();
Node&lt;Shape&gt;  ns = nc;
</code></pre>
<p><strong>答案：</strong> 不能。因为Node&lt;Circle&gt;不是Node&lt;Shape&gt;的子类型。</p>
<pre><code class="language-java">Consider this class:
class Node&lt;T&gt; implements Comparable&lt;T&gt; {
    public int compareTo(T obj) { /* ... */ }
    // ...
}
</code></pre>
</li>
<li>
<p>考虑这个类：</p>
<pre><code class="language-java">class Node&lt;T&gt; implements Comparable&lt;T&gt; {
    public int compareTo(T obj) { /* ... */ }
    // ...
}
</code></pre>
<p>下面的代码能编译通过吗？如果不能编译通过，为什么呢？</p>
<pre><code class="language-java">Node&lt;String&gt; node = new Node&lt;&gt;();
Comparable&lt;String&gt; comp = node;
</code></pre>
<p><strong>答案：</strong> 能。</p>
</li>
<li>
<p>如何调用以下方法来查找列表中的第一个和指定列表中的整数互质的整数？</p>
<pre><code class="language-java">public static &lt;T&gt;
    int findFirst(List&lt;T&gt; list, int begin, int end, UnaryPredicate&lt;T&gt; p)
</code></pre>
<p>注意，如果gcd(a, b) = 1，则两个整数a和b互质，其中gcd是最大公因子的缩写。</p>
<p><strong>答案：</strong></p>
<pre><code class="language-java">import java.util.*;

public final class Algorithm {

    public static &lt;T&gt;
        int findFirst(List&lt;T&gt; list, int begin, int end, UnaryPredicate&lt;T&gt; p) {

        for (; begin &lt; end; ++begin)
            if (p.test(list.get(begin)))
                return begin;
        return -1;
    }

    // x &gt; 0 and y &gt; 0
    public static int gcd(int x, int y) {
        for (int r; (r = x % y) != 0; x = y, y = r) { }
            return y;
    }
}
</code></pre>
<p>泛型UnaryPredicate接口定义如下：</p>
<pre><code class="language-java">public interface UnaryPredicate&lt;T&gt; {
    public boolean test(T obj);
}
</code></pre>
<p>以下程序测试findFirst方法：</p>
<pre><code class="language-java">import java.util.*;

class RelativelyPrimePredicate implements UnaryPredicate&lt;Integer&gt; {
    public RelativelyPrimePredicate(Collection&lt;Integer&gt; c) {
        this.c = c;
    }

    public boolean test(Integer x) {
        for (Integer i : c)
            if (Algorithm.gcd(x, i) != 1)
                return false;

        return c.size() &gt; 0;
    }

    private Collection&lt;Integer&gt; c;
}

public class Test {
    public static void main(String[] args) throws Exception {

        List&lt;Integer&gt; li = Arrays.asList(3, 4, 6, 8, 11, 15, 28, 32);
        Collection&lt;Integer&gt; c = Arrays.asList(7, 18, 19, 25);
        UnaryPredicate&lt;Integer&gt; p = new RelativelyPrimePredicate(c);

        int i = ALgorithm.findFirst(li, 0, li.size(), p);

        if (i != -1) {
            System.out.print(li.get(i) + &quot; is relatively prime to &quot;);
            for (Integer k : c)
                System.out.print(k + &quot; &quot;);
            System.out.println();
        }
    }
}
</code></pre>
<p>该程序输出如下：</p>
<pre><code class="language-txt">11 is relatively prime to 7 18 19 25
</code></pre>
</li>
</ol>
<h2 id="">备注</h2>
<h3 id="1">备注1</h3>
<p><a href="https://img.hacpai.com/file/2019/08/image-d19ac931.png"><img src="https://img.hacpai.com/file/2019/08/image-d19ac931.png" alt="image.png"></a><br>
<a href="https://en.wikipedia.org/wiki/Polymorphism_(computer_science)">Polymorphism (computer science) - Wikipedia</a></p>
<h3 id="2">备注2</h3>
<pre><code class="language-java">static &lt;T&gt; T pick(T a1, T a2) { return a2; }
Serializable s = pick(&quot;d&quot;, new ArrayList&lt;String&gt;());
</code></pre>
<p>The compiler looks at the expression and tries to find types that match, both for input and output. In your case we see that pick takes a T, what is T? It is used as return and as input of two things. In the call to pick we have &quot;d&quot;, a String literal, we have an ArrayList, and we have a result of type Serializable. The least type that matches all three is Serializable.</p>
<h3 id="3">备注3</h3>
<p><a href="https://stackoverflow.com/questions/18411440/why-we-call-unbounded-wild-card-parameterized-type-as-reifiable">java - Why we call unbounded wild-card parameterized type as reifiable? - Stack Overflow</a></p>
<h3 id="4">备注4</h3>
<p>You can use @SafeVarargs for constructors, for static methods, for final methods and since Java 9 for private methods.<br>
<a href="https://stackoverflow.com/questions/7728971/java-safevarargs-why-do-private-methods-need-to-be-final">java @SafeVarargs why do private methods need to be final - Stack Overflow</a><br>
<a href="https://bugs.openjdk.java.net/browse/JDK-7196160">[JDK-7196160] Project Coin: Allow @SafeVarargs on private methods - Java Bug System</a></p>
</div>]]></content:encoded></item><item><title><![CDATA[【笔记】字符编码]]></title><description><![CDATA[<div class="kg-card-markdown"><h2 id="unicode">Unicode平面</h2>
<p><img src="https://b3logfile.com/file/2020/10/2019070718444042814297-2636becf.jpg" alt="1"></p>
<p>Plane 0, 称作基本平面(Basic Plane), 剩余的称作扩展平面(Supplementary Plane)</p>
<table>
<thead>
<tr>
<th>Unicode code points</th>
<th>Range</th>
<th>Encoding Binary value (码点)</th>
</tr>
</thead>
<tbody>
<tr>
<td>U+000000-U+00007f</td>
<td>0xxxxxxx</td>
<td>0xxxxxxx</td>
</tr>
<tr>
<td>U+000080-U+0007ff</td>
<td>110yyyxx 10xxxxxx</td>
<td>00000yyy xxxxxxxx</td>
</tr>
<tr>
<td>U+000800-U+00ffff</td>
<td>1110yyyy 10yyyyxx 10xxxxxx</td>
<td>yyyyyyyy xxxxxxxx</td>
</tr>
<tr>
<td>U+010000-U+10ffff</td>
<td>11110zzz 10zzyyyy 10yyyyxx 10xxxxxx</td>
<td>000zzzzz yyyyyyyy xxxxxxxx</td>
</tr>
</tbody>
</table>
<h3 id="python">Python中的转义序列</h3>
<table>
<thead>
<tr>
<th>转义序列</th>
<th>意义</th></tr></thead></table></div>]]></description><link>https://zhoujin7.com/character-encoding/</link><guid isPermaLink="false">5f8818b9b230b90001638852</guid><category><![CDATA[笔记]]></category><category><![CDATA[字符编码]]></category><dc:creator><![CDATA[周蒙牛]]></dc:creator><pubDate>Sun, 07 Jul 2019 09:38:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><h2 id="unicode">Unicode平面</h2>
<p><img src="https://b3logfile.com/file/2020/10/2019070718444042814297-2636becf.jpg" alt="1"></p>
<p>Plane 0, 称作基本平面(Basic Plane), 剩余的称作扩展平面(Supplementary Plane)</p>
<table>
<thead>
<tr>
<th>Unicode code points</th>
<th>Range</th>
<th>Encoding Binary value (码点)</th>
</tr>
</thead>
<tbody>
<tr>
<td>U+000000-U+00007f</td>
<td>0xxxxxxx</td>
<td>0xxxxxxx</td>
</tr>
<tr>
<td>U+000080-U+0007ff</td>
<td>110yyyxx 10xxxxxx</td>
<td>00000yyy xxxxxxxx</td>
</tr>
<tr>
<td>U+000800-U+00ffff</td>
<td>1110yyyy 10yyyyxx 10xxxxxx</td>
<td>yyyyyyyy xxxxxxxx</td>
</tr>
<tr>
<td>U+010000-U+10ffff</td>
<td>11110zzz 10zzyyyy 10yyyyxx 10xxxxxx</td>
<td>000zzzzz yyyyyyyy xxxxxxxx</td>
</tr>
</tbody>
</table>
<h3 id="python">Python中的转义序列</h3>
<table>
<thead>
<tr>
<th>转义序列</th>
<th>意义</th>
<th>示例</th>
<th>码点范围</th>
</tr>
</thead>
<tbody>
<tr>
<td>\ooo(三个八进制数)</td>
<td>ooo码点的字符</td>
<td>'\141' (a)</td>
<td>U+0 - U+01FF</td>
</tr>
<tr>
<td>\xhh</td>
<td>hh码点的字符</td>
<td>'\x61' (a)</td>
<td>U+0 - U+FF</td>
</tr>
<tr>
<td>\uxxxx</td>
<td>xxxx码点的字符</td>
<td>'\u4e2d\u6587' (中文)</td>
<td>U+0 - U+FFFF</td>
</tr>
<tr>
<td>\Uxxxxxxxx</td>
<td>xxxxxxxx码点的字符</td>
<td>'\U0001f466' (👦)</td>
<td>U+0 - U+FFFFFFFF</td>
</tr>
</tbody>
</table>
<p>注: \u和\U仅在字符串字面值中可用. o为八进制digit, x和h均为十六进制digit.<br>
注: 对于\ooo, 和Standard C一样, up to three octal digits are accepted.<br>
对于\xhh和\ooo, In a bytes literal, 十六进制和八进制转义序列表示 the byte with the given value. In a string literal, these escapes denote a Unicode character with the given value.</p>
<pre><code>'中'.encode('utf-8')
# b'\xe4\xb8\xad'
# 11100100 10111000 10101101‬
</code></pre>
<h3 id="java">Java中的转义序列</h3>
<table>
<thead>
<tr>
<th>转义序列</th>
<th>意义</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td>\uxxxx</td>
<td>xxxx码点的字符</td>
<td>&quot;\u4e2d\u6587&quot; (中文)</td>
</tr>
<tr>
<td>\uxxxx</td>
<td>xxxx码点的字符</td>
<td>&quot;\ud83d\udc66&quot; (👦)</td>
</tr>
</tbody>
</table>
<p>注意: Java中没有\Uxxxxxxxx这种形式的转义序列</p>
<h3 id="howmanycharacterscanbemappedwithunicode">How many characters can be mapped with Unicode?</h3>
<p>The count of all the possible valid combinations in Unicode.<br>
1,111,998: 17 planes × 65,536 characters per plane - 2048 surrogates - 66 noncharacters<br>
Note that UTF-8 and UTF-32 could theoretically encode much more than 17 planes, but the range is restricted based on the limitations of the UTF-16 encoding.<br>
137,994 code points are actually assigned in Unicode 12.1.<br>
<a href="https://stackoverflow.com/questions/5924105/how-many-characters-can-be-mapped-with-unicode#targetText=Unicode+allows+for+17+planes,total+of+1%2C114%2C112+possible+characters.">utf 8 - How many characters can be mapped with Unicode? - Stack Overflow</a><br>
<a href="https://www.cnblogs.com/lanhaicode/p/11214827.html">编码方式之ASCII、ANSI、Unicode概述 - 蓝海人 - 博客园</a></p>
<h3 id="">十六进制、八进制和二进制的关系</h3>
<p>一个十六进制digit对应4个bit, 即半个字节. 一个八进制digit对应3个bit.</p>
<h2 id="utf16">UTF-16</h2>
<p>在基本平面内, 从U+D800到U+DFFF的码点不对应任何字符.<br>
UTF-16就利用保留下来的0xD800到0xDFFF的码点来映射扩展平面的字符.<br>
U+D800 - U+DBFF, U+DC00 - U+DFFF, 分别有1024个不同的码点.<br>
利用这两段码点结对编码扩展平面的字符, 扩展平面可编码字符数: 1024*1024=1048576.<br>
<a href="https://zhuanlan.zhihu.com/p/51202412">Unicode 编码及 UTF-32, UTF-16 和 UTF-8 - 知乎</a></p>
<h2 id="utf8">UTF-8</h2>
<p><img src="https://b3logfile.com/file/2020/10/2019070719113286217505-5e52395b.png" alt="2019070719113286217505.png"></p>
<h2 id="modifiedutf8">Modified UTF-8</h2>
<p>In Modified UTF-8 (MUTF-8), the null character (U+0000) uses the two-byte overlong encoding 11000000 10000000 (hexadecimal C0 80), instead of 00000000 (hexadecimal 00).<br>
BMP的字符, 除了U+0000, 其他的字符Modified UTF-8和UTF-8编码方式一致.<br>
non-BMP的字符, Modified UTF-8用6个字节编码, 而UTF-8用4个字节编码.<br>
UTF8 --&gt; CESU-8 (non-BMP用6个字节编码) --&gt; Modified UTF-8(U+0000用两个字节编码; non-BMP同CESU-8)<br>
<a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.7">Chapter 4. The class File Format</a><br>
<a href="https://docs.rs/cesu8/1.0.0/cesu8/">cesu8 - Rust</a><br>
<a href="https://en.wikipedia.org/wiki/CESU-8">CESU-8 - Wikipedia</a></p>
<h2 id="">码点和代码单元</h2>
<p>码点(Code Point): 是Unicode代码空间中的一个值, 取值从0x0到0x10FFFF, 代表一个字符.<br>
代码单元(Code Unit): 是具体编码形式中的最小单位. 比如UTF-16中一个代码单元为16 bits, UTF-8中一个代码单元为8 bits. 一个码点可能由一个或多个代码单元表示. 在U+10000之前的码点可以由一个UTF-16代码单元表示, U+10000及之后的码点由两个UTF-16代码单元表示.<br>
<a href="https://www.cnblogs.com/zhangzl419/archive/2013/05/21/3090601.html">代码点(Code Point)和代码单元(Code Unit) - zhangzl419 - 博客园</a><br>
<a href="http://zablog.me/2017/09/18/emoji/">Emoji与Unicode · Zablog</a></p>
<pre><code class="language-java">import java.nio.charset.StandardCharsets;

public class Demo {
    public static void main(String[] args) {
        String str = &quot;你&quot;;
        printLen(str);
        str = &quot;👦&quot;;
        printLen(str);
        str = &quot;你👦&quot;;
        printLen(str);

        //只得到了半个👦
        System.out.println(str.charAt(1));
        System.out.println(str.substring(str.indexOf(&quot;👦&quot;), str.toCharArray().length));
    }

    public static void printLen(String str) {
        System.out.println(str);
        System.out.println(&quot;string length = &quot; + str.length());
        System.out.println(&quot;string bytes length (UTF-8) = &quot; + str.getBytes(StandardCharsets.UTF_8).length);
        System.out.println(&quot;string bytes length (UTF-16BE) = &quot; + str.getBytes(StandardCharsets.UTF_16BE).length);
        System.out.println(&quot;string char length = &quot; + str.toCharArray().length);
        System.out.println(&quot;string code points count = &quot; + str.codePoints().count());
        System.out.println();
    }
}
/*
你
string length = 1
string bytes length (UTF-8) = 3
string bytes length (UTF-16BE) = 2
string char length = 1
string code points count = 1

👦
string length = 2
string bytes length (UTF-8) = 4
string bytes length (UTF-16BE) = 4
string char length = 2
string code points count = 1

你👦
string length = 3
string bytes length (UTF-8) = 7
string bytes length (UTF-16BE) = 6
string char length = 3
string code points count = 2

?
👦
</code></pre>
<h2 id="">代码片段</h2>
<pre><code class="language-java">//遍历码点
String str = &quot;👦abc&quot;;
str.codePoints().forEach(c -&gt; new String(Character.toChars(c)));

//取得第一个码点的字符
String firstSymbol = new String(Character.toChars(str.codePointAt(0)));
System.out.println(firstSymbol);
</code></pre>
<h3 id="utfgb18030">获取字符的码点及其编码(UTF,GB18030)</h3>
<pre><code class="language-java">str = '👦'
print('Code Point', format(ord(str), 'x'))

encodingList = [&quot;UTF-8&quot;, &quot;UTF-16-LE&quot;,
               &quot;UTF-16-BE&quot;, &quot;UTF-32-LE&quot;, &quot;UTF-32-BE&quot;, &quot;GB18030&quot;]

for encoding in encodingList:
    byteHexStr = ''
    for byte in bytearray(str, encoding):
        byteHexStr += format(byte, 'x').zfill(2)
    print(encoding, byteHexStr)

# 输出
&quot;&quot;&quot;
Code Point 1f466
UTF-8 f09f91a6
UTF-16-LE 3dd866dc
UTF-16-BE d83ddc66
UTF-32-LE 66f40100
UTF-32-BE 0001f466
GB18030 9439d336
&quot;&quot;&quot;
</code></pre>
<pre><code class="language-java">import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

class Demo {
  private static final char[] HEX_ARRAY = &quot;0123456789abcdef&quot;.toCharArray();

  public static String bytesToHex(byte[] bytes) {
    char[] hexChars = new char[bytes.length * 2];
    for (int j = 0; j &lt; bytes.length; j++) {
      int v = bytes[j] &amp; 0xFF;
      hexChars[j * 2] = HEX_ARRAY[v &gt;&gt;&gt; 4];
      hexChars[j * 2 + 1] = HEX_ARRAY[v &amp; 0x0F];
    }
    return new String(hexChars);
  }

  public static void main(String args[]) throws Exception {
    String str = &quot;👦&quot;;
    int[] codePointArr = str.codePoints().toArray();
    System.out.println(&quot;Code Point &quot; + Integer.toHexString(codePointArr[0]));

    String[] encodingArr = { &quot;UTF-8&quot;, &quot;UTF-16LE&quot;, &quot;UTF-16BE&quot;, &quot;UTF-32LE&quot;, &quot;UTF-32BE&quot;, &quot;GB18030&quot; };
    Map&lt;String, String&gt; encodingMap = new LinkedHashMap&lt;&gt;();

    for (String encoding : encodingArr) {
      byte[] arr = str.getBytes(encoding);
      encodingMap.put(encoding, bytesToHex(arr));
    }

    encodingMap.forEach((k, v) -&gt; System.out.println(k + &quot; &quot; + v));
  }
}
</code></pre>
<h2 id="bom">字节序和BOM</h2>
<ul>
<li>计算机硬件有两种储存数据的方式: 大端字节序(big endian)和小端字节序(little endian).<br>
例如, 数值0x2211使用两个字节储存: 高位字节是0x22, 低位字节是0x11.<br>
大端字节序: 高位字节在前, 低位字节在后, 即0x2211.<br>
小端字节序: 低位字节在前, 高位字节在后, 即0x1122.</li>
<li>BOM=Byte Order Mark, 即&quot;字节顺序标识&quot;.<br>
<img src="https://b3logfile.com/file/2020/10/2019090423370144817500-d6b823c8.png" alt="2019090423370144817500.png"><br>
UTF-16, JVM中缺省是大端法，Windows平台下缺省为小端法.<br>
<a href="http://www.ruanyifeng.com/blog/2016/11/byte-order.html">理解字节序 - 阮一峰的网络日志</a><br>
<a href="https://xiaogd.net/%E5%AD%97%E7%AC%A6%E9%9B%86%E4%B8%8E%E7%BC%96%E7%A0%81%EF%BC%88%E4%B8%83%EF%BC%89-bom/">字符集与编码（七）——BOM – 肖国栋的i自留地</a></li>
</ul>
<h2 id="mysql">MySQL排序规则</h2>
<table>
<thead>
<tr>
<th>排序规则</th>
<th>Unicode collation algorithm</th>
</tr>
</thead>
<tbody>
<tr>
<td>utf8mb4_unicode_ci</td>
<td>基于UCA 4.0.0</td>
</tr>
<tr>
<td>utf8mb4_unicode_520_ci</td>
<td>基于UCA 5.2.0</td>
</tr>
<tr>
<td>utf8mb4_0900_ai_ci</td>
<td>基于UCA 9.0.0, since MySQL 8.0</td>
</tr>
</tbody>
</table>
<p>The Unicode collation algorithm (UCA) is an algorithm defined in Unicode Technical Report #10, which defines a customizable method to compare two strings.</p>
</div>]]></content:encoded></item><item><title><![CDATA[【笔记】Java内部类]]></title><description><![CDATA[<div class="kg-card-markdown"><h2 id="">静态内部类和非静态内部类的区别</h2>
<p>A non-static nested class has full access to the members of the class within which it is nested. A static nested class does not have a reference to a nesting instance, so a static nested class cannot invoke non-static methods or access non-static fields of an instance of the</p></div>]]></description><link>https://zhoujin7.com/java-inner-class/</link><guid isPermaLink="false">5f8a59cf1d33b70001944847</guid><category><![CDATA[笔记]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[周蒙牛]]></dc:creator><pubDate>Tue, 25 Jun 2019 02:41:00 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><h2 id="">静态内部类和非静态内部类的区别</h2>
<p>A non-static nested class has full access to the members of the class within which it is nested. A static nested class does not have a reference to a nesting instance, so a static nested class cannot invoke non-static methods or access non-static fields of an instance of the class within which it is nested.<a href="https://stackoverflow.com/questions/1353309/java-static-vs-inner-class/1353326#1353326">Java: Static vs inner class - Stack Overflow</a><br>
<img src="https://zhoujin7.com/content/images/2020/10/20190625145358303_2504.png" alt="inner_class"></p>
<h2 id="localclassfinal">local class不能访问一般的局部变量，只能访问被final修饰的局部变量</h2>
<p>local class不能被static修饰, 因为static只能修饰成员变量, 不能修饰局部变量.<br>
local class中可以直接访问外部类的所有成员, 包括私有成员, 因为其默认持有外部类的引用.<br>
但不能访问它所在作用域中的一般的局部变量, 只能访问被final修饰的局部变量.<br>
<a href="http://cuipengfei.me/blog/2013/06/22/why-does-it-have-to-be-final/">为什么必须是final的呢？ - 崔鹏飞的Octopress Blog</a><br>
<a href="https://xm-king.iteye.com/blog/773292">Java的局部内部类以及final类型的参数和变量 - JAVA夜无眠 - ITeye博客</a></p>
<h2 id="">示例代码</h2>
<pre><code class="language-java">package com.zhoujin7;

public class Outer {
  private static String outerStaticField = &quot;Outer Static Field&quot;;
  private String outerInstanceField = &quot;Outer Instance Field&quot;;

  public Outer() {
    System.out.println(&quot;Outer Class&quot;);
  }

  public static void staticPrint() {
    final String localField = &quot;local Field&quot;;
    class Local {
      public Local() {
        System.out.println(&quot;Local Class&quot;);
        System.out.println(outerStaticField);
        System.out.println(localField);
      }
    }
    new Local();
  }

  public static void main(String[] args) {
    new StaticMember();
    System.out.println();
    new Outer().new NonStaticMember();
    System.out.println();
    new Outer().print();
    System.out.println();
    Outer.staticPrint();
  }

  public void print() {
    // JDK 8 可以不用加上这个final
    final String localField = &quot;local Field&quot;;
    class Local {
      public Local() {
        System.out.println(&quot;Local Class&quot;);
        System.out.println(outerInstanceField);
        System.out.println(outerStaticField);
        System.out.println(localField);
      }
    }
    new Local();
  }

  public static class StaticMember {
    public StaticMember() {
      System.out.println(&quot;Static Class&quot;);
      System.out.println(outerStaticField);
    }
  }

  public class NonStaticMember {
    public NonStaticMember() {
      System.out.println(&quot;Non-Static Member Class&quot;);
      System.out.println(outerInstanceField);
    }
  }
}
</code></pre>
</div>]]></content:encoded></item><item><title><![CDATA[Java中单例设计模式的实现]]></title><description><![CDATA[<div class="kg-card-markdown"><p>I spent a lot of time in Singleton Design Pattern, that's why I am singleton.</p>
<h2 id="">饿汉式单例设计模式的实现</h2>
<p>实现方法：</p>
<ol>
<li>将构造方法私有化。</li>
<li>在类中创建一个本类对象，并用一个私有静态变量引用该对象。</li>
<li>提供一个返回该对象的公有静态方法。</li>
</ol>
<pre><code class="language-java">class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }

    public static void singletonOperation() {
        //do something
    }
}
</code></pre>
<p><img src="https://zhoujin7.com/content/images/2018/06/singleton.png" alt="单例类图"><br>
饿汉式的实现是线程安全的。参见：<a href="http://zhoujin7.com/how-to-create-thread-safe-singleton-in-java/#Java%E5%8D%95%E4%BE%8B%E7%A4%BA%E4%BE%8B---%E4%BD%BF%E7%94%A8%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F%E5%88%9D%E5%A7%8B%E5%8C%96%E5%AE%9E%E7%8E%B0%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84%E5%8D%95%E4%BE%8B">Java单例示例 - 使用静态变量初始化实现线程安全的单例</a><br>
存在的问题：</p></div>]]></description><link>https://zhoujin7.com/the-implementation-of-singleton-pattern-in-java/</link><guid isPermaLink="false">5f87c03eb230b90001638821</guid><category><![CDATA[设计模式]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[周蒙牛]]></dc:creator><pubDate>Thu, 14 Jun 2018 07:39:08 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>I spent a lot of time in Singleton Design Pattern, that's why I am singleton.</p>
<h2 id="">饿汉式单例设计模式的实现</h2>
<p>实现方法：</p>
<ol>
<li>将构造方法私有化。</li>
<li>在类中创建一个本类对象，并用一个私有静态变量引用该对象。</li>
<li>提供一个返回该对象的公有静态方法。</li>
</ol>
<pre><code class="language-java">class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }

    public static void singletonOperation() {
        //do something
    }
}
</code></pre>
<p><img src="https://zhoujin7.com/content/images/2018/06/singleton.png" alt="单例类图"><br>
饿汉式的实现是线程安全的。参见：<a href="http://zhoujin7.com/how-to-create-thread-safe-singleton-in-java/#Java%E5%8D%95%E4%BE%8B%E7%A4%BA%E4%BE%8B---%E4%BD%BF%E7%94%A8%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F%E5%88%9D%E5%A7%8B%E5%8C%96%E5%AE%9E%E7%8E%B0%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84%E5%8D%95%E4%BE%8B">Java单例示例 - 使用静态变量初始化实现线程安全的单例</a><br>
存在的问题：如果单例类实例的创建是依赖外部参数或配置文件的，就不能使用饿汉式单例设计模式了，因为类装载时实例就创建了，来不及调用其中的方法来获取外部参数或配置文件。</p>
<h2 id="">懒汉式单例设计模式的实现</h2>
<h3 id="">非线程安全版</h3>
<pre><code class="language-java">class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public static void singletonOperation() {
        //do something
    }
}
</code></pre>
<p>上面的实现在多线程环境下不能正常工作。假设有两个线程初次并行调用Singleton.getInstance()方法，两个线程都同时执行到if (instance == null)，这时instance还不是null，因此会创建两个不同的实例，不符合单例设计模式的要求。</p>
<h3 id="">用同步锁解决线程安全问题</h3>
<p>为了保证线程安全，可以加同步锁来解决问题。</p>
<pre><code class="language-java">public static synchronized Singleton getInstance() {
    if (instance == null) {
        instance = new Singleton();
    }
    return instance;
}
</code></pre>
<p>但是这样的实现并不高效。当线程试图获取某个对象的同步锁时，如果该锁被其他线程所持有，则当前线程会进入阻塞状态。也就是在任意时刻，只有一个线程能调用Singleton.getInstance()方法，其他试图调用该方法的线程会进入阻塞状态。<br>
<a name="Double-Check-Locking"></a></p>
<h3 id="">双重检查锁方法</h3>
<p>不过同步操作只有第一次调用Singleton.getInstance()方法时才需要用到，所以我们可以换用下面的写法，也被称为双重检查锁（Double-Check Locking）。</p>
<pre><code class="language-java">class Singleton {
    private volatile static Singleton instance;  //声明成 volatile

    private Singleton() {
    }

    public static Singleton getSingleton() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    
    public static void singletonOperation() {
        //do something
    }
}
</code></pre>
<p>假设有两个线程初次并行调用Singleton.getInstance()方法，两个线程都同时执行到第一个if (instance == null)，这时instance还不是null，所以都继续往下执行。但是后续的代码加了同步锁，确保同一时刻只有一个线程能执行后面的instance = new Singleton()语句。等其中一个线程执行了instance = new Singleton()语句，这时候instance不为null了，所以另一个线程也无法进入第二个if (instance == null)语句块了，而是执行后续的return instance语句，这样就保证了该类只有唯一的实例。</p>
<p>但是如果<code>private volatile static Singleton instance</code>语句不加volatile关键字，仍然会存在问题，这是为什么呢？<br>
主要原因在于instance = new Singleton()语句并不是原子性的操作。<br>
创建一个对象可以分为三步：</p>
<ol>
<li>分配对象的内存空间</li>
<li>初始化对象</li>
<li>设置instance指向刚分配的内存地址</li>
</ol>
<p>当instance指向分配地址时，instance不为null。<br>
但是在 JVM 的即时编译器中存在指令重排的优化。也就是说上面的第二步和第三步的顺序是不能保证的，最终的执行顺序可能是1-2-3也可能是1-3-2。如果是1-3-2，那么在多线程环境下就会出现问题。<br>
下面是流程图和我的分析。<br>
<img src="https://zhoujin7.com/content/images/2018/06/flowchart.png" alt="流程图"><br>
当第一个线程A执行到new Singleton()子过程的第二步，此时instance不为null了，但是该对象并没有初始化完毕，而另外一个线程B刚好执行到第一个if (instance == null)语句块，这时线程B就会得到一个没有正确初始化的对象。线程B对这个未正确初始化的对象进行操作，可能会导致异常的发生。<br>
注意以上方法必须在Java 5.0以上版本才能保证不出问题。其原因是 Java 5.0 以前的 JMM（Java 内存模型）是存在缺陷的，即使将变量声明成volatile也不能完全避免指令重排，这个问题在Java 5.0中才得以修复。</p>
<h2 id="initializationondemandholderiodh">更优雅的办法，Initialization on Demand Holder（IoDH）</h2>
<pre><code class="language-java">class Singleton {
    private Singleton() {
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }

    public static void singletonOperation() {
        //do something
    }

    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }
}
</code></pre>
<p>这里使用到了静态内部类，静态内部类在外部类被加载的时候不会被同时加载，当且仅当其某个静态成员被调用时才会被加载，这样就达到了延迟加载的目的。<br>
同饿汉式单例设计模式一样，都是基于类加载机制避免了多线程的同步问题，保证初始化实例时只有一个线程。</p>
<h2 id="">使用枚举</h2>
<p>还可以使用枚举类来实现单例设计模式。</p>
<pre><code class="language-java">public enum Singleton {
    INSTANCE;

    public static void singletonOperation() {
        //do something
    }
}
</code></pre>
<p>通过调用Singleton.INSTANCE来获得Singleton类的实例。<br>
创建枚举实例默认就是线程安全的，而且还能防止反序列化导致重新创建新的对象。<br>
参见：<a href="http://zhoujin7.com/how-to-create-thread-safe-singleton-in-java/#Java%E5%8D%95%E4%BE%8B%E7%A4%BA%E4%BE%8B---%E4%BD%BF%E7%94%A8%E6%9E%9A%E4%B8%BE%E5%AE%9E%E7%8E%B0%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84%E5%8D%95%E4%BE%8B">Java单例示例 - 使用枚举实现线程安全的单例</a></p>
</div>]]></content:encoded></item><item><title><![CDATA[【翻译】如何在Java中编写线程安全的单例 - Java单例示例]]></title><description><![CDATA[<div class="kg-card-markdown"><p>英文原文：<a href="https://javarevisited.blogspot.com/2012/12/how-to-create-thread-safe-singleton-in-java-example.html">How to create thread safe Singleton in Java - Java Singleton Example</a></p>
<p>线程安全的单例指的是即使处于多线程环境，也总是返回完全相同实例的单例类。在Java中，单例设计模式就像工厂方法设计模式或装饰器设计模式那样，已经是一种经典的设计模式，甚至在JDK内部也用得很多，例如<code>java.lang.runtime</code>就是单例类的一个例子。单例设计模式确保了Java程序中的类在任意时间内只保持唯一一个实例。</p>
<p>在我们的上一篇文章“<a href="http://javarevisited.blogspot.com/2011/03/10-interview-questions-on-singleton.html">10个关于Java单例的面试问题</a>”中，我们已经讨论了许多不同的有关单例设计模式在面试中被问及的问题，其中一个写的就是关于线程安全的。在Java 5版本之前，用于编写线程安全单例的“双重检查锁”机制在这种情况下将会失效，如果一个线程没有看到另一个并发线程创建的实例，最终将会导致单例类的多个实例被创建。<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>从Java 5版本开始，volatile型变量保证了可以通过“双重检查锁”模式编写线程安全的单例。</p>
<p>我个人不喜欢“双重检查锁”这种方法，因为还有很多其他更简单的用于编写线程安全单例的替代方法可以使用，像使用静态变量来初始化单例类实例，或使用枚举作为单例。让我们来看一下这两种编写线程安全单例方法的例子。</p></div>]]></description><link>https://zhoujin7.com/how-to-create-thread-safe-singleton-in-java/</link><guid isPermaLink="false">5f87c03eb230b90001638820</guid><category><![CDATA[设计模式]]></category><category><![CDATA[翻译]]></category><category><![CDATA[Java]]></category><dc:creator><![CDATA[周蒙牛]]></dc:creator><pubDate>Thu, 14 Jun 2018 05:52:26 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><p>英文原文：<a href="https://javarevisited.blogspot.com/2012/12/how-to-create-thread-safe-singleton-in-java-example.html">How to create thread safe Singleton in Java - Java Singleton Example</a></p>
<p>线程安全的单例指的是即使处于多线程环境，也总是返回完全相同实例的单例类。在Java中，单例设计模式就像工厂方法设计模式或装饰器设计模式那样，已经是一种经典的设计模式，甚至在JDK内部也用得很多，例如<code>java.lang.runtime</code>就是单例类的一个例子。单例设计模式确保了Java程序中的类在任意时间内只保持唯一一个实例。</p>
<p>在我们的上一篇文章“<a href="http://javarevisited.blogspot.com/2011/03/10-interview-questions-on-singleton.html">10个关于Java单例的面试问题</a>”中，我们已经讨论了许多不同的有关单例设计模式在面试中被问及的问题，其中一个写的就是关于线程安全的。在Java 5版本之前，用于编写线程安全单例的“双重检查锁”机制在这种情况下将会失效，如果一个线程没有看到另一个并发线程创建的实例，最终将会导致单例类的多个实例被创建。<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup>从Java 5版本开始，volatile型变量保证了可以通过“双重检查锁”模式编写线程安全的单例。</p>
<p>我个人不喜欢“双重检查锁”这种方法，因为还有很多其他更简单的用于编写线程安全单例的替代方法可以使用，像使用静态变量来初始化单例类实例，或使用枚举作为单例。让我们来看一下这两种编写线程安全单例方法的例子。<br>
<a name="Thread-safeSingleton-in-Java-using-Enum"></a></p>
<h2 id="java">Java单例示例 - 使用枚举实现线程安全的单例</h2>
<p>我写作“<a href="http://javarevisited.blogspot.sg/2011/08/enum-in-java-example-tutorial.html">Java枚举的10个例子</a>”时，这个例子还没有被列举出来。到目前为止，在Java中使用枚举编写线程安全的单例是最简单和有效的方法。因为Java编程语言它自身就能提供线程安全的保证。你不需要担心线程安全问题。因为在Java中枚举实例默认是final修饰的，它也保证不会由于序列化导致出现多个实例。<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup></p>
<p>有一点值得牢记的是，当我们讨论线程安全的单例，我们讨论的是在单例类的实例在创建期间的线程安全，而不是当我们调用单例类的任意方法时的线程安全。如果你的单例类维护了任意状态信息，并且包含了修改该状态的方法，那么你需要编写避免出现线程安全和同步问题的代码。下面是使用枚举在Java中实现线程安全的单例的例子。如果这个办法满足你的需求，那么这是在Java中编写线程安全单例最简单的办法了。使用枚举作为单例也提供了其他几个好处，你可以在“<a href="http://javarevisited.blogspot.sg/2012/07/why-enum-singleton-are-better-in-java.html">为什么在Java中用枚举实现单例更好一些</a>”找到原因。</p>
<pre><code class="language-java">public enum Singleton{
    INSTANCE;
  
    public void show(){
        System.out.println(&quot;Singleton using Enum in Java&quot;);
    }
}

//你可以通过Singleton.INSTANCE访问该单例类的实例，并按如下方式调用该实例的任意方法
Singleton.INSTANCE.show();
</code></pre>
<p><a name="Thread-Safe-Singleton-using-Static-field-Initialization"></a></p>
<h2 id="java">Java单例示例 - 使用静态变量初始化实现线程安全的单例</h2>
<p>在Java中，你也可以通过使得在类的加载过程中就创建单例类实例的办法来编写线程安全的单例。在类的加载过程中，静态变量会被初始化，类加载器将保证实例在完全创建之前是不可见的。下面是在Java中使用静态工厂方法编写线程安全单例的例子。使用静态变量实现单例设计模式的唯一缺点就是它不是延迟加载的，甚至在任意客户端调用getInstance()方法之前，单例类就已经初始化了。</p>
<pre><code class="language-java">public class Singleton{
    private static final Singleton INSTANCE = new Singleton();
  
    private Singleton(){ }

    public static Singleton getInstance(){
        return INSTANCE;
    }
    public void show(){
        System.out.println(&quot;Singleon using static initialization in Java&quot;);
    }
}

//按如下方式访问该单例类的实例，并调用该实例的任意方法
Singleton.getInstance().show();
</code></pre>
<p>我们不用在getInstance()方法内部创建单例类的实例，而是它会被类加载器创建。同样，除了仅有的一个实例之外，私有化的构造方法使得创建其他实例是不可能的。你仍然可以通过反射和调用setAccessible(true)方法来访问私有化的构造方法。不过你可以通过在构造器中抛出异常来阻止这种情况导致新实例的创建。</p>
<p><strong>进一步了解</strong><br>
<a href="https://click.linksynergy.com/fs-bin/click?id=JVFxdTr9V80&amp;subid=0&amp;offerid=323058.1&amp;type=10&amp;tmpid=14538&amp;RD_PARM1=https%3A%2F%2Fwww.udemy.com%2Fmultithreading-and-parallel-computing-in-java%2F">Java中的多线程和并行计算</a><br>
<a href="https://aax-us-east.amazon-adsystem.com/x/c/QgL1IkkZUES1pwv6H3yZuAIAAAFj_L1afwEAAAFKAYNEB-M/https://assoc-redirect.amazon.com/g/r/http://www.amazon.com/dp/0321349601/ref=as_at?creativeASIN=0321349601&amp;linkCode=w61&amp;imprToken=9qKTlQLG.3B8Mn6HvufGEA&amp;slotNum=0&amp;tag=javamysqlanta-20">Java并发实战（书籍）</a><br>
<a href="https://pluralsight.pxf.io/c/1193463/424552/7490?u=https%3A%2F%2Fwww.pluralsight.com%2Fcourses%2Fjava-patterns-concurrency-multi-threading">将并发性和多线程应用于常见的Java设计模式</a><br>
<a href="https://learning.javaspecialists.eu/courses/concurrency-in-practice-bundle?affcode=92815_johrd7r8">Java并发实践课程</a></p>
<p>这两种都是编写线程安全单例的方法，不过我个人更喜欢使用枚举。因为它简单，能防止多重实例序列化攻击并且代码简洁。</p>
<p><strong>在Javarevisited博客中的其他Java设计模式教程</strong><br>
<a href="http://javarevisited.blogspot.sg/2011/12/observer-design-pattern-java-example.html">Java中的观察者设计模式和在真实世界中的相关例子</a><br>
<a href="http://javarevisited.blogspot.sg/2012/06/20-design-pattern-and-software-design.html">设计模式和软件设计的面试问题</a><br>
<a href="http://javarevisited.blogspot.de/2012/03/10-object-oriented-design-principles.html">Java程序员的10个OOPS和SOLID设计原则</a><br>
<a href="http://javarevisited.blogspot.ro/2012/06/builder-design-pattern-in-java-example.html">Java中的建造者设计模式和相关例子</a><br>
<a href="http://javarevisited.blogspot.sg/2012/02/why-wait-notify-and-notifyall-is.html">为什么在Java的Object类中有wait方法和notify方法</a></p>
<hr>
<p><strong>脚注</strong></p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>“在Java 5版本之前，用于编写线程安全单例的“双重检查锁”机制在这种情况下将会失效，如果一个线程没有看到另一个并发线程创建的实例，最终将会导致单例类的多个实例被创建。”<br>
这句话存在问题，应该不是多个实例被创建，而是仅有一个实例被创建，不过如果没有用volatile关键字，可能会导致异常的发生。参见：<a href="https://zhoujin7.com/the-implementation-of-singleton-pattern-in-java/#Double-Check-Locking">双重检查锁方法</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
<li id="fn2" class="footnote-item"><p>“因为在Java中枚举实例默认是final修饰的，它也保证不会由于序列化导致出现多个实例。”<br>
没明白这句话表达的意思。 <a href="#fnref2" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
</div>]]></content:encoded></item><item><title><![CDATA[About]]></title><description><![CDATA[<div class="kg-card-markdown"><h2 id="">自我介绍</h2>
<p>大家好，我是周蒙牛。</p>
<h2 id="">温馨提示</h2>
<ol>
<li>如何隐藏侧边栏，扩大文章阅读区域？<br>
点击文章目录右边的符号<code>&gt;</code>。<br>
<a href="https://zhoujin7.com/content/images/2018/08/expand.png"><img src="https://zhoujin7.com/content/images/2018/08/expand.png" alt="expand"></a></li>
<li>如何恢复显示侧边栏？<br>
点击文章内容右边的符号<code>+</code>。<br>
<a href="https://zhoujin7.com/content/images/2018/08/shrink.png"><img src="https://zhoujin7.com/content/images/2018/08/shrink.png" alt="shrink"></a></li>
<li>图片过小，如何放大查看？<br>
点击图片即可放大。</li>
</ol>
</div>]]></description><link>https://zhoujin7.com/about/</link><guid isPermaLink="false">5f87c03eb230b90001638822</guid><dc:creator><![CDATA[周蒙牛]]></dc:creator><pubDate>Mon, 21 May 2018 14:28:14 GMT</pubDate><content:encoded><![CDATA[<div class="kg-card-markdown"><h2 id="">自我介绍</h2>
<p>大家好，我是周蒙牛。</p>
<h2 id="">温馨提示</h2>
<ol>
<li>如何隐藏侧边栏，扩大文章阅读区域？<br>
点击文章目录右边的符号<code>&gt;</code>。<br>
<a href="https://zhoujin7.com/content/images/2018/08/expand.png"><img src="https://zhoujin7.com/content/images/2018/08/expand.png" alt="expand"></a></li>
<li>如何恢复显示侧边栏？<br>
点击文章内容右边的符号<code>+</code>。<br>
<a href="https://zhoujin7.com/content/images/2018/08/shrink.png"><img src="https://zhoujin7.com/content/images/2018/08/shrink.png" alt="shrink"></a></li>
<li>图片过小，如何放大查看？<br>
点击图片即可放大。</li>
</ol>
</div>]]></content:encoded></item></channel></rss>