﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-realsmy-文章分类-转载收藏</title><link>http://www.blogjava.net/realsmy/category/21394.html</link><description>久城的学习室</description><language>zh-cn</language><lastBuildDate>Mon, 17 Sep 2007 19:57:37 GMT</lastBuildDate><pubDate>Mon, 17 Sep 2007 19:57:37 GMT</pubDate><ttl>60</ttl><item><title>（转）ORACLE分析函数的使用</title><link>http://www.blogjava.net/realsmy/articles/145916.html</link><dc:creator>久城</dc:creator><author>久城</author><pubDate>Mon, 17 Sep 2007 09:21:00 GMT</pubDate><guid>http://www.blogjava.net/realsmy/articles/145916.html</guid><wfw:comment>http://www.blogjava.net/realsmy/comments/145916.html</wfw:comment><comments>http://www.blogjava.net/realsmy/articles/145916.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/realsmy/comments/commentRss/145916.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/realsmy/services/trackbacks/145916.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 来自：http://tb.blog.csdn.net/TrackBack.aspx?PostId=1776433分析函数是oracle816引入的一个全新的概念,为我们分析数据提供了一种简单高效的处理方式.在分析函数出现以前,我们必须使用自联查询,子查询或者内联视图,甚至复杂的存储过程实现的语句,现在只要一条简单的sql语句就可以实现了,而且在执行效率方面也有相当大的提高.下面我将针对分析...&nbsp;&nbsp;<a href='http://www.blogjava.net/realsmy/articles/145916.html'>阅读全文</a><img src ="http://www.blogjava.net/realsmy/aggbug/145916.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/realsmy/" target="_blank">久城</a> 2007-09-17 17:21 <a href="http://www.blogjava.net/realsmy/articles/145916.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>（转）重新认识面向对象 </title><link>http://www.blogjava.net/realsmy/articles/112048.html</link><dc:creator>久城</dc:creator><author>久城</author><pubDate>Thu, 19 Apr 2007 12:57:00 GMT</pubDate><guid>http://www.blogjava.net/realsmy/articles/112048.html</guid><wfw:comment>http://www.blogjava.net/realsmy/comments/112048.html</wfw:comment><comments>http://www.blogjava.net/realsmy/articles/112048.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/realsmy/comments/commentRss/112048.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/realsmy/services/trackbacks/112048.html</trackback:ping><description><![CDATA[<h3>来自：<a href="http://www.blogjava.net/huanzhugege/archive/2007/04/10/109647.html">http://www.blogjava.net/huanzhugege/archive/2007/04/10/109647.html</a></h3>
<h3>经常可以从开发人员口中听到&#8220;面向对象&#8221;这个词：</h3>
<p>场景1、
<p>A：我今天开始用面向对象的方法设计程序了！
<p>B：你怎么做的？
<p>A：我把保存文件、加载文件封装成了一个类，以后只要调用这个类就可以实现文件操作了。
<p>场景2、
<p>A：我开始学习Java了，面向对象的语言，你不要再学VB了，好土呀！
<p>B：VB怎么了？
<p>A：VB是面向过程的，已经过时了，Java中都是类，很时髦！
<p>B：VB中也有类呀！
<p>A：（无语）
<p>场景3、
<p>A：面向对象思想就是好呀，我真的离不开Java了！
<p>B：你又用什么高超技术了？
<p>A：我今天从一个操纵数据库的类继承了一个子类，然后重写了它的保存到数据库的方法，然后把数据通过Socket发送到了远程客户端了，而调用者根本不知道，哈哈！
<p>场景4、
<p>A：我推荐你用的Java不错吧？
<p>B：真是不错，面向对象就是好，JDK里边也有好多好多的类可以用，不用像在VB里边那样要去查API文档了。
<p>A：但是我听说现在又出了个面向方面编程，咱们看来又落伍了呀，看来做编程真的不是长久之计。
<p>写几个类就是面向对象了吗？继承父类就是为了重用父类的代码吗？覆盖父类的方法就可以瞒天过海了吗？VB中也有类，它是面向对象吗？
<h4>1.1</h4>
<p>类与对象
<p>&#8220;类&#8221;和&#8220;对象&#8221;是面向对象编程中最基本的概念，从语言的角度来讲，&#8220;类&#8221;是用户自定义的具有一定行为的数据类型，&#8220;对象&#8221;则是&#8220;类&#8221;这种数据类型的变量。通俗的讲，&#8220;类&#8221;是具有相同或相似行为的事物的抽象，&#8220;对象&#8221;是&#8220;类&#8221;的实例，是是一组具有相关性的代码和数据的组合体，是有一定责任的实体。
<p>类本身还可以进一步抽象为类型，类型是一种更高层次上的抽象，它只用来描述接口，比如抽象类和接口就是一种类型。当一个类型的接口包含另外一个类型的接口时，我们就可以说它是此类型的子类型。类型是用来标识特定接口的，如果一个对象接受某个接口定义的所有行为，那么我们就可以说该对象具有该类型。一个对象同时拥有多种类型。
<p>面向对象编程的特性
<p>面向对象编程有三个特性：封装，继承，多态。这三个特性从低级到高级描述了面向对象的特征。一种语言只有同时具备这三种特性才能被称为面向对象的语言。VB中也有类，它的类也支持封装和简单的继承，但是它不支持所有的继承语义和多态，因此VB只能被称为基于对象的语言。
<p>封装是所有抽象数据类型（ADT）的特性，很多刚刚接触面向对象的人认为封装就是就是面向对象。将程序按照一定的逻辑分成多个互相协作的部分，并将对外界有用的稳定的部分暴露出来，而将会发生的改变隐藏起来，外界只能通过暴露的部分向这个对象发送操作请求从而享受对象提供的服务，而不必管对象内部是如何运行的，这就是封装。理解封装是理解面向对象的第一个步骤，40%的程序员对面向对象的理解仅停留在封装这个层次。
<p>继承也称为派生，继承关系中，被继承的称为基类，从基类继承而得的被称为派生类或者子类。继承是保持对象差异性的同时共享对象相似性的复用。能够被继承的类总是含有并只含有它所抽象的那一类事务的共同特点。继承提供了实现复用，只要从一个类继承，我们就拥有了这个类的所有行为。理解继承是理解面向对象的第二个步骤，50%的程序员对面向对象的理解仅停留在继承这个层次。语义上的&#8220;继承&#8221;表示&#8220;是一种（is-a）&#8221;的关系。很多人体会到了继承在代码重用方面的优点，而忽视了继承的语义特征。于是很多滥用继承的情况就发生了，关于这一点我们将会在后边介绍。
<p>多态是&#8220;允许用户将父对象设置成为一个或更多的它的子对象相等的技术，赋值后，基类对象就可以根据当前赋值给它的派生类对象的特性以不同的方式运作&#8221;（Charlie Calvert）。多态扩大了对象的适应性，改变了对象单一继承的关系。多态是行为的抽象，它使得同名方法可以有不同的响应方式，我们可以通过名字调用某一方法而无需知道哪种实现将被执行，甚至无需知道执行这个实现的对象类型。多态是面向对象编程的核心概念，只有理解了多态，才能明白什么是真正的面向对象，才能真正发挥面向对象的最大能力。不过可惜的是，只有极少数程序员能真正理解多态。
<p><a>对象之间的关系</a>
<p>对象之间有两种最基本的关系：继承关系，组合关系。
<p>继承关系
<p>继承关系可以分为两种：一种是类对接口的继承，被称为接口继承；另一种是类对类的继承，被称为实现继承。继承关系是一种&#8220;泛化/特化&#8221;关系，基类代表一般，而派生类代表特殊。
<p>组合关系。
<p>组合是由已有的对象组合而成新对象的行为，组合只是重复运用既有程序的功能，而非重用其形式。组合与继承的不同点在于它表示了整体和部分的关系。比如电脑是由CPU、内存、显示器、硬盘等组成的，这些部件使得电脑有了计算、存储、显示图形的能力，但是不能说电脑是由CPU继承而来的。
<h4>1.2</h4>
<p>对象之间有两种最基本的关系：继承关系，组合关系。通过这两种关系的不断迭代组合最终组成了可用的程序。但是需要注意的就是要合理使用这两种关系。
<p>派生类是基类的一个特殊种类，而不是基类的一个角色。语义上的&#8220;继承&#8221;表示&#8220;is-a&#8221;（是一种）的关系，派生类&#8220;is-a&#8221;基类，这是使用继承关系的最基本前提。如果类A是类B的基类，那么类B应该可以在任何A出现的地方取代A，这就是&#8220;Liskov代换法则（LSP）&#8221;。如果类B不能在类A出现的地方取代类A的话，就不要把类B设计为类A的派生类。
<p>举例来说，&#8220;苹果&#8221;是&#8220;水果&#8221;的派生类，所以&#8220;水果是植物的果实&#8221;这句话中的&#8220;水果&#8221;可以用&#8220;苹果&#8221;来代替：&#8220;苹果是植物的果实&#8221;；而&#8220;苹果&#8221;不是&#8220;香蕉&#8221;的派生类，因为&#8220;香蕉是一种种子退化的了的植物果实&#8221;不能被&#8220;苹果&#8221;替换为&#8220;苹果是一种种子退化的了的植物果实&#8221;。
<p>举这个例子好像有点多余，不过现实的开发中却经常发生&#8220;苹果&#8221;从&#8220;香蕉&#8221;继承的事情。
<p>某企业中有一套信息系统，其中有一个&#8220;客户（Customer）&#8221;基础资料，里边记录了客户的名称、地址、email等信息。后来系统要进行升级，增加一个&#8220;供应商（Supplier）&#8221;基础资料，开发人员发现&#8220;供应商&#8221;中有&#8220;客户&#8221;中的所有属性，只是多了一个&#8220;银行帐号&#8221;属性，所以就把&#8220;供应商&#8221;设置成&#8220;客户&#8221;客户的子类。
<p><img height=235 src="http://www.blogjava.net/images/blogjava_net/huanzhugege/WindowsLiveWriter/XJL_D5BB/clip_image002.gif" width=190>
<p>图 2.1
<p>到了年终，老板要求给所有的客户通过Email发送新年祝福，由于&#8220;供应商&#8221;是一种（is-a）&#8220;客户&#8221;，所以系统就给&#8220;供应商&#8221;和&#8220;客户&#8221;都发送了新年祝福。第二天很多供应商都感动流涕的给老板打电话&#8220;谢谢老板呀，我们供应商每次都是求着贵公司买我们的东西，到了年终你们还忘不了我们，真是太感谢了！&#8221;。老板很茫然，找来开发人员，开发人员这才意识到问题，于是在发送Email的程序里做了判断&#8220;如果是供应商则不发送，否则发送&#8221;，一切ok了。到了年初，老板要求给所有很长时间没有购买他们产品的&#8220;客户&#8221;，打电话进行问候和意见征集。由于&#8220;供应商&#8221;是一种（is-a）&#8220;客户&#8221;，所以第二天电话里不断出现这样的回答：&#8220;你们搞错了吧，我们是你们的供应商呀！&#8221;。老板大发雷霆，开发人员这才意识到问题的严重性，所以在系统的所有涉及到客户的地方都加了判断&#8220;如果是供应商则&#8230;&#8230;&#8221;，一共修改了60多处，当然由于疏忽遗漏了两处，所以后来又出了一次类似的事故。
<p>我们可以看到错误使用继承的害处了。其实更好的解决方案应该是，从&#8220;客户&#8221;和&#8220;供应商&#8221;中抽取一个共同的基类&#8220;外部公司&#8221;出来：
<p><img height=263 src="http://www.blogjava.net/images/blogjava_net/huanzhugege/WindowsLiveWriter/XJL_D5BB/clip_image004.jpg" width=348>
<p>图 2.2
<p>这样就将&#8220;客户&#8221;和&#8220;供应商&#8221;之间的继承关系去除了。
<p>派生类不应大量覆盖基类的行为。派生类具有扩展基类的责任，而不是具有覆盖（override）基类的责任。如果派生类需要大量的覆盖或者替换掉基类的行为，那么就不应该在两个类之间建立继承关系。
<p>让我们再来看一个案例：
<p>一个开发人员要设计一个入库单、一张出库单和一张盘点单，并且这三张单都有登帐的功能，通过阅读客户需求，开发人员发现三张单的登帐逻辑都相同：遍历单据中的所有物品记录，然后逐笔登到台帐上去。所以他就设计出了如下的程序：
<p><img height=277 src="http://www.blogjava.net/images/blogjava_net/huanzhugege/WindowsLiveWriter/XJL_D5BB/clip_image006.jpg" width=456>
<p>图 2.3
<p>把登帐逻辑都写到了&#8220;库存业务单据&#8221;这个抽象类中，三张单据从这个类继承即可。过了三个月，用户提出了新的需求：盘点单在盘点过程中，如果发现某个货物的盘亏量大于50则停止登帐，并向操作人员报警。所以开发人员在盘点单中重写了&#8220;库存业务单据&#8221;的&#8220;登帐&#8221;方法，实现了客户要求的逻辑。又过了半个月，客户要求出库登帐的时候不仅要进行原先的登帐，还要以便登帐一边计算出库成本。所以开发人员在出库单中重写了&#8220;库存业务单据&#8221;的&#8220;登帐&#8221;方法，实现了客户要求的逻辑。到了现在&#8220;库存业务单据&#8221;的&#8220;登帐&#8221;方法的逻辑只是对&#8220;入库单&#8221;有用了，因为其他两张单据都&#8220;另立门户&#8221;了。
<p>这时候就是该我们重新梳理系统设计的时候了，我们把&#8220;库存业务单据&#8221;的&#8220;登帐&#8221;方法设置成抽象方法，具体的实现代码由具体子类自己决定：
<p><img height=294 src="http://www.blogjava.net/images/blogjava_net/huanzhugege/WindowsLiveWriter/XJL_D5BB/clip_image008.jpg" width=444>
<p>图 2.4
<p>注意此处的&#8220;库存业务单据&#8221;中的&#8220;登帐&#8221;方法是斜体，在UML中表示此方法是一个抽象方法。这个不难理解，每张单据都肯定有登帐行为，但是每张单据的登帐行为都有差异，因此在抽象类中定义类的&#8220;登帐&#8221;方法为抽象方法以延迟到子类中去实现。
<p>继承具有如下优点：实现新的类非常容易，因为基类的大部分功能都可以通过继承关系自动赋予派生类；修改或者扩展继承来的实现非常容易；只要修改父类，派生的类的行为就同时被修改了。
<p>初学面向对象编程的人会认为继承真是一个好东西，是实现复用的最好手段。但是随着应用的深入就会发现继承有很多缺点：继承破坏封装性。基类的很多内部细节都是对派生类可见的，因此这种复用是&#8220;白箱复用&#8221;；如果基类的实现发生改变，那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性；从基类继承来的实现是无法在运行期动态改变的，因此降低了应用的灵活性。
<p>继承关系有很多缺点，如果合理使用组合则可以有效的避免这些缺点，使用组合关系将系统对变化的适应力从静态提升到动态，而且由于组合将已有对象组合到了新对象中，因此新对象可以调用已有对象的功能。由于组合关系中各个各个对象的内部实现是隐藏的，我们只能通过接口调用，因此我们完全可以在运行期用实现了同样接口的另外一个对象来代替原对象，从而灵活实现运行期的行为控制。而且使用合成关系有助于保持每个类的职责的单一性，这样类的层次体系以及类的规模都不太可能增长为不可控制的庞然大物。因此我们优先使用组合而不是继承。
<p>当然这并不是说继承是不好的，我们可用的类总是不够丰富，而使用继承复用来创建一些实用的类将会不组合来的更快，因此在系统中合理的搭配使用继承和组合将会使你的系统强大而又牢固。
<h4>1.3</h4>
<p>接口的概念
<p>接口是一种类型，它定义了能被其他类实现的方法，接口不能被实例化，也不能自己实现其中的方法，只能被支持该接口的其他类来提供实现。接口只是一个标识，标识了对象能做什么，至于怎么做则不在其控制之内，它更像一个契约。
<p>任何一个类都可以实现一个接口，这样这个类的实例就可以在任何需要这个接口的地方起作用，这样系统的灵活性就大大增强了。
<p>接口编程的实例
<p>SQL语句在各个不同的数据库之间移植最大的麻烦就是各个数据库支持的语法不尽相同，比如取出表的前10行数据在不同数据库中就有不同的实现。
<p>MSSQLServer：Select top 10 * from T_Table
<p>MySQL:select * from T_Table limit 0,10
<p>Oracle:select * from T_Table where ROWNUM &lt;=10
<p>我们先来看一下最朴素的做法是怎样的：
<p>首先定义一个SQL语句翻译器类：
<p>public class Test1SQLTranslator
<p>{
<p>private int dbType;
<p>public Test1SQLTranslator(int dbType)
<p>{
<p>super();
<p>this.dbType = dbType;
<p>}
<p>public String translateSelectTop(String tableName, int count)
<p>{
<p>switch (dbType) {
<p>case 0:
<p>return "select top " + count + " * from " + tableName;
<p>case 1:
<p>return "select * from " + tableName + " limit 0," + count;
<p>case 2:
<p>return "select * from " + tableName + " where ROWNUM&lt;=" + count;
<p>default:
<p>return null;
<p>}
<p>}
<p>}
<p>然后如下调用
<p>public static void main(String[] args)
<p>{
<p>String tableName = "T_Table";
<p>int count = 10;
<p>int dbType = 0;
<p>Test1SQLTranslator translator = new Test1SQLTranslator(dbType);
<p>String sql = translator.translateSelectTop(tableName,count);
<p>System.out.println(sql);
<p>}
<p>如果要增加对新的数据库的支持，比如DB2，那么就必须修改Test1SQLTranslator类，增加一个对DB2的case语句，这种增加只能是在编辑源码的时候进行添加，无法在运行时动态添加。再来看一下如果用基于接口的编程方式是如何实现的。
<p>首先，定义接口ISQLTranslator，这个接口定义了所有SQL翻译器的方法，目前只有一个翻译Select top的方法：
<p>public interface ISQLTranslator
<p>{
<p>public String translateSelectTop(String tableName, int count);
<p>}
<p>接着我们为各个数据库写不同的翻译器类，这些翻译器类都实现了ISQLTranslator接口：
<p>public class MSSQLServerTranslator implements ISQLTranslator
<p>{
<p>public String translateSelectTop(String tableName, int count)
<p>{
<p>return "select top " + count + " * from " + tableName;
<p>}
<p>}
<p>public class MySQLTranslator implements ISQLTranslator
<p>{
<p>public String translateSelectTop(String tableName, int count)
<p>{
<p>return "select * from " + tableName +" limit 0,"+count;
<p>}
<p>}
<p>public class OracleSQLTranslator implements ISQLTranslator
<p>{
<p>public String translateSelectTop(String tableName, int count)
<p>{
<p>return "select * from " + tableName+" where ROWNUM&lt;="+count;
<p>}
<p>}
<p>如下调用：
<p>public static void main(String[] args)
<p>{
<p>String tableName = "T_Table";
<p>int count = 10;
<p>ISQLTranslator translator = new MSSQLServerTranslator();
<p>String sql = translator.translateSelectTop(tableName, count);
<p>System.out.println(sql);
<p>}
<p>运行以后，打印出了：
<p>select top 10 from T_Table
<p>可以看到，不同的数据库翻译实现由不同的类来承担，这样最大的好处就是可扩展性极强，比如也许某一天出现了了支持中文语法的数据库，我要为它做翻译器只需再增加一个类：
<p>public class SinoServerTranslator implements ISQLTranslator
<p>{
<p>public String translateSelectTop(String tableName, int count)
<p>{
<p>return "读取表"+tableName+"的前"+count+"行";
<p>}
<p>}
<p>修改调用代码：
<p>public static void main(String[] args)
<p>{
<p>String tableName = "T_Table";
<p>int count = 10;
<p>ISQLTranslator translator = new SinoServerTranslator();
<p>String sql = translator.translateSelectTop(tableName, count);
<p>System.out.println(sql);
<p>}
<p>运行后控制台打印出：
<p>读取表T_Table的前10行
<p>这里的translator 可以随意实例化，只要实例化的类实现了ISQLTranslator 就可以了，这个类也可以通过配置文件读取，甚至是其他类传递过来的，这都无所谓，只要是实现了ISQLTranslator 接口它就能正常工作。
<p>如果要给SQL语句加上验证功能，也就是翻译的时候首先验证一下翻译的结果是否能在数据库中执行，我们就可以采用偷天换日的方式来进行。
<p>首先创建一个VerifyTranslator类：
<p>public class VerifyTranslator implements ISQLTranslator
<p>{
<p>private ISQLTranslator translator;
<p>private Connection connection;
<p>public VerifyTranslator(ISQLTranslator translator, Connection connection)
<p>{
<p>super();
<p>this.translator = translator;
<p>this.connection = connection;
<p>}
<p>public String translateSelectTop(String tableName, int count)
<p>{
<p>String sql = translator.translateSelectTop(tableName, count);
<p>PreparedStatement ps = null;
<p>try
<p>{
<p>ps = connection.prepareStatement(sql);
<p>ps.execute();
<p>} catch (SQLException e)
<p>{
<p>DbUtils.close(ps);
<p>return "wrong sql";
<p>}
<p>return sql;
<p>}
<p>}
<p>这个类接受一个实现了ISQLTranslator 接口的变量和数据库连接做为构造参数，最重要的是这个类本身也实现了ISQLTranslator 接口，这样它就完全能&#8220;伪装&#8221;成SQL翻译器来行使翻译的责任了，不过它没有真正执行翻译，它把翻译的任务转发给了通过构造函数传递来的那个翻译器变量：
<p>String sql = translator.translateSelectTop(tableName, count);
<p>它自己的真正任务则是进行SQL语句的验证：
<p>ps = connection.prepareStatement(sql);
<p>再次修改调用代码：
<p>public static void main(String[] args)
<p>{
<p>String tableName = "T_Table";
<p>int count = 10;
<p>ISQLTranslator translator = new VerifyTranslator(
<p>new SinoServerTranslator(), getConnection());
<p>String sql = translator.translateSelectTop(tableName, count);
<p>System.out.println(sql);
<p>}
<p>运行后控制台打印出：
<p>wrong sql
<p>下面这段代码看上去是不是很眼熟呢？
<p>ISQLTranslator translator = new VerifyTranslator(new SinoServerTranslator(), getConnection());
<p>这段代码和我们经常写的流操作非常类似：
<p>InputStream is = new DataInputStream(new FileInputStream(new File(&#8220;c:/boot.ini&#8221;)));
<p>这就是设计模式中经常提到的&#8220;装饰者模式&#8221;。
<p>针对接口编程
<p>从上面的例子我们可以看出，当代码写到：
<p>String sql = translator.translateSelectTop(tableName, count);
<p>的时候，代码编写者根本不关心translator这个变量到底是哪个类的实例，它只知道它调用了接口约定支持的translateSelectTop方法。
<p>当一个对象需要与其他对象协作完成一项任务时，它就需要知道那个对象，这样才能调用那个对象的方法来获得服务，这种对象对另一个协作对象的依赖就叫做关联。如果一个关联不是针对具体类，而是针对接口的时候，任何实现这个接口的类都可以满足要求，因为调用者仅仅关心被依赖的对象是不是实现了特定接口。
<p>当发送的请求和具体的请求响应者之间的关系在运行的时候才能确定的时候，我们就称之为动态绑定。动态绑定允许在运行期用具有相同接口的对象进行替换，从而实现多态。多态使得对象间彼此独立，所有的交互操作都通过接口进行，并可以在运行时改变它们之间的依赖关系。
<p>针对接口编程，而不是针对实现编程是面向对象开发中的一个非常重要的原则，也是设计模式的精髓！
<p>针对接口编程有数不清的例子，比如在Hibernate中，集合属性必须声明为Set、Map、List等接口类型，而不能声明为HashSet、HashMap、ArrayList等具体的类型，这是因为Hibernate在为了实现LazyLoad，自己开发了能实现LazyLoad功能的实现了Set、Map、List等接口的类，因为我们的属性的类型只声明为这些属性为这些接口的类型，因此Hibernate才敢放心大胆的返回这些特定的实现类。
<p>现实的开发过程中有如下一些违反针对接口编程原则的陋习：
<p>陋习1
<p>ArrayList list = new ArrayList();
<p>for(int i=0;i&lt;10;i++)
<p>{
<p>list.add(&#8230;&#8230;);
<p>}
<p>这里使用的是ArrayList的add方法，而add方法是定义在List接口中的，因此没有必要声明list变量为ArrayList类型，修改如下：
<p>List list = new ArrayList();
<p>for(int i=0;i&lt;10;i++)
<p>{
<p>list.add(&#8230;&#8230;);
<p>}
<p>陋习2
<p>public void fooBar(HashMap map)
<p>{
<p>Object obj = map.get(&#8220;something&#8221;);
<p>&#8230;&#8230;
<p>}
<p>在这个方法中只是调用Map接口的get方法来取数据，所以就不能要求调用者一定要传递一个HashMap类型的变量进来。修改如下：
<p>public void fooBar(Map map)
<p>{
<p>Object obj = map.get(&#8220;something&#8221;);
<p>&#8230;&#8230;
<p>}
<p>这样修改以后用户为了防止传递给fooBar方法的Map被修改，用户就可以这样调用了：
<p>Map unModMap = Collections.unmodifiableMap(map);
<p>obj.fooBar(unModMap);
<p>Collections.unmodifiableMap是JDK提供的一个工具类，可以返回一个对map的包装，返回的map是不可修改的，这也是装饰者模式的典型应用。
<p>试想如果我们把接口声明为public void fooBar(HashMap map)用户还能这么调用吗？
<h4>1.4 抽象类</h4>
<p>抽象类的主要作用就是为它的派生类定义公共接口，抽象类把它的部分操作的实现延迟到派生类中来，派生类也能覆盖抽象基类的方法，这样可以很容易的定义新类。抽象类提供了一个继承的出发点，我们经常定义一个顶层的抽象类，然后将某些位置的实现定义为抽象的，也就是我们仅仅定义了实现的接口，而没有定义实现的细节。
<p>一个抽象类应该尽可能多的拥有共同的代码，但是不能把只有特定子类才需要的方法移动到抽象类中。Eclipse的某些实现方式在这一点上就做的不是很好，Eclipse的一些界面类中提供了诸如CreateEmailField之类的方法来创建界面对象，这些方法并不是所有子类都用得到的，应该把它们抽取到一个工具类中更好。同样的错误在我们的案例的JCownewDialog中也是存在的，这个类中就提供了CreateOKBtn、CreateCanceBtn两个方法用来创建确定、取消按钮。
<p>在设计模式中，最能体现抽象类优点的就是模版方法模式。模版方法模式定义了一个算法的骨架，而具体的实现步骤则由具体的子类类来实现。JDK中的InputStream类是模版方法的典型代表，它对skip等方法给出了实现，而将read等方法定义为抽象方法等待子类去实现。后边案例中的PISAbstractAction等类也是模版方法的一个应用。
<p>在实际开发中接口和抽象类从两个方向对系统的复用做出了贡献，接口定义了系统的服务契约，而抽象类则为这些服务定义了公共的实现，子类完全可以从这些抽象类继承，这样就不用自己实现自己所不关心的方法，如果抽象类提供的服务实现不满足自己的要求，那么就可以自己从头实现接口的服务契约。 </p>
<img src ="http://www.blogjava.net/realsmy/aggbug/112048.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/realsmy/" target="_blank">久城</a> 2007-04-19 20:57 <a href="http://www.blogjava.net/realsmy/articles/112048.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>（转）谈谈我对Java中Unicode、编码的理解</title><link>http://www.blogjava.net/realsmy/articles/110789.html</link><dc:creator>久城</dc:creator><author>久城</author><pubDate>Sun, 15 Apr 2007 08:41:00 GMT</pubDate><guid>http://www.blogjava.net/realsmy/articles/110789.html</guid><wfw:comment>http://www.blogjava.net/realsmy/comments/110789.html</wfw:comment><comments>http://www.blogjava.net/realsmy/articles/110789.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/realsmy/comments/commentRss/110789.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/realsmy/services/trackbacks/110789.html</trackback:ping><description><![CDATA[<strong><br></strong>
<p><span>我们经常会遇到编码问题。</span><font color=#ff0000><span>Java</span><span>号称国际化的语言，是因为它的</span><span>class</span><span>文件采用</span><span>UTF-8</span><span>，而</span><span>JVM</span><span>运行时使用</span><span>UTF-16</span></font><span>（至于为什么</span><span>JVM</span><span>中要采用</span><span>UTF-16</span><span>，我没看过</span> <span>相关的资料，但我猜可能是因为</span><span>JAVA</span><span>里面一个字符</span><span>(char)</span><span>就是</span><span>16</span><span>位的</span><span>，而UTF-16正是双字节编码），都是</span><span>unicode</span><span>的编码。</span><span><br><br>unicode </span><span>的目标就是能支持世界上所有的字符集，也就是说几乎所有的字符集包含的字符在</span><span>unicode</span><span>中都有对应的编码。在</span><span>unicode</span><span>中，字符与代码的映射关</span> <span>系，就是</span><span>unicode</span><span>字符集，称为</span><span>UCS(Unicode Character&nbsp;Set)</span><span>，每个</span><span>unicode</span><span>字符编码称为</span><span>code point</span><span>（代码点？）。</span><font color=#ff0000><span>UTF-8</span><span>和</span><span>UTF-16</span><span>是不同的</span><span>UCS</span><span>编码方法，</span><span>UTF</span><span>就是</span><span>UCS&nbsp;Transformation&nbsp;Format</span><span>。;</span></font><span><br><br></span><span>在</span><span>Java </span><span>中，</span><span>String</span><span>的</span><span>getBytes()</span><span>方法就是对特定的字符串</span><span>(unicode)</span><span>按照给定的字符集进行编码（</span><span>encode</span><span>），</span><span>new String()</span><span>则可以按照某个字符集将字节流转换回</span><span>unicode</span><span>（</span><span>decode</span><span>）。</span><span>Java</span><span>里面的每一个</span><span>String</span><span>都是</span><span>unicode</span><span>编码。</span><span><br><br></span><span>再来看页面，如果不做特殊处理，</span><span>Form</span><span>的提交就按照页面的</span><span>ContentType</span><span>设置中的字符集进行编码转换，发送到后台，后台必须利用</span><span>req.setCharacterEncoding</span><span>来指定参数的编码格式</span><span>(</span><span>不同的应用服务器应有不同的指定方式</span><span>)</span><span>，才能正确解码。</span><span><br><br>Java </span><span>里面的</span><span>encode</span><span>和</span><span>decode</span><span>都是相对于</span><span>unicode</span><span>而言的，</span><span>encode</span><span>的意思是将</span><span>char[] --&gt; XXX Encoding byte[]</span><span>，</span><span>decode</span><span>就是由</span><span>XXX Encoding byte[] --&gt; char[]</span><span>。平常，当我们说</span><span>&#8220;</span><span>将</span><span>GBK</span><span>编码转换为</span><span>UTF-8</span><span>编码</span><span>&#8221;</span><span>的时候，实际的意思就是：</span><font color=#ff0000><span>GBK Encoding byte[] --&gt; UTF-8 Encoding byte[]</span><span>，这种转换只有在需要用</span><span>byte[]</span><span>传输数据的时候才有意义，否则便是毫无意义的。</span></font><span><br><br></span><font color=#ff0000><span>首先要说明的一点是：</span><span>Java</span><span>中的</span><span>String</span><span>对象就是一个</span><span>unicode</span><span>编码的字符串。</span></font><span><br><br></span><span>但是，我们通常会听到有人说：</span><span>&#8220;</span><span>我们需要将</span><span>String</span><span>由</span><span>ISO-8859-1</span><span>转换为</span><span>GBK</span><span>编码</span><span>&#8221;</span><span>，这又是怎么回事呢？实际上，我们并<strong><span>不是</span></strong>要</span><span>&#8220;</span><span>将</span> <span>一个由</span><span>ISO-8859-1</span><span>编码的</span><span>String</span><span>转换为</span><span>GBK</span><span>编码的</span><span>String&#8221;</span><span>，反复说明的是，</span><span>JAVA</span><span>中的</span><span>String</span><span>都是</span><span>unicode</span><span>编码的，所以不存在</span><span>&#8220;ISO- 8859-1</span><span>编码的</span><span>String&#8221;</span><span>或</span><span>&#8220;GBK</span><span>编码的</span><span>String&#8221;</span><span>这样的说法。而需要转换的唯一的原因是</span><span>String</span><span>进行了错误的编码。我们经常会碰到由</span><span>ISO-8859- 1</span><span>转换为诸如</span><span>GBK/UTF-8</span><span>等等这样的需求。所谓的转换过程是：</span><span>String --&gt; byte[] --&gt;String</span><span>。</span><span><br></span><span>也许</span> <span>你非常清楚这个过程的代码：</span><span>new String(text.getBytes("ISO-8859-1"),"GBK")</span><span>。但是，要真正理解起来并不是那么简单。表面上看似乎很容易理解，</span> <span>不就是将</span><span>text String</span><span>对象按照</span><span>ISO-8859-1</span><span>的方式编码为</span><span>byte[]</span><span>然后再把它按照</span><span>GBK</span><span>的方式转换为</span><span>String</span><span>吗？但是这句代码很容易会被误解为：</span><span> &#8220;</span><span>将</span><span>text String</span><span>由</span><span>ISO-8859-1</span><span>转换为</span><span>GBK</span><span>编码</span><span>&#8221;</span><span>，这种说法是错误的。难道你见过用这样的代码：</span><span>new String(text.getBytes("GBK"),"UTF-8")</span><span>来对</span><span>String</span><span>进行编码转换的吗？</span><span><br><br></span><span>之所以你会经常看到</span><span>new String(text.getBytes("ISO-8859-1"),"GBK")</span><span>这句代码，是因为一个</span><span>GBK</span><span>的字节流被错误地以</span><span>ISO-8859- 1</span><span>的方式转换为</span><span>String</span><span>（</span><span>unicode</span><span>）了！发生这种情况最普遍的地方是一个</span><span>GBK</span><span>编码的网页向后台提交数据的时候，就有可能会看到这句代码的出</span> <span>现。</span><span>GBK</span><span>的流被错误的当成</span><span>ISO8859-1</span><span>的流，所以便得到了一个错误的</span><span>String</span><span>。由于</span><span>ISO8859-1</span><span>是单字节编码，所以每个字节被按照原样</span> <span>转换为</span><span>String</span><span>，也就是说，虽然这是一个错误的转换，但编码没有改变，所以我们仍然有机会把编码转换回来！所以那句经典的</span><span>new String(text.getBytes("ISO-8859-1"),"GBK")</span><span>便出现了。</span><span><br><br></span><span>如果系统误以为是其它编码格式，就有可能再也转换不回来了，因为编码转换并不是负负得正那么简单的</span>&nbsp;</p>
<img src ="http://www.blogjava.net/realsmy/aggbug/110789.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/realsmy/" target="_blank">久城</a> 2007-04-15 16:41 <a href="http://www.blogjava.net/realsmy/articles/110789.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>（转）学习JAVA的30个目标</title><link>http://www.blogjava.net/realsmy/articles/110493.html</link><dc:creator>久城</dc:creator><author>久城</author><pubDate>Fri, 13 Apr 2007 09:14:00 GMT</pubDate><guid>http://www.blogjava.net/realsmy/articles/110493.html</guid><wfw:comment>http://www.blogjava.net/realsmy/comments/110493.html</wfw:comment><comments>http://www.blogjava.net/realsmy/articles/110493.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/realsmy/comments/commentRss/110493.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/realsmy/services/trackbacks/110493.html</trackback:ping><description><![CDATA[<p>1.你需要精通面向对象分析与设计(OOA/OOD)、涉及模式(GOF，J2EEDP)以及综合模式。你应该十分了解UML，尤其是class，object，interaction以及statediagrams。</p>
<p>　　2.你需要学习JAVA语言的基础知识以及它的核心类库(collections，serialization，streams，networking， multithreading，reflection，event，handling，NIO，localization，以及其他)。</p>
<p>　　3.你应该了解JVM，classloaders，classreflect，以及垃圾回收的基本工作机制等。你应该有能力反编译一个类文件并且明白一些基本的汇编指令。</p>
<p>　　4.如果你将要写客户端程序，你需要学习WEB的小应用程序(applet)，必需掌握GUI设计的思想和方法，以及桌面程序的SWING，AWT， SWT。你还应该对UI部件的JAVABEAN组件模式有所了解。JAVABEANS也被应用在JSP中以把业务逻辑从表现层中分离出来。</p>
<p>　　5.你需要学习java数据库技术，如JDBCAPI并且会使用至少一种persistence/ORM构架，例如Hibernate，JDO， CocoBase，TopLink，InsideLiberator(国产JDO红工厂软件)或者iBatis。</p>
<p>　　6.你还应该了解对象关系的阻抗失配的含义，以及它是如何影响业务对象的与关系型数据库的交互，和它的运行结果，还需要掌握不同的数据库产品运用，比如:oracle，mysql，mssqlserver。</p>
<p>　　7.你需要学习JAVA的沙盒安全模式(classloaders，bytecodeverification，managers，policyandpermissions，<br>codesigning， digitalsignatures，cryptography，certification，Kerberos，以及其他)还有不同的安全/认证 API，例如JAAS(JavaAuthenticationandAuthorizationService)，JCE (JavaCryptographyExtension)，JSSE(JavaSecureSocketExtension)，以及JGSS (JavaGeneralSecurityService)。</p>
<p>　　8.你需要学习Servlets，JSP，以及JSTL(StandardTagLibraries)和可以选择的第三方TagLibraries。</p>
<p>　　9.你需要熟悉主流的网页框架，例如JSF，Struts，Tapestry，Cocoon，WebWork，以及他们下面的涉及模式，如MVC/MODEL2。</p>
<p>　　10.你需要学习如何使用及管理WEB服务器，例如tomcat，resin，Jrun，并且知道如何在其基础上扩展和维护WEB程序。</p>
<p>　11.你需要学习分布式对象以及远程API，例如RMI和RMI/IIOP。 <br>　　12.你需要掌握各种流行中间件技术标准和与java结合实现，比如Tuxedo、CROBA，当然也包括javaEE本身。</p>
<p>　　13.你需要学习最少一种的XMLAPI，例如JAXP(JavaAPIforXMLProcessing)，JDOM(JavaforXMLDocumentObjectModel)，DOM4J，或JAXR(JavaAPIforXMLRegistries)。</p>
<p>　　14.你应该学习如何利用JAVAAPI和工具来构建WebService。例如JAX-RPC(JavaAPIforXML/RPC)，SAAJ (SOAPwithAttachmentsAPIforJava)，JAXB(JavaArchitectureforXMLBinding)，JAXM(JavaAPIforXMLMessaging)， JAXR(JavaAPIforXMLRegistries)，或者JWSDP(JavaWebServicesDeveloperPack)。</p>
<p>　　15.你需要学习一门轻量级应用程序框架，例如Spring，PicoContainer，Avalon，以及它们的IoC/DI风格(setter，constructor，interfaceinjection)。</p>
<p>　　16.你需要熟悉不同的J2EE技术，例如JNDI(JavaNamingandDirectoryInterface)，JMS (JavaMessageService)，JTA/JTS(JavaTransactionAPI/JavaTransactionService)，JMX (JavaManagementeXtensions)，以及JavaMail。</p>
<p>　　17.你需要学习企业级JavaBeans(EJB)以及它们的不同组件模式：Stateless/StatefulSessionBeans，EntityBeans(包含Bean- ManagedPersistence[BMP]或者Container-ManagedPersistence[CMP]和它的EJB-QL)，或者 Message-DrivenBeans(MDB)。</p>
<p>　　18.你需要学习如何管理与配置一个J2EE应用程序服务器，如WebLogic，JBoss等，并且利用它的附加服务，例如簇类，连接池以及分布式处理支援。你还需要了解如何在它上面封装和配置应用程序并且能够监控、调整它的性能。</p>
<p>　　19.你需要熟悉面向方面的程序设计以及面向属性的程序设计(这两个都被很容易混淆的缩写为AOP)，以及他们的主流JAVA规格和执行。例如AspectJ和AspectWerkz。</p>
<p>　　20.你需要熟悉对不同有用的API和frame work等来为你服务。例如Log4J(logging/tracing)，Quartz (scheduling)，JGroups(networkgroupcommunication)，JCache(distributedcaching)， Lucene(full-textsearch)，JakartaCommons等等。</p>
<p>21.如果你将要对接或者正和旧的系统或者本地平台，你需要学习JNI (JavaNativeInterface) and JCA (JavaConnectorArchitecture)。</p>
<p>　　22.你需要熟悉JINI技术以及与它相关的分布式系统，比如掌握CROBA。</p>
<p>　　23.你需要JavaCommunityProcess(JCP)以及他的不同JavaSpecificationRequests(JSRs)，例如Portlets(168)，JOLAP(69)，DataMiningAPI(73)，等等。</p>
<p>　　24.你应该熟练掌握一种JAVAIDE例如sunOne，netBeans，IntelliJIDEA或者Eclipse。(有些人更喜欢VI或EMACS来编写文件。随便你用什么了：)</p>
<p>　　25.JAVA(精确的说是有些配置)是冗长的，它需要很多的人工代码(例如EJB)，所以你需要熟悉代码生成工具，例如XDoclet。</p>
<p>　　26.你需要熟悉一种单元测试体系(JNunit)，并且学习不同的生成、部署工具(Ant，Maven)。</p>
<p>　　27.你需要熟悉一些在JAVA开发中经常用到的软件工程过程。例如RUP(RationalUnifiedProcess)andAgilemethodologies。</p>
<p>　　28.你需要能够深入了解加熟练操作和配置不同的操作系统，比如GNU/linux，sunsolaris，macOS等，做为跨平台软件的开发者。</p>
<p>　　29.你还需要紧跟java发展的步伐，比如现在可以深入的学习javaME，以及各种java新规范，技术的运用，如新起的web富客户端技术。</p>
<p>　　30.你必需要对opensource有所了解，因为至少java的很多技术直接是靠开源来驱动发展的，如java3D技术。<br></p>
<img src ="http://www.blogjava.net/realsmy/aggbug/110493.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/realsmy/" target="_blank">久城</a> 2007-04-13 17:14 <a href="http://www.blogjava.net/realsmy/articles/110493.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>（转）初探Java类加载机制</title><link>http://www.blogjava.net/realsmy/articles/109210.html</link><dc:creator>久城</dc:creator><author>久城</author><pubDate>Sun, 08 Apr 2007 02:59:00 GMT</pubDate><guid>http://www.blogjava.net/realsmy/articles/109210.html</guid><wfw:comment>http://www.blogjava.net/realsmy/comments/109210.html</wfw:comment><comments>http://www.blogjava.net/realsmy/articles/109210.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/realsmy/comments/commentRss/109210.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/realsmy/services/trackbacks/109210.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 此文章由教程中国(http://www.upschool.com.cn)提供&nbsp;&nbsp;&nbsp; 一、在jdk1.2以后，类加载是通过委托来完成的，这意味着如果 ClassLoader 不能找到类，它会请求父代 ClassLoader 来执行此项任务，所有 ClassLoaders 的根是系统 ClassLoader，它会以缺省方式装入类 -- 即，从本地文件系统。今天我们就来探讨...&nbsp;&nbsp;<a href='http://www.blogjava.net/realsmy/articles/109210.html'>阅读全文</a><img src ="http://www.blogjava.net/realsmy/aggbug/109210.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/realsmy/" target="_blank">久城</a> 2007-04-08 10:59 <a href="http://www.blogjava.net/realsmy/articles/109210.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>（转）理解Java类加载原理(翻译) </title><link>http://www.blogjava.net/realsmy/articles/109198.html</link><dc:creator>久城</dc:creator><author>久城</author><pubDate>Sun, 08 Apr 2007 02:11:00 GMT</pubDate><guid>http://www.blogjava.net/realsmy/articles/109198.html</guid><wfw:comment>http://www.blogjava.net/realsmy/comments/109198.html</wfw:comment><comments>http://www.blogjava.net/realsmy/articles/109198.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/realsmy/comments/commentRss/109198.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/realsmy/services/trackbacks/109198.html</trackback:ping><description><![CDATA[第一部分. 提示<br>我需要读这篇文章吗？<br>Java类加载器对Java系统的运行是至关重要的，但是却常常被我们忽略。Java类加载器负载<br>在运行时查找和加载类。自定义类加载器可以完全改变类的加载方式，以自己喜欢的方式来<br>个性化你的Java虚拟机。本文简要的介绍Java类加载器，然后通过一个构造自定义类加载器<br>的例子来说明，这个类加载器在加载类前会自动编译代码。你将学到类加载器到底是干什么<br>的，如何创建你自己的类加载器。只要你有一些基本的Java知识，知道如何创建、编译、运<br>行一个命令行Java程序以及一些Java类文件的基本概念，你就可以理解本文的内容了。读完<br>本文，你应该能够：<br>* 扩张Java虚拟机的功能<br>* 创建一个自定义的类加载器<br>* 如何把自定义的类加载器整合到你的应用程序中<br>* 修改你的类加载器以兼容Java２<br>获得帮助　<br>对本文有任何问题，可以联系作者Greg Travis，油箱：mito@panix.com 。<br>第二部分. 简介<br>类加载器是什么？<br>Java和其他语言不同的是，Java是运行于Java虚拟机(JVM)。这就意味着编译后的代码是以<br>一种和平台无关的格式保存的，而不是某种特定的机器上运行的格式。这种格式和传统的可<br>执行代码格式有很多重要的区别。具体来说，不同于C或者Ｃ++程序，Java程序不是一个独<br>立的可执行文件，而是由很多分开的类文件组成，每个类文件对应一个Java类。 另外，这<br>些类文件并不是马上加载到内存，而是当程序需要的时候才加载。 类加载器就是Java虚拟<br>机中用来把类加载到内存的工具。而且，Java类加载器也是用Java实现的。这样你就不需要<br>对Java虚拟机有深入的理解就可以很容易创建自己的类加载器了。<br>为什么要创建类加载器?<br>既然Java虚拟金已经有了类加载器，我们还要自己创建其他的呢?问得好。默认的类加载器<br>只知道如何从本地系统加载类。当你的程序完全在本机编译的话，默认的类加载器一般都工<br>作的很好。但是Java中最激动人心的地方之一就是很容易的从网络上而不只是本地加载类。<br>举个例子，浏览器可以通过自定义的类加载器加载类。 还有<br>很多加载类的方式。除了简单的从本地或者网络外，你还可以通过自定义Java中最激动人心<br>的地方之一:<br>* 执行非信任代码前自动验证数字签名<br>* 根据用户提供的密码解密代码<br>* 根据用户的需要动态的创建类<br>你关心的任何东西都能方便的以字节码的形式集成到你的应用中<br>自定义类加载器的例子<br>如果你已经使用过JDK(Java软件开发包)中的appletviewer（小应用程序浏览器）或者其他<br>Java嵌入式浏览器，你就已经使用了自定义类加载器了。Sun刚刚发布Java语言的时候，最<br>令人兴奋的一件事就是观看Java如何执行从远程网站下载的代码。执行从远程站点通过HTT<br>P连接传送来的字节码看起来有点不可思议。之所以能够工作，因为Java有安装自定义类加<br>载器的能力。小应用程序浏览器包含了一个类加载器，这个类加载器不从本地找Java类，而<br>是访问远程服务器，通过HTTP加载原始字节码文件，然后在Java虚拟机中转化为Java类。当<br>然类加载器还做了其他的很多事情：他们阻止不安全的Java类，而且保持不同页面上的不同<br>小程序不会互相干扰。Luke Gorrie写的一个包Echidna是一个开放的Java软件包，他允许在<br>一个Java虚拟机中安全的运行多个Java应用程序。它通过使用自定义类加载器给每个应用程<br>序一份类文件的拷贝来阻止应用程序之间的干扰。<br>我们的类加载器例子<br>我们知道了类加载器是如何工作的，也知道如何定义自己的类加载器了，接下来我们创建一<br>个名字为CompilingClassLoader (CCL)的自定义类加载器。CCL为我们做编译工作，我们就<br>不用自己手动编译了。 这基本上相当于有一个"make"程序构建到我们的运行环境。<br>注意：我们进行下一步之前，有必要搞清楚一些相关的概念。<br>系统在JDK版本1.2（也就是我们说的Java 2平台）得到很到改进。本文是在JDK1.0和1.1的<br>版本下写的，但是所有的东西都能在后来的版本工作。ClassLoader也在Java2种有所改进，<br>第五部分有详细介绍。<br>第三部分.ClassLoader的结构<br>总揽<br>类加载器的基本目的是服务于对Java类的请求。Java虚拟机需要一个类的时候，就把一个类<br>名给类加载器,然后类加载器试图返回一个对应的类实例。可以通过在不同的阶段覆盖相应<br>的方法来创建自定义的类加载器。接下来我们将了解到类加载器的一些主要方法。你会明白<br>这些方法是干什么的，他们在加载类文件的时候是如何工作的。你还将知道创建自定义类加<br>载器的时候需要写哪些代码。在下一部分，你将利用这些知识和我们自定义的CompilingCl<br>assLoader一起工作。<br>方法 loadClass<br>ClassLoader.loadClass() 是ClassLoader的入口点。方法签名如下：<br>Class loadClass( String name, boolean resolve);<br>参数name指定Java虚拟机需要的类的全名(含包名)，比如Foo或者java.lang.Object。<br>参数 resolve指定该类是否需要解析<br>你可以把类的解析理解为完全为运行做好准备。解析一般都不需要。如果Java虚拟机只想知<br>道这个类是否存在或者想知道它的父类的话，解析就完全没有必要了。 在Java1.1和它以前<br>的版本，如果要自定义类加载器,loadClass方法是唯一需要在子类中覆盖的方法.<br>(ClassLoader在Java1.2中有所改变，提供了方法findClass())。<br>方法 defineClass<br>defineClass 是ClassLoader中一个很神秘的方法。这个方法通过一个字节数组来构建类实<br>例。这个包含数据的原始字节数组可能来自文件系统，也可能是来自网络。defineClass 表<br>明了Java虚拟机的复杂性，神秘性和平台依赖性-它通过解释字节码把它转化为运行时数据<br>结构，检查有效性等等。但是不用担心，这些都不用你去实现。其实，你根本不能覆盖它，<br>因为该方法被关键字final修饰。<br>方法 findSystemClass<br>findSystemClass方法从本地系统加载文件。它在本地系统寻找类文件，如果找到了，调用<br>defineClass把原始字节数组转化成类对象。这是运行Java应用时Java虚拟机加载类的默认<br>机制。对于自定义类加载器，只有在我们无法加载之后才需要用findSystemClass。 原因很<br>简单: 我们的类加载器负责执行类加载中的某些特定的步骤，但并不是对所有的类。比如，<br>即使我们的类加载器从远程站点加载了某些类，仍然有很多基本的类要从本地系统加载。<br>这些类不是我们关心的，所以我们让Java虚拟机以默认的方式加载他们：从本地系统。这就<br>是findSystemClass做的事情。整个过程大致如下：<br>* Java虚拟机请求我们自定义的类加载器加载类。<br>* 我们检查远程站点是否有这个需要加载的类。<br>* 如果有，我们获取这个类。<br>* 如果没有，我们认为这个是类在基本类库中，调用findSystemClass从文件系统中加载。<br><br>在大多数自定义类加载器中，你应该先调用findSystemClass来节省从远程查找的时间。<br>实际上，正如我们将在下一部分看到的，只有当我们确定我们已经自动编译完我们的代码后<br>才允许Java虚拟机从本地文件系统加载类。<br>方法resolveClass<br>正如上面说的，类记载可以分为部分加载（不解析）和完全加载（包括解析）。我们创建自<br>定义类加载器的时候，可能要调用resolveClass。<br>方法 findLoadedClass<br>findLoadedClass实现一个缓存:当要求loadClass来加载一个类的时候，可以先调用这个方<br>法看看这个类是否已经被加载，防止重新加载一个已经被加载的类。这个方法必须先被调用<br>，我们看一下这些方法是如何组织在一起的。<br>我们的例子实现loadClass执行以下的步骤。（我们不指定通过某种具体的技术获得类文件<br>，-它可能从网络，从压缩包或者动态编译的。无论如何，我们获得的是原始字节码文件）<br>* 调用findLoadedClass检查这个类是否已经加载。<br>* 如果没有加载，我们通过某种方式获得原始字节数组。<br>* 假如已经获得该数组，调用defineClass把它转化成类对象。<br>* 如果无法获得该原始字节数组，调用findSystemClass 检查是否可以从本地文件系统中记<br>载。<br>* 如果参数resolve为true,调用resolveClass来解析类对象。<br>* 如果还没有找到类，抛出一个ClassNotFoundException异常。<br>* 否则，返回这个类。<br>现在我们对类加载器的应用知识有个较全面的了解，可以创建自定义类加载器了。在下一部<br>分，我们将讨论CCL。<br>第四部分. CompilingClassLoader<br>CCL给我们展示了类加载器的功能, CCL的目的是让我们的代码能够自动编译和更新。下面描<br>述它是怎么工作的：<br>* 当有一个类的请求时，先检查磁盘的当前目录和子目录上是否存在这个类文件。<br>* 如果没有类文件，但是却有源代码文件，调用Java编译器编译生成类文件。<br>* 如果类文件已经存在，检查该类文件是否比源代码文件陈旧。如果类文件比源代码文件陈<br>旧，调用Java编译器重新生成类文件。<br>* 如果编译失败，或者由于其他原因导致无法从源文件生成类文件，抛出异常ClassNotFou<br>ndException。<br>* 如果还没有获得这个类，可能存在其他的类库里，调用findSystemClass看是否能找到。<br><br>* 如果没有找到，抛出异常ClassNotFoundException。<br>* 否则，返回该类。<br>Java编译是如何实现的？<br>在我们进一步讨论前，我们需要先弄清楚Java的编译过程。通常，Java编译器不仅仅编译指<br>定的那些类。如果指定的那些类需要的话，它还会编译其它的一些相关类。CCL会一个一个<br>的编译我们在应用程序中需要编译的那些类。不过，一般来说，编译器编译完第一个类后，<br>CCL将会发现其实其他需要的相关类已经被编译了。为什么呢？Java编译器使用我们差不多<br>的规则：如果一个了类不存在或者源文件已经被更新，就会编译这个类。Java编译器基本上<br>比CCL早了一步，大部分工作都被Java编译器完成了。我们看起来就像是CCL在编译这些类。<br>在大多数情况下，你将发现它是在主函数类中调用编译器，就仅仅这些而已--简单的一个调<br>用就够了。 不过有一种特殊情况，这些类在第一次出现的时候不编译。如果你根据类名加<br>载一个类，使用方法Class.forName,Java编译器并不知道是否需要这个类。在这种情况下，<br>你发现CCL再次调用编译器来编译该类。第六部分的代码说明了这个过程。<br>使用CompilationClassLoader<br>为了使用CCL,我们不能直接运行我们的程序，必须以一种特殊的方式运行，就像这样：<br>% java Foo arg1 arg2<br>我们这样运行它:<br>% java CCLRun Foo arg1 arg2<br>CCLRun是一个特殊的存根程序，它来创建CompilingClassLoader 并且用它来加载我们的主<br>函数类,这样可以确保所有的整个程序都是由CompilingClassLoader加载的。CCLRun利用Ja<br>va反射API来调用主函数类的主函数并且给这个函数传递参数。想了解更多，参考第六部分<br>的源代码。<br>运行示例<br>我们演示一下整个过程式怎么工作的。<br>主程序是一个叫做Foo的类，它创建一个类Bar的实例。这个Bar实例又创建一个类Baz的实例<br>，类Baz存在于包baz中，这是为了演示CCL如何从子包中加载类。Bar还根据类名加载类Boo<br>，这个也是CCL完成的。所有的类都加载了就可以运行了。利用第六章的源代码来执行这个<br>程序。编译CCLRun和CompilingClassLoader。确保你没有编译其它的类(Foo, Bar, Baz, a<br>nd Boo)，否则CCL将不起作用，。<br>% java CCLRun Foo arg1 arg2<br>CCL: Compiling Foo.java...<br>foo! arg1 arg2<br>bar! arg1 arg2<br>baz! arg1 arg2<br>CCL: Compiling Boo.java...<br>Boo!<br>注意到为了Foo.java第一次调用编译器，同时也把Bar和baz.Baz一起编译了俄。而类Boo<br>直道需要加载的时候，CCL才再次调用编译器来编译它。<br>第五部分.Java2中对类加载器的改进<br>概览<br>在Java1.2和以后的版本中, 类加载器有了很大的改进。以前的代码仍然可以工作， 但是新<br>的系统让我们的实现更容易。这种新模型就是代理委托模型，就是说如果这个类加载器找不<br>到某个类，它会让他的父类加载器来找。系统类加载器是所有类加载器的祖先, 系统类加载<br>器通过默认的方式加载类--也就是从本地文件系统中加载。覆盖loadClass方法一般都尝试<br>几种方式来加载类，如果你写了很多类加载器,你会发现你只是一次又一次在这个复杂的方<br>法中作一些修改而已。Java1.2种loadClass的默认实现包含了寻找类的最普通的途径，允许<br>你覆盖findClass方法，loadClass在适当的是否调用findClass方法。这样做的好处是你不<br>需要覆盖loadClass，你只需要覆盖findClass，这样可以减少工作量。<br>新增方法: findClass<br>这个方法被loadClass的默认实现调用。findClass的目标是包含所有类加载器特定的代码，<br>而不需要重复这些代码(比如在指定的方法失败的时候调用系统类加载器)。<br>新增方法: getSystemClassLoader<br>不论你是否覆盖方法findClass和loadClass, 方法getSystemClassLoader都可以直接访问系<br>统类加载器(而不是通过findSystemClass间接的访问)。<br>新增方法: getParent<br>为了把请求委托给父类加载器，通过这个方法可以获得这个类加载器的父类加载器。当自定<br>义类加载器中的特定方法无法找到类的时候你可能把请求委托给父类加载器。类加载器的父<br>类加载器包含创建这个类加载器的代码。<br>第六部分. 源代码<br>CompilingClassLoader.java<br>以下是文件CompilingClassLoader.java内容<br>import java.io.*;<br>/*<br>CompilingClassLoader动态的编译Java源文件。它检查.class文件是否存在，.class文件是<br>否比源文件陈旧。<br>*/<br>public class CompilingClassLoader extends ClassLoader<br>{<br>// 指定一个文件名，从磁盘读取整个文件内容，返回字节数组。<br>private byte[] getBytes( String filename ) throws IOException {<br>// 获得文件大小。<br>File file = new File( filename );<br>long len = file.length();<br>//创建一个数组刚好可以存放文件的内容。<br>byte raw[] = new byte[(int)len];<br>// 打开文件<br>FileInputStream fin = new FileInputStream( file );<br>// 读取所有内容，如果没法读取，表示发生了一个错误。<br>int r = fin.read( raw );<br>if (r != len)<br>throw new IOException( "Can't read all, "+r+" != "+len );<br>// 别忘了关闭文件。<br>fin.close();<br>// 返回这个数组。<br>return raw;<br>}<br>// 产生一个进程来编译指定的Java源文件，制定文件参数.如果编译成功返回true，否者,<br>// 返回false。<br>private boolean compile( String javaFile ) throws IOException {<br>// 显示当前进度<br>System.out.println( "CCL: Compiling "+javaFile+"..." );<br>// 启动编译器<br>Process p = Runtime.getRuntime().exec( "javac "+javaFile );<br>// 等待编译结束<br>try {<br>p.waitFor();<br>} catch( InterruptedException ie ) { System.out.println( ie ); }<br>// 检查返回码，看编译是否出错。<br>int ret = p.exitValue();<br>// 返回编译是否成功。<br>return ret==0;<br>}<br>// 类加载器的核心代码 -加载类在需要的时候自动编译源文件。<br>public Class loadClass( String name, boolean resolve )<br>throws ClassNotFoundException {<br>//我们的目的是获得一个类对象。<br>Class clas = null;<br>// 首先，检查是否已经出理过这个类。<br>clas = findLoadedClass( name );<br>//System.out.println( "findLoadedClass: "+clas );<br>// 通过类名获得路径名 比如：java.lang.Object =&gt; java/lang/Object<br>String fileStub = name.replace( '.', '/' );<br>// 构建指向源文件和类文件的对象。<br>String javaFilename = fileStub+".java";<br>String classFilename = fileStub+".class";<br>File javaFile = new File( javaFilename );<br>File classFile = new File( classFilename );<br>//System.out.println( "j "+javaFile.lastModified()+" c "<br>//+classFile.lastModified() );<br>// 首先，判断是否需要编译。如果源文件存在而类文件不存在，或者都存在，但是源文件<br>// 较新，说明需要编译。<br>if (javaFile.exists() &amp;&amp;(!classFile.exists() ||<br>javaFile.lastModified() &gt; classFile.lastModified())) {<br>try {<br>// 编译，如果编译失败，我们必须声明失败原因（仅仅使用陈旧的类是不够的）。<br>if (!compile( javaFilename ) || !classFile.exists()) {<br>throw new ClassNotFoundException( "Compile failed: "+javaFilename );<br>}<br>} catch( IOException ie ) {<br>// 可能编译时出现IO错误。<br>throw new ClassNotFoundException( ie.toString() );<br>}<br>}<br>// 确保已经正确编译或者不需要编译，我们开始加载原始字节。<br>try {<br>// 读取字节。<br>byte raw[] = getBytes( classFilename );<br>// 转化为类对象<br>clas = defineClass( name, raw, 0, raw.length );<br>} catch( IOException ie ) {<br>// 这里并不表示失败，可能我们处理的类在本地类库中，如java.lang.Object。<br>}<br>//System.out.println( "defineClass: "+clas );<br>//可能在类库中，以默认的方式加载。<br>if (clas==null) {<br>clas = findSystemClass( name );<br>}<br>//System.out.println( "findSystemClass: "+clas );<br>// 如果参数resolve为true，根据需要解释类。<br>if (resolve &amp;&amp; clas != null)<br>resolveClass( clas );<br>// 如果还没有获得类，说明出错了。<br>if (clas == null)<br>throw new ClassNotFoundException( name );<br>// 否则，返回这个类对象。<br>return clas;<br>}<br>}<br>CCRun.java<br>一下是CCRun.java文件<br>import java.lang.reflect.*;<br>/*<br>CCLRun通过CompilingClassLoader加载类来运行程序。<br>*/<br>public class CCLRun<br>{<br>static public void main( String args[] ) throws Exception {<br>// 第一个参数指定用户要运行的主函数类。<br>String progClass = args[0];<br>// 接下来的参数是传给这个主函数类的参数。<br>String progArgs[] = new String[args.length-1];<br>System.arraycopy( args, 1, progArgs, 0, progArgs.length );<br>// 创建CompilingClassLoader<br>CompilingClassLoader ccl = new CompilingClassLoader();<br>// 通过CCL加载主函数类。<br>Class clas = ccl.loadClass( progClass );<br>// 利用反射调用它的主函数和传递参数。<br>// 产生一个代表主函数的参数类型的类对象。<br>Class mainArgType[] = { (new String[0]).getClass() };<br>// 在类中找到标准的主函数。<br>Method main = clas.getMethod( "main", mainArgType );<br>// 创建参数列表 -在这里，是一个字符串数组。<br>Object argsArray[] = { progArgs };<br>// 调用主函数。<br>main.invoke( null, argsArray );<br>}<br>}<br>Foo.java<br>以下是文件Foo.java内容<br>public class Foo<br>{<br>static public void main( String args[] ) throws Exception {<br>System.out.println( "foo! "+args[0]+" "+args[1] );<br>new Bar( args[0], args[1] );<br>}<br>}<br>Bar.java<br>以下是文件Bar.java内容<br>import baz.*;<br>public class Bar<br>{<br>public Bar( String a, String b ) {<br>System.out.println( "bar! "+a+" "+b );<br>new Baz( a, b );<br>try {<br>Class booClass = Class.forName( "Boo" );<br>Object boo = booClass.newInstance();<br>} catch( Exception e ) {<br>e.printStackTrace();<br>}<br>}<br>}<br>baz/Baz.java<br>以下是文件baz/Baz.java内容<br>package baz;<br>public class Baz<br>{<br>public Baz( String a, String b ) {<br>System.out.println( "baz! "+a+" "+b );<br>}<br>}<br>Boo.java<br>以下是文件Boo.java内容<br>public class Boo<br>{<br>public Boo() {<br>System.out.println( "Boo!" );<br>}<br>}<br>第七部分. 总结<br>总结<br>通过本文你是否意识到，创建自定义类加载器可以让你深入到Java虚拟机的内部。你可以从<br>任何资源加载类文件，或者动态的生成它，这样你就可以通过扩展这些功能做很多你感兴趣<br>的事，还能完成一些强大的功能。<br>关于ClassLoader的其它话题<br>就像本文开头说的，自定义类加载器在Java嵌入式浏览器和小应用程序浏览器中起着重要的<br>作用。下面给出类加载器的其它功能。<br>* 安全： 自定义的类加载器可以在把这个类交给Java虚拟机之前检查它是否有正确的数字<br>签名。你也可以自己创建一个"沙箱"来阻止对某些方法的调用，这是通过检查源代码，阻止<br>该类对沙箱之外的操作来实现的。<br>* 加密：通过自定义类加载器可以动态的解码，所有你的类文件就无法通过反编译被查看到<br>代码。用户需要密码才能运行程序，这个密码用来对代码解密。<br>* 存档：你是否需要将你的代码以某种格式或者压缩形式发布吗？自定义ClassLoader可以<br>从你想要的任何资源中生成字节码文件。<br>* 自提取程序：可以把整个应用程序编译到一个可执行的类文件中，这个文件包括压缩过或<br>者加密过的数据，有了内部类加载器，当程序运行的时候，他把自己解包到内存-不需要事<br>前安装。<br>* 动态生成：可以动态的生成那些被引用的类-整个程序需要用的类都可以动态的生成然后<br>交给Java虚拟机。<br><br><br>
<p id=TBPingURL>Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=94763</p>
<img src ="http://www.blogjava.net/realsmy/aggbug/109198.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/realsmy/" target="_blank">久城</a> 2007-04-08 10:11 <a href="http://www.blogjava.net/realsmy/articles/109198.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>