<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title><![CDATA[51学习网]]></title> 
<atom:link href="http://www.51keeplearning.com/rss.php" rel="self" type="application/rss+xml" />
<description><![CDATA[51学习网]]></description>
<link>http://www.51keeplearning.com/</link>
<language>zh-cn</language>
<generator>www.emlog.net</generator>
<item>
    <title>AI人工智能：技术原理、应用场景与未来展望</title>
    <link>http://www.51keeplearning.com/?post=42</link>
    <description><![CDATA[<p><strong>引言</strong><br />
人工智能（Artificial Intelligence，AI）作为计算机科学的重要分支，旨在通过模拟人类智能行为构建能够感知、学习、推理和决策的智能系统。从1956年达特茅斯会议首次提出AI概念至今，其发展已渗透至社会各领域，成为推动数字化转型的核心驱动力。</p>
<p><strong>一、技术架构与核心术语解析</strong>  </p>
<ol>
<li>
<p><strong>机器学习（Machine Learning）</strong><br />
作为AI的基础实现方式，机器学习通过算法使计算机从数据中自动发现规律。其核心范式包括：  </p>
<ul>
<li><strong>监督学习</strong>：基于标注数据训练模型（如图像分类中的ResNet算法）  </li>
<li><strong>无监督学习</strong>：从无标注数据中发现潜在模式（如客户分群中的K-means聚类）  </li>
<li><strong>强化学习</strong>：通过奖励机制优化决策过程（如AlphaGo的自我博弈训练）  </li>
</ul>
</li>
<li>
<p><strong>深度学习（Deep Learning）</strong><br />
基于多层神经网络的技术，在计算机视觉和自然语言处理领域取得突破。其典型架构包括：  </p>
<ul>
<li><strong>卷积神经网络（CNN）</strong>：专用于处理网格结构数据（如医学影像分析）  </li>
<li><strong>循环神经网络（RNN）</strong>：适用于序列数据处理（如股票价格预测）  </li>
<li><strong>Transformer架构</strong>：通过自注意力机制实现并行计算（如GPT系列模型）  </li>
</ul>
</li>
<li>
<p><strong>关键技术支撑</strong>  </p>
<ul>
<li><strong>自然语言处理（NLP）</strong>：实现机器理解人类语言的技术集合，涵盖词嵌入（Word Embedding）、命名实体识别（NER）等  </li>
<li><strong>知识图谱（Knowledge Graph）</strong>：以图结构呈现的语义网络，支撑智能检索和决策推理  </li>
<li><strong>联邦学习（Federated Learning）</strong>：在数据不出域的前提下实现多方联合建模，满足隐私保护需求  </li>
</ul>
</li>
</ol>
<p><strong>二、行业应用实践</strong>  </p>
<ol>
<li>
<p><strong>医疗健康领域</strong>  </p>
<ul>
<li>辅助诊断系统通过CNN分析CT影像，早期肺癌检测准确率达96%以上  </li>
<li>药物研发平台利用生成式AI设计分子结构，将新药研发周期缩短60%  </li>
</ul>
</li>
<li>
<p><strong>智能制造场景</strong>  </p>
<ul>
<li>工业质检系统采用计算机视觉技术，实现微米级缺陷实时检测  </li>
<li>数字孪生（Digital Twin）技术通过物理实体与虚拟模型交互，优化生产参数  </li>
</ul>
</li>
<li>
<p><strong>金融服务创新</strong>  </p>
<ul>
<li>风险控制模型运用图神经网络识别异常交易网络  </li>
<li>智能投顾平台基于马科维茨投资组合理论，动态优化资产配置  </li>
</ul>
</li>
</ol>
<p><strong>三、技术挑战与发展瓶颈</strong>  </p>
<ol>
<li>
<p><strong>数据依赖性问题</strong><br />
监督学习模型需大量标注数据，而高质量标注成本高昂。弱监督学习与半监督学习正在突破此限制。</p>
</li>
<li>
<p><strong>算法可解释性困境</strong><br />
深度神经网络的黑盒特性阻碍其在关键领域的应用。LIME（局部可解释模型）和SHAP（沙普利加和解释）等解释性AI技术正在发展中。</p>
</li>
<li>
<p><strong>算力资源约束</strong><br />
大模型训练需消耗兆瓦级电力，模型压缩与神经架构搜索（NAS）成为优化方向。</p>
</li>
</ol>
<p><strong>四、未来演进趋势</strong>  </p>
<ol>
<li>
<p><strong>技术融合创新</strong>  </p>
<ul>
<li>脑科学与类脑计算结合，研发脉冲神经网络（SNN）  </li>
<li>量子计算与AI融合，突破组合优化问题计算瓶颈  </li>
</ul>
</li>
<li>
<p><strong>治理框架完善</strong><br />
欧盟《人工智能法案》提出基于风险的监管分级，推动可信AI体系建设  </p>
</li>
<li>
<p><strong>人机协作深化</strong><br />
增强智能（Augmented Intelligence）强调人类与AI的协同进化，在创意设计、科学发现等领域形成新范式  </p>
</li>
</ol>
<p><strong>结语</strong><br />
人工智能正从专用智能向通用智能演进，其发展需兼顾技术创新与伦理约束。随着Transformer架构推动大语言模型突破，以及神经符号系统融合知识推理能力，AI将继续重塑产业格局并拓展人类认知边界。未来十年，以具身智能（Embodied AI）为代表的新兴方向，或将最终实现感知-决策-行动的完整智能闭环。</p>]]></description>
    <pubDate>Sun, 22 Mar 2026 22:52:18 +0800</pubDate>
    <dc:creator>emer</dc:creator>
    <guid>http://www.51keeplearning.com/?post=42</guid>
</item>
<item>
    <title>java IO</title>
    <link>http://www.51keeplearning.com/?post=40</link>
    <description><![CDATA[<p><meta name="referrer" content="no-referrer" /><div id="cnblogs_post_body" contentScore="1927"></p>
<p><span><strong>java IO </strong></span></p>
<h2>主要内容</h2>
<ul>
<li>
<div><strong>java.io.File类的使用</strong></div>
</li>
<li>
<div><strong>IO原理及流的分类</strong></div>
</li>
<li>
<div><strong>文件流</strong></div>
</li>
<li>
<div><strong>FileInputStream / FileOutputStream / FileReader / FileWriter</strong></div>
</li>
<li>
<div><strong>缓冲流</strong></div>
<ul>
<li>
<div><strong>BufferedInputStream / BufferedOutputStream / </strong></div>
</li>
<li>
<div><strong>BufferedReader / BufferedWriter</strong></div>
</li>
</ul>
</li>
<li>
<div><strong>转换流</strong></div>
</li>
<li>
<div><strong>InputStreamReader / OutputStreamWriter</strong></div>
</li>
<li>
<div><strong>标准输入/输出流</strong></div>
</li>
<li>
<div><strong>打印流（了解）</strong></div>
</li>
<li>
<div><strong>PrintStream / PrintWriter</strong></div>
</li>
<li>
<div><strong>数据流（了解）</strong></div>
</li>
<li>
<div><strong>DataInputStream / DataOutputStream</strong></div>
</li>
<li>
<div><strong>对象流 ----涉及序列化、反序列化</strong></div>
</li>
<li>
<div><strong>ObjectInputStream / ObjectOutputStream</strong></div>
</li>
<li>
<div><strong>随机存取文件流</strong></div>
</li>
<li>
<div><strong>RandomAccessFile</strong></div>
</li>
</ul>
<h2>File类</h2>
<ul>
<li>
<div><strong>java.io.File类：文件和目录路径名的抽象表示形式，与平台无关</strong></div>
</li>
<li>
<div><strong>File 能新建、删除、重命名文件和目录，但 File 不能访问文件内容本身。如果需要访问文件内容本身，则需要使用输入/输出流。</strong></div>
</li>
<li>
<div><strong>File对象可以作为参数传递给流的构造函数</strong></div>
</li>
<li>
<div><strong>File类的常见构造方法：</strong></div>
<ul>
<li>
<div><span><strong>public File(String pathname)</strong> </span></div>
</li>
</ul>
</li>
</ul>
<p>以pathname为路径创建File对象，可以是绝对路径或者相对路径，如果pathname是相对路径，则默认的当前路径在系统属性user.dir中存储。</p>
<ul>
<li>
<div><span><strong>public File(String parent,String child)</strong> </span></div>
</li>
</ul>
<p>以parent为父路径，child为子路径创建File对象。</p>
<ul>
<li>
<div>File的静态属性String separator存储了当前系统的路径分隔符。</div>
</li>
<li>
<div>在UNIX中，此字段为'/'，在Windows中，为'\\'</div>
</li>
</ul>
<p>常见方法：</p>
<p><img alt="" src="https://images0.cnblogs.com/blog/651900/201412/172221382817208.png"></p>
<p>eg：</p>
<div>
<table><colgroup><col></colgroup>
<tbody valign="top">
<tr>
<td contentScore="691">
<p><span>File dir1 = new File("D:/IOTest/dir1"); </span></p>
<p><span>if (!dir1.exists()) { // 如果D:/IOTest/dir1不存在，就创建为目录 </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;dir1.mkdir(); } </span></p>
<p><span>// 创建以dir1为父目录,名为"dir2"的File对象 </span></p>
<p><span>File dir2 = new File(dir1, "dir2"); </span></p>
<p><span>if (!dir2.exists()) { // 如果还不存在，就创建为目录 </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;dir2.mkdirs(); } </span></p>
<p><span>File dir4 = new File(dir1, "dir3/dir4"); </span></p>
<p><span>if (!dir4.exists()) { </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;dir4.mkdirs(); </span></p>
<p><span>} </span></p>
<p><span>// 创建以dir2为父目录,名为"test.txt"的File对象 </span></p>
<p><span>File file = new File(dir2, "test.txt"); &nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p><span>if (!file.exists()) { // 如果还不存在，就创建为文件 </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;file.createNewFile();} </span></p>
</td>
</tr>
</tbody>
</table>
</div>
<h2>Java IO原理</h2>
<ul>
<li>
<div>IO流用来处理设备之间的数据传输。</div>
</li>
<li>
<div>Java程序中，对于数据的输入/输出操作以"流(stream)" 的方式进行。</div>
</li>
<li>
<div>java.io包下提供了各种"流"类和接口，用以获取不同种类的数据，并通过标准的<strong>方法</strong>输入或输出数据。</div>
</li>
</ul>
<p><img alt="" src="https://images0.cnblogs.com/blog/651900/201412/172221391566108.png"></p>
<p><img alt="" src="https://images0.cnblogs.com/blog/651900/201412/172221397033911.png"></p>
<h3>流的分类</h3>
<ul>
<li>
<div>按操作<strong>数据单位</strong>不同分为：<strong>字节流(8 bit)，字符流(16 bit) </strong></div>
</li>
<li>
<div>按数据流的<strong>流向</strong>不同分为：<strong>输入流，输出流</strong></div>
</li>
<li>
<div>按流的<strong>角色</strong>的不同分为：<strong>节点流，处理流</strong></div>
</li>
</ul>
<div>
<table><colgroup><col><col><col></colgroup>
<tbody valign="top">
<tr>
<td contentScore="14">
<p><span><strong>(抽象基类)</strong></span></p>
</td>
<td contentScore="0">
<p><strong>字节流</strong></p>
</td>
<td contentScore="0">
<p><strong>字符流</strong></p>
</td>
</tr>
<tr>
<td contentScore="0">
<p><strong>输入流</strong></p>
</td>
<td contentScore="11">
<p><strong>InputStream</strong></p>
</td>
<td contentScore="0">
<p><strong>Reader</strong></p>
</td>
</tr>
<tr>
<td contentScore="0">
<p><strong>输出流</strong></p>
</td>
<td contentScore="12">
<p><strong>OutputStream</strong></p>
</td>
<td contentScore="0">
<p><strong>Writer</strong></p>
</td>
</tr>
</tbody>
</table>
</div>
<ol>
<li>
<div>Java的IO流共涉及40多个类，实际上非常规则，都是从如上4个抽象基类派生的。</div>
</li>
<li>
<div>由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。</div>
</li>
<li>
<div>字节流：以byte为单位传输</div>
</li>
<li>
<div>字符流：以char为单位传输</div>
</li>
</ol>
<h3>IO流体系</h3>
<p><img alt="" src="https://images0.cnblogs.com/blog/651900/201412/172221401562281.png"></p>
<h3>InputStream &amp; Reader</h3>
<ul>
<li>
<div>InputStream 和 Reader 是所有<strong>输入流</strong>的基类。</div>
</li>
<li>
<div>InputStream（典型实现：<span><strong>FileInputStream</strong></span>）</div>
<ul>
<li>
<div>int read()</div>
</li>
<li>
<div><span><strong>int read(byte[] b)</strong> </span></div>
</li>
<li>
<div>int read(byte[] b, int off, int len)</div>
</li>
</ul>
</li>
<li>
<div>Reader（典型实现：<span><strong>FileReader</strong></span>）</div>
<ul>
<li>
<div>int read()</div>
</li>
<li>
<div><span><strong>int read(char [] c)</strong> </span></div>
</li>
<li>
<div>int read(char [] c, int off, int len)</div>
</li>
</ul>
</li>
<li>
<div><strong>程序中打开的文件 IO 资源不属于内存里的资源，</strong>垃圾回收机制无法回收该资源，所以应该<strong>显式关闭文件 IO 资源</strong>。</div>
</li>
</ul>
<h3>OutputStream &amp; Writer</h3>
<ul>
<li>
<div>OutputStream 和 Writer 也非常相似：</div>
<ul>
<li>
<div><strong><span>void write</span>(int b/int c);</strong></div>
</li>
<li>
<div><strong><span>void write</span>(byte[] b/char[] cbuf);</strong></div>
</li>
<li>
<div><strong><span>void write(</span>byte[] b/char[] buff, int off, int len);</strong></div>
</li>
<li>
<div><span><strong>void flush(); </strong></span></div>
</li>
<li>
<div><span><strong>void close(); </strong></span>需要先刷新，再关闭此流</div>
</li>
</ul>
</li>
<li>
<div><span><strong>因为字符流直接以字符作为操作单位，所以 Writer 可以用字符串来替换字符数组，即以 String 对象作为参数 </strong></span></div>
<ul>
<li>
<div><strong>void write(String str);</strong></div>
</li>
<li>
<div><strong>void write(String str, int off, int len);</strong></div>
</li>
</ul>
</li>
</ul>
<h2>文件流</h2>
<p><strong>读取文件 </strong></p>
<p>1.建立一个流对象，将已存在的一个文件加载进流。</p>
<ul>
<li>
<div><span><strong>FileReader fr = new FileReader("Test.txt");</strong> </span></div>
</li>
</ul>
<p>2.创建一个临时存放数据的数组。</p>
<ul>
<li>
<div><span><strong>char[] ch = new char[1024]; </strong></span></div>
</li>
</ul>
<p>3.调用流对象的读取方法将流中的数据读入到数组中。</p>
<ul>
<li>
<div><span><strong>fr.read(ch); </strong></span></div>
</li>
</ul>
<div>
<table><colgroup><col></colgroup>
<tbody valign="top">
<tr>
<td contentScore="1176">
<p><span> FileReader fr = null; </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;try{ </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fr = new FileReader("c:\\test.txt"); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;char[] buf = new char[1024]; </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int len= 0; </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while((len=fr.read(buf))!=-1){ </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(new String(buf ,0,len));} </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;}catch (IOException e){ </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("read-Exception :"+e.toString());} </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;finally{ </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(fr!=null){ </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try{ </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fr.close(); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}catch (IOException e){ </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("close-Exception :"+e.toString()); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} } } </span></p>
</td>
</tr>
</tbody>
</table>
</div>
<p>&nbsp;</p>
<p><strong>写入文件 </strong></p>
<p>1.创建流对象，建立数据存放文件</p>
<ul>
<li>
<div><span><strong>FileWriter fw = new FileWriter("Test.txt"); </strong></span></div>
</li>
</ul>
<p>2.调用流对象的写入方法，将数据写入流</p>
<ul>
<li>
<div><span><strong>fw.write("text"); </strong></span></div>
</li>
</ul>
<p>3.关闭流资源，并将流中的数据清空到文件中。</p>
<ul>
<li>
<div><span><strong>fw.close(); </strong></span></div>
</li>
</ul>
<div>
<table><colgroup><col></colgroup>
<tbody valign="top">
<tr>
<td contentScore="841">
<p><span> FileWriter fw = null; </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;try{ </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fw = new FileWriter("Test.txt"); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fw.write("text"); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;} </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;catch (IOException e){ </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(e.toString()); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;} </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;finally{ </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;If(fw!=null) </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try{ </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fw.close(); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch (IOException e){ </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(e.toString()); </span></p>
<p><span>}&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p><span>} </span></p>
</td>
</tr>
</tbody>
</table>
</div>
<p>&nbsp;</p>
<p>注意点：</p>
<ul>
<li>
<div>定义文件路径时，注意：可以用"/"或者"\\"。File.separator()</div>
</li>
<li>
<div>在写入一个文件时，如果目录下有同名文件将被覆盖。</div>
</li>
<li>
<div>在读取文件时，必须保证该文件已存在，否则出异常。</div>
</li>
</ul>
<p>&nbsp;</p>
<h2>处理流之一：缓冲流</h2>
<ul>
<li>
<div>为了提高数据读写的速度，J<span>ava API提供了带缓冲功能的流类</span>，在使用这些流类时，会创建一个内部缓冲区数组</div>
</li>
<li>
<div>根据数据操作单位可以把缓冲流分为：</div>
</li>
<li>
<div><span><strong>BufferedInputStream 和 BufferedOutputStream</strong> </span></div>
</li>
<li>
<div><span><strong>BufferedReader 和 BufferedWriter</strong> </span></div>
</li>
<li>
<div>缓冲流要"套接"在相应的节点流之上，对读写的数据提供了缓冲的功能，提高了读写的效率，同时增加了一些新的方法</div>
</li>
<li contentScore="0">
<div>对于输出的缓冲流，写出的数据会先在内存中缓存，使<span><strong>用flush()将</strong></span>会使内存中的数据立刻写出</div>
<p>&nbsp;</p>
</li>
</ul>
<div>
<table><colgroup><col></colgroup>
<tbody valign="top">
<tr>
<td contentScore="1337">
<p><span>BufferedReader br = <span><strong>null</strong><span>;</span> </span></span></p>
<p><span>BufferedWriter bw = <span><strong>null</strong><span>;</span> </span></span></p>
<p><span><strong>try</strong><span> {</span> </span></p>
<p><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;<span>//step1:</span></span><span>创建缓冲流对象：它是过滤流，是对节点流的包装</span></span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;br = <span><strong>new</strong><span> BufferedReader(<span><strong>new</strong><span> FileReader(<span>"d:\\IOTest\\source.txt"<span>));</span> </span></span></span></span></span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;bw = <span><strong>new</strong><span> BufferedWriter(<span><strong>new</strong><span> FileWriter(<span>"d:\\IOTest\\destBF.txt"<span>));</span> </span></span></span></span></span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;String str = <span><strong>null</strong><span>;</span> </span></span></p>
<p><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;<span><strong>while</strong><span> ((<span><strong>str = br.readLine()) </strong><span>!= <span><strong>null</strong><span>) { <span>//</span></span></span></span></span></span></span></span><span>一次读取字符文本文件的一行字符</span></span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bw.write(str); //</span><span>一次写入一行字符串</span></span></p>
<p><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bw.newLine(); <span> <span>//</span></span></span><span>写入行分隔符</span></span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;} </span> </span></p>
<p><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;bw.flush(); <span>//step2:</span></span><span>刷新缓冲区</span></span></span></p>
<p><span>} <span><strong>catch</strong><span> (IOException e) {</span> </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();</span> </span></p>
<p><span><span>} </span> </span></p>
<p><span><strong>finally</strong><span> {</span> </span></p>
<p><span><span><span>// step3: </span><span>关闭</span><span>IO</span><span>流对象</span></span></span></p>
<p><span><strong>try</strong><span> {</span> </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;<span><strong>if</strong><span> (bw != <span><strong>null</strong><span>) {</span> </span></span></span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;bw.close(); <span>//</span></span><span>关闭过滤流时</span><span><span>,</span><span>会自动关闭它所包装的底层节点流</span></span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;}</span> </span></p>
<p><span>} <span><strong>catch</strong><span> (IOException e) {</span> </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();</span> </span></p>
<p><span><span>}</span> </span></p>
<p><span><strong>try</strong><span> {</span> </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;<span><strong>if</strong><span> (br != <span><strong>null</strong><span>) {</span> </span></span></span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;br.close();</span> </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;} </span> </span></p>
<p><span>} <span><strong>catch</strong><span> (IOException e) {</span> </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();</span> </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;} </span> </span></p>
<p><span><span>}</span> </span></p>
</td>
</tr>
</tbody>
</table>
</div>
<h2>处理流之二：转换流</h2>
<ul>
<li>
<div>转换流提供了在字节流和字符流之间的转换</div>
</li>
<li>
<div>Java API提供了两个转换流：</div>
<ul>
<li>
<div><span><strong>InputStreamReader和OutputStreamWriter </strong></span></div>
</li>
</ul>
</li>
<li>
<div>字节流中的数据都是字符时，转成字符流操作更高效。</div>
</li>
</ul>
<h3>InputStreamReader</h3>
<ul>
<li>
<div>用于将字节流中读取到的字节按指定字符集解码成字符。需要和InputStream"套接"。</div>
</li>
<li>
<div>构造方法</div>
</li>
<li>
<div><span><strong>public InputStreamReader(InputStream in) </strong></span></div>
</li>
<li>
<div><span><strong>public InputSreamReader(InputStream in,String charsetName) </strong></span></div>
</li>
</ul>
<p>如： Reader isr = new</p>
<p>InputStreamReader(System.in<span>,"ISO5334_1</span>");//指定字符集</p>
<h3>OutputStreamWriter</h3>
<ul>
<li>
<div>用于将要写入到字节流中的字符按指定字符集编码成字节。需要和OutputStream"套接"。</div>
</li>
<li>
<div>构造方法</div>
</li>
<li>
<div><span><strong>public OutputStreamWriter(OutputStream out)</strong> </span></div>
</li>
<li>
<div><span><strong>public OutputStreamWriter(OutputStream out,String charsetName) </strong></span></div>
</li>
</ul>
<p><img alt="" src="https://images0.cnblogs.com/blog/651900/201412/172221405009553.png"></p>
<div>
<table><colgroup><col></colgroup>
<tbody valign="top">
<tr>
<td contentScore="501">
<p><span><strong>public void testMyInput() throws Exception{</strong> </span></p>
<p><span> FileInputStream fis = <strong>new FileInputStream("dbcp.txt");</strong> </span></p>
<p><span> FileOutputStream fos = <strong>new FileOutputStream("dbcp5.txt");</strong> </span></p>
<p><span> InputStreamReader isr = <strong>new InputStreamReader(fis,"GBK");</strong> </span></p>
<p><span> OutputStreamWriter osw = <strong>new OutputStreamWriter(fos,"GBK");</strong> </span></p>
<p><span> BufferedReader br = <strong>new BufferedReader(isr);</strong> </span></p>
<p><span> BufferedWriter bw = <strong>new BufferedWriter(osw);</strong> </span></p>
<p><span> String str = <strong>null;</strong> </span></p>
<p><span><strong> while((str = br.readLine()) != null){</strong> </span></p>
<p><span> bw.write(str); </span></p>
<p><span> bw.newLine(); </span></p>
<p><span> bw.flush(); </span></p>
<p><span>} bw.close(); br.close();} </span></p>
</td>
</tr>
</tbody>
</table>
</div>
<h3>补充：字符编码</h3>
<ul>
<li>
<div><strong>编码表的由来</strong></div>
</li>
</ul>
<p>计算机只能识别二进制数据，早期由来是电信号。为了方便应用计算机，让它可以识别各个国家的文字。就将各个国家的文字用数字来表示，并一一对应，形成一张表。这就是编码表。</p>
<ul>
<li>
<div><strong>常见的编码表</strong></div>
</li>
<li>
<div><span><strong>ASCII</strong></span>：美国标准信息交换码。</div>
<ul>
<li>
<div>用一个字节的7位可以表示。</div>
</li>
</ul>
</li>
<li>
<div><span><strong>ISO8859-1：</strong></span>拉丁码表。欧洲码表</div>
<ul>
<li>
<div>用一个字节的8位表示。</div>
</li>
</ul>
</li>
<li>
<div><span><strong>GB2312：</strong></span>中国的中文编码表。</div>
</li>
<li>
<div><span><strong>GBK：</strong></span>中国的中文编码表升级，融合了更多的中文文字符号。</div>
</li>
<li>
<div>U<span><strong>nicode：</strong></span>国际标准码，融合了多种文字。</div>
<ul>
<li>
<div>所有文字都用两个字节来表示,Java语言使用的就是unicode</div>
</li>
</ul>
</li>
<li>
<div><span><strong>UTF-8：</strong></span>最多用三个字节来表示一个字符。</div>
</li>
<li>
<div><strong>编码：字符串<span>à</span>字节数组 </strong></div>
</li>
<li>
<div><strong>解码：字节数组<span>à</span>字符串 </strong></div>
</li>
<li>
<div><strong>转换流的编码应用</strong></div>
</li>
<li>
<div>可以将字符按指定编码格式存储。</div>
</li>
<li>
<div>可以对文本数据按指定编码格式来解读。</div>
</li>
<li>
<div>指定编码表的动作由构造器完成。</div>
</li>
</ul>
<h2>处理流之三：标准输入输出流</h2>
<ul>
<li>
<div><span><strong>System.in和System.out</strong></span>分别代表了系统标准的输入和输出设备</div>
</li>
<li>
<div>默认输入设备是键盘，输出设备是显示器</div>
</li>
<li>
<div>System.in的类型是InputStream</div>
</li>
<li>
<div>System.out的类型是PrintStream，其是OutputStream的子类FilterOutputStream 的子类</div>
</li>
<li>
<div>通过System类的setIn，setOut方法对默认设备进行改变。</div>
<ul>
<li>
<div>public static void <strong>setIn</strong>(<a>InputStream</a> in)</div>
</li>
<li>
<div>public static void <strong>setOut</strong>(<a>PrintStream</a> out)</div>
</li>
</ul>
</li>
</ul>
<p>&nbsp;</p>
<div>
<table><colgroup><col></colgroup>
<tbody valign="top">
<tr>
<td contentScore="1710">
<p><span><span><span>S</span>ystem.out.println(<span>"</span></span><span>请输入信息</span><span><span>(</span><span>退出输入</span><span>e</span><span>或</span><span>exit):"<span>);</span> </span></span></span></p>
<p><span><span><span>//</span><span>把</span><span>"</span><span>标准</span><span>"</span><span>输入流</span><span>(</span><span>键盘输入</span><span>)</span><span>这个字节流包装成字符流</span><span>,</span><span>再包装成缓冲流</span></span></span></p>
<p><span><strong>BufferedReader br = new BufferedReader( </strong></span></p>
<p><span><strong>&nbsp;&nbsp;&nbsp;&nbsp;new InputStreamReader(System.in)); </strong></span></p>
<p><span>String s = <span><strong>null</strong><span>;</span> </span></span></p>
<p><span><strong>try</strong><span> {</span> </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;<span><strong>while</strong><span> ((s = br.readLine()) != <span><strong>null</strong><span>) { <span>//</span></span></span></span></span></span><span>读取用户输入的一行数据</span><span><span> --&gt; </span><span>阻塞程序</span></span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span><strong>if</strong><span> (s.equalsIgnoreCase(<span>"e"<span>) || s.equalsIgnoreCase(<span>"exit"<span>)) {</span> </span></span></span></span></span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span>"</span></span><span>安全退出</span><span>!!"<span>);</span> </span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span><strong>break</strong><span>;</span> </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span> </span></p>
<p><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>//</span></span><span>将读取到的整行字符串转成大写输出</span></span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span>"--&gt;:"<span>+s.toUpperCase());</span> </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(<span>"</span></span><span>继续输入信息</span><span>"<span>);</span> </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;</span> </span></p>
<p><span>} <span><strong>catch</strong><span> (IOException e) {</span> </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();</span> </span></p>
<p><span>} <span><strong>finally</strong><span> { </span> </span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;<span><strong>try</strong><span> {</span> </span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span><strong>if</strong><span> (br != <span><strong>null</strong><span>) {</span> </span></span></span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;br.close(); <span>//</span></span><span>关闭过滤流时</span><span><span>,</span><span>会自动关闭它包装的底层节点流</span></span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;</span> </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;} <span><strong>catch</strong><span> (IOException e) {</span> </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();</span> </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;</span> </span></p>
<p><span><span>}</span> </span></p>
<p>&nbsp;</p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> </span></p>
</td>
</tr>
</tbody>
</table>
</div>
<h2>处理流之四：打印流（了解）</h2>
<ul>
<li>
<div>在整个IO包中，打印流是输出信息最方便的类。</div>
</li>
<li>
<div><strong>PrintStream(字节打印流)</strong>和<strong>PrintWriter(字符打印流)</strong></div>
<ul>
<li>
<div>提供了一系列重载的print和println方法，用于多种数据类型的输出</div>
</li>
<li>
<div>PrintStream和PrintWriter的输出不会抛出异常</div>
</li>
<li>
<div>PrintStream和PrintWriter有自动flush功能</div>
</li>
<li>
<div>System.out返回的是PrintStream的实例</div>
</li>
</ul>
</li>
</ul>
<p>&nbsp;</p>
<div>
<table><colgroup><col></colgroup>
<tbody valign="top">
<tr>
<td contentScore="1142">
<p><span>FileOutputStream fos = <span><strong>null</strong><span>;</span> </span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;<span><strong>try</strong><span> {</span> </span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fos = <span><strong>new</strong><span> FileOutputStream(<span><strong>new</strong><span> File(<span>"D:\\IO\\text.txt"<span>));</span> </span></span></span></span></span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;} <span><strong>catch</strong><span> (FileNotFoundException e) {</span> </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();</span> </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;}<span>//</span></span><span>创建打印输出流</span><span><span>,</span><span>设置为自动刷新模式</span><span>(</span><span>写入换行符或字节</span><span> '\n' </span><span>时都会刷新输出缓冲区</span><span>)</span></span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;<strong>PrintStream ps = <span>new<span> PrintStream(fos,<span>true<span>);</span> </span></span></span></strong></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;<span><strong>if</strong><span> (ps != <span><strong>null</strong><span>) {&nbsp;&nbsp;&nbsp;&nbsp;<span>// </span></span></span></span></span></span><span>把标准输出流</span><span><span>(</span><span>控制台输出</span><span>)</span><span>改成文件</span></span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.setOut(ps);}</span> </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;<span><strong>for</strong><span> (<span><strong>int</strong><span> i = 0; i &lt;= 255; i++) { <span>//</span></span></span></span></span></span><span>输出</span><span><span>ASCII</span><span>字符</span></span></span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.print((<span><strong>char</strong><span>)i);</span> </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span><strong>if</strong><span> (i % 50 == 0) { <span>//</span></span></span></span><span>每</span><span><span>50</span><span>个数据一行</span></span></span></p>
<p><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(); <span>// </span></span><span>换行</span></span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} </span> </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;}</span> </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;ps.close();</span> </span></p>
<p><span>}</span></p>
</td>
</tr>
</tbody>
</table>
</div>
<p>&nbsp;</p>
<h2>处理流之五：数据流（了解）</h2>
<ul>
<li>
<div>为了方便地操作Java语言的基本数据类型的数据，可以使用数据流。</div>
</li>
<li>
<div>数据流有两个类：(用于读取和写出基本数据类型的数据）</div>
<ul>
<li>
<div><strong>DataInputStream </strong>和 <strong>DataOutputStream</strong></div>
</li>
<li>
<div><strong>分别"套接"在 InputStream 和 OutputStream 节点流上</strong></div>
</li>
</ul>
</li>
<li>
<div><strong>DataInputStream中的方法</strong></div>
</li>
</ul>
<p>boolean readBoolean()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;byte readByte()</p>
<p>char readChar()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float readFloat()</p>
<p>double readDouble()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;short readShort()</p>
<p>long readLong()&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int readInt()</p>
<p>String readUTF() void readFully(byte[] b)</p>
<ul>
<li>
<div><strong>DataOutputStream中的方法</strong></div>
</li>
<li>
<div>将上述的方法的read改为相应的write即可。</div>
</li>
</ul>
<p>&nbsp;</p>
<div>
<table><colgroup><col></colgroup>
<tbody valign="top">
<tr>
<td contentScore="1818">
<p><span>DataOutputStream dos = null; </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;try {&nbsp;&nbsp;&nbsp;&nbsp;//创建连接到指定文件的数据输出流对象 </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dos = new DataOutputStream(new FileOutputStream( </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"d:\\IOTest\\destData.dat")); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dos.writeUTF("ab中国"); //写UTF字符串 </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dos.writeBoolean(false); //写入布尔值 </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dos.writeLong(1234567890L); //写入长整数 </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println("写文件成功!"); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} catch (IOException e) { </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace(); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} finally {&nbsp;&nbsp;&nbsp;&nbsp;//关闭流对象 </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try { </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (dos != null) { </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 关闭过滤流时,会自动关闭它包装的底层节点流 </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dos.close(); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} catch (IOException e) { </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace(); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;&nbsp;&nbsp;}</span></p>
</td>
</tr>
</tbody>
</table>
</div>
<h2>处理流之六：对象流</h2>
<ul>
<li>
<div><strong>ObjectInputStream和OjbectOutputSteam</strong></div>
</li>
<li>
<div>用于存储和读取<strong>对象</strong>的处理流。它的强大之处就是可以把Java中的对象写入到数据源中，也能把对象从数据源中还原回来。</div>
</li>
<li>
<div><strong>序列化(Serialize)：</strong>用ObjectOutputStream类将一个Java对象写入IO流中</div>
</li>
<li>
<div><strong>反序列化(Deserialize)：</strong>用ObjectInputStream类从IO流中恢复该Java对象</div>
</li>
<li>
<div>ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量</div>
</li>
</ul>
<h2>对象的序列化</h2>
<ul>
<li>
<div><span><strong>对象序列化机制</strong><span>允许把内存中的Java对象转换成平台无关的二进制流</span>，从而允许把这种二进制流持久地保存在磁盘上，或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流，就可以恢复成原来的</span>Java对象</div>
</li>
<li>
<div>序列化的好处在于可将任<span><strong>何实现了Serializable接</strong></span>口的对象转化为<strong>字节数据</strong>，使其在保存和传输时可被还原</div>
</li>
<li>
<div>序列化是 RMI（Remote Method Invoke – 远程方法调用）过程的参数和返回值都必须实现的机制，而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础</div>
</li>
<li>
<div>如果需要让某个对象支持序列化机制，则必须让其类是可序列化的，为了让某个类是可序列化的，该类必须实现如下两个接口之一：</div>
<ul>
<li>
<div><span><strong>Serializable </strong></span></div>
</li>
<li>
<div>Externalizable</div>
</li>
</ul>
</li>
<li>
<div>凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量：</div>
<ul>
<li>
<div><strong>private static final long serialVersionUID;</strong></div>
</li>
<li>
<div>serialVersionUID用来表明类的不同版本间的兼容性</div>
</li>
<li>
<div>如果类没有显示定义这个静态变量，它的值是Java运行时环境根据类的内部细节自动生成的。若类的源代码作了修改，serialVersionUID 可能发生变化。故建议，显示声明</div>
</li>
</ul>
</li>
<li>
<div>显示定义serialVersionUID的用途</div>
<ul>
<li>
<div>希望类的不同版本对序列化兼容，因此需确保类的不同版本具有相同的serialVersionUID</div>
</li>
<li>
<div>不希望类的不同版本对序列化兼容，因此需确保类的不同版本具有不同的serialVersionUID</div>
</li>
</ul>
</li>
</ul>
<h3>使用对象流序列化对象</h3>
<ul>
<li>
<div>若某个类实现了 Serializable 接口，该类的对象就是可序列化的：</div>
<ul>
<li>
<div><span><strong>创建一个 ObjectOutputStream</strong> </span></div>
</li>
<li>
<div><span><strong>调用 ObjectOutputStream 对象的 writeObject(对象) 方法输出可序列化对象。注意写出一次，操作flush()</strong> </span></div>
</li>
</ul>
</li>
<li>
<div>反序列化</div>
<ul>
<li>
<div><span><strong>创建一个 ObjectInputStream</strong> </span></div>
</li>
<li>
<div><span><strong>调用 readObject() 方法读取流中的对象</strong> </span></div>
</li>
</ul>
</li>
<li>
<div><strong>强调：</strong>如果某个类的字段不是基本数据类型或 String 类型，而是另一个引用类型，那么这个引用类型必须是可序列化的，否则拥有该类型的 Field 的类也不能序列化</div>
</li>
</ul>
<p>&nbsp;</p>
<p>序列化:将对象写入到磁盘或者进行网络传输。</p>
<p>要求对象必须实现序列化</p>
<p>ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test3.txt"));</p>
<p>Person p = new Person("韩梅梅",18,"中华大街",new Pet());</p>
<p>oos.writeObject(p);</p>
<p>oos.flush();</p>
<p>oos.close();</p>
<p>//反序列化：将磁盘中的对象数据源读出。</p>
<p>ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test3.txt"));</p>
<p>Person p1 = (Person)ois.readObject();</p>
<p>System.out.println(p1.toString());</p>
<p>ois.close();</p>
<p>&nbsp;</p>
<h2>RandomAccessFile 类</h2>
<ul>
<li>
<div>RandomAccessFile 类支持 "随机访问" 的方式，程序可以直接跳到文件的任意地方来<strong>读、写文件</strong></div>
<ul>
<li>
<div>支持只访问文件的部分内容</div>
</li>
<li>
<div>可以向已存在的文件后追加内容</div>
</li>
</ul>
</li>
<li>
<div>RandomAccessFile 对象包含一个记录指针，用以标示当前读写处的位置。RandomAccessFile 类对象可以自由移动记录指针：</div>
<ul>
<li>
<div><span><strong>long getFilePointer()：获取文件记录指针的当前位置 </strong></span></div>
</li>
<li>
<div><span><strong>void seek(long pos)：将文件记录指针定位到 pos 位置 </strong></span></div>
</li>
</ul>
</li>
<li>
<div><strong>构造器</strong></div>
<ul>
<li>
<div>public <strong>RandomAccessFile</strong>(<a>File</a> file, <a>String</a> mode)</div>
</li>
<li>
<div>public <strong>RandomAccessFile</strong>(<a>String</a> name, <a>String</a> mode)</div>
</li>
</ul>
</li>
<li>
<div>创建 RandomAccessFile 类实例需要指定一个 mode 参数，该参数指定 RandomAccessFile 的访问模式：</div>
<ul>
<li>
<div><strong>r: 以只读方式打开</strong></div>
</li>
<li>
<div><strong>rw：打开以便读取和写入</strong></div>
</li>
<li>
<div><strong>rwd:打开以便读取和写入；同步文件内容的更新</strong></div>
</li>
<li>
<div><strong>rws:打开以便读取和写入；同步文件内容和元数据的更新</strong></div>
</li>
</ul>
</li>
</ul>
<p>&nbsp;</p>
<p><strong>读取文件内容 </strong></p>
<div>
<table><colgroup><col></colgroup>
<tbody valign="top">
<tr>
<td contentScore="519">
<p><span>RandomAccessFile raf = new RandomAccessFile("test.txt", "rw"）;&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p><span>raf.seek(5); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;byte [] b = new byte[1024]; </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;int off = 0; </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;int len = 5; </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;raf.read(b, off, len); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;String str = new String(b, 0, len); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(str); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;raf.close(); </span></p>
</td>
</tr>
</tbody>
</table>
</div>
<p><strong>写入文件内容</strong></p>
<p>&nbsp;</p>
<div>
<table><colgroup><col></colgroup>
<tbody valign="top">
<tr>
<td contentScore="519">
<p><span>RandomAccessFile raf = new RandomAccessFile("test.txt", "rw"); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;raf.seek(5); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;//先读出来 </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;String temp = raf.readLine(); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;<strong>raf.seek(5);</strong> </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;raf.write("xyz".getBytes()); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;raf.write(temp.getBytes()); </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></p>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;raf.close(); </span></p>
</td>
</tr>
</tbody>
</table>
</div>
<p><span><strong>流的基本应用小节</strong></span></p>
<ul>
<li>
<div><span>流是用来处理数据的。</span></div>
</li>
<li>
<div><span>处理数据时，一定要先明确<strong>数据源</strong>，与<strong>数据目的地</strong></span></div>
<ul>
<li>
<div><span>数据源可以是文件，可以是键盘。</span></div>
</li>
<li>
<div><span>数据目的地可以是文件、显示器或者其他设备。</span></div>
</li>
</ul>
</li>
<li>
<div><span>而流只是在帮助数据进行传输</span><span>,</span><span>并对传输的数据进行处理，比如过滤处理、转换处理等。</span></div>
</li>
</ul>
<p>&nbsp;</p>
<p>&nbsp;</p>
<ul>
<li>
<div><strong><span>字节流</span><span>-</span><span>缓冲流（重点）</span></strong></div>
</li>
<li>
<div><span>输入流</span><span>InputStream-FileInputStream-BufferedInputStream </span></div>
</li>
<li>
<div><span>输出流</span><span>OutputStream-FileOutputStream-BufferedOutputStream </span></div>
</li>
<li>
<div><strong><span>字符流</span><span>-</span><span>缓冲流（重点）</span></strong></div>
</li>
<li>
<div><span>输入流</span><span>Reader-FileReader-BufferedReader </span></div>
</li>
<li>
<div><span>输出流</span><span>Writer-FileWriter-BufferedWriter </span></div>
</li>
<li>
<div><span><strong>转换流</strong></span></div>
</li>
<li>
<div><span>InputSteamReader</span><span>和</span><span>OutputStreamWriter </span></div>
</li>
<li>
<div><span><strong>对象流</strong></span><span>ObjectInputStream</span><span>和</span><span>ObjectOutputStream</span><span>（难点）</span></div>
</li>
<li>
<div><span>序列化</span></div>
</li>
<li>
<div><span>反序列化</span></div>
</li>
<li>
<div><span><strong>随机存取流</strong></span><span>RandomAccessFile</span><span><strong>（掌握读取、写入）</strong></span></div>
</li>
</ul>
<p></div></p>
<footer>
    <p>本站部分内容来源于网络，如有侵犯您的权益，请通过以下方式联系我们：</p>
    <p>邮箱：zytlsd@163.com</p>
    <p>我们将在收到通知后第一时间处理。</p>
</footer>]]></description>
    <pubDate>Sun, 19 Oct 2025 21:30:53 +0800</pubDate>
    <dc:creator>emer</dc:creator>
    <guid>http://www.51keeplearning.com/?post=40</guid>
</item>
<item>
    <title>java泛型</title>
    <link>http://www.51keeplearning.com/?post=39</link>
    <description><![CDATA[<p><meta name="referrer" content="no-referrer" /><div id="cnblogs_post_body" contentScore="33251"><br />
什么是<strong>泛型</strong>？</p>
<p><strong>泛型</strong>（Generic type 或者 generics）是对 <strong>Java</strong> 语言的类型系统的一种扩展，以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符，就像方法的形式参数是运行时传递的值的占位符一样。</p>
<p>可以在集合框架（Collection framework）中看到<strong>泛型</strong>的动机。例如，Map 类允许您向一个 Map 添加任意类的对象，即使最常见的情况是在给定映射（map）中保存某个特定类型（比如 String）的对象。</p>
<p>因为 Map.get() 被定义为返回 Object，所以一般必须将 Map.get() 的结果强制类型转换为期望的类型，如下面的代码所示：</p>
<p>Map m = new HashMap();<br>
m.put("key", "blarg");<br>
String s = (String) m.get("key");</p>
<p>要让程序通过编译，必须将 get() 的结果强制类型转换为 String，并且希望结果真的是一个 String。但是有可能某人已经在该映射中保存了不是 String 的东西，这样的话，上面的代码将会抛出 ClassCastException。</p>
<p>理想情况下，您可能会得出这样一个观点，即 m 是一个 Map，它将 String 键映射到 String 值。这可以让您消除代码中的强制类型转换，同时获得一个附加的类型检查层，该检查层可以防止有人将错误类型的键或值保存在集合中。这就是<strong>泛型</strong>所做的工作。</p>
<p>&nbsp;</p>
<p><strong>泛型</strong>的好处</p>
<p><strong>Java</strong> 语言中引入<strong>泛型</strong>是一个较大的功能增强。不仅语言、类型系统和编译器有了较大的变化，以支持<strong>泛型</strong>，而且类库也进行了大翻修，所以许多重要的类，比如集合框架，都已经成为<strong>泛型</strong>化的了。这带来了很多好处：</p>
<p>类型安全。 <strong>泛型</strong>的主要目标是提高 <strong>Java</strong> 程序的类型安全。通过知道使用<strong>泛型</strong>定义的变量的类型限制，编译器可以在一个高得多的程度上验证类型假设。没有<strong>泛型</strong>，这些假设就只存在于程序员的头脑中（或者如果幸运的话，还存在于代码注释中）。</p>
<p><strong>Java</strong> 程序中的一种流行技术是定义这样的集合，即它的元素或键是公共类型的，比如“String 列表”或者“String 到 String 的映射”。通过在变量声明中捕获这一附加的类型信息，<strong>泛型</strong>允许编译器实施这些附加的类型约束。类型错误现在就可以在编译时被捕获了，而不是在运行时当作 ClassCastException 展示出来。将类型检查从运行时挪到编译时有助于您更容易找到错误，并可提高程序的可靠性。</p>
<p>消除强制类型转换。 <strong>泛型</strong>的一个附带好处是，消除源代码中的许多强制类型转换。这使得代码更加可读，并且减少了出错机会。</p>
<p>尽管减少强制类型转换可以降低使用<strong>泛型</strong>类的代码的罗嗦程度，但是声明<strong>泛型</strong>变量会带来相应的罗嗦。比较下面两个代码例子。</p>
<p>该代码不使用<strong>泛型</strong>：</p>
<p>List li = new ArrayList();<br>
li.put(new Integer(3));<br>
Integer i = (Integer) li.get(0);</p>
<p><br>
该代码使用<strong>泛型</strong>：</p>
<p>List&lt;Integer&gt; li = new ArrayList&lt;Integer&gt;();<br>
li.put(new Integer(3));<br>
Integer i = li.get(0);</p>
<p><br>
在简单的程序中使用一次<strong>泛型</strong>变量不会降低罗嗦程度。但是对于多次使用<strong>泛型</strong>变量的大型程序来说，则可以累积起来降低罗嗦程度。</p>
<p>潜在的性能收益。 <strong>泛型</strong>为较大的优化带来可能。在<strong>泛型</strong>的初始实现中，编译器将强制类型转换（没有<strong>泛型</strong>的话，程序员会指定这些强制类型转换）插入生成的字节码中。但是更多类型信息可用于编译器这一事实，为未来版本的 JVM 的优化带来可能。</p>
<p>由于<strong>泛型</strong>的实现方式，支持<strong>泛型</strong>（几乎）不需要 JVM 或类文件更改。所有工作都在编译器中完成，编译器生成类似于没有<strong>泛型</strong>（和强制类型转换）时所写的代码，只是更能确保类型安全而已。</p>
<p><br>
<strong>泛型</strong>用法的例子</p>
<p><strong>泛型</strong>的许多最佳例子都来自集合框架，因为<strong>泛型</strong>让您在保存在集合中的元素上指定类型约束。考虑这个使用 Map 类的例子，其中涉及一定程度的优化，即 Map.get() 返回的结果将确实是一个 String：</p>
<p><br>
Map m = new HashMap();<br>
m.put("key", "blarg");<br>
String s = (String) m.get("key");</p>
<p><br>
如果有人已经在映射中放置了不是 String 的其他东西，上面的代码将会抛出 ClassCastException。<strong>泛型</strong>允许您表达这样的类型约束，即 m 是一个将 String 键映射到 String 值的 Map。这可以消除代码中的强制类型转换，同时获得一个附加的类型检查层，这个检查层可以防止有人将错误类型的键或值保存在集合中。</p>
<p>下面的代码示例展示了 JDK 5.0 中集合框架中的 Map 接口的定义的一部分：</p>
<p><br>
public interface Map&lt;K, V&gt; {<br>
public void put(K key, V value);<br>
public V get(K key);<br>
}</p>
<p>注意该接口的两个附加物：</p>
<p>类型参数 K 和 V 在类级别的规格说明，表示在声明一个 Map 类型的变量时指定的类型的占位符。</p>
<p>在 get()、put() 和其他方法的方法签名中使用的 K 和 V。</p>
<p>为了赢得使用<strong>泛型</strong>的好处，必须在定义或实例化 Map 类型的变量时为 K 和 V 提供具体的值。以一种相对直观的方式做这件事：</p>
<p>Map&lt;String, String&gt; m = new HashMap&lt;String, String&gt;();<br>
m.put("key", "blarg");<br>
String s = m.get("key");</p>
<p>当使用 Map 的<strong>泛型</strong>化版本时，您不再需要将 Map.get() 的结果强制类型转换为 String，因为编译器知道 get() 将返回一个 String。</p>
<p>在使用<strong>泛型</strong>的版本中并没有减少键盘录入；实际上，比使用强制类型转换的版本需要做更多键入。使用<strong>泛型</strong>只是带来了附加的类型安全。因为编译器知道关于您将放进 Map 中的键和值的类型的更多信息，所以类型检查从执行时挪到了编译时，这会提高可靠性并加快开发速度。</p>
<p><br>
向后兼容</p>
<p>在 <strong>Java</strong> 语言中引入<strong>泛型</strong>的一个重要目标就是维护向后兼容。尽管 JDK 5.0 的标准类库中的许多类，比如集合框架，都已经<strong>泛型</strong>化了，但是使用集合类（比如 HashMap 和 ArrayList）的现有代码将继续不加修改地在 JDK 5.0 中工作。当然，没有利用<strong>泛型</strong>的现有代码将不会赢得<strong>泛型</strong>的类型安全好处。</p>
<p>&nbsp;</p>
<p><br>
<strong>二 <strong>泛型</strong>基础</strong></p>
<p>类型参数</p>
<p>在定义<strong>泛型</strong>类或声明<strong>泛型</strong>类的变量时，使用尖括号来指定形式类型参数。形式类型参数与实际类型参数之间的关系类似于形式方法参数与实际方法参数之间的关系，只是类型参数表示类型，而不是表示值。</p>
<p><strong>泛型</strong>类中的类型参数几乎可以用于任何可以使用类名的地方。例如，下面是 <strong>java</strong>.util.Map 接口的定义的摘录：</p>
<p>public interface Map&lt;K, V&gt; {<br>
public void put(K key, V value);<br>
public V get(K key);<br>
}</p>
<p>Map 接口是由两个类型参数化的，这两个类型是键类型 K 和值类型 V。（不使用<strong>泛型</strong>）将会接受或返回 Object 的方法现在在它们的方法签名中使用 K 或 V，指示附加的类型约束位于 Map 的规格说明之下。</p>
<p>当声明或者实例化一个<strong>泛型</strong>的对象时，必须指定类型参数的值：</p>
<p>Map&lt;String, String&gt; map = new HashMap&lt;String, String&gt;();</p>
<p>注意，在本例中，必须指定两次类型参数。一次是在声明变量 map 的类型时，另一次是在选择 HashMap 类的参数化以便可以实例化正确类型的一个实例时。</p>
<p>编译器在遇到一个 Map&lt;String, String&gt; 类型的变量时，知道 K 和 V 现在被绑定为 String，因此它知道在这样的变量上调用 Map.get() 将会得到 String 类型。</p>
<p>除了异常类型、枚举或匿名内部类以外，任何类都可以具有类型参数。</p>
<p><br>
命名类型参数</p>
<p>推荐的命名约定是使用大写的单个字母名称作为类型参数。这与 C++ 约定有所不同（参阅 附录 A：与 C++ 模板的比较），并反映了大多数<strong>泛型</strong>类将具有少量类型参数的假定。对于常见的<strong>泛型</strong>模式，推荐的名称是：</p>
<p>K —— 键，比如映射的键。 <br>
V —— 值，比如 List 和 Set 的内容，或者 Map 中的值。 <br>
E —— 异常类。 <br>
T —— <strong>泛型</strong>。</p>
<p><br>
<strong>泛型</strong>不是协变的</p>
<p>关于<strong>泛型</strong>的混淆，一个常见的来源就是假设它们像数组一样是协变的。其实它们不是协变的。List&lt;Object&gt; 不是 List&lt;String&gt; 的父类型。</p>
<p>如果 A 扩展 B，那么 A 的数组也是 B 的数组，并且完全可以在需要 B[] 的地方使用 A[]：</p>
<p>Integer[] intArray = new Integer[10]; <br>
Number[] numberArray = intArray;</p>
<p>上面的代码是有效的，因为一个 Integer 是 一个 Number，因而一个 Integer 数组是 一个 Number 数组。但是对于<strong>泛型</strong>来说则不然。下面的代码是无效的：</p>
<p>List&lt;Integer&gt; intList = new ArrayList&lt;Integer&gt;();<br>
List&lt;Number&gt; numberList = intList; // invalid</p>
<p>最初，大多数 <strong>Java</strong> 程序员觉得这缺少协变很烦人，或者甚至是“坏的（broken）”，但是之所以这样有一个很好的原因。如果可以将 List&lt;Integer&gt; 赋给 List&lt;Number&gt;，下面的代码就会违背<strong>泛型</strong>应该提供的类型安全：</p>
<p>List&lt;Integer&gt; intList = new ArrayList&lt;Integer&gt;();<br>
List&lt;Number&gt; numberList = intList; // invalid<br>
numberList.add(new Float(3.1415));</p>
<p>因为 intList 和 numberList 都是有别名的，如果允许的话，上面的代码就会让您将不是 Integers 的东西放进 intList 中。但是，正如下一屏将会看到的，您有一个更加灵活的方式来定义<strong>泛型</strong>。</p>
<p><br>
类型通配符</p>
<p>假设您具有该方法：</p>
<p>void printList(List l) { <br>
for (Object o : l) <br>
&nbsp;&nbsp;&nbsp; System.out.println(o); <br>
}</p>
<p>上面的代码在 JDK 5.0 上编译通过，但是如果试图用 List&lt;Integer&gt; 调用它，则会得到警告。出现警告是因为，您将<strong>泛型</strong>（List&lt;Integer&gt;）传递给一个只承诺将它当作 List（所谓的原始类型）的方法，这将破坏使用<strong>泛型</strong>的类型安全。</p>
<p>如果试图编写像下面这样的方法，那么将会怎么样？</p>
<p>void printList(List&lt;Object&gt; l) { <br>
for (Object o : l) <br>
&nbsp;&nbsp;&nbsp; System.out.println(o); <br>
}</p>
<p>它仍然不会通过编译，因为一个 List&lt;Integer&gt; 不是 一个 List&lt;Object&gt;（正如前一屏 <strong>泛型</strong>不是协变的 中所学的）。这才真正烦人 —— 现在您的<strong>泛型</strong>版本还没有普通的非<strong>泛型</strong>版本有用！</p>
<p>解决方案是使用类型通配符：</p>
<p>void printList(List&lt;?&gt; l) { <br>
for (Object o : l) <br>
&nbsp;&nbsp;&nbsp; System.out.println(o); <br>
}</p>
<p>上面代码中的问号是一个类型通配符。它读作“问号”。List&lt;?&gt; 是任何<strong>泛型</strong> List 的父类型，所以您完全可以将 List&lt;Object&gt;、List&lt;Integer&gt; 或 List&lt;List&lt;List&lt;Flutzpah&gt;&gt;&gt; 传递给 printList()。</p>
<p><br>
类型通配符的作用</p>
<p>前一屏 类型通配符 中引入了类型通配符，这让您可以声明 List&lt;?&gt; 类型的变量。您可以对这样的 List 做什么呢？非常方便，可以从中检索元素，但是不能添加元素。原因不是编译器知道哪些方法修改列表哪些方法不修改列表，而是（大多数）变化的方法比不变化的方法需要更多的类型信息。下面的代码则工作得很好：</p>
<p>List&lt;Integer&gt; li = new ArrayList&lt;Integer&gt;();<br>
li.add(new Integer(42));<br>
List&lt;?&gt; lu = li;<br>
System.out.println(lu.get(0));</p>
<p>为什么该代码能工作呢？对于 lu，编译器一点都不知道 List 的类型参数的值。但是编译器比较聪明，它可以做一些类型推理。在本例中，它推断未知的类型参数必须扩展 Object。（这个特定的推理没有太大的跳跃，但是编译器可以作出一些非常令人佩服的类型推理，后面就会看到（在 底层细节 一节中）。所以它让您调用 List.get() 并推断返回类型为 Object。</p>
<p>另一方面，下面的代码不能工作：</p>
<p>List&lt;Integer&gt; li = new ArrayList&lt;Integer&gt;();<br>
li.add(new Integer(42));<br>
List&lt;?&gt; lu = li;<br>
lu.add(new Integer(43)); // error</p>
<p>在本例中，对于 lu，编译器不能对 List 的类型参数作出足够严密的推理，以确定将 Integer 传递给 List.add() 是类型安全的。所以编译器将不允许您这么做。</p>
<p>以免您仍然认为编译器知道哪些方法更改列表的内容哪些不更改列表内容，请注意下面的代码将能工作，因为它不依赖于编译器必须知道关于 lu 的类型参数的任何信息：</p>
<p>List&lt;Integer&gt; li = new ArrayList&lt;Integer&gt;();<br>
li.add(new Integer(42));<br>
List&lt;?&gt; lu = li;<br>
lu.clear();</p>
<p><br>
<strong>泛型</strong>方法</p>
<p>（在 类型参数 一节中）您已经看到，通过在类的定义中添加一个形式类型参数列表，可以将类<strong>泛型</strong>化。方法也可以被<strong>泛型</strong>化，不管它们定义在其中的类是不是<strong>泛型</strong>化的。</p>
<p><strong>泛型</strong>类在多个方法签名间实施类型约束。在 List&lt;V&gt; 中，类型参数 V 出现在 get()、add()、contains() 等方法的签名中。当创建一个 Map&lt;K, V&gt; 类型的变量时，您就在方法之间宣称一个类型约束。您传递给 add() 的值将与 get() 返回的值的类型相同。</p>
<p>类似地，之所以声明<strong>泛型</strong>方法，一般是因为您想要在该方法的多个参数之间宣称一个类型约束。例如，下面代码中的 ifThenElse() 方法，根据它的第一个参数的布尔值，它将返回第二个或第三个参数：</p>
<p>public &lt;T&gt; T ifThenElse(boolean b, T first, T second) {<br>
return b ? first : second;<br>
}</p>
<p>注意，您可以调用 ifThenElse()，而不用显式地告诉编译器，您想要 T 的什么值。编译器不必显式地被告知 T 将具有什么值；它只知道这些值都必须相同。编译器允许您调用下面的代码，因为编译器可以使用类型推理来推断出，替代 T 的 String 满足所有的类型约束：</p>
<p>String s = ifThenElse(b, "a", "b");</p>
<p>类似地，您可以调用：</p>
<p>Integer i = ifThenElse(b, new Integer(1), new Integer(2));</p>
<p>但是，编译器不允许下面的代码，因为没有类型会满足所需的类型约束：</p>
<p>String s = ifThenElse(b, "pi", new Float(3.14));</p>
<p>为什么您选择使用<strong>泛型</strong>方法，而不是将类型 T 添加到类定义呢？（至少）有两种情况应该这样做：</p>
<p>当<strong>泛型</strong>方法是静态的时，这种情况下不能使用类类型参数。</p>
<p>当 T 上的类型约束对于方法真正是局部的时，这意味着没有在相同类的另一个 方法签名中使用相同 类型 T 的约束。通过使得<strong>泛型</strong>方法的类型参数对于方法是局部的，可以简化封闭类型的签名。</p>
<p><br>
有限制类型</p>
<p>在前一屏 <strong>泛型</strong>方法 的例子中，类型参数 V 是无约束的或无限制的 类型。有时在还没有完全指定类型参数时，需要对类型参数指定附加的约束。</p>
<p>考虑例子 Matrix 类，它使用类型参数 V，该参数由 Number 类来限制：</p>
<p>public class Matrix&lt;V extends Number&gt; { ... }</p>
<p>编译器允许您创建 Matrix&lt;Integer&gt; 或 Matrix&lt;Float&gt; 类型的变量，但是如果您试图定义 Matrix&lt;String&gt; 类型的变量，则会出现错误。类型参数 V 被判断为由 Number 限制 。在没有类型限制时，假设类型参数由 Object 限制。这就是为什么前一屏 <strong>泛型</strong>方法 中的例子，允许 List.get() 在 List&lt;?&gt; 上调用时返回 Object，即使编译器不知道类型参数 V 的类型。</p>
<p>&nbsp;</p>
<p><strong>三 一个简单的<strong>泛型</strong>类</strong></p>
<p>编写基本的容器类</p>
<p>此时，您可以开始编写简单的<strong>泛型</strong>类了。到目前为止，<strong>泛型</strong>类最常见的用例是容器类（比如集合框架）或者值持有者类（比如 WeakReference 或 ThreadLocal）。我们来编写一个类，它类似于 List，充当一个容器。其中，我们使用<strong>泛型</strong>来表示这样一个约束，即 Lhist 的所有元素将具有相同类型。为了实现起来简单，Lhist 使用一个固定大小的数组来保存值，并且不接受 null 值。</p>
<p>Lhist 类将具有一个类型参数 V（该参数是 Lhist 中的值的类型），并将具有以下方法：</p>
<p>public class Lhist&lt;V&gt; { <br>
public Lhist(int capacity) { ... }<br>
public int size() { ... }<br>
public void add(V value) { ... }<br>
public void remove(V value) { ... }<br>
public V get(int index) { ... }<br>
}</p>
<p>要实例化 Lhist，只要在声明时指定类型参数和想要的容量：</p>
<p>Lhist&lt;String&gt; stringList = new Lhist&lt;String&gt;(10);</p>
<p><br>
实现构造函数</p>
<p>在实现 Lhist 类时，您将会遇到的第一个拦路石是实现构造函数。您可能会像下面这样实现它：</p>
<p>public class Lhist&lt;V&gt; { <br>
private V[] array;<br>
public Lhist(int capacity) {<br>
&nbsp;&nbsp;&nbsp; array = new V[capacity]; // illegal<br>
}<br>
}</p>
<p>这似乎是分配后备数组最自然的一种方式，但是不幸的是，您不能这样做。具体原因很复杂，当学习到 底层细节 一节中的“擦除”主题时，您就会明白。分配后备数组的实现方式很古怪且违反直觉。下面是构造函数的一种可能的实现（该实现使用集合类所采用的方法）：</p>
<p>public class Lhist&lt;V&gt; { <br>
private V[] array;<br>
public Lhist(int capacity) {<br>
&nbsp;&nbsp;&nbsp; array = (V[]) new Object[capacity];<br>
}<br>
}</p>
<p><br>
另外，也可以使用反射来实例化数组。但是这样做需要给构造函数传递一个附加的参数 —— 一个类常量，比如 Foo.class。后面在 Class&lt;T&gt; 一节中将讨论类常量。</p>
<p><br>
实现方法</p>
<p>实现 Lhist 的方法要容易得多。下面是 Lhist 类的完整实现：</p>
<p>public class Lhist&lt;V&gt; {<br>
&nbsp;&nbsp;&nbsp; private V[] array;<br>
&nbsp;&nbsp;&nbsp; private int size;</p>
<p>&nbsp;&nbsp;&nbsp; public Lhist(int capacity) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; array = (V[]) new Object[capacity];<br>
&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; public void add(V value) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (size == array.length)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new IndexOutOfBoundsException(Integer.toString(size));<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if (value == null)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new NullPointerException();<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; array[size++] = value;<br>
&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; public void remove(V value) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int removalCount = 0;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i=0; i&lt;size; i++) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (array[i].equals(value))<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ++removalCount;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if (removalCount &gt; 0) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; array[i-removalCount] = array[i];<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; array[i] = null;<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size -= removalCount;<br>
&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; public int size() { return size; }</p>
<p>&nbsp;&nbsp;&nbsp; public V get(int i) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (i &gt;= size)<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new IndexOutOfBoundsException(Integer.toString(i));<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return array[i];<br>
&nbsp;&nbsp;&nbsp; }<br>
}</p>
<p>注意，您在将会接受或返回 V 的方法中使用了形式类型参数 V，但是您一点也不知道 V 具有什么样的方法或域，因为这些对<strong>泛型</strong>代码是不可知的。</p>
<p><br>
使用 Lhist 类</p>
<p>使用 Lhist 类很容易。要定义一个整数 Lhist，只需要在声明和构造函数中为类型参数提供一个实际值即可：</p>
<p>Lhist&lt;Integer&gt; li = new Lhist&lt;Integer&gt;(30);</p>
<p>编译器知道，li.get() 返回的任何值都将是 Integer 类型，并且它还强制传递给 li.add() 或 li.remove() 的任何东西都是 Integer。除了实现构造函数的方式很古怪之外，您不需要做任何十分特殊的事情以使 Lhist 是一个<strong>泛型</strong>类。</p>
<p>&nbsp;</p>
<p><strong>四 <strong>Java</strong>类库中的<strong>泛型</strong></strong></p>
<p>集合类</p>
<p>到目前为止，<strong>Java</strong> 类库中<strong>泛型</strong>支持存在最多的地方就是集合框架。就像容器类是 C++ 语言中模板的主要动机一样（参阅 附录 A：与 C++ 模板的比较）（尽管它们随后用于很多别的用途），改善集合类的类型安全是 <strong>Java</strong> 语言中<strong>泛型</strong>的主要动机。集合类也充当如何使用<strong>泛型</strong>的模型，因为它们演示了<strong>泛型</strong>的几乎所有的标准技巧和方言。</p>
<p>所有的标准集合接口都是<strong>泛型</strong>化的 —— Collection&lt;V&gt;、List&lt;V&gt;、Set&lt;V&gt; 和 Map&lt;K,V&gt;。类似地，集合接口的实现都是用相同类型参数<strong>泛型</strong>化的，所以 HashMap&lt;K,V&gt; 实现 Map&lt;K,V&gt; 等。</p>
<p>集合类也使用<strong>泛型</strong>的许多“技巧”和方言，比如上限通配符和下限通配符。例如，在接口 Collection&lt;V&gt; 中，addAll 方法是像下面这样定义的：</p>
<p>interface Collection&lt;V&gt; {<br>
boolean addAll(Collection&lt;? extends V&gt;);<br>
}</p>
<p>该定义组合了通配符类型参数和有限制类型参数，允许您将 Collection&lt;Integer&gt; 的内容添加到 Collection&lt;Number&gt;。</p>
<p>如果类库将 addAll() 定义为接受 Collection&lt;V&gt;，您就不能将 Collection&lt;Integer&gt; 的内容添加到 Collection&lt;Number&gt;。不是限制 addAll() 的参数是一个与您将要添加到的集合包含相同类型的集合，而有可能建立一个更合理的约束，即传递给 addAll() 的集合的元素 适合于添加到您的集合。有限制类型允许您这样做，并且使用有限制通配符使您不需要使用另一个不会用在其他任何地方的占位符名称。</p>
<p>应该可以将 addAll() 的类型参数定义为 Collection&lt;V&gt;。但是，这不但没什么用，而且还会改变 Collection 接口的语义，因为<strong>泛型</strong>版本的语义将会不同于非<strong>泛型</strong>版本的语义。这阐述了<strong>泛型</strong>化一个现有的类要比定义一个新的<strong>泛型</strong>类难得多，因为您必须注意不要更改类的语义或者破坏现有的非<strong>泛型</strong>代码。</p>
<p>作为<strong>泛型</strong>化一个类（如果不小心的话）如何会更改其语义的一个更加微妙的例子，注意 Collection.removeAll() 的参数的类型是 Collection&lt;?&gt;，而不是 Collection&lt;? extends V&gt;。这是因为传递混合类型的集合给 removeAll() 是可接受的，并且更加限制地定义 removeAll 将会更改方法的语义和有用性。</p>
<p>&nbsp;</p>
<p>其他容器类</p>
<p>除了集合类之外，<strong>Java</strong> 类库中还有几个其他的类也充当值的容器。这些类包括 WeakReference、SoftReference 和 ThreadLocal。它们都已经在其包含的值的类型上<strong>泛型</strong>化了，所以 WeakReference&lt;T&gt; 是对 T 类型的对象的弱引用，ThreadLocal&lt;T&gt; 则是到 T 类型的线程局部变量的句柄。</p>
<p><br>
<strong>泛型</strong>不止用于容器</p>
<p><strong>泛型</strong>最常见最直观的使用是容器类，比如集合类或引用类（比如 WeakReference&lt;T&gt;）。Collection&lt;V&gt; 中类型参数的含义很明显 —— “一个所有值都是 V 类型的集合”。类似地，ThreadLocal&lt;T&gt; 也有一个明显的解释 —— “一个其类型是 T 的线程局部变量”。但是，<strong>泛型</strong>规格说明中没有指定容积。</p>
<p>像 Comparable&lt;T&gt; 或 Class&lt;T&gt; 这样的类中类型参数的含义更加微妙。有时，就像 Class&lt;T&gt; 中一样，类型变量主要是帮助编译器进行类型推理。有时，就像隐含的 Enum&lt;E extends Enum&lt;E&gt;&gt; 中一样，类型变量只是在类层次结构上加一个约束。</p>
<p><br>
Comparable&lt;T&gt;</p>
<p>Comparable 接口已经<strong>泛型</strong>化了，所以实现 Comparable 的对象声明它可以与什么类型进行比较。（通常，这是对象本身的类型，但是有时也可能是父类。）</p>
<p>public interface Comparable&lt;T&gt; { <br>
public boolean compareTo(T other);<br>
}</p>
<p>所以 Comparable 接口包含一个类型参数 T，该参数是一个实现 Comparable 的类可以与之比较的对象的类型。这意味着如果定义一个实现 Comparable 的类，比如 String，就必须不仅声明类支持比较，还要声明它可与什么比较（通常是与它本身比较）：</p>
<p>public class String implements Comparable&lt;String&gt; { ... }</p>
<p>现在来考虑一个二元 max() 方法的实现。您想要接受两个相同类型的参数，二者都是 Comparable，并且相互之间是 Comparable。幸运的是，如果使用<strong>泛型</strong>方法和有限制类型参数的话，这相当直观：</p>
<p>public static &lt;T extends Comparable&lt;T&gt;&gt; T max(T t1, T t2) {<br>
if (t1.compareTo(t2) &gt; 0)<br>
&nbsp;&nbsp;&nbsp; return t1;<br>
else <br>
&nbsp;&nbsp;&nbsp; return t2;<br>
}</p>
<p>在本例中，您定义了一个<strong>泛型</strong>方法，在类型 T 上<strong>泛型</strong>化，您约束该类型扩展（实现） Comparable&lt;T&gt;。两个参数都必须是 T 类型，这表示它们是相同类型，支持比较，并且相互可比较。容易！</p>
<p>更好的是，编译器将使用类型推理来确定当调用 max() 时 T 的值表示什么意思。所以根本不用指定 T，下面的调用就能工作：</p>
<p>String s = max("moo", "bark");</p>
<p>编译器将计算出 T 的预定值是 String，因此它将进行编译和类型检查。但是如果您试图用不实现 Comparable&lt;X&gt; 的 类 X 的参数调用 max()，那么编译器将不允许这样做。</p>
<p>&nbsp;</p>
<p>Class&lt;T&gt;</p>
<p>类 Class 已经<strong>泛型</strong>化了，但是很多人一开始都感觉其<strong>泛型</strong>化的方式很混乱。Class&lt;T&gt; 中类型参数 T 的含义是什么？事实证明它是所引用的类接口。怎么会是这样的呢？那是一个循环推理？如果不是的话，为什么这样定义它？</p>
<p>在以前的 JDK 中，Class.newInstance() 方法的定义返回 Object，您很可能要将该返回类型强制转换为另一种类型：</p>
<p>class Class { <br>
Object newInstance();<br>
}</p>
<p>但是使用<strong>泛型</strong>，您定义 Class.newInstance() 方法具有一个更加特定的返回类型：</p>
<p>class Class&lt;T&gt; { <br>
T newInstance();<br>
}</p>
<p>如何创建一个 Class&lt;T&gt; 类型的实例？就像使用非<strong>泛型</strong>代码一样，有两种方式：调用方法 Class.forName() 或者使用类常量 X.class。Class.forName() 被定义为返回 Class&lt;?&gt;。另一方面，类常量 X.class 被定义为具有类型 Class&lt;X&gt;，所以 String.class 是 Class&lt;String&gt; 类型的。</p>
<p>让 Foo.class 是 Class&lt;Foo&gt; 类型的有什么好处？大的好处是，通过类型推理的魔力，可以提高使用反射的代码的类型安全。另外，还不需要将 Foo.class.newInstance() 强制类型转换为 Foo。</p>
<p>考虑一个方法，它从数据库检索一组对象，并返回 JavaBeans 对象的一个集合。您通过反射来实例化和初始化创建的对象，但是这并不意味着类型安全必须完全被抛至脑后。考虑下面这个方法：</p>
<p>public static&lt;T&gt; List&lt;T&gt; getRecords(Class&lt;T&gt; c, Selector s) {<br>
// Use Selector to select rows<br>
List&lt;T&gt; list = new ArrayList&lt;T&gt;();<br>
for (/* iterate over results */) {<br>
&nbsp;&nbsp;&nbsp; T row = c.newInstance();<br>
&nbsp;&nbsp;&nbsp; // use reflection to set fields from result<br>
&nbsp;&nbsp;&nbsp; list.add(row); <br>
}<br>
return list;<br>
}</p>
<p>可以像下面这样简单地调用该方法：</p>
<p>List&lt;FooRecord&gt; l = getRecords(FooRecord.class, fooSelector);</p>
<p>编译器将会根据 FooRecord.class 是 Class&lt;FooRecord&gt; 类型的这一事实，推断 getRecords() 的返回类型。您使用类常量来构造新的实例并提供编译器在类型检查中要用到的类型信息。</p>
<p>&nbsp;</p>
<p>用 Class&lt;T&gt; 替换 T[]</p>
<p>Collection 接口包含一个方法，用于将集合的内容复制到一个调用者指定类型的数组中：</p>
<p>public Object[] toArray(Object[] prototypeArray) { ... }</p>
<p>toArray(Object[]) 的语义是，如果传递的数组足够大，就会使用它来保存结果，否则，就会使用反射分配一个相同类型的新数组。一般来说，单独传递一个数组作为参数来提供想要的返回类型是一个小技巧，但是在引入<strong>泛型</strong>之前，这是与方法交流类型信息最方便的方式。</p>
<p>有了<strong>泛型</strong>，就可以用一种更加直观的方式来做这件事。不像上面这样定义 toArray()，<strong>泛型</strong> toArray() 可能看起来像下面这样：</p>
<p>public&lt;T&gt; T[] toArray(Class&lt;T&gt; returnType)</p>
<p>调用这样一个 toArray() 方法很简单：</p>
<p>FooBar[] fba = something.toArray(FooBar.class);</p>
<p>Collection 接口还没有改变为使用该技术，因为这会破坏许多现有的集合实现。但是如果使用<strong>泛型</strong>从新构建 Collection，则当然会使用该方言来指定它想要返回值是哪种类型。</p>
<p>&nbsp;</p>
<p>Enum&lt;E&gt;</p>
<p>JDK 5.0 中 <strong>Java</strong> 语言另一个增加的特性是枚举。当您使用 enum 关键字声明一个枚举时，编译器就会在内部为您生成一个类，用于扩展 Enum 并为枚举的每个值声明静态实例。所以如果您说：</p>
<p>public enum Suit {HEART, DIAMOND, CLUB, SPADE};</p>
<p>编译器就会在内部生成一个叫做 Suit 的类，该类扩展 <strong>java</strong>.lang.Enum&lt;Suit&gt; 并具有叫做 HEART、DIAMOND、CLUB 和 SPADE 的常量（public static final）成员，每个成员都是 Suit 类。</p>
<p>与 Class 一样，Enum 也是一个<strong>泛型</strong>类。但是与 Class 不同，它的签名稍微更复杂一些：</p>
<p>class Enum&lt;E extends Enum&lt;E&gt;&gt; { . . . }</p>
<p>这究竟是什么意思？这难道不会导致无限递归？</p>
<p>我们逐步来分析。类型参数 E 用于 Enum 的各种方法中，比如 compareTo() 或 getDeclaringClass()。为了这些方法的类型安全，Enum 类必须在枚举的类上<strong>泛型</strong>化。</p>
<p>所以 extends Enum&lt;E&gt; 部分如何理解？该部分又具有两个部分。第一部分指出，作为 Enum 的类型参数的类本身必须是 Enum 的子类型，所以您不能声明一个类 X 扩展 Enum&lt;Integer&gt;。第二部分指出，任何扩展 Enum 的类必须传递它本身 作为类型参数。您不能声明 X 扩展 Enum&lt;Y&gt;，即使 Y 扩展 Enum。</p>
<p>总之，Enum 是一个参数化的类型，只可以为它的子类型实例化，并且这些子类型然后将根据子类型来继承方法。幸运的是，在 Enum 情况下，编译器为您做这些工作，一切都很好。</p>
<p>&nbsp;</p>
<p>与非<strong>泛型</strong>代码相互操作</p>
<p>数百万行现有代码使用已经<strong>泛型</strong>化的 <strong>Java</strong> 类库中的类，比如集合框架、Class 和 ThreadLocal。JDK 5.0 中的改进不要破坏所有这些代码是很重要的，所以编译器允许您在不指定其类型参数的情况下使用<strong>泛型</strong>类。</p>
<p>当然，以“旧方式”做事没有新方式安全，因为忽略了编译器准备提供的类型安全。如果您试图将 List&lt;String&gt; 传递给一个接受 List 的方法，它将能够工作，但是编译器将会发出一个可能丧失类型安全的警告，即所谓的“unchecked conversion（不检查转换）”警告。</p>
<p>没有类型参数的<strong>泛型</strong>，比如声明为 List 类型而不是 List&lt;Something&gt; 类型的变量，叫做原始类型。原始类型与参数化类型的任何实例化是赋值兼容的，但是这样的赋值会生成 unchecked-conversion 警告。</p>
<p>为了消除一些 unchecked-conversion 警告，假设您不准备<strong>泛型</strong>化所有的代码，您可以使用通配符类型参数。使用 List&lt;?&gt; 而不使用 List。List 是原始类型；List&lt;?&gt; 是具有未知类型参数的<strong>泛型</strong>。编译器将以不同的方式对待它们，并很可能发出更少的警告。</p>
<p>无论在哪种情况下，编译器在生成字节码时都会生成强制类型转换，所以生成的字节码在每种情况下都不会比没有<strong>泛型</strong>时更不安全。如果您设法通过使用原始类型或类文件来破坏类型安全，就会得到与不使用<strong>泛型</strong>时得到的相同的 ClassCastException 或 ArrayStoreException。</p>
<p><br>
已检查集合</p>
<p>作为从原始集合类型迁移到<strong>泛型</strong>集合类型的帮助，集合框架添加了一些新的集合包装器，以便为一些类型安全 bug 提供早期警告。就像 Collections.unmodifiableSet() 工厂方法用一个不允许任何修改的 Set 包装一个现有 Set 一样，Collections.checkedSet()（以及 checkedList() 和 checkedMap()）工厂方法创建一个包装器（或者视图）类，以防止您将错误类型的变量放在集合中。</p>
<p>checkedXxx() 方法都接受一个类常量作为参数，所以它们可以（在运行时）检查这些修改是允许的。典型的实现可能像下面这样：</p>
<p><br>
public class Collections { <br>
public static &lt;E&gt; Collection&lt;E&gt; checkedCollection(Collection&lt;E&gt; c, Class&lt;E&gt; type ) { <br>
&nbsp;&nbsp;&nbsp; return new CheckedCollection&lt;E&gt;(c, type); <br>
}</p>
<p>private static class CheckedCollection&lt;E&gt; implements Collection&lt;E&gt; { <br>
&nbsp;&nbsp;&nbsp; private final Collection&lt;E&gt; c; <br>
&nbsp;&nbsp;&nbsp; private final Class&lt;E&gt; type;</p>
<p>&nbsp;&nbsp;&nbsp; CheckedCollection(Collection&lt;E&gt; c, Class&lt;E&gt; type) { <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.c = c; <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.type = type; <br>
&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; public boolean add(E o) { <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!type.isInstance(o)) <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; throw new ClassCastException(); <br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return c.add(o); <br>
&nbsp;&nbsp;&nbsp; } <br>
} <br>
} </p>
<p></div></p>
<footer>
    <p>本站部分内容来源于网络，如有侵犯您的权益，请通过以下方式联系我们：</p>
    <p>邮箱：zytlsd@163.com</p>
    <p>我们将在收到通知后第一时间处理。</p>
</footer>]]></description>
    <pubDate>Sun, 19 Oct 2025 21:30:51 +0800</pubDate>
    <dc:creator>emer</dc:creator>
    <guid>http://www.51keeplearning.com/?post=39</guid>
</item>
<item>
    <title>java io 继谈</title>
    <link>http://www.51keeplearning.com/?post=38</link>
    <description><![CDATA[<p><meta name="referrer" content="no-referrer" /><div id="cnblogs_post_body" contentScore="29754"></p>
<p><strong>接上次写的《java IO整理》</strong>
<p>这次是关于java nio,有一些重复的发的地方。本文中的源代码可以在此处下载，下载链接为：http://115.com/file/cltlj10i#nio-src.zip</p>
<p><strong>本文简介：</strong>&nbsp; JDK 1.4 中引入的新输入输出 (NIO) 库在标准 Java 代码中提供了高速的、面向块的 I/O。本实用教程从高级概念到底层的编程细节，非常详细地介绍了 NIO 库。您将学到诸如缓冲区和通道这样的关键 I/O 元素的知识，并考察更新后的库中的标准 I/O 是如何工作的。您还将了解只能通过 NIO 来完成的工作，如异步 I/O 和直接缓冲区。</p>
<p><span>输入/输出：概念性描述</span></p>
<p><a name="N10095"></a><span>I/O 简介</span></p>
<p>I/O ? 或者输入/输出 ? 指的是计算机与外部世界或者一个程序与计算机的其余部分的之间的接口。它对于任何计算机系统都非常关键，因而所有 I/O 的主体实际上是内置在操作系统中的。单独的程序一般是让系统为它们完成大部分的工作。</p>
<p>在 Java 编程中，直到最近一直使用&nbsp;<em>流&nbsp;</em>的方式完成 I/O。所有 I/O 都被视为单个的字节的移动，通过一个称为 Stream 的对象一次移动一个字节。流 I/O 用于与外部世界接触。它也在内部使用，用于将对象转换为字节，然后再转换回对象。</p>
<p>NIO 与原来的 I/O 有同样的作用和目的，但是它使用不同的方式?&nbsp;<em>块 I/O</em>。正如您将在本教程中学到的，块 I/O 的效率可以比流 I/O 高许多。</p>
<p><a name="N100A9"></a><span>为什么要使用 NIO?</span></p>
<p>NIO 的创建目的是为了让 Java 程序员可以实现高速 I/O 而无需编写自定义的本机代码。NIO 将最耗时的 I/O 操作(即填充和提取缓冲区)转移回操作系统，因而可以极大地提高速度。</p>
<p><a name="N100B1"></a><span>流与块的比较</span></p>
<p>原来的 I/O 库(在&nbsp;<code>java.io.*</code>中) 与 NIO 最重要的区别是数据打包和传输的方式。正如前面提到的，原来的 I/O 以流的方式处理数据，而 NIO 以块的方式处理数据。</p>
<p><em>面向流&nbsp;</em>的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据，一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器，以便每个过滤器只负责单个复杂处理机制的一部分，这样也是相对简单的。不利的一面是，面向流的 I/O 通常相当慢。</p>
<p>一个&nbsp;<em>面向块&nbsp;</em>的 I/O 系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。</p>
<p><a name="N100C9"></a><span>集成的 I/O</span></p>
<p>在 JDK 1.4 中原来的 I/O 包和 NIO 已经很好地集成了。&nbsp;<code>java.io.*</code>&nbsp;已经以 NIO 为基础重新实现了，所以现在它可以利用 NIO 的一些特性。例如，&nbsp;<code>java.io.*</code>&nbsp;包中的一些类包含以块的形式读写数据的方法，这使得即使在更面向流的系统中，处理速度也会更快。</p>
<p>也可以用 NIO 库实现标准 I/O 功能。例如，可以容易地使用块 I/O 一次一个字节地移动数据。但是正如您会看到的，NIO 还提供了原 I/O 包中所没有的许多好处。</p>
<p><span>通道和缓冲区</span></p>
<p><a name="N100E5"></a><span>概述</span></p>
<p><code>通道&nbsp;</code>和&nbsp;<code>缓冲区&nbsp;</code>是 NIO 中的核心对象，几乎在每一个 I/O 操作中都要使用它们。</p>
<p>通道是对原 I/O 包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个 Channel 对象。一个 Buffer 实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中；同样地，从通道中读取的任何数据都要读到缓冲区中。</p>
<p>在本节中，您会了解到 NIO 中通道和缓冲区是如何工作的。</p>
<p><a name="N100FB"></a><span>什么是缓冲区？</span></p>
<p><code>Buffer</code>&nbsp;是一个对象， 它包含一些要写入或者刚读出的数据。 在 NIO 中加入&nbsp;<code>Buffer</code>&nbsp;对象，体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中，您将数据直接写入或者将数据直接读到&nbsp;<code>Stream</code>&nbsp;对象中。</p>
<p>在 NIO 库中，所有数据都是用缓冲区处理的。在读取数据时，它是直接读到缓冲区中的。在写入数据时，它是写入到缓冲区中的。任何时候访问 NIO 中的数据，您都是将它放到缓冲区中。</p>
<p>缓冲区实质上是一个数组。通常它是一个字节数组，但是也可以使用其他种类的数组。但是一个缓冲区不&nbsp;<em>仅仅&nbsp;</em>是一个数组。缓冲区提供了对数据的结构化访问，而且还可以跟踪系统的读/写进程。</p>
<p><a name="N10118"></a><span>缓冲区类型</span></p>
<p>最常用的缓冲区类型是&nbsp;<code>ByteBuffer</code>。一个&nbsp;<code>ByteBuffer</code>&nbsp;可以在其底层字节数组上进行 get/set 操作(即字节的获取和设置)。</p>
<p><code>ByteBuffer</code>&nbsp;不是 NIO 中唯一的缓冲区类型。事实上，对于每一种基本 Java 类型都有一种缓冲区类型：</p>
<ul>
<li><code>ByteBuffer</code></li>
<li><code>CharBuffer</code></li>
<li><code>ShortBuffer</code></li>
<li><code>IntBuffer</code></li>
<li><code>LongBuffer</code></li>
<li><code>FloatBuffer</code></li>
<li><code>DoubleBuffer</code></li>
</ul>
<p>每一个&nbsp;<code>Buffer</code>&nbsp;类都是&nbsp;<code>Buffer</code>&nbsp;接口的一个实例。 除了&nbsp;<code>ByteBuffer</code>，每一个 Buffer 类都有完全一样的操作，只是它们所处理的数据类型不一样。因为大多数标准 I/O 操作都使用&nbsp;<code>ByteBuffer</code>，所以它具有所有共享的缓冲区操作以及一些特有的操作。</p>
<p>现在您可以花一点时间运行 UseFloatBuffer.java，它包含了类型化的缓冲区的一个应用例子。</p>
<p><a name="N10179"></a><span>什么是通道？</span></p>
<p><code>Channel</code>是一个对象，可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较，通道就像是流。</p>
<p>正如前面提到的，所有数据都通过&nbsp;<code>Buffer</code>&nbsp;对象来处理。您永远不会将字节直接写入通道中，相反，您是将数据写入包含一个或者多个字节的缓冲区。同样，您不会直接从通道中读取字节，而是将数据从通道读入缓冲区，再从缓冲区获取这个字节。</p>
<p><a name="N1018C"></a><span>通道类型</span></p>
<p>通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是&nbsp;<code>InputStream</code>&nbsp;或者&nbsp;<code>OutputStream</code>&nbsp;的子类)， 而&nbsp;<code>通道&nbsp;</code>可以用于读、写或者同时用于读写。</p>
<p>因为它们是双向的，所以通道可以比流更好地反映底层操作系统的真实情况。特别是在 UNIX 模型中，底层操作系统通道是双向的。</p>
<p><span>从理论到实践：NIO 中的读和写</span></p>
<p><a name="N101AC"></a><span>概述</span></p>
<p>读和写是 I/O 的基本过程。从一个通道中读取很简单：只需创建一个缓冲区，然后让通道将数据读到这个缓冲区中。写入也相当简单：创建一个缓冲区，用数据填充它，然后让通道用这些数据来执行写入操作。</p>
<p>在本节中，我们将学习有关在 Java 程序中读取和写入数据的一些知识。我们将回顾 NIO 的主要组件(缓冲区、通道和一些相关的方法)，看看它们是如何交互以进行读写的。在接下来的几节中，我们将更详细地分析这其中的每个组件以及其交互。</p>
<p><a name="N101B7"></a><span>从文件中读取</span></p>
<p>在我们第一个练习中，我们将从一个文件中读取一些数据。如果使用原来的 I/O，那么我们只需创建一个&nbsp;<code>FileInputStream</code>&nbsp;并从它那里读取。而在 NIO 中，情况稍有不同：我们首先从&nbsp;<code>FileInputStream</code>&nbsp;获取一个&nbsp;<code>FileInputStream</code>&nbsp;对象，然后使用这个通道来读取数据。</p>
<p>在 NIO 系统中，任何时候执行一个读操作，您都是从通道中读取，但是您不是&nbsp;<em>直接&nbsp;</em>从通道读取。因为所有数据最终都驻留在缓冲区中，所以您是从通道读到缓冲区中。</p>
<p>因此读取文件涉及三个步骤：(1) 从&nbsp;<code>FileInputStream</code>&nbsp;获取&nbsp;<code>Channel</code>，(2) 创建&nbsp;<code>Buffer</code>，(3) 将数据从&nbsp;<code>Channel</code>&nbsp;读到&nbsp;<code>Buffer&nbsp;</code>中。</p>
<p>现在，让我们看一下这个过程。</p>
<p><a name="N101EB"></a><span>三个容易的步骤</span></p>
<p>第一步是获取通道。我们从&nbsp;<code>FileInputStream</code>&nbsp;获取通道：</p>
<div>
<pre>FileInputStream fin = new FileInputStream( "readandshow.txt" );
FileChannel fc = fin.getChannel();
</pre>
</div>
<p>下一步是创建缓冲区：</p>
<div>
<pre>ByteBuffer buffer = ByteBuffer.allocate( 1024 );
</pre>
</div>
<p>最后，需要将数据从通道读到缓冲区中，如下所示：</p>
<div>
<pre>fc.read( buffer );
</pre>
</div>
<p>您会注意到，我们不需要告诉通道要读&nbsp;<em>多少数据&nbsp;</em>到缓冲区中。每一个缓冲区都有复杂的内部统计机制，它会跟踪已经读了多少数据以及还有多少空间可以容纳更多的数据</p>
<p><a name="N10213"></a><span>写入文件</span></p>
<p>在 NIO 中写入文件类似于从文件中读取。首先从&nbsp;<code>FileOutputStream</code>&nbsp;获取一个通道：</p>
<div>
<pre>FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );
FileChannel fc = fout.getChannel();
</pre>
</div>
<p>下一步是创建一个缓冲区并在其中放入一些数据 - 在这里，数据将从一个名为&nbsp;<code>message</code>&nbsp;的数组中取出，这个数组包含字符串 "Some bytes" 的 ASCII 字节(本教程后面将会解释&nbsp;<code>buffer.flip()</code>&nbsp;和&nbsp;<code>buffer.put()</code>&nbsp;调用)。</p>
<div>
<pre>ByteBuffer buffer = ByteBuffer.allocate( 1024 );

for (int i=0; i&lt;message.length; ++i) {
     buffer.put( message[i] );
}
buffer.flip();
</pre>
</div>
<p>最后一步是写入缓冲区中</p>
<div>
<pre>fc.write( buffer );
</pre>
</div>
<p>注意在这里同样不需要告诉通道要写入多数据。缓冲区的内部统计机制会跟踪它包含多少数据以及还有多少数据要写入。</p>
<p><a name="N10240"></a><span>读写结合</span></p>
<p>下面我们将看一下在结合读和写时会有什么情况。我们以一个名为 CopyFile.java 的简单程序作为这个练习的基础，它将一个文件的所有内容拷贝到另一个文件中。CopyFile.java 执行三个基本操作：首先创建一个&nbsp;<code>Buffer</code>，然后从源文件中将数据读到这个缓冲区中，然后将缓冲区写入目标文件。这个程序不断重复 ― 读、写、读、写 ― 直到源文件结束。</p>
<p>CopyFile 程序让您看到我们如何检查操作的状态，以及如何使用&nbsp;<code>clear()</code>&nbsp;和&nbsp;<code>flip()</code>&nbsp;方法重设缓冲区，并准备缓冲区以便将新读取的数据写到另一个通道中。</p>
<p><a name="N10257"></a><span>运行 CopyFile 例子</span></p>
<p>因为缓冲区会跟踪它自己的数据，所以 CopyFile 程序的内部循环 (inner loop) 非常简单，如下所示：</p>
<div>
<pre>fcin.read( buffer );
fcout.write( buffer );
</pre>
</div>
<p>第一行将数据从输入通道&nbsp;<code>fcin</code>&nbsp;中读入缓冲区，第二行将这些数据写到输出通道&nbsp;<code>fcout</code>&nbsp;。</p>
<p><a name="N1026E"></a><span>检查状态</span></p>
<p>下一步是检查拷贝何时完成。当没有更多的数据时，拷贝就算完成，并且可以在&nbsp;<code>read()</code>&nbsp;方法返回 -1 是判断这一点，如下所示：</p>
<div>
<pre>int r = fcin.read( buffer );

if (r==-1) {
     break;
}
</pre>
</div>
<p><a name="N1027E"></a><span>重设缓冲区</span></p>
<p>最后，在从输入通道读入缓冲区之前，我们调用&nbsp;<code>clear()</code>&nbsp;方法。同样，在将缓冲区写入输出通道之前，我们调用&nbsp;<code>flip()</code>&nbsp;方法，如下所示</p>
<div>
<pre>buffer.clear();
int r = fcin.read( buffer );

if (r==-1) {
     break;
}

buffer.flip();
fcout.write( buffer );
</pre>
</div>
<p><code>clear()</code>&nbsp;方法重设缓冲区，使它可以接受读入的数据。&nbsp;<code>flip()</code>&nbsp;方法让缓冲区可以将新读入的数据写入另一个通道。</p>
<p></p>
<p><span>缓冲区内部细节</span></p>
<p><a name="N102A6"><span>概述</span></a></p>
<p>本节将介绍 NIO 中两个重要的缓冲区组件：状态变量和访问方法 (accessor)。</p>
<p>状态变量是前一节中提到的"内部统计机制"的关键。每一个读/写操作都会改变缓冲区的状态。通过记录和跟踪这些变化，缓冲区就可能够内部地管理自己的资源。</p>
<p>在从通道读取数据时，数据被放入到缓冲区。在有些情况下，可以将这个缓冲区直接写入另一个通道，但是在一般情况下，您还需要查看数据。这是使用&nbsp;<em>访问方法&nbsp;</em><code>get()</code>&nbsp;来完成的。同样，如果要将原始数据放入缓冲区中，就要使用访问方法&nbsp;<code>put()</code>。</p>
<p>在本节中，您将学习关于 NIO 中的状态变量和访问方法的内容。我们将描述每一个组件，并让您有机会看到它的实际应用。虽然 NIO 的内部统计机制初看起来可能很复杂，但是您很快就会看到大部分的实际工作都已经替您完成了。您可能习惯于通过手工编码进行簿记 ― 即使用字节数组和索引变量，现在它已在 NIO 中内部地处理了。</p>
<p><a name="N102C2"><span>状态变量</span></a></p>
<p>可以用三个值指定缓冲区在任意时刻的状态：</p>
<ul>
<li><code>position</code></li>
<li><code>limit</code></li>
<li><code>capacity</code></li>
</ul>
<p>这三个变量一起可以跟踪缓冲区的状态和它所包含的数据。我们将在下面的小节中详细分析每一个变量，还要介绍它们如何适应典型的读/写(输入/输出)进程。在这个例子中，我们假定要将数据从一个输入通道拷贝到一个输出通道。</p>
<p><a name="N102E5"><span>Position</span></a></p>
<p>您可以回想一下，缓冲区实际上就是美化了的数组。在从通道读取时，您将所读取的数据放到底层的数组中。&nbsp;<code>position</code>&nbsp;变量跟踪已经写了多少数据。更准确地说，它指定了下一个字节将放到数组的哪一个元素中。因此，如果您从通道中读三个字节到缓冲区中，那么缓冲区的&nbsp;<code>position</code>&nbsp;将会设置为3，指向数组中第四个元素。</p>
<p>同样，在写入通道时，您是从缓冲区中获取数据。&nbsp;<code>position</code>&nbsp;值跟踪从缓冲区中获取了多少数据。更准确地说，它指定下一个字节来自数组的哪一个元素。因此如果从缓冲区写了5个字节到通道中，那么缓冲区的&nbsp;<code>position</code>&nbsp;将被设置为5，指向数组的第六个元素。</p>
<p><a name="N10300"><span>Limit</span></a></p>
<p><code>limit</code>&nbsp;变量表明还有多少数据需要取出(在从缓冲区写入通道时)，或者还有多少空间可以放入数据(在从通道读入缓冲区时)。</p>
<p><code>position</code>&nbsp;总是小于或者等于&nbsp;<code>limit</code>。</p>
<p><a name="N10317"><span>Capacity</span></a></p>
<p>缓冲区的&nbsp;<code>capacity</code>&nbsp;表明可以储存在缓冲区中的最大数据容量。实际上，它指定了底层数组的大小 ― 或者至少是指定了准许我们使用的底层数组的容量。</p>
<p><code>limit</code>&nbsp;决不能大于&nbsp;<code>capacity</code>。</p>
<p><a name="N1032E"><span>观察变量</span></a></p>
<p>我们首先观察一个新创建的缓冲区。出于本例子的需要，我们假设这个缓冲区的&nbsp;<code>总容量&nbsp;</code>为8个字节。&nbsp;<code>Buffer</code>&nbsp;的状态如下所示：</p>
<p><br><img alt="Buffer state" src="http://www.ibm.com/developerworks/cn/education/java/j-nio/figure1.gif">&nbsp;</p>
<p>回想一下 ，<code>limit</code>&nbsp;决不能大于&nbsp;<code>capacity</code>，此例中这两个值都被设置为 8。我们通过将它们指向数组的尾部之后(如果有第8个槽，则是第8个槽所在的位置)来说明这点。</p>
<p><br><img alt="Array" src="http://www.ibm.com/developerworks/cn/education/java/j-nio/figure2.gif">&nbsp;</p>
<p><code>position</code>&nbsp;设置为0。如果我们读一些数据到缓冲区中，那么下一个读取的数据就进入 slot 0 。如果我们从缓冲区写一些数据，从缓冲区读取的下一个字节就来自 slot 0 。&nbsp;<code>position</code>&nbsp;设置如下所示：</p>
<p><br><img alt="Position setting" src="http://www.ibm.com/developerworks/cn/education/java/j-nio/figure3.gif">&nbsp;</p>
<p>由于&nbsp;<code>capacity</code>&nbsp;不会改变，所以我们在下面的讨论中可以忽略它。</p>
<p><a name="N10379"><span>第一次读取</span></a></p>
<p>现在我们可以开始在新创建的缓冲区上进行读/写操作。首先从输入通道中读一些数据到缓冲区中。第一次读取得到三个字节。它们被放到数组中从&nbsp;<code>position</code>&nbsp;开始的位置，这时 position 被设置为 0。读完之后，position 就增加到 3，如下所示：</p>
<p><br><img alt="Position increased to 3" src="http://www.ibm.com/developerworks/cn/education/java/j-nio/figure4.gif">&nbsp;</p>
<p><code>limit</code>&nbsp;没有改变。</p>
<p><a name="N10396"><span>第二次读取</span></a></p>
<p>在第二次读取时，我们从输入通道读取另外两个字节到缓冲区中。这两个字节储存在由&nbsp;<code>position</code>&nbsp;所指定的位置上，&nbsp;<code>position</code>&nbsp;因而增加 2：</p>
<p><br><img alt="Position increased by 2" src="http://www.ibm.com/developerworks/cn/education/java/j-nio/figure5.gif">&nbsp;</p>
<p><code>limit</code>&nbsp;没有改变。</p>
<p><a name="N103B7"><span>flip</span></a></p>
<p>现在我们要将数据写到输出通道中。在这之前，我们必须调用&nbsp;<code>flip()</code>&nbsp;方法。这个方法做两件非常重要的事：</p>
<ol>
<li>它将&nbsp;<code>limit</code>&nbsp;设置为当前&nbsp;<code>position</code>。</li>
<li>它将&nbsp;<code>position</code>&nbsp;设置为 0。</li>
</ol>
<p>前一小节中的图显示了在 flip 之前缓冲区的情况。下面是在 flip 之后的缓冲区：</p>
<p><br><img alt="Buffer after the flip" src="http://www.ibm.com/developerworks/cn/education/java/j-nio/figure6.gif">&nbsp;</p>
<p>我们现在可以将数据从缓冲区写入通道了。&nbsp;<code>position</code>&nbsp;被设置为 0，这意味着我们得到的下一个字节是第一个字节。&nbsp;<code>limit</code>&nbsp;已被设置为原来的&nbsp;<code>position</code>，这意味着它包括以前读到的所有字节，并且一个字节也不多。</p>
<p><a name="N103F4"><span>第一次写入</span></a></p>
<p>在第一次写入时，我们从缓冲区中取四个字节并将它们写入输出通道。这使得&nbsp;<code>position</code>&nbsp;增加到 4，而&nbsp;<code>limit</code>&nbsp;不变，如下所示：</p>
<p></p>
<p><br><img alt="Position advanced to 4, limit unchanged" src="http://www.ibm.com/developerworks/cn/education/java/j-nio/figure7.gif">&nbsp;</p>
<p></p>
<p><a name="N1040E"><span>第二次写入</span></a></p>
<p>我们只剩下一个字节可写了。&nbsp;<code>limit</code>在我们调用&nbsp;<code>flip()</code>&nbsp;时被设置为 5，并且&nbsp;<code>position</code>&nbsp;不能超过&nbsp;<code>limit</code>。所以最后一次写入操作从缓冲区取出一个字节并将它写入输出通道。这使得&nbsp;<code>position</code>&nbsp;增加到 5，并保持&nbsp;<code>limit</code>&nbsp;不变，如下所示：</p>
<p></p>
<p><br><img alt="Position advanced to 5, limit unchanged" src="http://www.ibm.com/developerworks/cn/education/java/j-nio/figure8.gif">&nbsp;</p>
<p><a name="N10438"></a><span>clear</span></p>
<p>最后一步是调用缓冲区的&nbsp;<code>clear()</code>&nbsp;方法。这个方法重设缓冲区以便接收更多的字节。&nbsp;<code>Clear</code>&nbsp;做两种非常重要的事情：</p>
<ol>
<li>它将&nbsp;<code>limit</code>&nbsp;设置为与&nbsp;<code>capacity</code>&nbsp;相同。</li>
<li>它设置&nbsp;<code>position</code>&nbsp;为 0。</li>
</ol>
<p>下图显示了在调用&nbsp;<code>clear()</code>&nbsp;后缓冲区的状态：</p>
<p><br><img alt="State of the buffer after clear() has been called" src="http://www.ibm.com/developerworks/cn/education/java/j-nio/figure9.gif">&nbsp;</p>
<p>缓冲区现在可以接收新的数据了。</p>
<p><a name="N10471"></a><span>访问方法</span></p>
<p>到目前为止，我们只是使用缓冲区将数据从一个通道转移到另一个通道。然而，程序经常需要直接处理数据。例如，您可能需要将用户数据保存到磁盘。在这种情况下，您必须将这些数据直接放入缓冲区，然后用通道将缓冲区写入磁盘。</p>
<p>或者，您可能想要从磁盘读取用户数据。在这种情况下，您要将数据从通道读到缓冲区中，然后检查缓冲区中的数据。</p>
<p>在本节的最后，我们将详细分析如何使用&nbsp;<code>ByteBuffer</code>&nbsp;类的&nbsp;<code>get()</code>&nbsp;和&nbsp;<code>put()</code>&nbsp;方法直接访问缓冲区中的数据。</p>
<p><a name="N1048B"></a><span>get() 方法</span></p>
<p><code>ByteBuffer</code>&nbsp;类中有四个&nbsp;<code>get()</code>&nbsp;方法：</p>
<ol>
<li><code>byte get();</code></li>
<li><code>ByteBuffer get( byte dst[] );</code></li>
<li><code>ByteBuffer get( byte dst[], int offset, int length );</code></li>
<li><code>byte get( int index );</code></li>
</ol>
<p>第一个方法获取单个字节。第二和第三个方法将一组字节读到一个数组中。第四个方法从缓冲区中的特定位置获取字节。那些返回<code>ByteBuffer</code>&nbsp;的方法只是返回调用它们的缓冲区的&nbsp;<code>this</code>&nbsp;值。</p>
<p>此外，我们认为前三个&nbsp;<code>get()</code>&nbsp;方法是相对的，而最后一个方法是绝对的。&nbsp;<em>相对&nbsp;</em>意味着&nbsp;<code>get()</code>&nbsp;操作服从&nbsp;<code>limit</code>&nbsp;和&nbsp;<code>position</code>&nbsp;值 ― 更明确地说，字节是从当前&nbsp;<code>position</code>&nbsp;读取的，而&nbsp;<code>position</code>&nbsp;在&nbsp;<code>get</code>&nbsp;之后会增加。另一方面，一个&nbsp;<em>绝对&nbsp;</em>方法会忽略&nbsp;<code>limit</code>&nbsp;和&nbsp;<code>position</code>&nbsp;值，也不会影响它们。事实上，它完全绕过了缓冲区的统计方法。</p>
<p>上面列出的方法对应于&nbsp;<code>ByteBuffer</code>&nbsp;类。其他类有等价的&nbsp;<code>get()</code>&nbsp;方法，这些方法除了不是处理字节外，其它方面是是完全一样的，它们处理的是与该缓冲区类相适应的类型。</p>
<p><a name="N104FD"></a><span>put()方法</span></p>
<p><code>ByteBuffer</code>&nbsp;类中有五个&nbsp;<code>put()</code>&nbsp;方法：</p>
<ol>
<li><code>ByteBuffer put( byte b );</code></li>
<li><code>ByteBuffer put( byte src[] );</code></li>
<li><code>ByteBuffer put( byte src[], int offset, int length );</code></li>
<li><code>ByteBuffer put( ByteBuffer src );</code></li>
<li><code>ByteBuffer put( int index, byte b );</code></li>
</ol>
<p>第一个方法&nbsp;<code>写入（put）&nbsp;</code>单个字节。第二和第三个方法写入来自一个数组的一组字节。第四个方法将数据从一个给定的源<code>ByteBuffer</code>&nbsp;写入这个&nbsp;<code>ByteBuffer</code>。第五个方法将字节写入缓冲区中特定的&nbsp;<code>位置&nbsp;</code>。那些返回&nbsp;<code>ByteBuffer</code>&nbsp;的方法只是返回调用它们的缓冲区的&nbsp;<code>this</code>&nbsp;值。</p>
<p>与&nbsp;<code>get()</code>&nbsp;方法一样，我们将把&nbsp;<code>put()</code>&nbsp;方法划分为&nbsp;<em>相对&nbsp;</em>或者&nbsp;<em>绝对&nbsp;</em>的。前四个方法是相对的，而第五个方法是绝对的。</p>
<p>上面显示的方法对应于&nbsp;<code>ByteBuffer</code>&nbsp;类。其他类有等价的&nbsp;<code>put()</code>&nbsp;方法，这些方法除了不是处理字节之外，其它方面是完全一样的。它们处理的是与该缓冲区类相适应的类型。</p>
<p><a name="N1056A"></a><span>类型化的 get() 和 put() 方法</span></p>
<p>除了前些小节中描述的&nbsp;<code>get()</code>&nbsp;和&nbsp;<code>put()</code>&nbsp;方法，&nbsp;<code>ByteBuffer</code>&nbsp;还有用于读写不同类型的值的其他方法，如下所示：</p>
<ul>
<li><code>getByte()</code></li>
<li><code>getChar()</code></li>
<li><code>getShort()</code></li>
<li><code>getInt()</code></li>
<li><code>getLong()</code></li>
<li><code>getFloat()</code></li>
<li><code>getDouble()</code></li>
<li><code>putByte()</code></li>
<li><code>putChar()</code></li>
<li><code>putShort()</code></li>
<li><code>putInt()</code></li>
<li><code>putLong()</code></li>
<li><code>putFloat()</code></li>
<li><code>putDouble()</code></li>
</ul>
<p>事实上，这其中的每个方法都有两种类型 ― 一种是相对的，另一种是绝对的。它们对于读取格式化的二进制数据（如图像文件的头部）很有用。</p>
<p>您可以在例子程序 TypesInByteBuffer.java 中看到这些方法的实际应用。</p>
<p><a name="N105E9"></a><span>缓冲区的使用：一个内部循环</span></p>
<p>下面的内部循环概括了使用缓冲区将数据从输入通道拷贝到输出通道的过程。</p>
<div>
<pre>while (true) {
     buffer.clear();
     int r = fcin.read( buffer );

     if (r==-1) {
       break;
     }

     buffer.flip();
     fcout.write( buffer );
}
</pre>
</div>
<p><code>read()</code>&nbsp;和&nbsp;<code>write()</code>&nbsp;调用得到了极大的简化，因为许多工作细节都由缓冲区完成了。&nbsp;<code>clear()</code>&nbsp;和&nbsp;<code>flip()</code>&nbsp;方法用于让缓冲区在读和写之间切换。</p>
<p><span>关于缓冲区的更多内容</span></p>
<p><a name="N10611"></a><span>概述</span></p>
<p>到目前为止，您已经学习了使用缓冲区进行日常工作所需要掌握的大部分内容。我们的例子没怎么超出标准的读/写过程种类，在原来的 I/O 中可以像在 NIO 中一样容易地实现这样的标准读写过程。</p>
<p>本节将讨论使用缓冲区的一些更复杂的方面，比如缓冲区分配、包装和分片。我们还会讨论 NIO 带给 Java 平台的一些新功能。您将学到如何创建不同类型的缓冲区以达到不同的目的，如可保护数据不被修改的&nbsp;<em>只读&nbsp;</em>缓冲区，和直接映射到底层操作系统缓冲区的&nbsp;<em>直接&nbsp;</em>缓冲区。我们将在本节的最后介绍如何在 NIO 中创建内存映射文件。</p>
<p><a name="N10622"></a><span>缓冲区分配和包装</span></p>
<p>在能够读和写之前，必须有一个缓冲区。要创建缓冲区，您必须&nbsp;<em>分配&nbsp;</em>它。我们使用静态方法&nbsp;<code>allocate()</code>&nbsp;来分配缓冲区：</p>
<div>
<pre>ByteBuffer buffer = ByteBuffer.allocate( 1024 );
</pre>
</div>
<p><code>allocate()</code>&nbsp;方法分配一个具有指定大小的底层数组，并将它包装到一个缓冲区对象中 ― 在本例中是一个&nbsp;<code>ByteBuffer</code>。</p>
<p>您还可以将一个现有的数组转换为缓冲区，如下所示：</p>
<div>
<pre>byte array[] = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap( array );
</pre>
</div>
<p>本例使用了&nbsp;<code>wrap()</code>&nbsp;方法将一个数组包装为缓冲区。必须非常小心地进行这类操作。一旦完成包装，底层数据就可以通过缓冲区或者直接访问。</p>
<p><a name="N1064E"></a><span>缓冲区分片</span></p>
<p><code>slice()</code>&nbsp;方法根据现有的缓冲区创建一种&nbsp;<em>子缓冲区&nbsp;</em>。也就是说，它创建一个新的缓冲区，新缓冲区与原来的缓冲区的一部分共享数据。</p>
<p>使用例子可以最好地说明这点。让我们首先创建一个长度为 10 的&nbsp;<code>ByteBuffer</code>：</p>
<div>
<pre>ByteBuffer buffer = ByteBuffer.allocate( 10 )
</pre>
</div>
<p>然后使用数据来填充这个缓冲区，在第&nbsp;<em>n</em>&nbsp;个槽中放入数字&nbsp;<em>n</em>：</p>
<div>
<pre>for (int i=0; i&lt;buffer.capacity(); ++i) {
     buffer.put( (byte)i );
}
</pre>
</div>
<p>现在我们对这个缓冲区&nbsp;<em>分片&nbsp;</em>，以创建一个包含槽 3 到槽 6 的子缓冲区。在某种意义上，子缓冲区就像原来的缓冲区中的一个&nbsp;<em>窗口&nbsp;</em>。</p>
<p>窗口的起始和结束位置通过设置&nbsp;<code>position</code>&nbsp;和&nbsp;<code>limit</code>&nbsp;值来指定，然后调用&nbsp;<code>Buffer</code>&nbsp;的&nbsp;<code>slice()</code>&nbsp;方法：</p>
<div>
<pre>buffer.position( 3 );
buffer.limit( 7 );
ByteBuffer slice = buffer.slice();
</pre>
</div>
<p><code>片&nbsp;</code>是缓冲区的&nbsp;<code>子缓冲区&nbsp;</code>。不过，&nbsp;<code>片段&nbsp;</code>和&nbsp;<code>缓冲区&nbsp;</code>共享同一个底层数据数组，我们在下一节将会看到这一点。</p>
<p><a name="N106A8"></a><span>缓冲区份片和数据共享</span></p>
<p>我们已经创建了原缓冲区的子缓冲区，并且我们知道缓冲区和子缓冲区共享同一个底层数据数组。让我们看看这意味着什么。</p>
<p>我们遍历子缓冲区，将每一个元素乘以 11 来改变它。例如，5 会变成 55。</p>
<div>
<pre>for (int i=0; i&lt;slice.capacity(); ++i) {
     byte b = slice.get( i );
     b *= 11;
     slice.put( i, b );
}
</pre>
</div>
<p>最后，再看一下原缓冲区中的内容：</p>
<div>
<pre>buffer.position( 0 );
buffer.limit( buffer.capacity() );

while (buffer.remaining()&gt;0) {
     System.out.println( buffer.get() );
}
</pre>
</div>
<p>结果表明只有在子缓冲区窗口中的元素被改变了：</p>
<table cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td>
<pre>$ java SliceBuffer
0
1
2
33
44
55
66
7
8
9</pre>
<pre></pre>
</td>
</tr>
</tbody>
</table>
<p>缓冲区片对于促进抽象非常有帮助。可以编写自己的函数处理整个缓冲区，而且如果想要将这个过程应用于子缓冲区上，您只需取主缓冲区的一个片，并将它传递给您的函数。这比编写自己的函数来取额外的参数以指定要对缓冲区的哪一部分进行操作更容易。</p>
<p></p>
<p><a name="N106C8"></a><span>只读缓冲区</span></p>
<p></p>
<p>只读缓冲区非常简单 ― 您可以读取它们，但是不能向它们写入。可以通过调用缓冲区的&nbsp;<code>asReadOnlyBuffer()</code>&nbsp;方法，将任何常规缓冲区转换为只读缓冲区，这个方法返回一个与原缓冲区完全相同的缓冲区(并与其共享数据)，只不过它是只读的。</p>
<p></p>
<p>只读缓冲区对于保护数据很有用。在将缓冲区传递给某个对象的方法时，您无法知道这个方法是否会修改缓冲区中的数据。创建一个只读的缓冲区可以&nbsp;<em>保证&nbsp;</em>该缓冲区不会被修改。</p>
<p></p>
<p>不能将只读的缓冲区转换为可写的缓冲区。</p>
<p></p>
<p><a name="N106DD"></a><span>直接和间接缓冲区</span></p>
<p>另一种有用的&nbsp;<code>ByteBuffer</code>&nbsp;是直接缓冲区。&nbsp;<em>直接缓冲区&nbsp;</em>是为加快 I/O 速度，而以一种特殊的方式分配其内存的缓冲区。</p>
<p>实际上，直接缓冲区的准确定义是与实现相关的。Sun 的文档是这样描述直接缓冲区的：</p>
<p><em>给定一个直接字节缓冲区，Java 虚拟机将尽最大努力直接对它执行本机 I/O 操作。也就是说，它会在每一次调用底层操作系统的本机 I/O 操作之前(或之后)，尝试避免将缓冲区的内容拷贝到一个中间缓冲区中(或者从一个中间缓冲区中拷贝数据)。</em></p>
<p>您可以在例子程序 FastCopyFile.java 中看到直接缓冲区的实际应用，这个程序是 CopyFile.java 的另一个版本，它使用了直接缓冲区以提高速度。</p>
<p>还可以用内存映射文件创建直接缓冲区。</p>
<p><a name="N106F9"></a><span>内存映射文件 I/O</span></p>
<p>内存映射文件 I/O 是一种读和写文件数据的方法，它可以比常规的基于流或者基于通道的 I/O 快得多。</p>
<p>内存映射文件 I/O 是通过使文件中的数据神奇般地出现为内存数组的内容来完成的。这其初听起来似乎不过就是将整个文件读到内存中，但是事实上并不是这样。一般来说，只有文件中实际读取或者写入的部分才会送入（或者&nbsp;<em>映射&nbsp;</em>）到内存中。</p>
<p>内存映射并不真的神奇或者多么不寻常。现代操作系统一般根据需要将文件的部分映射为内存的部分，从而实现文件系统。Java 内存映射机制不过是在底层操作系统中可以采用这种机制时，提供了对该机制的访问。</p>
<p>尽管创建内存映射文件相当简单，但是向它写入可能是危险的。仅只是改变数组的单个元素这样的简单操作，就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。</p>
<p><a name="N1070D"></a><span>将文件映射到内存</span></p>
<p>了解内存映射的最好方法是使用例子。在下面的例子中，我们要将一个&nbsp;<code>FileChannel</code>&nbsp;(它的全部或者部分)映射到内存中。为此我们将使用&nbsp;<code>FileChannel.map()</code>&nbsp;方法。下面代码行将文件的前 1024 个字节映射到内存中：</p>
<div>
<pre>MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE,
     0, 1024 );
</pre>
</div>
<p><code>map()</code>&nbsp;方法返回一个&nbsp;<code>MappedByteBuffer</code>，它是&nbsp;<code>ByteBuffer</code>&nbsp;的子类。因此，您可以像使用其他任何&nbsp;<code>ByteBuffer</code>&nbsp;一样使用新映射的缓冲区，操作系统会在需要时负责执行行映射。</p>
<p><span>分散和聚集</span></p>
<p><a name="N1073D"></a><span>概述</span></p>
<p>分散/聚集 I/O 是使用多个而不是单个缓冲区来保存数据的读写方法。</p>
<p>一个分散的读取就像一个常规通道读取，只不过它是将数据读到一个缓冲区数组中而不是读到单个缓冲区中。同样地，一个聚集写入是向缓冲区数组而不是向单个缓冲区写入数据。</p>
<p>分散/聚集 I/O 对于将数据流划分为单独的部分很有用，这有助于实现复杂的数据格式。</p>
<p><a name="N1074B"></a><span>分散/聚集 I/O</span></p>
<p>通道可以有选择地实现两个新的接口：&nbsp;<code>ScatteringByteChannel</code>&nbsp;和&nbsp;<code>GatheringByteChannel</code>。一个&nbsp;<code>ScatteringByteChannel</code>&nbsp;是一个具有两个附加读方法的通道：</p>
<ul>
<li><code>long read( ByteBuffer[] dsts );</code></li>
<li><code>long read( ByteBuffer[] dsts, int offset, int length );</code></li>
</ul>
<p>这些&nbsp;<code>long read()</code>&nbsp;方法很像标准的&nbsp;<code>read</code>&nbsp;方法，只不过它们不是取单个缓冲区而是取一个缓冲区数组。</p>
<p>在&nbsp;<em>分散读取&nbsp;</em>中，通道依次填充每个缓冲区。填满一个缓冲区后，它就开始填充下一个。在某种意义上，缓冲区数组就像一个大缓冲区。</p>
<p><a name="N10781"></a><span>分散/聚集的应用</span></p>
<p>分散/聚集 I/O 对于将数据划分为几个部分很有用。例如，您可能在编写一个使用消息对象的网络应用程序，每一个消息被划分为固定长度的头部和固定长度的正文。您可以创建一个刚好可以容纳头部的缓冲区和另一个刚好可以容难正文的缓冲区。当您将它们放入一个数组中并使用分散读取来向它们读入消息时，头部和正文将整齐地划分到这两个缓冲区中。</p>
<p>我们从缓冲区所得到的方便性对于缓冲区数组同样有效。因为每一个缓冲区都跟踪自己还可以接受多少数据，所以分散读取会自动找到有空间接受数据的第一个缓冲区。在这个缓冲区填满后，它就会移动到下一个缓冲区。</p>
<p><a name="N1078C"></a><span>聚集写入</span></p>
<p><em>聚集写入&nbsp;</em>类似于分散读取，只不过是用来写入。它也有接受缓冲区数组的方法：</p>
<ul>
<li><code>long write( ByteBuffer[] srcs );</code></li>
<li><code>long write( ByteBuffer[] srcs, int offset, int length );</code></li>
</ul>
<p>聚集写对于把一组单独的缓冲区中组成单个数据流很有用。为了与上面的消息例子保持一致，您可以使用聚集写入来自动将网络消息的各个部分组装为单个数据流，以便跨越网络传输消息。</p>
<p>从例子程序 UseScatterGather.java 中可以看到分散读取和聚集写入的实际应用。</p>
<p><span>文件锁定</span></p>
<p><a name="N107B7"></a><span>概述</span></p>
<p>文件锁定初看起来可能让人迷惑。它&nbsp;<em>似乎&nbsp;</em>指的是防止程序或者用户访问特定文件。事实上，文件锁就像常规的 Java 对象锁 ― 它们是&nbsp;<em>劝告式的（advisory）&nbsp;</em>锁。它们不阻止任何形式的数据访问，相反，它们通过锁的共享和获取赖允许系统的不同部分相互协调。</p>
<p>您可以锁定整个文件或者文件的一部分。如果您获取一个排它锁，那么其他人就不能获得同一个文件或者文件的一部分上的锁。如果您获得一个共享锁，那么其他人可以获得同一个文件或者文件一部分上的共享锁，但是不能获得排它锁。文件锁定并不总是出于保护数据的目的。例如，您可能临时锁定一个文件以保证特定的写操作成为原子的，而不会有其他程序的干扰。</p>
<p>大多数操作系统提供了文件系统锁，但是它们并不都是采用同样的方式。有些实现提供了共享锁，而另一些仅提供了排它锁。事实上，有些实现使得文件的锁定部分不可访问，尽管大多数实现不是这样的。</p>
<p>在本节中，您将学习如何在 NIO 中执行简单的文件锁过程，我们还将探讨一些保证被锁定的文件尽可能可移植的方法。</p>
<p><a name="N107CE"></a><span>锁定文件</span></p>
<p>要获取文件的一部分上的锁，您要调用一个打开的&nbsp;<code>FileChannel</code>&nbsp;上的&nbsp;<code>lock()</code>&nbsp;方法。注意，如果要获取一个排它锁，您必须以写方式打开文件。</p>
<div>
<pre>RandomAccessFile raf = new RandomAccessFile( "usefilelocks.txt", "rw" );
FileChannel fc = raf.getChannel();
FileLock lock = fc.lock( start, end, false );
</pre>
</div>
<p>在拥有锁之后，您可以执行需要的任何敏感操作，然后再释放锁：</p>
<div>
<pre>lock.release();
</pre>
</div>
<p></p>
<p>在释放锁后，尝试获得锁的其他任何程序都有机会获得它。</p>
<p>本小节的例子程序 UseFileLocks.java 必须与它自己并行运行。这个程序获取一个文件上的锁，持有三秒钟，然后释放它。如果同时运行这个程序的多个实例，您会看到每个实例依次获得锁。</p>
<p>文件锁定可能是一个复杂的操作，特别是考虑到不同的操作系统是以不同的方式实现锁这一事实。下面的指导原则将帮助您尽可能保持代码的可移植性：</p>
<ul>
<li>只使用排它锁。</li>
<li>将所有的锁视为劝告式的（advisory）。</li>
<li contentScore="11036">
<p><span>连网和异步 I/O</span></p>
<p><a name="N10809"></a><span>概述</span></p>
<p>连网是学习异步 I/O 的很好基础，而异步 I/O 对于在 Java 语言中执行任何输入/输出过程的人来说，无疑都是必须具备的知识。NIO 中的连网与 NIO 中的其他任何操作没有什么不同 ― 它依赖通道和缓冲区，而您通常使用&nbsp;<code>InputStream</code>&nbsp;和&nbsp;<code>OutputStream</code>&nbsp;来获得通道。</p>
<p>本节首先介绍异步 I/O 的基础 ― 它是什么以及它不是什么，然后转向更实用的、程序性的例子。</p>
<p><a name="N1081C"></a><span>异步 I/O</span></p>
<p>异步 I/O 是一种&nbsp;<em>没有阻塞地&nbsp;</em>读写数据的方法。通常，在代码进行&nbsp;<code>read()</code>&nbsp;调用时，代码会阻塞直至有可供读取的数据。同样，<code>write()</code>&nbsp;调用将会阻塞直至数据能够写入。</p>
<p>另一方面，异步 I/O 调用不会阻塞。相反，您将注册对特定 I/O 事件的兴趣 ― 可读的数据的到达、新的套接字连接，等等，而在发生这样的事件时，系统将会告诉您。</p>
<p>异步 I/O 的一个优势在于，它允许您同时根据大量的输入和输出执行 I/O。同步程序常常要求助于轮询，或者创建许许多多的线程以处理大量的连接。使用异步 I/O，您可以监听任何数量的通道上的事件，不用轮询，也不用额外的线程。</p>
<p>我们将通过研究一个名为 MultiPortEcho.java 的例子程序来查看异步 I/O 的实际应用。这个程序就像传统的&nbsp;<em>echo server</em>，它接受网络连接并向它们回响它们可能发送的数据。不过它有一个附加的特性，就是它能同时监听多个端口，并处理来自所有这些端口的连接。并且它只在单个线程中完成所有这些工作。</p>
<p><a name="N1083B"></a><span>Selectors</span></p>
<p>本节的阐述对应于&nbsp;<code>MultiPortEcho</code>&nbsp;的源代码中的&nbsp;<code>go()</code>&nbsp;方法的实现，因此应该看一下源代码，以便对所发生的事情有个更全面的了解。</p>
<p>异步 I/O 中的核心对象名为&nbsp;<code>Selector</code>。<code>Selector</code>&nbsp;就是您注册对各种 I/O 事件的兴趣的地方，而且当那些事件发生时，就是这个对象告诉您所发生的事件。</p>
<p>所以，我们需要做的第一件事就是创建一个&nbsp;<code>Selector</code>：</p>
<div>
<pre>Selector selector = Selector.open();
</pre>
</div>
<p>然后，我们将对不同的通道对象调用&nbsp;<code>register()</code>&nbsp;方法，以便注册我们对这些对象中发生的 I/O 事件的兴趣。<code>register()</code>&nbsp;的第一个参数总是这个&nbsp;<code>Selector</code>。</p>
<p><a name="N10870"></a><span>打开一个 ServerSocketChannel</span></p>
<p>为了接收连接，我们需要一个&nbsp;<code>ServerSocketChannel</code>。事实上，我们要监听的每一个端口都需要有一个&nbsp;<code>ServerSocketChannel</code>&nbsp;。对于每一个端口，我们打开一个&nbsp;<code>ServerSocketChannel</code>，如下所示：</p>
<div>
<pre>ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking( false );

ServerSocket ss = ssc.socket();
InetSocketAddress address = new InetSocketAddress( ports[i] );
ss.bind( address );
</pre>
</div>
<p>第一行创建一个新的&nbsp;<code>ServerSocketChannel</code>&nbsp;，最后三行将它绑定到给定的端口。第二行将&nbsp;<code>ServerSocketChannel</code>&nbsp;设置为&nbsp;<em>非阻塞的&nbsp;</em>。我们必须对每一个要使用的套接字通道调用这个方法，否则异步 I/O 就不能工作。</p>
<p><a name="N10896"></a><span>选择键</span></p>
<p>下一步是将新打开的&nbsp;<code>ServerSocketChannels</code>&nbsp;注册到&nbsp;<code>Selector</code>上。为此我们使用 ServerSocketChannel.register() 方法，如下所示</p>
<div>
<pre>SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT );
</pre>
</div>
<p><code>register()</code>&nbsp;的第一个参数总是这个&nbsp;<code>Selector</code>。第二个参数是&nbsp;<code>OP_ACCEPT</code>，这里它指定我们想要监听&nbsp;<em>accept</em>&nbsp;事件，也就是在新的连接建立时所发生的事件。这是适用于&nbsp;<code>ServerSocketChannel</code>&nbsp;的唯一事件类型。</p>
<p>请注意对&nbsp;<code>register()</code>&nbsp;的调用的返回值。&nbsp;<code>SelectionKey</code>&nbsp;代表这个通道在此&nbsp;<code>Selector</code>&nbsp;上的这个注册。当某个&nbsp;<code>Selector</code>&nbsp;通知您某个传入事件时，它是通过提供对应于该事件的&nbsp;<code>SelectionKey</code>&nbsp;来进行的。<code>SelectionKey</code>&nbsp;还可以用于取消通道的注册。</p>
<p><a name="N108DB"></a><span>内部循环</span></p>
<p>现在已经注册了我们对一些 I/O 事件的兴趣，下面将进入主循环。使用&nbsp;<code>Selectors</code>&nbsp;的几乎每个程序都像下面这样使用内部循环：</p>
<div>
<pre>int num = selector.select();

Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator();

while (it.hasNext()) {
     SelectionKey key = (SelectionKey)it.next();
     // ... deal with I/O event ...
}
</pre>
</div>
<p>首先，我们调用&nbsp;<code>Selector</code>&nbsp;的&nbsp;<code>select()</code>&nbsp;方法。这个方法会阻塞，直到至少有一个已注册的事件发生。当一个或者更多的事件发生时，<code>select()</code>&nbsp;方法将返回所发生的事件的数量。</p>
<p>接下来，我们调用&nbsp;<code>Selector</code>&nbsp;的&nbsp;<code>selectedKeys()</code>&nbsp;方法，它返回发生了事件的&nbsp;<code>SelectionKey</code>&nbsp;对象的一个&nbsp;<code>集合&nbsp;</code>。</p>
<p>我们通过迭代&nbsp;<code>SelectionKeys</code>&nbsp;并依次处理每个&nbsp;<code>SelectionKey</code>&nbsp;来处理事件。对于每一个&nbsp;<code>SelectionKey</code>，您必须确定发生的是什么 I/O 事件，以及这个事件影响哪些 I/O 对象。</p>
<p><a name="N1091C"></a><span>监听新连接</span></p>
<p>程序执行到这里，我们仅注册了&nbsp;<code>ServerSocketChannel</code>，并且仅注册它们“接收”事件。为确认这一点，我们对&nbsp;<code>SelectionKey</code>&nbsp;调用<code>readyOps()</code>&nbsp;方法，并检查发生了什么类型的事件：</p>
<div>
<pre>if ((key.readyOps() &amp; SelectionKey.OP_ACCEPT)
     == SelectionKey.OP_ACCEPT) {

     // Accept the new connection
     // ...
}
</pre>
</div>
<p>可以肯定地说，&nbsp;<code>readOps()</code>&nbsp;方法告诉我们该事件是新的连接。</p>
<p><span>接受新的连接</span></p>
<p>因为我们知道这个服务器套接字上有一个传入连接在等待，所以可以安全地接受它；也就是说，不用担心&nbsp;<code>accept()</code>&nbsp;操作会阻塞：</p>
<div>
<pre>ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
</pre>
</div>
<p>下一步是将新连接的&nbsp;<code>SocketChannel</code>&nbsp;配置为非阻塞的。而且由于接受这个连接的目的是为了读取来自套接字的数据，所以我们还必须将&nbsp;<code>SocketChannel</code>&nbsp;注册到&nbsp;<code>Selector</code>上，如下所示：</p>
<div>
<pre>sc.configureBlocking( false );
SelectionKey newKey = sc.register( selector, SelectionKey.OP_READ );
</pre>
</div>
<p>注意我们使用&nbsp;<code>register()</code>&nbsp;的&nbsp;<code>OP_READ</code>&nbsp;参数，将&nbsp;<code>SocketChannel</code>&nbsp;注册用于&nbsp;<em>读取&nbsp;</em>而不是&nbsp;<em>接受&nbsp;</em>新连接。</p>
<p><a name="N10973"></a><span>删除处理过的 SelectionKey</span></p>
<p>在处理&nbsp;<code>SelectionKey</code>&nbsp;之后，我们几乎可以返回主循环了。但是我们必须首先将处理过的&nbsp;<code>SelectionKey</code>&nbsp;从选定的键集合中删除。如果我们没有删除处理过的键，那么它仍然会在主集合中以一个激活的键出现，这会导致我们尝试再次处理它。我们调用迭代器的<code>remove()</code>&nbsp;方法来删除处理过的&nbsp;<code>SelectionKey</code>：</p>
<div>
<pre>it.remove();
</pre>
</div>
<p>现在我们可以返回主循环并接受从一个套接字中传入的数据(或者一个传入的 I/O 事件)了。</p>
<p><a name="N10992"></a><span>传入的 I/O</span></p>
<p>当来自一个套接字的数据到达时，它会触发一个 I/O 事件。这会导致在主循环中调用&nbsp;<code>Selector.select()</code>，并返回一个或者多个 I/O 事件。这一次，&nbsp;<code>SelectionKey</code>&nbsp;将被标记为&nbsp;<code>OP_READ</code>&nbsp;事件，如下所示：</p>
<div>
<pre>} else if ((key.readyOps() &amp; SelectionKey.OP_READ)
     == SelectionKey.OP_READ) {
     // Read the data
     SocketChannel sc = (SocketChannel)key.channel();
     // ...
}
</pre>
</div>
<p>与以前一样，我们取得发生 I/O 事件的通道并处理它。在本例中，由于这是一个 echo server，我们只希望从套接字中读取数据并马上将它发送回去。</p>
<p></p>
<p>每次返回主循环，我们都要调用&nbsp;<code>select</code>&nbsp;的&nbsp;<code>Selector()</code>方法，并取得一组&nbsp;<code>SelectionKey</code>。每个键代表一个 I/O 事件。我们处理事件，从选定的键集中删除&nbsp;<code>SelectionKey</code>，然后返回主循环的顶部。</p>
<p>这个程序有点过于简单，因为它的目的只是展示异步 I/O 所涉及的技术。在现实的应用程序中，您需要通过将通道从&nbsp;<code>Selector</code>&nbsp;中删除来处理关闭的通道。而且您可能要使用多个线程。这个程序可以仅使用一个线程，因为它只是一个演示，但是在现实场景中，创建一个线程池来负责 I/O 事件处理中的耗时部分会更有意义。</p>
<p></p>
<p><span>字符集</span></p>
<p>根据 Sun 的文档，一个&nbsp;<code>Charset</code>&nbsp;是“十六位 Unicode 字符序列与字节序列之间的一个命名的映射”。实际上，一个&nbsp;<code>Charset</code>&nbsp;允许您以尽可能最具可移植性的方式读写字符序列。</p>
<p>Java 语言被定义为基于 Unicode。然而在实际上，许多人编写代码时都假设一个字符在磁盘上或者在网络流中用一个字节表示。这种假设在许多情况下成立，但是并不是在所有情况下都成立，而且随着计算机变得对 Unicode 越来越友好，这个假设就日益变得不能成立了。</p>
<p>在本节中，我们将看一下如何使用&nbsp;<code>Charsets</code>&nbsp;以适合现代文本格式的方式处理文本数据。这里将使用的示例程序相当简单，不过，它触及了使用&nbsp;<code>Charset</code>&nbsp;的所有关键方面：为给定的字符编码创建&nbsp;<code>Charset</code>，以及使用该&nbsp;<code>Charset</code>&nbsp;解码和编码文本数据。</p>
<p></p>
<p><a name="N109FF"></a><span>编码/解码</span></p>
<p>要读和写文本，我们要分别使用&nbsp;<code>CharsetDecoder</code>&nbsp;和&nbsp;<code>CharsetEncoder</code>。将它们称为&nbsp;<em>编码器&nbsp;</em>和&nbsp;<em>解码器&nbsp;</em>是有道理的。一个&nbsp;<em>字符&nbsp;</em>不再表示一个特定的位模式，而是表示字符系统中的一个实体。因此，由某个实际的位模式表示的字符必须以某种特定的&nbsp;<em>编码&nbsp;</em>来表示。</p>
<p><code>CharsetDecoder</code>&nbsp;用于将逐位表示的一串字符转换为具体的&nbsp;<code>char</code>&nbsp;值。同样，一个&nbsp;<code>CharsetEncoder</code>&nbsp;用于将字符转换回位。</p>
<p>在下一个小节中，我们将考察一个使用这些对象来读写数据的程序。</p>
<p><a name="N10A2D"></a><span>处理文本的正确方式</span></p>
<p>现在我们将分析这个例子程序 UseCharsets.java。这个程序非常简单 ― 它从一个文件中读取一些文本，并将该文本写入另一个文件。但是它把该数据当作文本数据，并使用&nbsp;<code>CharBuffer</code>&nbsp;来将该数句读入一个&nbsp;<code>CharsetDecoder</code>&nbsp;中。同样，它使用&nbsp;<code>CharsetEncoder</code>&nbsp;来写回该数据。</p>
<p>我们将假设字符以 ISO-8859-1(Latin1) 字符集（这是 ASCII 的标准扩展）的形式储存在磁盘上。尽管我们必须为使用 Unicode 做好准备，但是也必须认识到不同的文件是以不同的格式储存的，而 ASCII 无疑是非常普遍的一种格式。事实上，每种 Java 实现都要求对以下字符编码提供完全的支持：</p>
<ul>
<li>US-ASCII</li>
<li>ISO-8859-1</li>
<li>UTF-8</li>
<li>UTF-16BE</li>
<li>UTF-16LE</li>
<li>UTF-16</li>
<li contentScore="196">
<p><a name="N10A59"></a><span>示例程序</span></p>
<p>在打开相应的文件、将输入数据读入名为&nbsp;<code>inputData</code>&nbsp;的&nbsp;<code>ByteBuffer</code>&nbsp;之后，我们的程序必须创建 ISO-8859-1 (Latin1) 字符集的一个实例：</p>
<div>
<pre>Charset latin1 = Charset.forName( "ISO-8859-1" );</pre>
</div>
</li>
</ul>
<p>然后，创建一个解码器（用于读取）和一个编码器 （用于写入）：</p>
<div>
<pre>CharsetDecoder decoder = latin1.newDecoder();
CharsetEncoder encoder = latin1.newEncoder();
</pre>
</div>
<p>　为了将字节数据解码为一组字符，我们把&nbsp;<code>ByteBuffer</code>&nbsp;传递给&nbsp;<code>CharsetDecoder</code>，结果得到一个&nbsp;<code>CharBuffer</code></p>
<div>
<pre>CharBuffer cb = decoder.decode( inputData );
</pre>
</div>
<p>　如果想要处理字符，我们可以在程序的此处进行。但是我们只想无改变地将它写回，所以没有什么要做的。</p>
<p>要写回数据，我们必须使用&nbsp;<code>CharsetEncoder</code>&nbsp;将它转换回字节：</p>
<div>
<pre>ByteBuffer outputData = encoder.encode( cb );
</pre>
</div>
<p>　在转换完成之后，我们就可以将数据写到文件中了。</p>
<p><span>结束语和参考资料</span></p>
<p><a name="N10AA1"><span>结束语</span></a></p>
<p>正如您所看到的， NIO 库有大量的特性。在一些新特性（例如文件锁定和字符集）提供新功能的同时，许多特性在优化方面也非常优秀。</p>
<p>在基础层次上，通道和缓冲区可以做的事情几乎都可以用原来的面向流的类来完成。但是通道和缓冲区允许以&nbsp;<em>快得多&nbsp;</em>的方式完成这些相同的旧操作 ― 事实上接近系统所允许的最大速度。</p>
<p>不过 NIO 最强大的长度之一在于，它提供了一种在 Java 语言中执行进行输入/输出的新的（也是迫切需要的）结构化方式。随诸如缓冲区、通道和异步 I/O 这些概念性（且可实现的）实体而来的，是我们重新思考 Java 程序中的 I/O过程的机会。这样，NIO 甚至为我们最熟悉的 I/O 过程也带来了新的活力，同时赋予我们通过和以前不同并且更好的方式执行它们的机会。</p>
<p></p>
<p>　</p>
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
</li>
</ul>
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
</div>
<footer>
    <p>本站部分内容来源于网络，如有侵犯您的权益，请通过以下方式联系我们：</p>
    <p>邮箱：zytlsd@163.com</p>
    <p>我们将在收到通知后第一时间处理。</p>
</footer>]]></description>
    <pubDate>Sun, 19 Oct 2025 21:30:50 +0800</pubDate>
    <dc:creator>emer</dc:creator>
    <guid>http://www.51keeplearning.com/?post=38</guid>
</item>
<item>
    <title>Java继承</title>
    <link>http://www.51keeplearning.com/?post=37</link>
    <description><![CDATA[<p><meta name="referrer" content="no-referrer" /><div id="cnblogs_post_body" contentScore="4224"></p>
<h1>继承与合成基本概念</h1>
<p><strong>继承：</strong>可以基于已经存在的类构造一个新类。继承已经存在的类就可以复用这些类的方法和域。在此基础上，可以添加新的方法和域，从而扩充了类的功能。</p>
<p><strong>合成：</strong>在新类里创建原有的对象称为合成。这种方式可以重复利用现有的代码而不更改它的形式。</p>
<h3>1.继承的语法</h3>
<p>关键字<code>extends</code>表明新类派生于一个已经存在的类。已存在的类称为父类或基类，新类称为子类或派生类。例如:</p>
<div>
<pre><span>class</span> Student <span>extends</span><span> Person {

}</span></pre>
</div>
<p>类Student继承了Person，Person类称为父类或基类，Student类称为子类或派生类。</p>
<h3>2.合成的语法</h3>
<p>合成比较简单，就是在一个类中创建一个已经存在的类。</p>
<div>
<pre><span>class</span><span> Student {
    Dog dog;
}</span></pre>
</div>
<h1>&nbsp;</h1>
<h1>上溯造型</h1>
<h3>1.基本概念</h3>
<p>继承的作用在于代码的复用。由于继承意味着父类的所有方法亦可在子类中使用，所以发给父类的消息亦可发给衍生类。如果Person类中有一个eat方法，那么Student类中也会有这个方法，这意味着Student对象也是Person的一种类型。</p>
<div>
<pre><span>class</span><span> Person {
    </span><span>public</span> <span>void</span><span> eat() {
        System.out.println(</span>"eat"<span>);
    }

    </span><span>static</span> <span>void</span><span> show(Person p) {
        p.eat();
    }
}
</span><span>public</span> <span>class</span> Student <span>extends</span><span> Person{
    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
        Student s </span>= <span>new</span><span> Student();
        Person.show(s);     </span><span>//</span><span> ①</span>
<span>    }
}</span></pre>
</div>
<p>&nbsp;</p>
<p>【运行结果】：<br>eat</p>
<p>在Person中定义的show方法是用来接收Person句柄的，但是在①处接收的却是Student对象的引用。这是因为Student对象也是Person对象。在show方法中，传入的句柄(对象的引用)可以是Person对象以及Person的衍生类对象。这种将Student句柄转换成Person句柄的行为成为<strong>上溯造型</strong>。</p>
<h3>2.为什么要上溯造型</h3>
<p>为什么在调用eat是要有意忽略调用它的对象类型呢？如果让show方法简单地获取Student句柄似乎更加直观易懂，但是那样会使衍生自Person类的每一个新类都要实现专属自己的show方法：</p>
<div>
<pre><span>class</span><span> Value {
    </span><span>private</span> <span>int</span> count = 1<span>;

    </span><span>private</span> Value(<span>int</span><span> count) {
        </span><span>this</span>.count =<span> count;
    }

    </span><span>public</span> <span>static</span> <span>final</span><span> Value
            v1 </span>= <span>new</span> Value(1<span>),
            v2 </span>= <span>new</span> Value(2<span>),
            v3 </span>= <span>new</span> Value(3<span>);
}

</span><span>class</span><span> Person {

    </span><span>public</span> <span>void</span><span> eat(Value v) {
        System.out.println(</span>"Person.eat()"<span>);
    }
}

</span><span>class</span> Teacher <span>extends</span><span> Person {
    </span><span>public</span> <span>void</span><span> eat(Value v) {
        System.out.println(</span>"Teacher.eat()"<span>);
    }
}

</span><span>class</span> Student <span>extends</span><span> Person {
    </span><span>public</span> <span>void</span><span> eat(Value v) {
        System.out.println(</span>"Student.eat()"<span>);
    }
}

</span><span>public</span> <span>class</span><span> UpcastingDemo {
    </span><span>public</span> <span>static</span> <span>void</span><span> show(Student s) {
        s.eat(Value.v1);
    }

    </span><span>public</span> <span>static</span> <span>void</span><span> show(Teacher t) {
        t.eat(Value.v1);
    }

    </span><span>public</span> <span>static</span> <span>void</span><span> show(Person p) {
        p.eat(Value.v1);
    }

    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
        Student s </span>= <span>new</span><span> Student();
        Teacher t </span>= <span>new</span><span> Teacher();
        Person p </span>= <span>new</span><span> Person();
        show(s);
        show(t);
        show(p);
    }
}</span></pre>
</div>
<p>这种做法一个很明显的缺陷就是必须为每一个Person类的衍生类定义与之紧密相关的方法，产生了很多重复的代码。另一方面，对于如果忘记了方法的重载也不会报错。上例中的三个show方法完全可以合并为一个：</p>
<div>
<pre><span>public</span> <span>static</span> <span>void</span><span> show(Person p) {
     p.eat(Value.v1);
}</span></pre>
</div>
<p>&nbsp;</p>
<h1>动态绑定</h1>
<p>当执行show(s)时,输出结果是Student.eat()，这确实是希望得到的结果，但是似乎没有按照我们希望的形式来执行，再来看一下show方法：</p>
<div>
<pre><span>public</span> <span>static</span> <span>void</span><span> show(Person p) {
     p.eat(Value.v1);
}</span></pre>
</div>
<p>它接收的是Person句柄，当执行show(s)时，它是如何知道Person句柄指向的是一个Student对象而不是Teacher对象呢？编译器是无从得知的，这涉及到接下来要说明的绑定问题。</p>
<h3>1.方法调用的绑定</h3>
<p>将一个方法同一个方法主体连接在一起就称为<strong>绑定(Binding)</strong>。若在运行运行前执行绑定，就称为“早期绑定”。上面的例子中，在只有一个Person句柄的情况下，编译器不知道具体调用哪个方法。Java实现了一种方法调用机制，可在运行期间判断对象的类型，然后调用相应的方法，这种在运行期间进行，以对象的类型为基础的绑定称为<strong>动态绑定</strong>。除非一个方法被声明为final，Java中的所有方法都是动态绑定的。</p>
<p>用一张图表示上溯造型的继承关系：</p>
<p><img src="https://images2015.cnblogs.com/blog/525435/201512/525435-20151203233242393-347102988.jpg" alt=""></p>
<pre><code>&nbsp;</code></pre>
<p data-anchor-id="44e8">用代码概括为：</p>
<div>
<pre>Shape s = <span>new</span> Shape();</pre>
</div>
<p>按照继承关系，将创建的Circle对象句柄赋给一个Shape是合法的，因为Circle属于Shape的一种。</p>
<p>当调用其中一个基础类方法时：</p>
<div>
<pre>Shape s = <span>new</span> Shape();</pre>
</div>
<p data-anchor-id="h0zj">此时，调用的是Circle.draw(),这是由于动态绑定的原因。</p>
<div>
<pre><span>class</span><span> Person {
    </span><span>void</span><span> eat() {}
    </span><span>void</span><span> speak() {}
}
</span><span>class</span> Boy <span>extends</span><span> Person {
    </span><span>void</span><span> eat() {
        System.out.println(</span>"Boy.eat()"<span>);
    }
    </span><span>void</span><span> speak() {
        System.out.println(</span>"Boy.speak()"<span>);
    }
}
</span><span>class</span> Girl <span>extends</span><span> Person {
    </span><span>void</span><span> eat() {
        System.out.println(</span>"Girl.eat()"<span>);
    }
    </span><span>void</span><span> speak() {
        System.out.println(</span>"Girl.speak()"<span>);
    }
}
</span><span>public</span> <span>class</span><span> Persons {
    </span><span>public</span> <span>static</span><span> Person randPerson() {
        </span><span>switch</span> ((<span>int</span>)(Math.random() * 2<span>)) {
        </span><span>default</span><span>:
        </span><span>case</span> 0<span>:
            </span><span>return</span> <span>new</span><span> Boy();
        </span><span>case</span> 1<span>:
            </span><span>return</span> <span>new</span><span> Girl();
        }
    }
    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
        Person[] p </span>= <span>new</span> Person[4<span>];
        </span><span>for</span> (<span>int</span> i = 0; i &lt; p.length; i++<span>) {
            p[i] </span>= randPerson();    <span>//</span><span> 随机生成Boy或Girl</span>
<span>        }
        </span><span>for</span> (<span>int</span> i = 0; i &lt; p.length; i++<span>) {
            p[i].eat();
        }
    }
}</span></pre>
</div>
<p data-anchor-id="3ct7">对所有从Person衍生出来的类，Person建立了一个通用接口，所有衍生的类都有eat和speak两种行为。衍生类覆盖了这些定义，重新定义了这两种行为。在主类中，randPerson随机选择Person对象的句柄。**上诉造型是在return语句里发生的。**return语句取得一个Boy或Girl的句柄并将其作为Person类型返回，此时并不知道具体是什么类型，只知道是Person对象句柄。在main方法中调用randPerson方法为数组填入Person对象，但不知具体情况。当调用数组每个元素的eat方法时，动态绑定的作用就是执行对象的重新定义了的方法。</p>
<p data-anchor-id="g641">然而，动态绑定是有前提的，<strong>绑定的方法必须存在于基类中</strong>，否则无法编译通过。</p>
<div>
<pre><span>class</span><span> Person {
    </span><span>void</span><span> eat() {
        System.out.println(</span>"Person.eat()"<span>);
    }
}
</span><span>class</span> Boy <span>extends</span><span> Person {
    </span><span>void</span><span> eat() {
        System.out.println(</span>"Boy.eat()"<span>);
    }
    </span><span>void</span><span> speak() {
        System.out.println(</span>"Boy.speak()"<span>);
    }
}
</span><span>public</span> <span>class</span><span> Persons {
    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
        Person p </span>= <span>new</span><span> Boy();
        p.eat();
        p.speak();  </span><span>//</span><span> The method speak() is undefined for the type Person</span>
<span>    }
}</span></pre>
</div>
<p data-anchor-id="kz0j">如果子类中没有定义覆盖方法，则会调用父类中的方法：</p>
<div>
<pre><span>class</span><span> Person {
    </span><span>void</span><span> eat() {
        System.out.println(</span>"Person.eat()"<span>);
    }
}
</span><span>class</span> Boy <span>extends</span><span> Person {
}
</span><span>public</span> <span>class</span><span> Persons {
    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
        Person p </span>= <span>new</span><span> Boy();
        p.eat();
    }
}</span></pre>
</div>
<p data-anchor-id="725w">【运行结果】:<span>&nbsp;<br>Person.eat()</span></p>
<h3 id="2静态方法的绑定" data-anchor-id="hvfk">2.静态方法的绑定</h3>
<p data-anchor-id="wiag">将上面的方法都加上static关键字，变成静态方法：</p>
<div>
<pre><span>class</span><span> Person {
    </span><span>static</span> <span>void</span><span> eat() {
        System.out.println(</span>"Person.eat()"<span>);
    }
    </span><span>static</span> <span>void</span><span> speak() {
        System.out.println(</span>"Person.speak()"<span>);
    }
}
</span><span>class</span> Boy <span>extends</span><span> Person {
    </span><span>static</span> <span>void</span><span> eat() {
        System.out.println(</span>"Boy.eat()"<span>);
    }
    </span><span>static</span> <span>void</span><span> speak() {
        System.out.println(</span>"Boy.speak()"<span>);
    }
}
</span><span>class</span> Girl <span>extends</span><span> Person {
    </span><span>static</span> <span>void</span><span> eat() {
        System.out.println(</span>"Girl.eat()"<span>);
    }
    </span><span>static</span> <span>void</span><span> speak() {
        System.out.println(</span>"Girl.speak()"<span>);
    }
}
</span><span>public</span> <span>class</span><span> Persons {
    </span><span>public</span> <span>static</span><span> Person randPerson() {
        </span><span>switch</span> ((<span>int</span>)(Math.random() * 2<span>)) {
        </span><span>default</span><span>:
        </span><span>case</span> 0<span>:
            </span><span>return</span> <span>new</span><span> Boy();
        </span><span>case</span> 1<span>:
            </span><span>return</span> <span>new</span><span> Girl();
        }
    }
    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
        Person[] p </span>= <span>new</span> Person[4<span>];
        </span><span>for</span> (<span>int</span> i = 0; i &lt; p.length; i++<span>) {
            p[i] </span>= randPerson();    <span>//</span><span> 随机生成Boy或Girl</span>
<span>        }
        </span><span>for</span> (<span>int</span> i = 0; i &lt; p.length; i++<span>) {
            p[i].eat();
        }
    }
}</span></pre>
</div>
<p data-anchor-id="35g1">【运行结果】：<span>&nbsp;<br>Person.eat()<span>&nbsp;<br>Person.eat()<span>&nbsp;<br>Person.eat()<span>&nbsp;<br>Person.eat()<span>&nbsp;<br>观察结果，<strong>对于静态方法而言，不管父类引用指向的什么子类对象，调用的都是父类的方法。</strong></span></span></span></span></span></p>
<blockquote data-anchor-id="3axo" contentScore="104">
<p><strong>助记口诀</strong><span>&nbsp;<br>- 静态方法:静态方法看父类<span>&nbsp;<br>- 非静态方法：非静态方法看子类</span></span></p>

</blockquote>
<p></div></p>
<footer>
    <p>本站部分内容来源于网络，如有侵犯您的权益，请通过以下方式联系我们：</p>
    <p>邮箱：zytlsd@163.com</p>
    <p>我们将在收到通知后第一时间处理。</p>
</footer>]]></description>
    <pubDate>Sun, 19 Oct 2025 21:30:49 +0800</pubDate>
    <dc:creator>emer</dc:creator>
    <guid>http://www.51keeplearning.com/?post=37</guid>
</item>
<item>
    <title>Java学习之二-Java反射机制</title>
    <link>http://www.51keeplearning.com/?post=35</link>
    <description><![CDATA[<p><meta name="referrer" content="no-referrer" /><div id="cnblogs_post_body" contentScore="6074"></p>
<p><strong>问题：</strong>  </p><p>在运行时，对一个JAVA类，能否知道属性和方法；能否调用它的任意方法？  </p><p>答案是可以的，JAVA提供一种反射机制可以实现。  </p><p>&nbsp; </p><p><strong>目录</strong>  </p><ol> <li>什么是JAVA的反射机制  </li><li>JDK中提供的Reflection API  </li><li>JAVA反射机制提供了什么功能  <ul> <li>获取类的Class对象  </li><li>获取类的Fields  </li><li>获取类的Method  </li><li>获取类的Constructor  </li><li>新建类的实例<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Class&lt;T&gt;的函数newInstance<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 通过Constructor对象的方法newInstance</li></ul> </li><li>调用类的函数<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 调用private函数  </li><li>设置/获取类的属性值<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private属性  </li><li>动态创建代理类<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 动态代理源码分析  </li><li>JAVA反射Class&lt;T&gt;类型源代码分析  </li><li>JAVA反射原理分析<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Class文件结构<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JVM加载类对象，对反射的支持  </li><li>JAVA反射的应用</li></ol> <p>&nbsp;</p> <p><strong>一、什么是JAVA的反射机制</strong>  </p><p>Java反射是Java被视为动态（或准动态）语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息，包括其modifiers（诸如public, static 等）、superclass（例如Object）、实现之interfaces（例如Cloneable），也包括fields和methods的所有信息，并可于运行时改变fields内容或唤起methods。  </p><p>Java反射机制容许程序在运行时加载、探知、使用编译期间完全未知的classes。  </p><p>换言之，Java可以加载一个运行时才得知名称的class，获得其完整结构。  </p><p>&nbsp; </p><p><strong>二、JDK中提供的Reflection API</strong>  </p><p>Java反射相关的API在包<a>java.lang.reflect</a>中，JDK 1.6.0的reflect包如下图：  </p><p><a href="http://images.cnblogs.com/cnblogs_com/Quincy/201106/201106191021251045.png"><img title="clip_image001" alt="clip_image001" src="https://images.cnblogs.com/cnblogs_com/Quincy/201106/201106191021272672.png" width="232" height="377"></a>  </p><p> </p><table cellspacing="0" cellpadding="2" width="984"> <tbody> <tr> <td valign="top" width="122">Member接口</td> <td valign="top" width="860">该接口可以获取有关类成员（域或者方法）后者构造函数的信息。</td></tr> <tr> <td valign="top" width="128">AccessibleObject类</td> <td valign="top" width="855">该类是域(field)对象、方法(method)对象、构造函数(constructor)对象的基础类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。</td></tr> <tr> <td valign="top" width="133">Array类</td> <td valign="top" width="850">该类提供动态地生成和访问JAVA数组的方法。</td></tr> <tr> <td valign="top" width="138">Constructor类</td> <td valign="top" width="845">提供一个类的构造函数的信息以及访问类的构造函数的接口。</td></tr> <tr> <td valign="top" width="143">Field类</td> <td valign="top" width="841">提供一个类的域的信息以及访问类的域的接口。</td></tr> <tr> <td valign="top" width="147">Method类</td> <td valign="top" width="837">提供一个类的方法的信息以及访问类的方法的接口。</td></tr> <tr> <td valign="top" width="151">Modifier类</td> <td valign="top" width="834">提供了 static 方法和常量，对类和成员访问修饰符进行解码。</td></tr> <tr> <td valign="top" width="154">Proxy类</td> <td valign="top" width="831" contentScore="60"> <p>提供动态地生成代理类和类实例的静态方法。</p></td></tr></tbody></table> <p>&nbsp; </p><p><strong>三、JAVA反射机制提供了什么功能</strong>  </p><p>Java反射机制提供如下功能：  </p><p>在运行时判断任意一个对象所属的类  </p><p>在运行时构造任意一个类的对象  </p><p>在运行时判段任意一个类所具有的成员变量和方法  </p><p>在运行时调用任一个对象的方法  </p><p>在运行时创建新类对象  </p><p>在使用Java的反射功能时，基本首先都要获取类的Class对象，再通过Class对象获取其他的对象。  </p><p>这里首先定义用于测试的类:</p><pre>class Type{
    public int pubIntField;
    public String pubStringField;
    private int prvIntField;

    public Type(){
        Log("Default Constructor");
    }

    Type(int arg1, String arg2){
        pubIntField = arg1;
        pubStringField = arg2;

        Log("Constructor with parameters");
    }

    public void setIntField(int val) {
        this.prvIntField = val;
    }
    public int getIntField() {
        return prvIntField;
    }

    private void Log(String msg){
        System.out.println("Type:" + msg);
    }
}

class ExtendType extends Type{
    public int pubIntExtendField;
    public String pubStringExtendField;
    private int prvIntExtendField;

    public ExtendType(){
        Log("Default Constructor");
    }   

    ExtendType(int arg1, String arg2){      
        pubIntExtendField = arg1;
        pubStringExtendField = arg2;

        Log("Constructor with parameters");
    }

    public void setIntExtendField(int field7) {
        this.prvIntExtendField = field7;
    }
    public int getIntExtendField() {
        return prvIntExtendField;
    }

    private void Log(String msg){
        System.out.println("ExtendType:" + msg);
    }
}
</pre>
<p>&nbsp;</p>
<p><strong>1、获取类的Class对象</strong></p>
<p>Class 类的实例表示正在运行的 Java 应用程序中的类和接口。获取类的Class对象有多种方式：</p>
<table cellspacing="0" cellpadding="2" width="986">
<tbody>
<tr>
<td valign="top" width="167">调用getClass</td>
<td valign="top" width="817" contentScore="133">
<p>Boolean var1 = true; 
</p><p>Class&lt;?&gt; classType2 = var1.getClass(); 
</p><p>System.out.println(classType2); 
</p><p>输出：class java.lang.Boolean</p></td></tr>
<tr>
<td valign="top" width="171">运用.class 语法</td>
<td valign="top" width="813" contentScore="109">
<p>Class&lt;?&gt; classType4 = Boolean.class; 
</p><p>System.out.println(classType4); 
</p><p>输出：class java.lang.Boolean</p></td></tr>
<tr>
<td valign="top" width="175">运用static method Class.forName()</td>
<td valign="top" width="810" contentScore="130">
<p>Class&lt;?&gt; classType5 = Class.forName("java.lang.Boolean"); 
</p><p>System.out.println(classType5); 
</p><p>输出：class java.lang.Boolean</p></td></tr>
<tr>
<td valign="top" width="178" contentScore="111">
<p>运用primitive wrapper classes的TYPE 语法 
</p><p>这里返回的是原生类型，和Boolean.class返回的不同</p></td>
<td valign="top" width="807" contentScore="139">
<p>Class&lt;?&gt; classType3 = Boolean.TYPE; 
</p><p>System.out.println(classType3);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </p><p>输出：boolean</p></td></tr></tbody></table>
<p>&nbsp; </p><p><strong>2、获取类的Fields</strong> 
</p><p>可以通过反射机制得到某个类的某个属性，然后改变对应于这个类的某个实例的该属性值。JAVA 的Class&lt;T&gt;类提供了几个方法获取类的属性。</p>
<table cellspacing="0" cellpadding="2" width="985">
<tbody>
<tr>
<td valign="top" width="168">public <a>Field</a> getField(<a>String</a> name)</td>
<td valign="top" width="815">返回一个 Field 对象，它反映此 Class 对象所表示的类或接口的指定公共成员字段</td></tr>
<tr>
<td valign="top" width="173">public <a>Field</a>[] getFields()</td>
<td valign="top" width="811">返回一个包含某些 Field 对象的数组，这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段</td></tr>
<tr>
<td valign="top" width="177">public <a>Field</a> getDeclaredField(<a>String</a> name)</td>
<td valign="top" width="807">返回一个 Field 对象，该对象反映此 Class 对象所表示的类或接口的指定已声明字段</td></tr>
<tr>
<td valign="top" width="181">public <a>Field</a>[] getDeclaredFields()</td>
<td valign="top" width="803" contentScore="119">
<p>返回 Field 对象的一个数组，这些对象反映此 Class 对象所表示的类或接口所声明的所有字段</p></td></tr></tbody></table>
<p>&nbsp;</p><pre>  Class&lt;?&gt; classType = ExtendType.class;

    // 使用getFields获取属性
    Field[] fields = classType.getFields();
    for (Field f : fields)
    {
        System.out.println(f);
    }

    System.out.println();

    // 使用getDeclaredFields获取属性
    fields = classType.getDeclaredFields();
    for (Field f : fields)
    {
        System.out.println(f);
    }
</pre>
<p>&nbsp;</p>
<p>输出： 
</p><p>public int com.quincy.ExtendType.pubIntExtendField 
</p><p>public java.lang.String com.quincy.ExtendType.pubStringExtendField 
</p><p>public int com.quincy.Type.pubIntField 
</p><p>public java.lang.String com.quincy.Type.pubStringField 
</p><p>public int com.quincy.ExtendType.pubIntExtendField 
</p><p>public java.lang.String com.quincy.ExtendType.pubStringExtendField 
</p><p>private int com.quincy.ExtendType.prvIntExtendField 
</p><p>可见getFields和getDeclaredFields区别： 
</p><p>getFields返回的是申明为public的属性，包括父类中定义， 
</p><p>getDeclaredFields返回的是指定类定义的所有定义的属性，不包括父类的。 
</p><p>&nbsp; </p><p><strong>3、获取类的Method</strong> 
</p><p>通过反射机制得到某个类的某个方法，然后调用对应于这个类的某个实例的该方法 
</p><p>Class&lt;T&gt;类提供了几个方法获取类的方法。</p>
<table cellspacing="0" cellpadding="2" width="985">
<tbody>
<tr>
<td valign="top" width="171">public <a>Method</a> getMethod(<a>String</a> name, <a>Class</a>&lt;?&gt;... parameterTypes)</td>
<td valign="top" width="812" contentScore="105">
<p>返回一个 Method 对象，它反映此 Class 对象所表示的类或接口的指定公共成员方法</p></td></tr>
<tr>
<td valign="top" width="176">public <a>Method</a>[] getMethods()</td>
<td valign="top" width="807" contentScore="233">
<p>返回一个包含某些 Method 对象的数组，这些对象反映此 Class 对象所表示的类或接口（包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口）的公共 member 方法</p></td></tr>
<tr>
<td valign="top" width="181" contentScore="77">
<p>public <a>Method</a> getDeclaredMethod(<a>String</a> name,<a>Class</a>&lt;?&gt;... parameterTypes)</p></td>
<td valign="top" width="803" contentScore="108">
<p>返回一个 Method 对象，该对象反映此 Class 对象所表示的类或接口的指定已声明方法</p></td></tr>
<tr>
<td valign="top" width="185">public <a>Method</a>[] getDeclaredMethods()</td>
<td valign="top" width="799" contentScore="207">
<p>返回 Method 对象的一个数组，这些对象反映此 Class 对象表示的类或接口声明的所有方法，包括公共、保护、默认（包）访问和私有方法，但不包括继承的方法</p></td></tr></tbody></table>
<p>&nbsp;</p><pre>  // 使用getMethods获取函数 
    Class&lt;?&gt; classType = ExtendType.class;
    Method[] methods = classType.getMethods();
    for (Method m : methods)
    {
        System.out.println(m);
    }

    System.out.println();

    // 使用getDeclaredMethods获取函数 
    methods = classType.getDeclaredMethods();
    for (Method m : methods)
    {
        System.out.println(m);
    }
</pre>
<p>输出： 
</p><p>public void com.quincy.ExtendType.setIntExtendField(int) 
</p><p>public int com.quincy.ExtendType.getIntExtendField() 
</p><p>public void com.quincy.Type.setIntField(int) 
</p><p>public int com.quincy.Type.getIntField() 
</p><p>public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException 
</p><p>public final void java.lang.Object.wait() throws java.lang.InterruptedException 
</p><p>public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException 
</p><p>public boolean java.lang.Object.equals(java.lang.Object) 
</p><p>public java.lang.String java.lang.Object.toString() 
</p><p>public native int java.lang.Object.hashCode() 
</p><p>public final native java.lang.Class java.lang.Object.getClass() 
</p><p>public final native void java.lang.Object.notify() 
</p><p>public final native void java.lang.Object.notifyAll() 
</p><p>private void com.quincy.ExtendType.Log(java.lang.String) 
</p><p>public void com.quincy.ExtendType.setIntExtendField(int) 
</p><p>public int com.quincy.ExtendType.getIntExtendField() 
</p><p>&nbsp; </p><p><strong>4、获取类的Constructor</strong> 
</p><p>通过反射机制得到某个类的构造器，然后调用该构造器创建该类的一个实例&nbsp; </p><p>Class&lt;T&gt;类提供了几个方法获取类的构造器。</p>
<table cellspacing="0" cellpadding="2" width="985">
<tbody>
<tr>
<td valign="top" width="179">public <a>Constructor</a>&lt;<a>T</a>&gt; getConstructor(<a>Class</a>&lt;?&gt;... parameterTypes)</td>
<td valign="top" width="804" contentScore="101">
<p>返回一个 Constructor 对象，它反映此 Class 对象所表示的类的指定公共构造方法</p></td></tr>
<tr>
<td valign="top" width="185">public <a>Constructor</a>&lt;?&gt;[] getConstructors()</td>
<td valign="top" width="798" contentScore="131">
<p>返回一个包含某些 Constructor 对象的数组，这些对象反映此 Class 对象所表示的类的所有公共构造方法</p></td></tr>
<tr>
<td valign="top" width="190" contentScore="84">
<p>public <a>Constructor</a>&lt;<a>T</a>&gt; getDeclaredConstructor(<a>Class</a>&lt;?&gt;... parameterTypes)</p></td>
<td valign="top" width="793" contentScore="110">
<p>返回一个 Constructor 对象，该对象反映此 Class 对象所表示的类或接口的指定构造方法</p></td></tr>
<tr>
<td valign="top" width="195" contentScore="55">
<p>public <a>Constructor</a>&lt;?&gt;[] getDeclaredConstructors()</p></td>
<td valign="top" width="789" contentScore="188">
<p>返回 Constructor 对象的一个数组，这些对象反映此 Class 对象表示的类声明的所有构造方法。它们是公共、保护、默认（包）访问和私有构造方法</p></td></tr></tbody></table>
<p>&nbsp;</p><pre>  // 使用getConstructors获取构造器  
    Constructor&lt;?&gt;[] constructors = classType.getConstructors();
    for (Constructor&lt;?&gt; m : constructors)
    {
        System.out.println(m);
    }

    System.out.println();

    // 使用getDeclaredConstructors获取构造器   
    constructors = classType.getDeclaredConstructors();
    for (Constructor&lt;?&gt; m : constructors)
    {
        System.out.println(m);
    }

    输出：
    public com.quincy.ExtendType()

    public com.quincy.ExtendType()
    com.quincy.ExtendType(int,java.lang.String)
</pre>
<p>&nbsp;</p>
<p><strong>5、新建类的实例</strong> 
</p><p>通过反射机制创建新类的实例，有几种方法可以创建</p>
<table cellspacing="0" cellpadding="2" width="987">
<tbody>
<tr>
<td valign="top" width="183">调用无自变量ctor</td>
<td valign="top" width="802" contentScore="685">
<p><strong>1、调用类的Class对象的newInstance方法，该方法会调用对象的默认构造器，如果没有默认构造器，会调用失败.</strong></p>
<p>Class&lt;?&gt; classType = ExtendType.class; 
</p><p>Object inst = classType.newInstance(); 
</p><p>System.out.println(inst); 
</p><p>输出： 
</p><p>Type:Default Constructor 
</p><p>ExtendType:Default Constructor 
</p><p><a href="mailto:com.quincy.ExtendType@d80be3" rel="noopener nofollow">com.quincy.ExtendType@d80be3</a></p>
<p>&nbsp;</p>
<p><strong>2、调用默认Constructor对象的newInstance方法</strong> 
</p><p>Class&lt;?&gt; classType = ExtendType.class; 
</p><p>Constructor&lt;?&gt; constructor1 = classType.getConstructor(); 
</p><p>Object inst = constructor1.newInstance(); 
</p><p>System.out.println(inst); 
</p><p>输出： 
</p><p>Type:Default Constructor 
</p><p>ExtendType:Default Constructor 
</p><p>com.quincy.ExtendType@1006d75</p></td></tr>
<tr>
<td valign="top" width="186">调用带参数ctor</td>
<td valign="top" width="800" contentScore="339">
<p><strong>3、调用带参数Constructor对象的newInstance方法</strong> 
</p><p>Constructor&lt;?&gt; constructor2 = 
</p><p>classType.getDeclaredConstructor(int.class, String.class); 
</p><p>Object inst = constructor2.newInstance(1, "123"); 
</p><p>System.out.println(inst); 
</p><p>输出： 
</p><p>Type:Default Constructor 
</p><p>ExtendType:Constructor with parameters 
</p><p>com.quincy.ExtendType@15e83f9</p></td></tr></tbody></table>
<p>&nbsp; </p><p><strong>6、调用类的函数</strong> 
</p><p>通过反射获取类Method对象，调用Field的Invoke方法调用函数。</p><pre>   Class&lt;?&gt; classType = ExtendType.class;
    Object inst = classType.newInstance();
    Method logMethod = classType.<strong>getDeclaredMethod</strong>("Log", String.class);
    logMethod.invoke(inst, "test");

    输出：
    Type:Default Constructor
    ExtendType:Default Constructor
    Class com.quincy.ClassT can not access a member of class com.quincy.ExtendType with modifiers "private"

    上面失败是由于没有权限调用private函数，这里需要设置Accessible为true;
    Class&lt;?&gt; classType = ExtendType.class;
    Object inst = classType.newInstance();
    Method logMethod = classType.getDeclaredMethod("Log", String.class);
    logMethod.setAccessible(true);
    logMethod.invoke(inst, "test");
</pre>
<p><strong>7、设置/获取类的属性值</strong> 
</p><p>通过反射获取类的Field对象，调用Field方法设置或获取值</p><pre> Class&lt;?&gt; classType = ExtendType.class;
    Object inst = classType.newInstance();
    Field intField = classType.getField("pubIntExtendField");
    intField.<strong>setInt</strong>(inst, 100);
        int value = intField.<strong>getInt</strong>(inst);</pre><pre>&nbsp;</pre>
<p><strong>四、动态创建代理类</strong> 
</p><p>代理模式：代理模式的作用=为其他对象提供一种代理以控制对这个对象的访问。 
</p><p>代理模式的角色： 
</p><blockquote contentScore="63">
<p>抽象角色：声明真实对象和代理对象的共同接口</p></blockquote>
<blockquote contentScore="99">
<p>代理角色：代理角色内部包含有真实对象的引用，从而可以操作真实对象。</p></blockquote>
<blockquote contentScore="90">
<p>真实角色：代理角色所代表的真实对象，是我们最终要引用的对象。</p></blockquote>
<p>动态代理：</p>
<table cellspacing="0" cellpadding="2" width="985">
<tbody>
<tr>
<td valign="top" width="180">java.lang.reflect.Proxy</td>
<td valign="top" width="803" contentScore="129">
<p>Proxy 提供用于创建动态代理类和实例的静态方法，它还是由这些方法创建的所有动态代理类的超类</p></td></tr>
<tr>
<td valign="top" width="185">InvocationHandler</td>
<td valign="top" width="798" contentScore="243">
<p>是代理实例的调用处理程序 实现的接口，每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时，将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。</p></td></tr></tbody></table>
<p>&nbsp; </p><p>动态Proxy是这样的一种类: 
</p><p>它是在运行生成的类，在生成时你必须提供一组Interface给它，然后该class就宣称它实现了这些interface。你可以把该class的实例当作这些interface中的任何一个来用。当然，这个Dynamic Proxy其实就是一个Proxy，它不会替你作实质性的工作，在生成它的实例时你必须提供一个handler，由它接管实际的工作。 
</p><p>在使用动态代理类时，我们必须实现InvocationHandler接口 
</p><p>步骤： 
</p><p><strong>1、定义抽象角色</strong></p>
<p>public interface Subject { 
</p><p>public void Request(); 
</p><p>} 
</p><p>&nbsp; </p><p><strong>2、定义真实角色</strong></p>
<p>public class RealSubject implements Subject { 
</p><p>@Override 
</p><p>public void Request() { 
</p><p>// TODO Auto-generated method stub 
</p><p>System.out.println("RealSubject"); 
</p><p>} 
</p><p>} 
</p><p>&nbsp; </p><p><strong>3、定义代理角色</strong></p>
<p>public class DynamicSubject implements InvocationHandler { 
</p><p>private Object sub; 
</p><p>public DynamicSubject(Object obj){ 
</p><p>this.sub = obj; 
</p><p>} 
</p><p>@Override 
</p><p>public Object invoke(Object proxy, Method method, Object[] args) 
</p><p>throws Throwable { 
</p><p>// TODO Auto-generated method stub 
</p><p>System.out.println("Method:"+ method + ",Args:" + args); 
</p><p>method.invoke(sub, args); 
</p><p>return null; 
</p><p>} 
</p><p>} 
</p><p>&nbsp; </p><p><strong>4、通过Proxy.newProxyInstance构建代理对象</strong></p>
<p>RealSubject realSub = new RealSubject(); 
</p><p>InvocationHandler handler = new DynamicSubject(realSub); 
</p><p>Class&lt;?&gt; classType = handler.getClass(); 
</p><p>Subject sub = (Subject)Proxy.newProxyInstance(classType.getClassLoader(), 
</p><p>realSub.getClass().getInterfaces(), handler); 
</p><p>System.out.println(sub.getClass());&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </p><p>&nbsp; </p><p><strong>5、通过调用代理对象的方法去调用真实角色的方法。</strong></p>
<p>sub.Request(); 
</p><p>输出： 
</p><p>class $Proxy0 新建的代理对象，它实现指定的接口 
</p><p>Method:public abstract void DynamicProxy.Subject.Request(),Args:null 
</p><p>RealSubject 调用的真实对象的方法</p>
<p>待续...</p>
</div>
<footer>
    <p>本站部分内容来源于网络，如有侵犯您的权益，请通过以下方式联系我们：</p>
    <p>邮箱：zytlsd@163.com</p>
    <p>我们将在收到通知后第一时间处理。</p>
</footer>]]></description>
    <pubDate>Sun, 19 Oct 2025 21:30:48 +0800</pubDate>
    <dc:creator>emer</dc:creator>
    <guid>http://www.51keeplearning.com/?post=35</guid>
</item>
<item>
    <title>Java RMI</title>
    <link>http://www.51keeplearning.com/?post=36</link>
    <description><![CDATA[<p><meta name="referrer" content="no-referrer" /><div contentScore="2380">/*<em> <br></em> Created by IntelliJ IDEA. <br><em> User: leizhimin <br></em> Date: 2008-8-7 22:03:35 <br><em> 创建RMI注册表，启动RMI服务，并将远程对象注册到RMI注册表中。 <br></em>/ <br>public class HelloServer { <br>&nbsp;&nbsp;&nbsp;&nbsp;public static void main(String args[]) { <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//创建一个远程对象 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;IHello rhello = new HelloImpl(); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//本地主机上的远程对象注册表Registry的实例，并指定端口为8888，这一步必不可少（Java默认端口是1099），必不可缺的一步，缺少注册表创建，则无法绑定对象到远程注册表上 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LocateRegistry.createRegistry(8888); </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//把远程对象注册到RMI注册服务器上，并命名为RHello <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//绑定的URL标准格式为：rmi://host:port/name(其中协议名可以省略，下面两种写法都是正确的） <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Naming.bind(&quot;rmi://localhost:8888/RHello&quot;,rhello); <br>//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Naming.bind(&quot;//localhost:8888/RHello&quot;,rhello); </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&quot;&gt;&gt;&gt;&gt;&gt;INFO:远程IHello对象绑定成功！&quot;); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} catch (RemoteException e) { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&quot;创建远程对象发生异常！&quot;); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace(); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} catch (AlreadyBoundException e) { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&quot;发生重复绑定对象异常！&quot;); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace(); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} catch (MalformedURLException e) { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(&quot;发生URL畸形异常！&quot;); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace(); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} <br>&nbsp;&nbsp;&nbsp;&nbsp;} <br>}</p></div></p>
<footer>
    <p>本站部分内容来源于网络，如有侵犯您的权益，请通过以下方式联系我们：</p>
    <p>邮箱：zytlsd@163.com</p>
    <p>我们将在收到通知后第一时间处理。</p>
</footer>]]></description>
    <pubDate>Sun, 19 Oct 2025 21:30:48 +0800</pubDate>
    <dc:creator>emer</dc:creator>
    <guid>http://www.51keeplearning.com/?post=36</guid>
</item>
<item>
    <title>【JAVA】浅谈java枚举类</title>
    <link>http://www.51keeplearning.com/?post=34</link>
    <description><![CDATA[<p><meta name="referrer" content="no-referrer" /><div id="cnblogs_post_body" contentScore="2224"></p>
<p><span>一、什么情况下使用枚举类？</span></p>
<p>　　有的时候一个类的对象是有限且固定的，这种情况下我们使用枚举类就比较方便？</p>
<p><span>二、为什么不用静态常量来替代枚举类呢？</span></p>
<div>
<pre>    <span>public</span> <span>static</span> <span>final</span> <span>int</span> SEASON_SPRING = 1<span>;
    </span><span>public</span> <span>static</span> <span>final</span> <span>int</span> SEASON_SUMMER = 2<span>;
    </span><span>public</span> <span>static</span> <span>final</span> <span>int</span> SEASON_FALL = 3<span>;
    </span><span>public</span> <span>static</span> <span>final</span> <span>int</span> SEASON_WINTER = 4;</pre>
</div>
<p>　　枚举类更加直观，类型安全。使用常量会有以下几个缺陷：</p>
<p>　　1. 类型不安全。若一个方法中要求传入季节这个参数，用常量的话，形参就是int类型，开发者传入任意类型的int类型值就行，但是如果是枚举类型的话，就只能传入枚举类中包含的对象。</p>
<p>　　2. 没有命名空间。开发者要在命名的时候以SEASON_开头，这样另外一个开发者再看这段代码的时候，才知道这四个常量分别代表季节。</p>
<p><span>三、枚举类入门</span></p>
<p>　　先看一个简单的枚举类。</p>
<div>
<pre><span>package</span><span> enumcase;

</span><span>public</span> <span>enum</span><span> SeasonEnum {
    SPRING,SUMMER,FALL,WINTER;
}</span></pre>
</div>
<ol>
<li>enum和class、interface的地位一样</li>
<li>使用enum定义的枚举类默认继承了<span>java.lang.</span>Enum，而不是继承Object类。枚举类可以实现一个或多个接口。</li>
<li>枚举类的所有实例都必须放在第一行展示，不需使用new 关键字，不需显式调用构造器。自动添加public static final修饰。</li>
<li>使用enum定义、非抽象的枚举类默认使用final修饰，不可以被继承。</li>
<li>枚举类的构造器只能是私有的。</li>
</ol>
<p><span>四、枚举类介绍</span></p>
<p>　　枚举类内也可以定义属性和方法，可是是静态的和非静态的。</p>
<div>
<pre><span>package</span><span> enumcase;

</span><span>public</span> <span>enum</span><span> SeasonEnum {
    SPRING(</span>"春天"),SUMMER("夏天"),FALL("秋天"),WINTER("冬天"<span>);

    </span><span>private</span><span> final String name;

    </span><span>private</span><span> SeasonEnum(String name)
    {
        </span><span>this</span>.name =<span> name;
    }

    </span><span>public</span><span> String getName() {
        </span><span>return</span><span> name;
    }
}</span></pre>
</div>
<p>&nbsp;　&nbsp; 实际上在第一行写枚举类实例的时候，默认是调用了构造器的，所以此处需要传入参数，因为没有显式申明无参构造器，只能调用有参数的构造器。</p>
<p>　　构造器需定义成私有的，这样就不能在别处申明此类的对象了。枚举类通常应该设计成不可变类，它的Field不应该被改变，这样会更安全，而且代码更加简洁。所以我们将Field用private final修饰。</p>
<p><span>五、枚举类实现接口</span></p>
<p>　　枚举类可以实现一个或多个接口。与普通类一样，实现接口的时候需要实现接口中定义的所有方法，若没有完全实现，那这个枚举类就是抽象的，只是不需显式加上abstract修饰，系统化会默认加上。</p>
<p>　　</p>
<div>
<pre><span>package</span><span> enumcase;

</span><span>public</span> <span>enum</span><span> Operation {
    PLUS{

        @Override
        </span><span>public</span> <span>double</span> eval(<span>double</span> x, <span>double</span><span> y) {
            </span><span>return</span> x +<span> y;
        }

    },
    MINUS{

        @Override
        </span><span>public</span> <span>double</span> eval(<span>double</span> x, <span>double</span><span> y) {
            </span><span>return</span> x -<span> y;
        }

    },
    TIMES{

        @Override
        </span><span>public</span> <span>double</span> eval(<span>double</span> x, <span>double</span><span> y) {
            </span><span>return</span> x *<span> y;
        }

    },
    DIVIDE{

        @Override
        </span><span>public</span> <span>double</span> eval(<span>double</span> x, <span>double</span><span> y) {
            </span><span>return</span> x /<span> y;
        }

    };

    </span><span>/**</span><span>
     * 抽象方法，由不同的枚举值提供不同的实现。
     * </span><span>@param</span><span> x
     * </span><span>@param</span><span> y
     * </span><span>@return</span>
     <span>*/</span>
    <span>public</span> <span>abstract</span> <span>double</span> eval(<span>double</span> x, <span>double</span><span> y);

    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
        System.out.println(Operation.PLUS.eval(</span>10, 2<span>));
        System.out.println(Operation.MINUS.eval(</span>10, 2<span>));
        System.out.println(Operation.TIMES.eval(</span>10, 2<span>));
        System.out.println(Operation.DIVIDE.eval(</span>10, 2<span>));
    }
}</span></pre>
</div>
<p>　　<span>Operatio类实际上是抽象的，不可以创建枚举值，所以此处在申明枚举值的时候，都实现了抽象方法，这其实是匿名内部类的实现，花括号部分是一个类体。我们可以看下编译以后的文件。</span></p>
<p><span>　　<img alt="" src="data:image/png;base64,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"> </span></p>
<p><span>　　共生成了五个class文件，这样就证明了PLUS,MINUS,TIMES,DIVIDE是Operation的匿名内部类的实例。</span></p>
<p><span>六、switch语句里的表达式可以是枚举值</span></p>
<p>　　Java5新增了enum关键字，同时扩展了switch。</p>
<div>
<pre><span>package</span><span> enumcase;

</span><span>public</span> <span>class</span><span> SeasonTest {
    </span><span>public</span> <span>void</span><span> judge(SeasonEnum s)
    {
        </span><span>switch</span><span>(s)
        {
        </span><span>case</span><span> SPRING:
            System.out.println(</span>"春天适合踏青。"<span>);
            </span><span>break</span><span>;
        </span><span>case</span><span> SUMMER:
            System.out.println(</span>"夏天要去游泳啦。"<span>);
            </span><span>break</span><span>;
        </span><span>case</span><span> FALL:
            System.out.println(</span>"秋天一定要去旅游哦。"<span>);
            </span><span>break</span><span>;
        </span><span>case</span><span> WINTER:
            System.out.println(</span>"冬天要是下雪就好啦。"<span>);
            </span><span>break</span><span>;
        }
    }

    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
        SeasonEnum s </span>=<span> SeasonEnum.SPRING;
        SeasonTest test </span>= <span>new</span><span> SeasonTest();
        test.judge(s);
    }
}</span></pre>
</div>
<p>　　case表达式中直接写入枚举值，不需加入枚举类作为限定。</p>
<p></div></p>
<footer>
    <p>本站部分内容来源于网络，如有侵犯您的权益，请通过以下方式联系我们：</p>
    <p>邮箱：zytlsd@163.com</p>
    <p>我们将在收到通知后第一时间处理。</p>
</footer>]]></description>
    <pubDate>Sun, 19 Oct 2025 21:30:47 +0800</pubDate>
    <dc:creator>emer</dc:creator>
    <guid>http://www.51keeplearning.com/?post=34</guid>
</item>
<item>
    <title>Java提高篇——Java 异常处理</title>
    <link>http://www.51keeplearning.com/?post=33</link>
    <description><![CDATA[<p><meta name="referrer" content="no-referrer" /><div contentScore="14658"></p>
<div id="cnblogs_post_body" contentScore="14592">
<h2 id="0">异常的概念</h2>
<p>异常是程序中的一些错误，但并不是所有的错误都是异常，并且错误有时候是可以避免的。</p>
<p>比如说，你的代码少了一个分号，那么运行出来结果是提示是错误<code>java.lang.Error</code>；如果你用<code>System.out.println(11/0)</code>，那么你是因为你用0做了除数，会抛出<code>java.lang.ArithmeticException</code>的异常。</p>
<p>异常发生的原因有很多，通常包含以下几大类：</p>
<ul>
<li>用户输入了非法数据。</li>
<li>要打开的文件不存在。</li>
<li>网络通信时连接中断，或者JVM内存溢出。</li>
</ul>
<p>这些异常有的是因为用户错误引起，有的是程序错误引起的，还有其它一些是因为物理错误引起的。-</p>
<p>要理解Java异常处理是如何工作的，你需要掌握以下三种类型的异常：</p>
<ul>
<li><strong>检查性异常：</strong>最具代表的检查性异常是用户错误或问题引起的异常，这是程序员无法预见的。例如要打开一个不存在文件时，一个异常就发生了，这些异常在编译时不能被简单地忽略。</li>
<li><strong>运行时异常：</strong>&nbsp;运行时异常是可能被程序员避免的异常。与检查性异常相反，运行时异常可以在编译时被忽略。</li>
<li><strong>错误：</strong>&nbsp;错误不是异常，而是脱离程序员控制的问题。错误在代码中通常被忽略。例如，当栈溢出时，一个错误就发生了，它们在编译也检查不到的。</li>
</ul>
<p>&nbsp;</p>
<p>异常指不期而至的各种状况，如：文件找不到、网络连接失败、除0操作、非法参数等。异常是一个事件，它发生在程序运行期间，干扰了正常的指令流程。</p>
<p>Java语言在设计的当初就考虑到这些问题，提出异常处理的框架的方案，所有的异常都可以用一个异常类来表示，不同类型的异常对应不同的子类异常（目前我们所说的异常包括错误概念），定义异常处理的规范，在<code>JDK1.4</code>版本以后增加了异常链机制，从而便于跟踪异常。</p>
<p>Java异常是一个描述在代码段中发生异常的对象，当发生异常情况时，一个代表该异常的对象被创建并且在导致该异常的方法中被抛出，而该方法可以选择自己处理异常或者传递该异常。</p>
<h2 id="1">异常的体系结构</h2>
<p>Java把异常当作对象来处理，并定义一个基类<code>java.lang.Throwable</code>作为所有异常的超类。</p>
<p>在Java API中已经定义了许多异常类，这些异常类分为两大类，<span><strong>错误<code>Error</code>和异常<code>Exception</code></strong></span>。</p>
<p>Java异常层次结构图如下图所示：</p>
<p><img src="https://images2015.cnblogs.com/blog/690102/201607/690102-20160728164909622-1770558953.png" alt=""></p>
<p>从图中可以看出所有异常类型都是内置类<code>Throwable</code>的子类，因而<code>Throwable</code>在异常类的层次结构的顶层。</p>
<p>接下来<code>Throwable</code>分成了两个不同的分支，<span><strong>一个分支是<code>Error</code>，它表示不希望被程序捕获或者是程序无法处理的错误</strong>。<strong>另一个分支是<code>Exception</code>，它表示用户程序可能捕捉的异常情况或者说是程序可以处理的异常</strong></span>。其中异常类<code>Exception</code>又分为运行时异常(<code>RuntimeException</code>)和非运行时异常。</p>
<p>Java异常又可以分为不受检查异常（<code>Unchecked Exception</code>）和检查异常（<code>Checked Exception</code>）。</p>
<p>&nbsp;</p>
<p>下面将详细讲述这些异常之间的区别与联系：</p>
<ul>
<li><span><strong><code>Error</code></strong>：<span><code>Error</code>类对象由 Java 虚拟机生成并抛出，大多数错误与代码编写者所执行的操作无关</span></span><span>。</span>例如，Java虚拟机运行错误（<code>Virtual MachineError</code>），当JVM不再有继续执行操作所需的内存资源时，将出现&nbsp;<code>OutOfMemoryError</code>。这些异常发生时，Java虚拟机（JVM）一般会选择线程终止；还有发生在虚拟机试图执行应用时，如类定义错误（<code>NoClassDefFoundError</code>）、链接错误（<code>LinkageError</code>）。这些错误是不可查的，因为它们在应用程序的控制和处理能力之 外，而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说，即使确实发生了错误，本质上也不应该试图去处理它所引起的异常状况。在Java中，错误通常是使用<code>Error</code>的子类描述。</li>
<li><strong><code>Exception</code></strong>：在<code>Exception</code>分支中有一个重要的<span>子类<code>RuntimeException</code>（运行时异常）</span>，该类型的异常自动为你所编写的程序定义<code>ArrayIndexOutOfBoundsException</code>（数组下标越界）、<code>NullPointerException</code>（空指针异常）、<code>ArithmeticException</code>（算术异常）、<code>MissingResourceException</code>（丢失资源）、<code>ClassNotFoundException</code>（找不到类）等异常，<span>这些异常是不检查异常，程序中可以选择捕获处理，也可以不处理。这些异常一般是由程序逻辑错误引起的，程序应该从逻辑角度尽可能避免这类异常的发生</span>；而<code>RuntimeException</code>之外的异常我们统称为非运行时异常，类型上属于<code>Exception</code>类及其子类，从程序语法角度讲是必须进行处理的异常，如果不处理，程序就不能编译通过。如<code>IOException</code>、<code>SQLException</code>等以及用户自定义的<code>Exception</code>异常，一般情况下不自定义检查异常。</li>
</ul>
<blockquote contentScore="314">
<p>&nbsp;<img src="https://images2015.cnblogs.com/blog/690102/201607/690102-20160728170617356-848311160.png" alt="">注意</p>
<p><code>Error</code>和<code>Exception</code>的区别：<code>Error</code>通常是灾难性的致命的错误，是程序无法控制和处理的，当出现这些异常时，Java虚拟机（JVM）一般会选择终止线程；<code>Exception</code>通常情况下是可以被程序处理的，并且在程序中应该尽可能的去处理这些异常。</p>
</blockquote>
<ul>
<li><span><strong><code>检查异常</code></strong>：在正确的程序运行过程中，很容易出现的、情理可容的异常状况，在一定程度上这种异常的发生是可以预测的，并且一旦发生该种异常，就必须采取某种方式进行处理。</span></li>
</ul>
<blockquote contentScore="238">
<p>♠提示</p>
<p><strong>除了<code>RuntimeException</code>及其子类以外，其他的<code>Exception</code>类及其子类都属于检查异常</strong>，当程序中可能出现这类异常，<strong>要么使用<code>try-catch</code>语句进行捕获，要么用<code>throws</code>子句抛出</strong>，否则编译无法通过。</p>
</blockquote>
<ul>
<li><span><strong><code>不受检查异常</code></strong>：<strong>包括<code>RuntimeException</code>及其子类和<code>Error</code></strong>。</span></li>
</ul>
<blockquote contentScore="120">
<p>♠提示</p>
<p><code>不受检查异常</code>为编译器不要求强制处理的异常，<code>检查异常</code>则是编译器要求必须处置的异常。</p>
</blockquote>
<h2 id="2">Java 异常的处理机制</h2>
<p><span>Java的异常处理本质上是<strong>抛出异常</strong>和<strong>捕获异常</strong>。</span></p>
<ul>
<li><strong><code>抛出异常</code></strong>：要理解抛出异常，首先要明白什么是异常情形（exception condition），它是指阻止当前方法或作用域继续执行的问题。其次把异常情形和普通问题相区分，普通问题是指在当前环境下能得到足够的信息，总能处理这个错误。对于异常情形，已经无法继续下去了，因为在当前环境下无法获得必要的信息来解决问题，你所能做的就是从当前环境中跳出，并把问题提交给上一级环境，这就是抛出异常时所发生的事情。抛出异常后，会有几件事随之发生。首先，是像创建普通的java对象一样将使用<code>new</code>在堆上创建一个异常对象；然后，当前的执行路径（已经无法继续下去了）被终止，并且从当前环境中弹出对异常对象的引用。此时，异常处理机制接管程序，并开始寻找一个恰当的地方继续执行程序，这个恰当的地方就是异常处理程序或者异常处理器，它的任务是将程序从错误状态中恢复，以使程序要么换一种方式运行，要么继续运行下去。</li>
</ul>
<p>举个简单的例子，假使我们创建了一个学生对象Student的一个引用stu,在调用的时候可能还没有初始化。所以在使用这个对象引用调用其他方法之前，要先对它进行检查，可以创建一个代表错误信息的对象，并且将它从当前环境中抛出，这样就把错误信息传播到更大的环境中。</p>
<div>
<pre><span>if</span>(stu == <span>null</span><span>){
    </span><span>throw</span> <span>new</span><span> NullPointerException();
}</span></pre>
</div>
<p>这就抛出了异常，它将在其他的地方得到执行或者处理，具体是哪个地方后面将很快介绍，代码中出现的&nbsp;<code>throw</code>&nbsp;是一个关键字，暂时先不做过多讲解，后面会详细讲解。</p>
<ul>
<li><strong><code>捕获异常</code></strong>：在方法抛出异常之后，运行时系统将转为寻找合适的异常处理器（exception handler）。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时，即为合适的异常处理器。运行时系统从发生异常的方法开始，依次回查调用栈中的方法，直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器，则运行时系统终止。同时，意味着Java程序的终止。</li>
</ul>
<blockquote contentScore="777">
<p>&nbsp;提示</p>
<p>对于<code>运行时异常</code>、<code>错误</code>和<code>检查异常</code>，Java技术所要求的异常处理方式有所不同。</p>
<p>由于运行时异常及其子类的不可查性，为了更合理、更容易地实现应用程序，Java规定，<strong>运行时异常将由Java运行时系统自动抛出，允许应用程序忽略运行时异常</strong>。</p>
<p>对于方法运行中可能出现的<code>Error</code>，当运行方法不欲捕捉时，Java允许该方法不做任何抛出声明。因为，大多数<code>Error</code>异常属于永远不能被允许发生的状况，也属于合理的应用程序不该捕捉的异常。</p>
<p>对于所有的检查异常，Java规定：一个方法必须捕捉，或者声明抛出方法之外。也就是说，当一个方法选择不捕捉检查异常时，它必须声明将抛出异常。</p>
</blockquote>
<p>Java异常处理涉及到五个关键字，分别是：<code>try</code>、<code>catch</code>、<code>finally</code>、<code>throw</code>、<code>throws</code>。下面将骤一介绍，通过认识这五个关键字，掌握基本异常处理知识。</p>
<p>　　•&nbsp;<strong>try</strong>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;-- 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内，当try语句块内发生异常时，异常就被抛出。<br>　　•&nbsp;<strong>catch</strong>&nbsp;&nbsp; -- 用于捕获异常。catch用来捕获try语句块中发生的异常。<br>　　•&nbsp;<strong>finally</strong>&nbsp;&nbsp;-- finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块，执行完成之后，才会回来执行try或者catch块中的return或者throw语句，如果finally中使用了return或者throw等终止方法的语句，则就不会跳回执行，直接停止。<br>　　•&nbsp;<strong>throw</strong>&nbsp;&nbsp; -- 用于抛出异常。<br>　　•&nbsp;<strong>throws</strong>&nbsp;-- 用在方法签名中，用于声明该方法可能抛出的异常。</p>
<h2 id="3">异常处理的基本语法</h2>
<h3 id="4">1. try-catch</h3>
<div>
<pre><span>try</span><span>{
    </span><span>//</span><span>code that might generate exceptions    </span>
}<span>catch</span><span>(Exception e){
    </span><span>//</span><span>the code of handling exception1</span>
}<span>catch</span><span>(Exception e){
    </span><span>//</span><span>the code of handling exception2</span>
}</pre>
</div>
<p>要明白异常捕获，还要理解<code>监控区域</code>（guarded region）的概念。它是一段可能产生异常的代码，并且后面跟着处理这些异常的代码。</p>
<p>因而可知，上述<code>try-catch</code>所描述的即是监控区域，关键词<code>try</code>后的一对大括号将一块可能发生异常的代码包起来，即为监控区域。Java方法在运行过程中发生了异常，则创建异常对象。将异常抛出监控区域之外，由Java运行时系统负责寻找匹配的<code>catch</code>子句来捕获异常。若有一个<code>catch</code>语句匹配到了，则执行该<code>catch</code>块中的异常处理代码，就不再尝试匹配别的<code>catch</code>块了。</p>
<blockquote contentScore="190">
<p>匹配的原则是：如果抛出的异常对象属于<code>catch</code>子句的异常类，或者属于该异常类的子类，则认为生成的异常对象与<code>catch</code>块捕获的异常类型相匹配。</p>
</blockquote>
<p>举个例子算术异常：</p>
<div>
<pre><span>public</span> <span>class</span><span> TestException {  
    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {  
        </span><span>int</span> a = 1<span>;  
        </span><span>int</span> b = 0<span>;  
        </span><span>try</span> { <span>//</span><span> try监控区域               </span>
            <span>if</span> (b == 0) <span>throw</span> <span>new</span> ArithmeticException(); <span>//</span><span> 通过throw语句抛出异常  </span>
            System.out.println("a/b的值是：" + a /<span> b);  
            System.out.println(</span>"this will not be printed!"<span>);
        }  
        </span><span>catch</span> (ArithmeticException e) { <span>//</span><span> catch捕捉异常  </span>
            System.out.println("程序出现异常，变量b不能为0！"<span>);  
        }  
        System.out.println(</span>"程序正常结束。"<span>);  
    }  
}  </span></pre>
</div>
<p>运行结果：</p>
<div>
<pre>D:\java&gt;<span>java TestException

程序出现异常，变量b不能为0！

程序正常结束。</span></pre>
</div>
<blockquote contentScore="186">
<p>显示一个异常的描述，<code>Throwable</code>重载了<code>toString()</code>方法（由<code>Object</code>定义），所以它将返回一个包含异常描述的字符串。例如，将前面的<code>catch</code>块重写成：</p>
<div>
<pre><span>catch</span> (ArithmeticException e) { <span>//</span><span> catch捕捉异常  </span>
    System.out.println("程序出现异常"+<span>e);  
} </span></pre>
</div>
<p>结果：</p>
<div>
<pre>D:\java&gt;<span>java TestException

程序出现异常java.lang.ArithmeticException

程序正常结束。</span></pre>
</div>
</blockquote>
<p>根据前面讲述的，算术异常属于运行时异常，因而实际上该异常不需要程序抛出，运行时系统自动抛出，将例子改为如下：</p>
<div>
<pre><span>public</span> <span>class</span><span> TestException {  
    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {  
        </span><span>int</span> a = 1<span>;  
        </span><span>int</span> b = 0<span>;    
        System.out.println(</span>"a/b的值是：" + a /<span> b);
        System.out.println(</span>"this will not be printed!"<span>);
    }  
}  </span></pre>
</div>
<p>结果：</p>
<div>
<pre>D:\java&gt;<span>java TestException

Exception in thread </span>"main" java.lang.ArithmeticException: /<span> by zero
    at TestException.main(TestException.java:</span>7)</pre>
</div>
<p><strong><code>使用多重的catch语句</code></strong>：很多情况下，由单个的代码段可能引起多个异常。处理这种情况，我们需要定义两个或者更多的<code>catch</code>子句，每个子句捕获一种类型的异常，当异常被引发时，每个<code>catch</code>子句被依次检查，第一个匹配异常类型的子句执行，当一个<code>catch</code>子句执行以后，其他的子句将被旁路。</p>
<p>编写多重catch语句块注意事项：</p>
<p>　　顺序问题：先小后大，即先子类后父类</p>
<p>&nbsp;<img src="https://images2015.cnblogs.com/blog/690102/201607/690102-20160729100020388-1471612369.jpg" alt=""></p>
<blockquote contentScore="584">
<p>Java通过异常类描述异常类型。对于有多个<code>catch</code>子句的异常程序而言，应该尽量将捕获底层异常类的<code>catch</code>子句放在前面，同时尽量将捕获相对高层的异常类的<code>catch</code>子句放在后面。否则，捕获底层异常类的<code>catch</code>子句将可能会被屏蔽。</p>
<p><code>RuntimeException</code>异常类包括运行时各种常见的异常，<code>ArithmeticException</code>类和<code>ArrayIndexOutOfBoundsException</code>类都是它的子类。因此，<code>RuntimeException</code>异常类的<code>catch</code>子句应该放在最后面，否则可能会屏蔽其后的特定异常处理或引起编译错误。</p>
</blockquote>
<p><strong><code>嵌套try语句</code></strong>：<code>try</code>语句可以被嵌套。也就是说，一个<code>try</code>语句可以在另一个<code>try</code>块的内部。每次进入<code>try</code>语句，异常的前后关系都会被推入堆栈。如果一个内部的<code>try</code>语句不含特殊异常的<code>catch</code>处理程序，堆栈将弹出，下一个<code>try</code>语句的<code>catch</code>处理程序将检查是否与之匹配。这个过程将继续直到一个<code>catch</code>语句被匹配成功，或者是直到所有的嵌套<code>try</code>语句被检查完毕。如果没有<code>catch</code>语句匹配，Java运行时系统将处理这个异常。</p>
<p>例如：</p>
<div>
<pre><span>class</span><span> NestTry{
    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args){
        </span><span>try</span><span>{
            </span><span>int</span> a =<span> args.length;
            </span><span>int</span> b = 42 /<span> a;
            System.out.println(</span>"a = "+<span> a);
            </span><span>try</span><span>{
                </span><span>if</span>(a == 1<span>){
                a </span>= a/(a-<span>a);
                }
                </span><span>if</span>(a == 2<span>){
                    </span><span>int</span> c[] = {1<span>};
                    c[</span>42] =99<span>;
                }
            }</span><span>catch</span><span>(ArrayIndexOutOfBoundsException e){
                System.out.println(</span>"ArrayIndexOutOfBounds :"+<span>e);
            }    
        }</span><span>catch</span><span>(ArithmeticException e){
            System.out.println(</span>"Divide by 0"+<span> e);
        }
    }
}</span></pre>
</div>
<p>正如程序中所显示的，该程序在一个try块中嵌套了另一个<code>try</code>块。程序工作如下：当你在没有命令行参数的情况下执行该程序，外面的<code>try</code>块将产生一个被0除的异常。程序在有一个命令行参数条件下执行，由嵌套的<code>try</code>块产生一个被0除的异常，由于内部的<code>catch</code>块不匹配这个异常，它将把异常传给外部的<code>try</code>块，在外部异常被处理。如果你在具有两个命令行参数的条件下执行该程序，将由内部<code>try</code>块产生一个数组边界异常。</p>
<p>结果：</p>
<div>
<pre>D:\java&gt;<span>javac estTry.java

D:\java</span>&gt;&gt;<span>java NestTry

Divide by </span>0 java.lang.ArithmeticExceptio: /<span> by zero

D:\java</span>&gt;<span>java NestTry one

a </span>= 1<span>

Divide by 0java.lang.ArithmeticException: </span>/<span> by zero

D:\java</span>&gt;<span>java NestTry one two

a </span>= 2<span>

ArrayIndexOutOfBounds :java.lang.ArrayIndexOutOfBoundsException: </span>42</pre>
</div>
<p>注意：当有方法调用时，<code>try</code>语句的嵌套可以很隐蔽的发生。例如，我们可以将对方法的调用放在一个<code>try</code>块中。在该方法的内部，有另一个<code>try</code>语句。在这种情况下，方法内部的<code>try</code>仍然是嵌套在外部调用该方法的<code>try</code>块中的。下面我们将对上述例子进行修改，嵌套的<code>try</code>块移到方法nesttry()的内部：</p>
<div>
<pre><span>class</span><span> NestTry{
    </span><span>static</span> <span>void</span> nesttry(<span>int</span><span> a){
        </span><span>try</span><span>{
            </span><span>if</span>(a == 1<span>){
                a </span>= a/(a-<span>a);
            }
            </span><span>if</span>(a == 2<span>){
                </span><span>int</span> c[] = {1<span>};
                c[</span>42] =99<span>;
            }
        }</span><span>catch</span><span>(ArrayIndexOutOfBoundsException e){
            System.out.println(</span>"ArrayIndexOutOfBounds :"+<span>e);
        }    
    }
    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args){
        </span><span>try</span><span>{
            </span><span>int</span> a =<span> args.length;
            </span><span>int</span> b = 42 /<span> a;
            System.out.println(</span>"a = "+<span> a);
            nesttry(a);
        }</span><span>catch</span><span>(ArithmeticException e){
            System.out.println(</span>"Divide by 0"+<span> e);
        }
    }
}</span></pre>
</div>
<p>结果输出与前面例子一致：<code></code></p>
<div>
<pre>D:\java&gt;<span>javac NestTry.java

D:\java</span>&gt;<span>java NestTry

Divide by 0java.lang.ArithmeticException: </span>/<span> by zero

D:\java</span>&gt;<span>java NestTry one

a </span>= 1<span>

Divide by 0java.lang.ArithmeticException: </span>/<span> by zero

D:\java</span>&gt;<span>java NestTry one two

a </span>= 2<span>

ArrayIndexOutOfBounds :java.lang.ArrayIndexOutOfBoundsException: </span>42</pre>
</div>
<h3 id="5">2. throw</h3>
<p>到目前为止，我们只是获取了被Java运行时系统引发的异常。然而，我们还可以用<code>throw</code>语句抛出明确的异常。<code>Throw</code>的语法形式如下：</p>
<div>
<pre><span>throw</span> ThrowableInstance;</pre>
</div>
<p>这里的ThrowableInstance一定是<code>Throwable</code>类类型或者<code>Throwable</code>子类类型的一个对象。简单的数据类型，例如<code>int</code>，<code>char</code>,以及非<code>Throwable</code>类，例如<code>String</code>或<code>Object</code>，不能用作异常。有两种方法可以获取<code>Throwable</code>对象：在<code>catch</code>子句中使用参数或者使用<code>new</code>操作符创建。</p>
<p>程序执行完<code>throw</code>语句之后立即停止；<code>throw</code>后面的任何语句不被执行，最邻近的<code>try</code>块用来检查它是否含有一个与异常类型匹配的<code>catch</code>语句。如果发现了匹配的块，控制转向该语句；如果没有发现，次包围的<code>try</code>块来检查，以此类推。如果没有发现匹配的<code>catch</code>块，默认异常处理程序中断程序的执行并且打印堆栈轨迹。</p>
<p>例如：</p>
<div>
<pre><span>class</span><span> TestThrow{
    </span><span>static</span> <span>void</span><span> proc(){
        </span><span>try</span><span>{
            </span><span>throw</span> <span>new</span> NullPointerException("demo"<span>);
        }</span><span>catch</span><span>(NullPointerException e){
            System.out.println(</span>"Caught inside proc"<span>);
            </span><span>throw</span><span> e;
        }
    }

    </span><span>public</span> <span>static</span> <span>void</span><span> main(String [] args){
        </span><span>try</span><span>{
            proc();
        }</span><span>catch</span><span>(NullPointerException e){
            System.out.println(</span>"Recaught: "+<span>e);
        }
    }
}</span></pre>
</div>
<p>结果：</p>
<div>
<pre>D:\java&gt;<span>java TestThrow

Caught inside proc

Recaught: java.lang.NullPointerException: demo</span></pre>
</div>
<p>该程序两次处理相同的错误，首先，<code>main()</code>方法设立了一个异常关系然后调用proc()。proc()方法设立了另一个异常处理关系并且立即抛出一个<code>NullPointerException</code>实例，<code>NullPointerException</code>在<code>main()</code>中被再次捕获。</p>
<p>该程序阐述了怎样创建Java的标准异常对象，特别注意这一行：</p>
<div>
<pre>throw new NullPointerException("demo");
</pre>
</div>
<blockquote contentScore="409">
<p>此处<code>new</code>用来构造一个<code>NullPointerException</code>实例，所有的Java内置的运行时异常有两个构造方法：一个没有参数，一个带有一个字符串参数。当用第二种形式时，参数指定描述异常的字符串。如果对象用作<code>print()</code>或者<code>println()</code>的参数时，该字符串被显示。这同样可以通过调用getMessage()来实现，getMessage()是由<code>Throwable</code>定义的。</p>
</blockquote>
<h3 id="6">3. throws</h3>
<p>如果一个方法可以导致一个异常但不处理它，它必须指定这种行为以使方法的调用者可以保护它们自己而不发生异常。要做到这点，我们可以在方法声明中包含一个<code>throws</code>子句。一个<code>throws</code>子句列举了一个方法可能引发的所有异常类型。这对于除了<code>Error</code>或<code>RuntimeException</code>及它们子类以外类型的所有异常是必要的。一个方法可以引发的所有其他类型的异常必须在<code>throws</code>子句中声明，否则会导致编译错误。</p>
<p>下面是<code>throws</code>子句的方法声明的通用形式：</p>
<div>
<pre><span>public</span> <span>void</span> info() <span>throws</span><span> Exception
{
   </span><span>//</span><span>body of method</span>
}</pre>
</div>
<p>Exception 是该方法可能引发的所有的异常,也可以是异常列表，中间以逗号隔开。</p>
<p>例如：</p>
<div>
<pre><span>class</span><span> TestThrows{
    </span><span>static</span> <span>void</span><span> throw1(){
        System.out.println(</span>"Inside throw1 . "<span>);
        </span><span>throw</span> <span>new</span> IllegalAccessException("demo"<span>);
    }
    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args){
        throw1();
    }
}</span></pre>
</div>
<p>上述例子中有两个地方存在错误，你能看出来吗？</p>
<p>该例子中存在两个错误，首先，throw1()方法不想处理所导致的异常，因而它必须声明<code>throws</code>子句来列举可能引发的异常即<code>IllegalAccessException</code>；其次，<code>main()</code>方法必须定义<code>try/catch</code>语句来捕获该异常。</p>
<p>正确例子如下：</p>
<div>
<pre><span>class</span><span> TestThrows{
    </span><span>static</span> <span>void</span> throw1() <span>throws</span><span> IllegalAccessException {
        System.out.println(</span>"Inside throw1 . "<span>);
        </span><span>throw</span> <span>new</span> IllegalAccessException("demo"<span>);
    }
    </span><span>public</span> <span>static</span> <span>void</span><span> main(String[] args){
        </span><span>try</span><span> {
            throw1();
        }</span><span>catch</span><span>(IllegalAccessException e ){
            System.out.println(</span>"Caught " +<span> e);
        }
    }
}</span></pre>
</div>
<blockquote contentScore="30">
<p><code>Throws</code>抛出异常的规则：</p>
<ul>
<li>如果是不受检查异常（<code>unchecked exception</code>），即<code>Error</code>、<code>RuntimeException</code>或它们的子类，那么可以不使用<code>throws</code>关键字来声明要抛出的异常，编译仍能顺利通过，但在运行时会被系统抛出。</li>
<li>必须声明方法可抛出的任何检查异常（<code>checked exception</code>）。即如果一个方法可能出现受可查异常，要么用<code>try-catch</code>语句捕获，要么用<code>throws</code>子句声明将它抛出，否则会导致编译错误</li>
<li>仅当抛出了异常，该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候，应该继续抛出，而不是囫囵吞枣。</li>
<li>调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法，则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。</li>
</ul>
</blockquote>
<h3 id="7">4. finally</h3>
<p>当异常发生时，通常方法的执行将做一个陡峭的非线性的转向，它甚至会过早的导致方法返回。例如，如果一个方法打开了一个文件并关闭，然后退出，你不希望关闭文件的代码被异常处理机制旁路。<code>finally</code>关键字为处理这种意外而设计。</p>
<p><code>finally</code>创建的代码块在<code>try/catch</code>块完成之后另一个<code>try/catch</code>出现之前执行。<code>finally</code>块无论有没有异常抛出都会执行。如果抛出异常，即使没有<code>catch</code>子句匹配，<code>finally</code>也会执行。一个方法将从一个<code>try/catch</code>块返回到调用程序的任何时候，经过一个未捕获的异常或者是一个明确的返回语句，<code>finally</code>子句在方法返回之前仍将执行。这在关闭文件句柄和释放任何在方法开始时被分配的其他资源是很有用。</p>
<blockquote contentScore="118">
<p><code>finally</code>子句是可选项，可以有也可以无，但是每个<code>try</code>语句至少需要一个<code>catch</code>或者<code>finally</code>子句。</p>
</blockquote>
<div>
<pre><span>class</span><span> TestFinally{
    </span><span>static</span> <span>void</span><span> proc1(){
        </span><span>try</span><span>{
            System.out.println(</span>"inside proc1"<span>);
            </span><span>throw</span> <span>new</span> RuntimeException("demo"<span>);
        }</span><span>finally</span><span>{
            System.out.println(</span>"proc1's finally"<span>);
        }
    }
    </span><span>static</span> <span>void</span><span> proc2(){
        </span><span>try</span><span>{
            System.out.println(</span>"inside proc2"<span>);
            </span><span>return</span><span> ;
        } </span><span>finally</span><span>{
            System.out.println(</span>"proc2's finally"<span>);
        }
    } 
    </span><span>static</span> <span>void</span><span> proc3(){
        </span><span>try</span><span>{
            System.out.println(</span>"inside proc3"<span>);
        }</span><span>finally</span><span>{
            System.out.println(</span>"proc3's finally"<span>);
        }
    }
    </span><span>public</span> <span>static</span> <span>void</span><span> main(String [] args){
        </span><span>try</span><span>{
            proc1();
        }</span><span>catch</span><span>(Exception e){
            System.out.println(</span>"Exception caught"<span>);
        }
        proc2();
        proc3();
    }
}</span></pre>
</div>
<p>该例子中，proc1()抛出了异常中断了<code>try</code>，它的<code>finally</code>子句在退出时执行。proc2的<code>try</code>语句通过<code>return</code>语句返回，但在返回之前<code>finally</code>语句执行。在proc3()中<code>try</code>语句正常执行，没有错误，<code>finally</code>语句也被执行。</p>
<p>输出结果：</p>
<div>
<pre>D:\java&gt;<span>java TestFinally

inside proc1

proc1</span>'s finally
<span>
Exception caught

inside proc2

proc2</span>'s finally
<span> 
inside proc3

proc3</span>'s finally</pre>
</div>
<blockquote contentScore="89">
<p>注：如果<code>finally</code>块与一个<code>try</code>联合使用，<code>finally</code>块将在<code>try</code>结束之前执行。</p>
</blockquote>
<h3><strong>问题扩展（面试题）</strong>：</h3>
<p id="a90cca7caf301432632ebdf031c45c4c"><strong>1、try{} 里有一个 return 语句，那么紧跟在这个 try 后的 finally{} 里的 code 会不会被执行，什么时候被执行，在 return 前还是后?</strong></p>
<p>答案：会执行，在方法返回调用者前执行。</p>
<blockquote contentScore="666">
<p><strong>注意：</strong>在finally中改变返回值的做法是不好的，因为如果存在finally代码块，try中的return语句不会立马返回调用者，而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值，然后如果在finally中修改了返回值，就会返回修改后的值。显然，在finally中返回或者修改返回值会对程序造成很大的困扰，C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情，Java中也可以通过提升编译器的语法检查级别来产生警告或错误，Eclipse中可以在如图所示的地方进行设置，强烈建议将此项设置为编译错误。</p>
</blockquote>
<p>&nbsp;<img src="https://images2015.cnblogs.com/blog/690102/201607/690102-20160729110647216-246845823.jpg" alt=""></p>
<p><strong>2、Java语言如何进行异常处理，关键字：throws、throw、try、catch、finally分别如何使用？</strong></p>
<p>答：Java通过面向对象的方法进行异常处理，把各种不同的异常进行分类，并提供了良好的接口。在Java中，每个异常都是一个对象，它是Throwable类或其子类的实例。当一个方法出现异常后便抛出一个异常对象，该对象中包含有异常信息，调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java的异常处理是通过5个关键词来实现的：try、catch、throw、throws和finally。一般情况下是用try来执行一段程序，如果系统会抛出（throw）一个异常对象，可以通过它的类型来捕获（catch）它，或通过总是执行代码块（finally）来处理；try用来指定一块预防所有异常的程序；catch子句紧跟在try块后面，用来指定你想要捕获的异常的类型；throw语句用来明确地抛出一个异常；throws用来声明一个方法可能抛出的各种异常（当然声明异常时允许无病呻吟）；finally为确保一段代码不管发生什么异常状况都要被执行；try语句可以嵌套，每当遇到一个try语句，异常的结构就会被放入异常栈中，直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理，异常栈就会执行出栈操作，直到遇到有处理这种异常的try语句或者最终将异常抛给JVM。</p>
<p><strong>3、运行时异常与受检异常有何异同？</strong>&nbsp;</p>
<p>答：异常表示程序运行过程中可能出现的非正常状态，运行时异常表示虚拟机的通常操作中可能遇到的异常，是一种常见运行错误，只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关，即使程序设计无误，仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常，但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样，是面向对象程序设计中经常被滥用的东西，在<em>Effective Java</em>中对异常的使用给出了以下指导原则：&nbsp;<br>- 不要将异常处理用于正常的控制流（设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常）&nbsp;<br>- 对可以恢复的情况使用受检异常，对编程错误使用运行时异常&nbsp;<br>- 避免不必要的使用受检异常（可以通过一些状态检测手段来避免异常的发生）&nbsp;<br>- 优先使用标准的异常&nbsp;<br>- 每个方法抛出的异常都要有文档&nbsp;<br>- 保持异常的原子性&nbsp;<br>- 不要在catch中忽略掉捕获到的异常</p>
<p><strong>4、列出一些你常见的运行时异常？</strong>&nbsp;</p>
<p>答：&nbsp;<br>- ArithmeticException（算术异常）&nbsp;<br>- ClassCastException （类转换异常）&nbsp;<br>- IllegalArgumentException （非法参数异常）&nbsp;<br>- IndexOutOfBoundsException （下标越界异常）&nbsp;<br>- NullPointerException （空指针异常）&nbsp;<br>- SecurityException （安全异常）</p>
<p>&nbsp;<img src="https://images2015.cnblogs.com/blog/690102/201607/690102-20160729131101138-1250422700.jpg" alt="" width="1542" height="351"></p>
<h2 id="8">异常链</h2>
<p>异常链顾名思义就是将异常发生的原因一个传一个串起来，即把底层的异常信息传给上层，这样逐层抛出。 Java API文档中给出了一个简单的模型：</p>
<div>
<pre><span>try</span><span> {   
    lowLevelOp();   
} </span><span>catch</span><span> (LowLevelException le) {   
    </span><span>throw</span> (HighLevelException) <span>new</span><span> HighLevelException().initCause(le);   
}</span></pre>
</div>
<p>当程序捕获到了一个底层异常，在处理部分选择了继续抛出一个更高级别的新异常给此方法的调用者。 这样异常的原因就会逐层传递。这样，位于高层的异常递归调用getCause()方法，就可以遍历各层的异常原因。 这就是<code>Java异常链</code>的原理。异常链的实际应用很少，发生异常时候逐层上抛不是个好注意， 上层拿到这些异常又能奈之何？而且异常逐层上抛会消耗大量资源， 因为要保存一个完整的异常链信息.</p>
<h2 id="9">自定义异常</h2>
<p>使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外，用户还可以自定义异常。用户自定义异常类，只需继承<code>Exception</code>类即可。</p>
<p>在程序中使用自定义异常类，大体可分为以下几个步骤:</p>
<ul>
<li>创建自定义异常类。</li>
<li>在方法中通过<code>throw</code>关键字抛出异常对象。</li>
<li>如果在当前抛出异常的方法中处理异常，可以使用<code>try-catch</code>语句捕获并处理；否则在方法的声明处通过<code>throws</code>关键字指明要抛出给方法调用者的异常，继续进行下一步操作。</li>
<li>在出现异常方法的调用者中捕获并处理异常。</li>
</ul>
<p>举例自定义异常：</p>
<div>
<pre><span>class</span> MyException <span>extends</span><span> Exception {
    </span><span>private</span> <span>int</span><span> detail;
    MyException(</span><span>int</span><span> a){
        detail </span>=<span> a;
    }
    </span><span>public</span><span> String toString(){
        </span><span>return</span> "MyException ["+ detail + "]"<span>;
    }
}
</span><span>public</span> <span>class</span><span> TestMyException{
    </span><span>static</span> <span>void</span> compute(<span>int</span> a) <span>throws</span><span> MyException{
        System.out.println(</span>"Called compute(" + a + ")"<span>);
        </span><span>if</span>(a &gt; 10<span>){
            </span><span>throw</span> <span>new</span><span> MyException(a);
        }
        System.out.println(</span>"Normal exit!"<span>);
    }
    </span><span>public</span> <span>static</span> <span>void</span><span> main(String [] args){
        </span><span>try</span><span>{
            compute(</span>1<span>);
            compute(</span>20<span>);
        }</span><span>catch</span><span>(MyException me){
            System.out.println(</span>"Caught " +<span> me);
        }
    }
}</span></pre>
</div>
<p>该例子完全按照上述步骤。</p>
<div>
<pre><span>运行结果如下：
D:\java</span>&gt;<span>java TestMyException

Called compute(</span>1<span>)

Normal exit</span>!<span>

Called compute(</span>20<span>)

Caught MyException [</span>20]</pre>
</div>
<h2>总结</h2>
<p>&nbsp;<img src="https://images2015.cnblogs.com/blog/690102/201607/690102-20160729105042231-937633681.jpg" alt=""></p>
<p><img src="https://images2015.cnblogs.com/blog/690102/201607/690102-20160729100445981-600538221.jpg" alt=""></p>
<hr>
<p>参考文档：</p>
<p id="tutorial-title"><span><a href="http://www.tianmaying.com/tutorial/Java-Exception" target="_blank" rel="noopener nofollow"><span>Java入门之异常处理</span></a></span></p>
<p>&nbsp;</p>
</div>
<div id="MySignature" role="contentinfo" contentScore="66">
    -------------我是低调的分割线--------------------------<br>
<p>如果对你有帮助，可以点击“推荐”哦`(*∩_∩*)′</p>
<img src="http://files.cnblogs.com/files/Qian123/rabbit.gif">
<img src="http://files.cnblogs.com/files/Qian123/rabbit.gif">
<img src="http://files.cnblogs.com/files/Qian123/rabbit.gif">
<img src="http://files.cnblogs.com/files/Qian123/rabbit.gif">
</div>
<div></div>
<div id="blog_post_info_block" role="contentinfo">
    <div id="blog_post_info"></div>
    <div></div>
    <div id="post_next_prev"></div>
</div>
<pre><code>        &lt;/div&gt;</code></pre>
<footer>
    <p>本站部分内容来源于网络，如有侵犯您的权益，请通过以下方式联系我们：</p>
    <p>邮箱：zytlsd@163.com</p>
    <p>我们将在收到通知后第一时间处理。</p>
</footer>]]></description>
    <pubDate>Sun, 19 Oct 2025 21:30:46 +0800</pubDate>
    <dc:creator>emer</dc:creator>
    <guid>http://www.51keeplearning.com/?post=33</guid>
</item>
<item>
    <title>Java：Java快速入门</title>
    <link>http://www.51keeplearning.com/?post=32</link>
    <description><![CDATA[<p><meta name="referrer" content="no-referrer" /><div id="cnblogs_post_body" contentScore="4323"></p>
<h1>你好，世界！</h1>
<h2>源代码组织方式</h2>
<p>Java程序由package+class组成，package对应目录的相对路径，class对应文件，如</p>
<p><strong>E:\Workspaces\MyEclipse 10\JavaStudy\src\com\happyframework\javastudy\hello\Hello.java</strong></p>
<div>
<pre><span>1</span> <span>package</span><span> com.happyframework.javastudy.hello;
</span><span>2</span> 
<span>3</span> <span>public</span> <span>final</span> <span>class</span><span> Hello {
</span><span>4</span>     <span>public</span> <span>static</span> <span>void</span><span> hello(){
</span><span>5</span>         System.out.println("hello!"<span>);
</span><span>6</span> <span>    }
</span><span>7</span> }</pre>
</div>
<p><strong>关于class有如下几点规则：</strong></p>
<ol>
<li>文件的名字必须和class的名字一致（public级别的class名字）。</li>
<li>文件必须只包含一个public访问基本的class（可以包含多个非public级别的class）。</li>
<li>package名字必须和目录一致。</li>
</ol>
<h2>入口方法</h2>
<p><strong>App.java</strong></p>
<div>
<pre><span>1</span> <span>public</span> <span>class</span><span> App {
</span><span>2</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span>3</span> <span>        com.happyframework.javastudy.hello.Hello.hello();
</span><span>4</span> <span>    }
</span><span>5</span> }</pre>
</div>
<h2>最终的项目结构</h2>
<p><img src="https://images0.cnblogs.com/blog/492619/201309/21221554-3db17718f28f4415a611c492a573dadf.png" alt=""></p>
<h1>数据类型</h1>
<p><strong>8种原子类型</strong></p>
<ol>
<li>整数类型：byte、short、int和long。</li>
<li>小数类型：float和double。</li>
<li>字符类型：char。</li>
<li>布尔类型：bool。</li>
</ol>
<p>除此之外的是interface、class和array。</p>
<p>小数类型的常量默认是double类型，声明float类型的常量需要使用F作为后缀。</p>
<div>
<pre><span> 1</span> <span>public</span> <span>class</span><span> Program {
</span><span> 2</span> 
<span> 3</span>     <span>/**</span>
<span> 4</span> <span>     * </span><span>@param</span><span> args
</span><span> 5</span>      <span>*/</span>
<span> 6</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 7</span>             <span>float</span> age = 28.0F<span>;
</span><span> 8</span> <span>            System.out.println(age);
</span><span> 9</span> <span>    }
</span><span>10</span> 
<span>11</span> }</pre>
</div>
<h1>运算符</h1>
<ol>
<li>算术运算符：+、-、*、/ 和 %，两个整数相除，结果还是整数。</li>
<li>赋值运算符：=、+=、-=、*=、/=、%=、&amp;=、|=、~=、^=、&lt;&lt;=、&gt;&gt;= 、 &gt;&gt;&gt;=、++ 和 --。</li>
<li>比较运算符：==、!=、&lt;、&lt;=、&gt; 和 &gt;=。</li>
<li>逻辑运算符：&amp;&amp;、|| 和 !。</li>
<li>位运算符：&amp;、|、~、^、&lt;&lt;、&gt;&gt; 和 &gt;&gt;&gt;。</li>
</ol>
<h1>字符串</h1>
<p>String是拥有“值语义”的引用类型，字符串常量实现了“享元模式”，equals会按照内容进行比较，==按照地址比较。</p>
<div>
<pre><span> 1</span> <span>public</span> <span>class</span><span> Program {
</span><span> 2</span> 
<span> 3</span>     <span>/**</span>
<span> 4</span> <span>     * </span><span>@param</span><span> args
</span><span> 5</span>      <span>*/</span>
<span> 6</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 7</span>         String x = "段光伟"<span>;
</span><span> 8</span>         String y = <span>new</span> String("段光伟"<span>);
</span><span> 9</span>         
<span>10</span>         System.out.println(x.equals(y)); <span>//</span><span> true</span>
<span>11</span>         System.out.println(x == y); <span>//</span><span> false</span>
<span>12</span> <span>    }
</span><span>13</span> 
<span>14</span> }</pre>
</div>
<p>为了高效的修改字符串Java引入了StringBuffer。</p>
<div>
<pre><span>1</span> <span>        {
</span><span>2</span>             StringBuffer sb = 
<span>3</span>                     <span>new</span><span> StringBuffer()
</span><span>4</span>                     .append("段"<span>)
</span><span>5</span>                     .append("光"<span>)
</span><span>6</span>                     .append("伟"<span>);
</span><span>7</span>             
<span>8</span> <span>            System.out.println(sb.toString());
</span><span>9</span>         }</pre>
</div>
<h1>数组</h1>
<p><strong>声明语法</strong></p>
<p>DataType[]&nbsp;name 或 DataType name[]。</p>
<p><strong>初始化语法</strong></p>
<p>DataType[]&nbsp;name = new DataType[length]。</p>
<p>DataType[]&nbsp;name = new DataType[] { element1, element2, ...elementn&nbsp;}。</p>
<p>DataType[]&nbsp;name = { element1,&nbsp;element2, ...elementn&nbsp;}。</p>
<div>
<pre><span> 1</span> <span>public</span> <span>class</span><span> Program {
</span><span> 2</span> 
<span> 3</span>     <span>/**</span>
<span> 4</span> <span>     * </span><span>@param</span><span> args
</span><span> 5</span>      <span>*/</span>
<span> 6</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 7</span> <span>        {
</span><span> 8</span>             String[] strs = { "段", "光", "伟"<span> };
</span><span> 9</span> 
<span>10</span>             <span>for</span><span> (String item : strs) {
</span><span>11</span> <span>                System.out.print(item);
</span><span>12</span> <span>            }
</span><span>13</span> <span>        }
</span><span>14</span> <span>    }
</span><span>15</span> 
<span>16</span> }</pre>
</div>
<p><strong>多维数组</strong></p>
<p>只有不等长多维数组DataType[][]，没有DataType[xxx, xxx]。</p>
<h1>控制结构</h1>
<ol>
<li>条件：if-else if-else、switch-case-default和三元运算符（?:）。</li>
<li>循环：while、do-while、for和foreach。</li>
<li>Labeled block。</li>
</ol>
<div>
<pre><span> 1</span> <span>public</span> <span>class</span><span> Program {
</span><span> 2</span> 
<span> 3</span>     <span>/**</span>
<span> 4</span> <span>     * </span><span>@param</span><span> args
</span><span> 5</span>      <span>*/</span>
<span> 6</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 7</span> <span>        task: {
</span><span> 8</span>             <span>int</span> age = 25<span>;
</span><span> 9</span> 
<span>10</span>             System.out.println("start"<span>);
</span><span>11</span> 
<span>12</span>             <span>if</span> (age &lt; 30<span>) {
</span><span>13</span>                 <span>break</span><span> task;
</span><span>14</span> <span>            }
</span><span>15</span> 
<span>16</span>             System.out.println("end"<span>);
</span><span>17</span> <span>        }
</span><span>18</span> <span>    }
</span><span>19</span> }</pre>
</div>
<p>最近觉得label是个不错的东西，最起码多了一种选择。</p>
<h1>方法</h1>
<p>Java中所有的赋值和方法调用都是“按值“处理的，引用类型的值是对象的地址，原始类型的值是其自身。</p>
<p>Java支持变长方法参数。</p>
<div>
<pre><span> 1</span> <span>public</span> <span>class</span><span> Program {
</span><span> 2</span> 
<span> 3</span>     <span>/**</span>
<span> 4</span> <span>     * </span><span>@param</span><span> args
</span><span> 5</span>      <span>*/</span>
<span> 6</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 7</span>         print("段光伟", "段光宇"<span>);
</span><span> 8</span>         print(<span>new</span> String[] { "段光伟", "段光宇"<span> });
</span><span> 9</span> <span>    }
</span><span>10</span> 
<span>11</span>     <span>private</span> <span>static</span> <span>void</span><span> print(String... args) {
</span><span>12</span>         <span>for</span><span> (String item : args) {
</span><span>13</span> <span>            System.out.println(item);
</span><span>14</span> <span>        }
</span><span>15</span> <span>    }
</span><span>16</span> }</pre>
</div>
<h1>类</h1>
<div>
<pre><span> 1</span> <span>public</span> <span>class</span><span> Program {
</span><span> 2</span> 
<span> 3</span>     <span>/*</span><span>*
</span><span> 4</span> <span>     * @param args
</span><span> 5</span>      <span>*/</span>
<span> 6</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 7</span>         Point point = <span>new</span> Point(<span>100</span><span>);
</span><span> 8</span> 
<span> 9</span>         System.<span>out</span><span>.print(point);
</span><span>10</span> <span>    }
</span><span>11</span> <span>}
</span><span>12</span> 
<span>13</span> <span>class</span><span> Point {
</span><span>14</span>     <span>private</span> <span>int</span> x = <span>0</span><span>;
</span><span>15</span>     <span>private</span> <span>int</span> y = <span>0</span><span>;
</span><span>16</span> 
<span>17</span>     <span>public</span> Point(<span>int</span> x, <span>int</span><span> y) {
</span><span>18</span>         <span>this</span>.x =<span> x;
</span><span>19</span>         <span>this</span>.y =<span> y;
</span><span>20</span> <span>    }
</span><span>21</span> 
<span>22</span>     <span>public</span> Point(<span>int</span><span> x) {
</span><span>23</span>         <span>this</span><span>(x, x);
</span><span>24</span> <span>    }
</span><span>25</span> 
<span>26</span>     <span>public</span><span> String toString() {
</span><span>27</span>         <span>return</span> <span>"</span><span>(x:</span><span>"</span> + <span>this</span>.x + <span>"</span><span>,y:</span><span>"</span> + <span>this</span>.y + <span>"</span><span>)</span><span>"</span><span>;
</span><span>28</span> <span>    }
</span><span>29</span> }</pre>
</div>
<p>注意：调用自身的构造方法是用this(xxx,xxx,...)来完成，且必须位于第一行。</p>
<h1>静态成员</h1>
<p>Java中类似静态构造方法的结构，称之为：静态初始化代码块，与之对应的是实例初始化代码块，见下例：</p>
<div>
<pre><span> 1</span> <span>public</span> <span>class</span><span> Program {
</span><span> 2</span> 
<span> 3</span>     <span>/**</span>
<span> 4</span> <span>     * </span><span>@param</span><span> args
</span><span> 5</span>      <span>*/</span>
<span> 6</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 7</span> <span>        System.out.println(Point.getValue());
</span><span> 8</span>         System.out.println(<span>new</span><span> Point());
</span><span> 9</span> <span>    }
</span><span>10</span> <span>}
</span><span>11</span> 
<span>12</span> <span>class</span><span> Point {
</span><span>13</span>     <span>private</span> <span>static</span> <span>int</span> value = 0<span>;
</span><span>14</span> 
<span>15</span>     <span>public</span> <span>static</span> <span>int</span><span> getValue() {
</span><span>16</span>         <span>return</span><span> value;
</span><span>17</span> <span>    }
</span><span>18</span> 
<span>19</span>     <span>static</span><span> {
</span><span>20</span>         value++<span>;
</span><span>21</span> <span>    }
</span><span>22</span> 
<span>23</span>     <span>static</span><span> {
</span><span>24</span>         value++<span>;
</span><span>25</span> <span>    }
</span><span>26</span> 
<span>27</span>     <span>private</span> <span>int</span> x = 0<span>;
</span><span>28</span>     <span>private</span> <span>int</span> y = 0<span>;
</span><span>29</span> 
<span>30</span> <span>    {
</span><span>31</span>         <span>this</span>.x = 10<span>;
</span><span>32</span> <span>    }
</span><span>33</span> 
<span>34</span> <span>    {
</span><span>35</span>         <span>this</span>.y = 10<span>;
</span><span>36</span> <span>    }
</span><span>37</span> 
<span>38</span>     <span>public</span><span> String toString() {
</span><span>39</span>         <span>return</span> "(x:" + <span>this</span>.x + ",y:" + <span>this</span>.y + ")"<span>;
</span><span>40</span> <span>    }
</span><span>41</span> }</pre>
</div>
<h1>继承</h1>
<p>继承使用 extends，抽象类和抽象方法使用abstract声明，向下转型使用 (ChildType)instance，判断是否是某个类型使用 instanceof，见下例：</p>
<div>
<pre><span> 1</span> <span>public</span> <span>class</span><span> Program {
</span><span> 2</span> 
<span> 3</span>     <span>/**</span>
<span> 4</span> <span>     * </span><span>@param</span><span> args
</span><span> 5</span>      <span>*/</span>
<span> 6</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 7</span>         printAnimal(<span>new</span><span> Animal());
</span><span> 8</span>         printAnimal(<span>new</span><span> Dog());
</span><span> 9</span> <span>    }
</span><span>10</span> 
<span>11</span>     <span>private</span> <span>static</span> <span>void</span><span> printAnimal(Animal animal) {
</span><span>12</span>         <span>if</span>(animal <span>instanceof</span><span> Dog){
</span><span>13</span>             System.out.println("I am a " +<span> (Dog) animal);
</span><span>14</span> <span>        }
</span><span>15</span>         <span>else</span>
<span>16</span> <span>        {
</span><span>17</span>             System.out.println("I am an " +<span> animal);
</span><span>18</span> <span>        }
</span><span>19</span> <span>    }
</span><span>20</span> <span>}
</span><span>21</span> 
<span>22</span> <span>class</span><span> Animal {
</span><span>23</span>     <span>public</span><span> String toString() {
</span><span>24</span>         <span>return</span> "Animal"<span>;
</span><span>25</span> <span>    }
</span><span>26</span> <span>}
</span><span>27</span> 
<span>28</span> <span>class</span> Dog <span>extends</span><span> Animal {
</span><span>29</span>     <span>public</span><span> String toString() {
</span><span>30</span>         <span>return</span> "Dog"<span>;
</span><span>31</span> <span>    }
</span><span>32</span> }</pre>
</div>
<h1>重写</h1>
<p>Java中的重写规则比较灵活，具体如下：</p>
<ol>
<li>除了 private 修饰之外的所有实例方法都可以重写，不需要显式的声明。</li>
<li>重写的方法为了显式的表达重写这一概念，使用&nbsp;@Override进行注解。</li>
<li>重写的方法可以修改访问修饰符和返回类型，只要和父类的方法兼容（访问级别更高，返回类型更具体）。</li>
<li>可以使用final将某个方法标记为不可重写。</li>
<li>在构造方法中使用 super(xxx, xxx)调用父类构造方法，在常规实例方法中使用 super.method(xxx, xxx)调用父类方法。</li>
<li>Java不支持覆盖（new）。</li>
</ol>
<div>
<pre><span> 1</span> <span>public</span> <span>class</span><span> Program {
</span><span> 2</span> 
<span> 3</span>     <span>/**</span>
<span> 4</span> <span>     * </span><span>@param</span><span> args
</span><span> 5</span>      <span>*/</span>
<span> 6</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 7</span>         Animal animal = <span>new</span><span> Animal();
</span><span> 8</span>         Animal dog = <span>new</span><span> Dog();
</span><span> 9</span> 
<span>10</span> <span>        animal.say();
</span><span>11</span> <span>        dog.say();
</span><span>12</span> 
<span>13</span> <span>        animal.eat(animal);
</span><span>14</span> <span>        dog.eat(dog);
</span><span>15</span>         
<span>16</span> <span>        System.out.println(animal.info());
</span><span>17</span> <span>        System.out.println(dog.info());
</span><span>18</span> <span>    }
</span><span>19</span> <span>}
</span><span>20</span> 
<span>21</span> <span>class</span><span> Animal {
</span><span>22</span>     <span>private</span> String name = "Animal"<span>;
</span><span>23</span> 
<span>24</span>     <span>protected</span> <span>void</span><span> say() {
</span><span>25</span>         System.out.println("Animal" + " " + <span>this</span><span>.name);
</span><span>26</span> <span>    }
</span><span>27</span> 
<span>28</span>     <span>public</span> <span>void</span><span> eat(Animal food) {
</span><span>29</span>         System.out.println("Animal eat " +<span> food);
</span><span>30</span> <span>    }
</span><span>31</span> 
<span>32</span>     <span>public</span><span> Object info() {
</span><span>33</span>         <span>return</span> "Animal"<span>;
</span><span>34</span> <span>    }
</span><span>35</span>     
<span>36</span> <span>    @Override
</span><span>37</span>     <span>public</span><span> String toString() {
</span><span>38</span>         <span>return</span> "Animal"<span>;
</span><span>39</span> <span>    }
</span><span>40</span> <span>}
</span><span>41</span> 
<span>42</span> <span>class</span> Dog <span>extends</span><span> Animal {
</span><span>43</span>     <span>private</span> String name = "Dog"<span>;
</span><span>44</span> 
<span>45</span> <span>    @Override
</span><span>46</span>     <span>public</span> <span>final</span> <span>void</span><span> say() {
</span><span>47</span>         System.out.println("Dog" + " " + <span>this</span><span>.name);
</span><span>48</span> <span>    }
</span><span>49</span> 
<span>50</span> <span>    @Override
</span><span>51</span>     <span>public</span> <span>final</span> <span>void</span><span> eat(Animal food) {
</span><span>52</span>         <span>super</span><span>.eat(food);
</span><span>53</span>         
<span>54</span>         System.out.println("Dog eated"<span>);
</span><span>55</span> <span>    }
</span><span>56</span> 
<span>57</span> <span>    @Override
</span><span>58</span>     <span>public</span> <span>final</span><span> String info() {
</span><span>59</span>         <span>return</span> "Dog"<span>;
</span><span>60</span> <span>    }
</span><span>61</span> 
<span>62</span> <span>    @Override
</span><span>63</span>     <span>public</span> <span>final</span><span> String toString() {
</span><span>64</span>         <span>return</span> "Dog"<span>;
</span><span>65</span> <span>    }
</span><span>66</span> }</pre>
</div>
<h1>包</h1>
<p>包的名字和项目路径下的目录路径相对应，比如：项目路径为：C:\Study，有一个Java源文件位于：C：\Study\com\happyframework\study\App.java，那么App.java的包名字必须为：com.happyframework.study，且 App.java 的第一行语句必须为：package&nbsp;com.happyframework.study。</p>
<p>Java支持三种导入语法：</p>
<ol>
<li>导入类型：import xxx.xxx.xxxClass。</li>
<li>导入包：import xxx.xxx.xxx.*。</li>
<li>导入静态成员：import static xxx.xxx.*。</li>
</ol>
<div>
<pre><span> 1</span> <span>import</span> <span>static</span> util.Helper.*<span>;
</span><span> 2</span> 
<span> 3</span> <span>public</span> <span>class</span><span> Program {
</span><span> 4</span> 
<span> 5</span>     <span>/**</span>
<span> 6</span> <span>     * </span><span>@param</span><span> args
</span><span> 7</span>      <span>*/</span>
<span> 8</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 9</span>         puts("段光伟"<span>);
</span><span>10</span> <span>    }
</span><span>11</span> }</pre>
</div>
<h1>访问级别</h1>
<p>Java支持四种访问级别：public、private、protected 和 default（默认），类型和接口只能使用public 和 default，成员和嵌套类型可以使用所有，下面简单的解释一下 protected 和 default。</p>
<ul>
<li>protected 修饰过的成员只能被自己、子类和同一个包里的（不包括子包）其他类型访问。</li>
<li>default 修改过的类型或成员只能被自己和同一个包里的（不包括子包）其他类型访问。</li>
</ul>
<h1>嵌套类</h1>
<p>Java支持如下几种嵌套类：</p>
<ol>
<li>nested class，定义在类型内部的类型。<ol>
<li>static nested class，使用 static 声明的 nested class，static nested class 可以访问所有外部类的静态成员。</li>
<li>inner class，没有使用 static 声明的 nested class，inner class 可以访问所有外部类的实例成员，inner class 不能定义静态成员。</li>
</ol></li>
</ol>
<p><strong>代码示例</strong></p>
<div>
<pre><span> 1</span> <span>public</span> <span>class</span><span> Program {
</span><span> 2</span> 
<span> 3</span>     <span>/**</span>
<span> 4</span> <span>     * </span><span>@param</span><span> args
</span><span> 5</span>      <span>*/</span>
<span> 6</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 7</span>         OuterClass outer = <span>new</span><span> OuterClass();
</span><span> 8</span>         OuterClass.InnerClass inner = outer.<span>new</span><span> InnerClass();
</span><span> 9</span>         OuterClass.InnerClass.InnerInnerClass innerInner = inner.<span>new</span><span> InnerInnerClass();
</span><span>10</span> <span>        outer.show();
</span><span>11</span> <span>        inner.show();
</span><span>12</span> <span>        innerInner.show();
</span><span>13</span>         
<span>14</span>         OuterClass.StaticNestedClass staticNested=<span>new</span><span> OuterClass.StaticNestedClass();
</span><span>15</span>         OuterClass.StaticNestedClass.StaticNestedNestedClass staticNestedNested=<span>new</span><span> OuterClass.StaticNestedClass.StaticNestedNestedClass();
</span><span>16</span>         
<span>17</span> <span>        staticNested.show();
</span><span>18</span> <span>        staticNestedNested.show();
</span><span>19</span> <span>    }
</span><span>20</span> <span>}
</span><span>21</span> 
<span>22</span> <span>class</span><span> OuterClass {
</span><span>23</span>     <span>int</span> x = 1<span>;
</span><span>24</span>     <span>static</span> <span>int</span> i = 1<span>;
</span><span>25</span> 
<span>26</span>     <span>void</span><span> show() {
</span><span>27</span> <span>        System.out.println(x);
</span><span>28</span> <span>        System.out.println(i);
</span><span>29</span> <span>    }
</span><span>30</span> 
<span>31</span>     <span>class</span><span> InnerClass {
</span><span>32</span>         <span>int</span> y = 2<span>;
</span><span>33</span> 
<span>34</span>         <span>void</span><span> show() {
</span><span>35</span> <span>            System.out.println(x);
</span><span>36</span> <span>            System.out.println(y);
</span><span>37</span> <span>        }
</span><span>38</span> 
<span>39</span>         <span>class</span><span> InnerInnerClass {
</span><span>40</span>             <span>int</span> z = 3<span>;
</span><span>41</span> 
<span>42</span>             <span>void</span><span> show() {
</span><span>43</span>                 System.out.println(OuterClass.<span>this</span><span>.x);
</span><span>44</span> <span>                System.out.println(y);
</span><span>45</span> <span>                System.out.println(z);
</span><span>46</span> <span>            }
</span><span>47</span> <span>        }
</span><span>48</span> <span>    }
</span><span>49</span> 
<span>50</span>     <span>static</span> <span>class</span><span> StaticNestedClass {
</span><span>51</span>         <span>static</span> <span>int</span> j = 2<span>;
</span><span>52</span> 
<span>53</span>         <span>void</span><span> show() {
</span><span>54</span> <span>            System.out.println(i);
</span><span>55</span> <span>            System.out.println(j);
</span><span>56</span> <span>        }
</span><span>57</span> 
<span>58</span>         <span>static</span> <span>class</span><span> StaticNestedNestedClass {
</span><span>59</span>             <span>static</span> <span>int</span> k = 3<span>;
</span><span>60</span> 
<span>61</span>             <span>void</span><span> show() {
</span><span>62</span> <span>                System.out.println(i);
</span><span>63</span> <span>                System.out.println(j);
</span><span>64</span> <span>                System.out.println(k);
</span><span>65</span> <span>            }
</span><span>66</span> <span>        }
</span><span>67</span> <span>    }
</span><span>68</span> }</pre>
</div>
<p><strong>特殊的inner class：local class</strong></p>
<div>
<pre><span> 1</span> <span>public</span> <span>class</span><span> LocalClassExample {
</span><span> 2</span> 
<span> 3</span>     <span>static</span> String staticValue = <span>"</span><span>static value</span><span>"</span><span>;
</span><span> 4</span>     String instanceValue = <span>"</span><span>instance value</span><span>"</span><span>;
</span><span> 5</span> 
<span> 6</span>     <span>public</span> <span>void</span><span> test() {
</span><span> 7</span> 
<span> 8</span>         final String finalLocalValue = <span>"</span><span>final local value</span><span>"</span><span>;
</span><span> 9</span> 
<span>10</span>         <span>class</span><span> LocalClass {
</span><span>11</span>             <span>void</span><span> test() {
</span><span>12</span>                 System.<span>out</span><span>.println(staticValue);
</span><span>13</span>                 System.<span>out</span><span>.println(instanceValue);
</span><span>14</span>                 System.<span>out</span><span>.println(finalLocalValue);
</span><span>15</span> <span>            }
</span><span>16</span> <span>        }
</span><span>17</span> 
<span>18</span>         LocalClass local = <span>new</span><span> LocalClass();
</span><span>19</span> <span>        local.test();
</span><span>20</span> <span>    }
</span><span>21</span> }</pre>
</div>
<p>除了inner class的规则之外，local class可以访问局部final变量，在Java8中有更多的改进。</p>
<p><strong>特殊的local class：anonymous class</strong></p>
<div>
<pre><span> 1</span> <span>public</span> <span>class</span><span> Program {
</span><span> 2</span> 
<span> 3</span>     <span>/**</span>
<span> 4</span> <span>     * </span><span>@param</span><span> args
</span><span> 5</span>      <span>*/</span>
<span> 6</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 7</span>         execute(<span>new</span><span> Action() {
</span><span> 8</span> <span>            @Override
</span><span> 9</span>             <span>public</span> <span>void</span><span> execute() {
</span><span>10</span>                 System.out.println("执行业务逻辑"<span>);
</span><span>11</span> <span>            }
</span><span>12</span> <span>        });
</span><span>13</span> <span>    }
</span><span>14</span> 
<span>15</span>     <span>static</span> <span>void</span><span> execute(Action action) {
</span><span>16</span>         System.out.println("事物开始"<span>);
</span><span>17</span> <span>        action.execute();
</span><span>18</span>         System.out.println("事物结束"<span>);
</span><span>19</span> <span>    }
</span><span>20</span> <span>}
</span><span>21</span> 
<span>22</span> <span>interface</span><span> Action {
</span><span>23</span>     <span>void</span><span> execute();
</span><span>24</span> }</pre>
</div>
<h1>常量</h1>
<p>不废话了，直接看代码：</p>
<div>
<pre><span> 1</span> <span>public</span> <span>final</span> <span>class</span><span> Program {
</span><span> 2</span>     <span>static</span> <span>final</span> String STATIC_CONSTANTS = "STATIC_CONSTANTS"<span>;
</span><span> 3</span>     <span>final</span> String INSTANCE_CONSTANTS = "INSTANCE_CONSTANTS"<span>;
</span><span> 4</span> 
<span> 5</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 6</span>         <span>final</span> String LOCAL_CONSTANTS = "LOCAL_CONSTANTS"<span>;
</span><span> 7</span> 
<span> 8</span> <span>        System.out.println(STATIC_CONSTANTS);
</span><span> 9</span>         System.out.println(<span>new</span><span> Program().INSTANCE_CONSTANTS);
</span><span>10</span> <span>        System.out.println(LOCAL_CONSTANTS);
</span><span>11</span>         <span>new</span> Program().test("PARAMETER_CONSTANTS"<span>);
</span><span>12</span> <span>    }
</span><span>13</span> 
<span>14</span>     <span>public</span> <span>final</span> <span>void</span> test(<span>final</span><span> String msg) {
</span><span>15</span> <span>        System.out.println(msg);
</span><span>16</span> <span>    }
</span><span>17</span> }</pre>
</div>
<p>有一点需要注意的是：只有一种情况Java的常量是编译时常量（编译器会帮你替换），其它情况都是运行时常量，这种情况是：静态类型常量且常量的值可以编译时确定。</p>
<h1>接口</h1>
<p>Java的接口可以包含方法签名、常量和嵌套类，见下例：</p>
<div>
<pre><span> 1</span> <span>public</span> <span>final</span> <span>class</span><span> Program {
</span><span> 2</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 3</span> <span>        Playable.EMPTY.play();
</span><span> 4</span> 
<span> 5</span>         <span>new</span><span> Dog().play();
</span><span> 6</span> <span>    }
</span><span> 7</span> <span>}
</span><span> 8</span> 
<span> 9</span> <span>interface</span><span> Playable {
</span><span>10</span>     Playable EMPTY = <span>new</span><span> EmptyPlayable();
</span><span>11</span> 
<span>12</span>     <span>void</span><span> play();
</span><span>13</span> 
<span>14</span>     <span>class</span> EmptyPlayable <span>implements</span><span> Playable {
</span><span>15</span> 
<span>16</span> <span>        @Override
</span><span>17</span>         <span>public</span> <span>void</span><span> play() {
</span><span>18</span>             System.out.println("无所事事"<span>);
</span><span>19</span> <span>        }
</span><span>20</span> 
<span>21</span> <span>    }
</span><span>22</span> <span>}
</span><span>23</span> 
<span>24</span> <span>class</span> Dog <span>implements</span><span> Playable {
</span><span>25</span> 
<span>26</span> <span>    @Override
</span><span>27</span>     <span>public</span> <span>void</span><span> play() {
</span><span>28</span>         System.out.println("啃骨头"<span>);
</span><span>29</span> <span>    }
</span><span>30</span> 
<span>31</span> }</pre>
</div>
<h1>枚举</h1>
<p>Java枚举是class，继承自java.lang.Enum，枚举中可以定义任何类型可以定义的内容，构造方法只能是private或package private，枚举成员会被编译器动态翻译为枚举实例常量，见下例：</p>
<div>
<pre><span> 1</span> <span>public</span> <span>final</span> <span>class</span><span> Program {
</span><span> 2</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 3</span> <span>        System.out.println(State.ON);
</span><span> 4</span> <span>        System.out.println(State.OFF);
</span><span> 5</span> 
<span> 6</span>         <span>for</span><span> (State item : State.values()) {
</span><span> 7</span> <span>            System.out.println(item);
</span><span> 8</span> <span>            System.out.println(State.valueOf(item.name()));
</span><span> 9</span> <span>        }
</span><span>10</span> <span>    }
</span><span>11</span> <span>}
</span><span>12</span> 
<span>13</span> <span>enum</span><span> State {
</span><span>14</span>     ON(1), OFF(0<span>);
</span><span>15</span> 
<span>16</span>     <span>int</span> value = 1<span>;
</span><span>17</span> 
<span>18</span>     State(<span>int</span><span> value) {
</span><span>19</span>         <span>this</span>.value =<span> value;
</span><span>20</span> <span>    }
</span><span>21</span> }</pre>
</div>
<p>调用枚举的构造方法格式是：常量名字(xxx, xxx)，如果构造方法没有参数只需要：常量名子，如：</p>
<div>
<pre><span>1</span> <span>enum</span><span> State {
</span><span>2</span> <span>    ON, OFF
</span><span>3</span> }</pre>
</div>
<h1>异常</h1>
<p>Java中的异常分为checked和unchecked，checked异常必须声明在方法中或被捕获，这点我觉得比较好，必定：异常也是API的一部分，见下例：</p>
<div>
<pre><span> 1</span> <span>public</span> <span>final</span> <span>class</span><span> Program {
</span><span> 2</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 3</span>         <span>try</span><span> {
</span><span> 4</span> <span>            test();
</span><span> 5</span>         } <span>catch</span><span> (Exception e) {
</span><span> 6</span> <span>            System.out.println(e.getMessage());
</span><span> 7</span> <span>        }
</span><span> 8</span> <span>    }
</span><span> 9</span> 
<span>10</span>     <span>public</span> <span>static</span> <span>void</span> test() <span>throws</span><span> Exception {
</span><span>11</span>         <span>throw</span> <span>new</span> Exception("I am wrong!"<span>);
</span><span>12</span> <span>    }
</span><span>13</span> }</pre>
</div>
<p>所有继承Exception的异常（除了RuntimeException和它的后代之外）都是checked异常。</p>
<h1>装箱和拆箱</h1>
<p>Java提供了原始类型对应的引用类型，在1.5之后的版本还提供了自动装箱和自动拆箱，结合最新版本的泛型，几乎可以忽略这块。</p>
<div>
<pre><span> 1</span> <span>import</span> java.util.*<span>;
</span><span> 2</span> 
<span> 3</span> <span>public</span> <span>final</span> <span>class</span><span> Program {
</span><span> 4</span>     <span>public</span> <span>static</span> <span>void</span><span> main(String[] args) {
</span><span> 5</span>         ArrayList list = <span>new</span><span> ArrayList();
</span><span> 6</span>         
<span> 7</span>         list.add(1<span>);
</span><span> 8</span>         <span>int</span> item1 = (Integer) list.get(0<span>);
</span><span> 9</span>         
<span>10</span> <span>        System.out.println(item1);
</span><span>11</span> <span>    }
</span><span>12</span> }</pre>
</div>
<p>注意：自动装箱和自动拆箱是Java提供的语法糖。</p>
<h1>泛型</h1>
<p>Java的泛型是编译器提供的语法糖，官方称之为：类型参数搽除，先看一下语法，然后总结一点规律：</p>
<h2>泛型方法</h2>
<p><strong>测试代码</strong></p>
<div>
<pre><span> 1</span>     <span>static</span> &lt;T&gt; <span>void</span><span> puts(T msg) {
</span><span> 2</span> <span>        println(msg);
</span><span> 3</span> <span>    }
</span><span> 4</span> 
<span> 5</span>     <span>static</span> <span>void</span><span> println(Object msg) {
</span><span> 6</span>         System.out.println("Object:" +<span> msg);
</span><span> 7</span> <span>    }
</span><span> 8</span> 
<span> 9</span>     <span>static</span> <span>void</span><span> println(String msg) {
</span><span>10</span>         System.out.println("String:" +<span> msg);
</span><span>11</span>     }</pre>
</div>
<p><strong>调用泛型方法</strong></p>
<div>
<pre><span>1</span>         System.out.println("generic method test"<span>);
</span><span>2</span>         puts("hello"<span>);
</span><span>3</span>         Program.&lt;String&gt; puts("hello");</pre>
</div>
<p><strong>输出的结果是</strong></p>
<div>
<pre><span>1</span> <span>generic method test
</span><span>2</span> <span>Object:hello
</span><span>3</span> Object:hello</pre>
</div>
<h2>泛型类</h2>
<p><strong>测试代码</strong></p>
<div>
<pre><span>1</span> <span>class</span> TestGenericClass&lt;T&gt;<span> {
</span><span>2</span> <span>    T value;
</span><span>3</span> 
<span>4</span>     <span>void</span><span> setValue(T value) {
</span><span>5</span>         <span>this</span>.value =<span> value;
</span><span>6</span> <span>    }
</span><span>7</span> }</pre>
</div>
<p><strong>调用代码</strong></p>
<div>
<pre><span>1</span>         System.out.println("generic class test"<span>);
</span><span>2</span>         System.out.println(t.value);</pre>
</div>
<p><strong>输出结果</strong></p>
<div>
<pre><span>1</span> generic <span>class</span><span> test
</span><span>2</span> 1</pre>
</div>
<h2>泛型接口</h2>
<p><strong>测试代码</strong></p>
<div>
<pre><span> 1</span> <span>interface</span> TestInterface&lt;T&gt;<span> {
</span><span> 2</span>     <span>void</span><span> test(T item);
</span><span> 3</span> <span>}
</span><span> 4</span> 
<span> 5</span> <span>class</span> TestInterfaceImp1 <span>implements</span> TestInterface&lt;String&gt;<span> {
</span><span> 6</span> 
<span> 7</span> <span>    @Override
</span><span> 8</span>     <span>public</span> <span>void</span><span> test(String item) {
</span><span> 9</span> <span>        System.out.println(item);
</span><span>10</span> <span>    }
</span><span>11</span> <span>}
</span><span>12</span> 
<span>13</span> <span>class</span> TestInterfaceImp2&lt;T&gt; <span>implements</span> TestInterface&lt;T&gt;<span> {
</span><span>14</span> 
<span>15</span> <span>    @Override
</span><span>16</span>     <span>public</span> <span>void</span><span> test(T item) {
</span><span>17</span> <span>        System.out.println(item);
</span><span>18</span> <span>    }
</span><span>19</span> }</pre>
</div>
<p><strong>调用代码</strong></p>
<div>
<pre><span> 1</span>         System.out.println("generic interface test"<span>);
</span><span> 2</span>         TestInterface&lt;String&gt; testInterface1 = <span>new</span><span> TestInterfaceImp1();
</span><span> 3</span>         testInterface1.test("hi"<span>);
</span><span> 4</span>         <span>for</span><span> (Method item : testInterface1.getClass().getMethods()) {
</span><span> 5</span>             <span>if</span> (item.getName() == "test"<span>) {
</span><span> 6</span>                 System.out.println(item.getParameterTypes()[0<span>].getName());
</span><span> 7</span> <span>            }
</span><span> 8</span> <span>        }
</span><span> 9</span> 
<span>10</span>         TestInterface&lt;String&gt; testInterface2 = <span>new</span> TestInterfaceImp2&lt;&gt;<span>();
</span><span>11</span>         testInterface2.test("hi"<span>);
</span><span>12</span>         <span>for</span><span> (Method item : testInterface2.getClass().getMethods()) {
</span><span>13</span>             <span>if</span> (item.getName() == "test"<span>) {
</span><span>14</span>                 System.out.println(item.getParameterTypes()[0<span>].getName());
</span><span>15</span> <span>            }
</span><span>16</span>         }</pre>
</div>
<p><strong>输出结果</strong></p>
<div>
<pre><span>1</span> generic <span>interface</span><span> test
</span><span>2</span> <span>hi
</span><span>3</span> <span>java.lang.String
</span><span>4</span> <span>java.lang.Object
</span><span>5</span> <span>hi
</span><span>6</span> java.lang.Object</pre>
</div>
<h2>类型参数约束</h2>
<p><strong>测试代码</strong></p>
<div>
<pre><span> 1</span> <span>class</span><span> Animal {
</span><span> 2</span> <span>}
</span><span> 3</span> 
<span> 4</span> <span>class</span> Dog <span>extends</span><span> Animal {
</span><span> 5</span> <span>}
</span><span> 6</span> 
<span> 7</span> <span>class</span> Base&lt;T <span>extends</span> Animal&gt;<span> {
</span><span> 8</span>     <span>public</span> <span>void</span><span> test(T item) {
</span><span> 9</span>         System.out.println("Base:" +<span> item);
</span><span>10</span> <span>    }
</span><span>11</span> <span>}
</span><span>12</span> 
<span>13</span> <span>class</span> Child <span>extends</span> Base&lt;Dog&gt;<span> {
</span><span>14</span> 
<span>15</span> <span>    @Override
</span><span>16</span>     <span>public</span> <span>void</span><span> test(Dog item) {
</span><span>17</span>         System.out.println("Child:" +<span> item);
</span><span>18</span> <span>    }
</span><span>19</span> }</pre>
</div>
<p><strong>调用代码</strong></p>
<div>
<pre><span>1</span>         System.out.println("bounded type parameters test"<span>);
</span><span>2</span>         Base&lt;Dog&gt; base = <span>new</span><span> Child();
</span><span>3</span>         base.test(<span>new</span><span> Dog());
</span><span>4</span>         <span>for</span><span> (Method item : base.getClass().getMethods()) {
</span><span>5</span>             <span>if</span> (item.getName() == "test"<span>) {
</span><span>6</span>                 System.out.println(item.getParameterTypes()[0<span>].getName());
</span><span>7</span> <span>            }
</span><span>8</span>         }</pre>
</div>
<p><strong>输出结果</strong></p>
<div>
<pre><span>1</span> <span>bounded type parameters test
</span><span>2</span> <span>Child:Dog@533c2ac3
</span><span>3</span> <span>Dog
</span><span>4</span> Animal</pre>
</div>
<h2>类型搽除过程</h2>
<ol>
<li>将泛型定义中的类型参数去掉。<br>
<div>
<pre><span>class</span><span> Base {
    </span><span>public</span> <span>void</span><span> test(T item) {
        System.out.println(</span>"Base:" +<span> item);
    }
}</span></pre>
</div>
</li>
<li>将T换成extends指定的约束类型，默认是Object。<br>
<div>
<pre><span>1</span> <span>class</span><span> Base {
</span><span>2</span>     <span>public</span> <span>void</span><span> test(Animal item) {
</span><span>3</span>         System.out.println("Base:" +<span> item);
</span><span>4</span> <span>    }
</span><span>5</span> }</pre>
</div>
</li>
<li>如果有非泛型类型继承或实现了泛型基类或接口，而且进行了重写，根据情况，编译器会自动生成一些方法。<br>
<div>
<pre><span> 1</span> <span>class</span> Child <span>extends</span><span> Base {
</span><span> 2</span> <span>    @Override
</span><span> 3</span>     <span>public</span> <span>void</span><span> test(Animal item) {
</span><span> 4</span>         <span>this</span><span>.test((Dog)item);
</span><span> 5</span> <span>    }
</span><span> 6</span>     
<span> 7</span>     <span>public</span> <span>void</span><span> test(Dog item) {
</span><span> 8</span>         System.out.println("Child:" +<span> item);
</span><span> 9</span> <span>    }
</span><span>10</span> }</pre>
</div>
</li>
<li>根据泛型参数的实际参数搽除调用代码。<br>
<div>
<pre><span>1</span>         System.out.println("bounded type parameters test"<span>);
</span><span>2</span>         Base base = <span>new</span><span> Child();
</span><span>3</span>         base.test(<span>new</span><span> Dog());
</span><span>4</span>         <span>for</span><span> (Method item : base.getClass().getMethods()) {
</span><span>5</span>             <span>if</span> (item.getName() == "test"<span>) {
</span><span>6</span>                 System.out.println(item.getParameterTypes()[0<span>].getName());
</span><span>7</span> <span>            }
</span><span>8</span>         }</pre>
</div>
</li>
</ol>
<p>&nbsp;这里说的不一定正确，特别是Java泛型的约束支持&amp;（如：可以约束实行多个接口），不过过程估计差别不大，我没有看Java语言规范，这里只是大概的猜测。</p>
<h1>备注</h1>
<p>这几天完成了Java基本语法的学习，关于一些高级特性在后面再慢慢总结，如：运行时进程模型、类型加载机制、反射、注解、动态代理等。</p>
<p>&nbsp;</p>
<p></div></p>
<footer>
    <p>本站部分内容来源于网络，如有侵犯您的权益，请通过以下方式联系我们：</p>
    <p>邮箱：zytlsd@163.com</p>
    <p>我们将在收到通知后第一时间处理。</p>
</footer>]]></description>
    <pubDate>Sun, 19 Oct 2025 21:30:45 +0800</pubDate>
    <dc:creator>emer</dc:creator>
    <guid>http://www.51keeplearning.com/?post=32</guid>
</item></channel>
</rss>