<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>小何与小乌的故事</title>
	<atom:link href="http://robinhesky.blog.ubuntu.org.cn/feed/" rel="self" type="application/rss+xml" />
	<link>http://robinhesky.blog.ubuntu.org.cn</link>
	<description>谁都不是一座孤岛，自成一体</description>
	<lastBuildDate>Wed, 23 Jan 2008 03:51:32 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>放假了，笔记暂缓</title>
		<link>http://robinhesky.blog.ubuntu.org.cn/2008/01/23/%e6%94%be%e5%81%87%e4%ba%86%ef%bc%8c%e7%ac%94%e8%ae%b0%e6%9a%82%e7%bc%93/</link>
		<comments>http://robinhesky.blog.ubuntu.org.cn/2008/01/23/%e6%94%be%e5%81%87%e4%ba%86%ef%bc%8c%e7%ac%94%e8%ae%b0%e6%9a%82%e7%bc%93/#comments</comments>
		<pubDate>Wed, 23 Jan 2008 03:51:32 +0000</pubDate>
		<dc:creator>robinhesky</dc:creator>
		
		<guid isPermaLink="false">http://robinhesky.blog.ubuntu.org.cn/2008/01/23/%e6%94%be%e5%81%87%e4%ba%86%ef%bc%8c%e7%ac%94%e8%ae%b0%e6%9a%82%e7%bc%93/</guid>
		<description><![CDATA[放假了，好好过年！笔记就暂缓了！
]]></description>
			<content:encoded><![CDATA[<p>放假了，好好过年！笔记就暂缓了！</p>
]]></content:encoded>
			<wfw:commentRss>http://robinhesky.blog.ubuntu.org.cn/2008/01/23/%e6%94%be%e5%81%87%e4%ba%86%ef%bc%8c%e7%ac%94%e8%ae%b0%e6%9a%82%e7%bc%93/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>thinking in java 第六、七章</title>
		<link>http://robinhesky.blog.ubuntu.org.cn/2008/01/21/thinking-in-java-%e7%ac%ac%e5%85%ad%e3%80%81%e4%b8%83%e7%ab%a0/</link>
		<comments>http://robinhesky.blog.ubuntu.org.cn/2008/01/21/thinking-in-java-%e7%ac%ac%e5%85%ad%e3%80%81%e4%b8%83%e7%ab%a0/#comments</comments>
		<pubDate>Mon, 21 Jan 2008 15:01:43 +0000</pubDate>
		<dc:creator>robinhesky</dc:creator>
				<category><![CDATA[java学习]]></category>

		<guid isPermaLink="false">http://robinhesky.blog.ubuntu.org.cn/2008/01/21/thinking-in-java-%e7%ac%ac%e5%85%ad%e3%80%81%e4%b8%83%e7%ab%a0/</guid>
		<description><![CDATA[ 1.类再生
Java引人注目的一项特性是代码的重复使用或者再生。但最具革命意义的是，除代码的复制和修改以外，我们还能做多得多的其他事情。”
在象C那样的程序化语言里，代码的重复使用早已可行，但效果不是特别显著。与Java的其他地方一样，这个方案解决的也是与类有关的问题。我们通过创建新类来重复使用代码，但却用不着重新创建，可以直接使用别人已建好并调试好的现成类。但这样做必须保证不会干扰原有的代码。
有两个达到这一目标的方法：
（1）在新类里简单地创建原有类的对象。我们把这种方法叫作“合成”，因为新类由现有类的对象合并而成。我们只是简单地重复利用代码的功能，而不是采用它的形式。
（2）创建一个新类，将其作为现有类的一个“类型”。我们可以原样采取现有类的形式，并在其中加入新代码，同时不会对现有的类产生影响。这种魔术般的行为叫作“继承”（Inheritance），涉及的大多数工作都是由编译器完成的
每种非基本类型的对象都有一个toString()方法。若编译器本来希望一个String，但却获得某个这样的对象，就会调用这个方法。所以在下面这个表达式中：
System.out.println(&#8221;source = &#8221; + source) ;
编译器会发现我们试图向一个WaterSource添加一个String对象（&#8221;source =&#8221;）。这对它来说是不可接受的，因为我们只能将一个字串“添加”到另一个字串，所以它会说：“我要调用toString()，把source转换成字串！”经这样处理后，它就能编译两个字串，并将结果字串传递给一个System.out.println()。每次随同自己创建的一个类允许这种行为的时候，都只需要写一个toString()方法。
2.初始化的顺序
我个人的理解是，如果有派生的关系，先从根类开始初始化（其它类都由它派生出来），如果各个类中有static成员，那最初初始化的是static，再从根类开始初始化。而根类的初始化顺序应为先初始化成员，再调用构造函数进行初始化，其派生类也依从这样的顺序。而如果的确要实行清除操作，则先清除最底层的派生类，与初始化顺序刚好相反，这样做的理由时，再清除过程中派生类可能还用到基类的方法。
3.关于多态性
典型的例子如下：
public class PrivateOverride {
  //private static Test monitor = new Test();
  private void f(String s) {
    System.out.println(&#8221;private f()&#8221;+s);
  }
  public static void main(String[] args) {
   PrivateOverride po = new Derived();
    po.f(&#8221;哈哈&#8221;);
    /*monitor.expect(new String[] {
      &#8220;private f()&#8221;
    });*/
  }
}
class Derived extends PrivateOverride {
    private void f(String s) {
    System.out.println(&#8221;public f()&#8221;+s);
  [...]]]></description>
			<content:encoded><![CDATA[<p> 1.类再生</p>
<p>Java引人注目的一项特性是代码的重复使用或者再生。但最具革命意义的是，除代码的复制和修改以外，我们还能做多得多的其他事情。”</p>
<p>在象C那样的程序化语言里，代码的重复使用早已可行，但效果不是特别显著。与Java的其他地方一样，这个方案解决的也是与类有关的问题。我们通过创建新类来重复使用代码，但却用不着重新创建，可以直接使用别人已建好并调试好的现成类。但这样做必须保证不会干扰原有的代码。</p>
<p>有两个达到这一目标的方法：</p>
<p>（1）在新类里简单地创建原有类的对象。我们把这种方法叫作“合成”，因为新类由现有类的对象合并而成。我们只是简单地重复利用代码的功能，而不是采用它的形式。</p>
<p>（2）创建一个新类，将其作为现有类的一个“类型”。我们可以原样采取现有类的形式，并在其中加入新代码，同时不会对现有的类产生影响。这种魔术般的行为叫作“继承”（Inheritance），涉及的大多数工作都是由编译器完成的</p>
<p>每种非基本类型的对象都有一个toString()方法。若编译器本来希望一个String，但却获得某个这样的对象，就会调用这个方法。所以在下面这个表达式中：<br />
System.out.println(&#8221;source = &#8221; + source) ;<br />
编译器会发现我们试图向一个WaterSource添加一个String对象（&#8221;source =&#8221;）。这对它来说是不可接受的，因为我们只能将一个字串“添加”到另一个字串，所以它会说：“我要调用toString()，把source转换成字串！”经这样处理后，它就能编译两个字串，并将结果字串传递给一个System.out.println()。每次随同自己创建的一个类允许这种行为的时候，都只需要写一个toString()方法。</p>
<p>2.初始化的顺序</p>
<p>我个人的理解是，如果有派生的关系，先从根类开始初始化（其它类都由它派生出来），如果各个类中有static成员，那最初初始化的是static，再从根类开始初始化。而根类的初始化顺序应为先初始化成员，再调用构造函数进行初始化，其派生类也依从这样的顺序。而如果的确要实行清除操作，则先清除最底层的派生类，与初始化顺序刚好相反，这样做的理由时，再清除过程中派生类可能还用到基类的方法。</p>
<p>3.关于多态性</p>
<p>典型的例子如下：</p>
<p>public class PrivateOverride {<br />
  //private static Test monitor = new Test();<br />
  private void f(String s) {<br />
    System.out.println(&#8221;private f()&#8221;+s);<br />
  }<br />
  public static void main(String[] args) {<br />
   PrivateOverride po = new Derived();<br />
    po.f(&#8221;哈哈&#8221;);<br />
    /*monitor.expect(new String[] {<br />
      &#8220;private f()&#8221;<br />
    });*/<br />
  }<br />
}</p>
<p>class Derived extends PrivateOverride {<br />
    private void f(String s) {<br />
    System.out.println(&#8221;public f()&#8221;+s);<br />
  }<br />
} ///:~</p>
<p>编译不会报错，但结果不是输出：publicf()哈,因为再基类里的private方法表明它是final的，即是不会被重载的，这时侯，虽然f函数名称相同，但派生类中的f相当于以个全新的类，而基类中的f函数对派生类来说是不可见的，所以输出的为privatef)哈</p>
<p>再方法前加了private，static,final都可认为是早期绑定！</p>
<p>多态性”（Polymorphism）从另一个角度将接口从具体的实施细节中分离出来，亦即实现了“是什么”与“怎样做”两个模块的分离。利用多形性的概念，代码的组织以及可读性均能获得改善。此外，还能创建“易于扩展”的程序。无论在项目的创建过程中，还是在需要加入新特性的时候，它们都可以方便地“成长”。</p>
<p>3.方法调用的绑定</p>
<p>将一个方法调用同一个方法主体连接到一起就称为“绑定”（Binding）。若在程序运行以前执行绑定（由编译器和链接程序，如果有的话），就叫作“早期绑定”。大家以前或许从未听说过这个术语，因为它在任何程序化语言里都是不可能的。C编译器只有一种方法调用，那就是“早期绑定”。<br />
后期绑定”，它意味着绑定在运行期间进行，以对象的类型为基础。后期绑定也叫作“动态绑定”或“运行期绑定”。若一种语言实现了后期绑定，同时必须提供一些机制，可在运行期间判断对象的类型，并分别调用适当的方法。也就是说，编译器此时依然不知道对象的类型，但方法调用机制能自己去调查，找到正确的方法主体。不同的语言对后期绑定的实现方法是有所区别的。但我们至少可以这样认为：它们都要在对象中安插某些特殊类型的信息。<br />
Java中绑定的所有方法都采用后期绑定技术，除非一个方法已被声明成final，private,static。这意味着我们通常不必决定是否应进行后期绑定——它是自动发生的。</p>
<p>4.抽象类和抽象方法</p>
<p>Java专门提供了一种机制，名为“抽象方法”。它属于一种不完整的方法，只含有一个声明，没有方法主体。下面是抽象方法声明时采用的语法：<br />
abstract void X();<br />
包含了抽象方法的一个类叫作“抽象类”。如果一个类里包含了一个或多个抽象方法，类就必须指定成abstract（抽象）。否则，编译器会向我们报告一条出错消息。<br />
若一个抽象类是不完整的，那么一旦有人试图生成那个类的一个对象，编译器又会采取什么行动呢？由于不能安全地为一个抽象类创建属于它的对象，所以会从编译器那里获得一条出错提示。通过这种方法，编译器可保证抽象类的“纯洁性”，我们不必担心会误用它。<br />
如果从一个抽象类继承，而且想生成新类型的一个对象，就必须为基础类中的所有抽象方法提供方法定义。如果不这样做（完全可以选择不做），则衍生类也会是抽象的，而且编译器会强迫我们用abstract关键字标志那个类的“抽象”本质。</p>
]]></content:encoded>
			<wfw:commentRss>http://robinhesky.blog.ubuntu.org.cn/2008/01/21/thinking-in-java-%e7%ac%ac%e5%85%ad%e3%80%81%e4%b8%83%e7%ab%a0/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>深入浅出MFC-MFC六大关键技术笔记</title>
		<link>http://robinhesky.blog.ubuntu.org.cn/2008/01/20/%e6%b7%b1%e5%85%a5%e6%b5%85%e5%87%bamfc-mfc%e5%85%ad%e5%a4%a7%e5%85%b3%e9%94%ae%e6%8a%80%e6%9c%af%e7%ac%94%e8%ae%b0/</link>
		<comments>http://robinhesky.blog.ubuntu.org.cn/2008/01/20/%e6%b7%b1%e5%85%a5%e6%b5%85%e5%87%bamfc-mfc%e5%85%ad%e5%a4%a7%e5%85%b3%e9%94%ae%e6%8a%80%e6%9c%af%e7%ac%94%e8%ae%b0/#comments</comments>
		<pubDate>Sun, 20 Jan 2008 15:58:03 +0000</pubDate>
		<dc:creator>robinhesky</dc:creator>
				<category><![CDATA[VC]]></category>

		<guid isPermaLink="false">http://robinhesky.blog.ubuntu.org.cn/2008/01/20/%e6%b7%b1%e5%85%a5%e6%b5%85%e5%87%bamfc-mfc%e5%85%ad%e5%a4%a7%e5%85%b3%e9%94%ae%e6%8a%80%e6%9c%af%e7%ac%94%e8%ae%b0/</guid>
		<description><![CDATA[
上图为MFC各主要类的关系图
1.RTTI(执行时期类型识别)
以shape 为例，希望达到的技术效果如下，再程序执行期间可以识别对象的类型：
CSquare* pSquare = new CSquare;
cout &#60;&#60; pSquare-&#62;IsKindOf(CSquare); // 应该获得1（TRUE）
cout &#60;&#60; pSquare-&#62;IsKindOf(CRect); // 应该获得1（TRUE）
cout &#60;&#60; pSquare-&#62;IsKindOf(CShape); // 应该获得1（TRUE）
以MFC 的类别阶层来说，希望：
CMyDoc* pMyDoc = new CMyDoc;
cout &#60;&#60; pMyDoc-&#62;IsKindOf(CMyDoc); // 应该获得1（TRUE）
cout &#60;&#60; pMyDoc-&#62;IsKindOf(CDocument); // 应该获得1（TRUE）
cout &#60;&#60; pMyDoc-&#62;IsKindOf(CCmdTarget); // 应该获得1（TRUE）
cout &#60;&#60; pMyDoc-&#62;IsKindOf(CWnd); // 应该获得0（FALSE）
要达到RTTI 的能力，我们（类别库的设计者）一定要在类别构造起来的时候，记录必要的信息，以建立型录。型录中的类别信息，最好以串行（linked list）方式串接起来，将来方便一一比对。我们这份「类别型录」的串行元素将以CRuntimeClass 描述之，那是一个结构。结构如下：
struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema; // schema number of the loaded class
CObject* (PASCAL* [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://robinhesky.blog.ubuntu.org.cn/files/2008/01/mfc.JPG" title="mfc类别阶层" rel="lightbox"><img src="http://robinhesky.blog.ubuntu.org.cn/files/2008/01/mfc.thumbnail.JPG" alt="mfc类别阶层" /></a></p>
<p>上图为MFC各主要类的关系图</p>
<p>1.RTTI(执行时期类型识别)</p>
<p>以shape 为例，希望达到的技术效果如下，再程序执行期间可以识别对象的类型：<br />
CSquare* pSquare = new CSquare;<br />
cout &lt;&lt; pSquare-&gt;IsKindOf(CSquare); // 应该获得1（TRUE）<br />
cout &lt;&lt; pSquare-&gt;IsKindOf(CRect); // 应该获得1（TRUE）<br />
cout &lt;&lt; pSquare-&gt;IsKindOf(CShape); // 应该获得1（TRUE）</p>
<p>以MFC 的类别阶层来说，希望：<br />
CMyDoc* pMyDoc = new CMyDoc;<br />
cout &lt;&lt; pMyDoc-&gt;IsKindOf(CMyDoc); // 应该获得1（TRUE）<br />
cout &lt;&lt; pMyDoc-&gt;IsKindOf(CDocument); // 应该获得1（TRUE）<br />
cout &lt;&lt; pMyDoc-&gt;IsKindOf(CCmdTarget); // 应该获得1（TRUE）<br />
cout &lt;&lt; pMyDoc-&gt;IsKindOf(CWnd); // 应该获得0（FALSE）</p>
<p>要达到RTTI 的能力，我们（类别库的设计者）一定要在类别构造起来的时候，记录必要的信息，以建立型录。型录中的类别信息，最好以串行（linked list）方式串接起来，将来方便一一比对。我们这份「类别型录」的串行元素将以CRuntimeClass 描述之，那是一个结构。结构如下：</p>
<p>struct CRuntimeClass<br />
{<br />
// Attributes<br />
LPCSTR m_lpszClassName;<br />
int m_nObjectSize;<br />
UINT m_wSchema; // schema number of the loaded class<br />
CObject* (PASCAL* m_pfnCreateObject)(); // NULL =&gt; abstract class<br />
CRuntimeClass* m_pBaseClass;<br />
// CRuntimeClass objects linked together in simple list<br />
static CRuntimeClass* pFirstClass; // start of class list<br />
CRuntimeClass* m_pNextClass; // linked list of registered classes</p>
<p>}；</p>
<p>每一个类别都能拥有这样一个CRuntimeClass 成员变量，并且最好有一定的命名规则（例如在类别名称之前冠以&#8221;class&#8221; 作为它的名称），然后，经由某种手段将整个类别库构造好之后，「类别型录」能呈现类似这样的风貌：</p>
<p><a href="http://robinhesky.blog.ubuntu.org.cn/files/2008/01/list.JPG" title="类别型录表" rel="lightbox"><img src="http://robinhesky.blog.ubuntu.org.cn/files/2008/01/list.thumbnail.JPG" alt="类别型录表" /></a></p>
<p>上图的实现， 是通过以系列宏的声明实现的：</p>
<p>#define DECLARE_DYNAMIC(class_name) \<br />
public: \<br />
static CRuntimeClass class##class_name; \<br />
virtual CRuntimeClass* GetRuntimeClass() const</p>
<p>但这样并没有把类型表连接起来，还要通通过下面的宏定义来实现：</p>
<p>#define IMPLEMENT_DYNAMIC(class_name, base_class_name) \<br />
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL)</p>
<p>#define _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name,wSchema,pfnNew) \<br />
static char _lpsz##class_name[] = #class_name; \<br />
CRuntimeClass class_name::class##class_name = { \<br />
_lpsz##class_name, sizeof(class_name), wSchema, pfnNew, \<br />
RUNTIME_CLASS(base_class_name), NULL }; \<br />
static AFX_CLASSINIT _init_##class_name(&amp;class_name::class##class_name); \结构体的构造函数</p>
<p>CRuntimeClass* class_name::GetRuntimeClass() const \<br />
{ return &amp;class_name::class##class_name; } \</p>
<p>#define RUNTIME_CLASS(class_name) \<br />
(&amp;class_name::class##class_name)</p>
<p>而其中负责把各个类别连接起来的，主要靠下面这段宏程序：</p>
<p>struct AFX_CLASSINIT<br />
{ AFX_CLASSINIT(CRuntimeClass* pNewClass); }</p>
<p>AFX_CLASSINIT::AFX_CLASSINIT(CRuntimeClass* pNewClass)<br />
{<br />
pNewClass-&gt;m_pNextClass = CRuntimeClass::pFirstClass;<br />
CRuntimeClass::pFirstClass = pNewClass;</p>
<p>}</p>
<p>这样利用静态指针PFIRSTCLASS,就可以把各个类型别录表连接起来，每当创建一个CRUNTIMECLASS后，pfirstclass都指向当前结构体。</p>
<p>对于classcobject这个头部，因为它的基类为Null,故要特别设计</p>
<p>// in header file<br />
class CObject<br />
{<br />
public:<br />
virtual CRuntimeClass* GetRuntimeClass() const;<br />
&#8230;<br />
public:<br />
static CRuntimeClass classCObject</p>
<p>}；</p>
<p>// in implementation file<br />
static char szCObject[] = &#8220;CObject&#8221;;<br />
struct CRuntimeClass CObject::classCObject =<br />
{ szCObject, sizeof(CObject), 0xffff, NULL, NULL };<br />
static AFX_CLASSINIT _init_CObject(&amp;CObject::classCObject);<br />
CRuntimeClass* CObject::GetRuntimeClass() const<br />
{<br />
return &amp;CObject::classCObject;</p>
<p>}；</p>
<p>并且，CRuntimeClass 中的static 成员变量应该要初始化</p>
<p>CRuntimeClass* CRuntimeClass::pFirstClass = NULL</p>
<p>这样，整个类型别录表就建立起来了</p>
<p>那么类型识别就可以很容易实现：</p>
<p>为CObject 加上一个IsKindOf 函数，于是此函数将被所有类别继承。它将把<br />
参数所指定的某个CRuntimeClass 对象拿来与类别型录中的元素一一比对。比<br />
对成功（在型录中有发现），就传回TRUE，否则传回FALSE：<br />
// in header file<br />
class CObject<br />
{<br />
public:<br />
&#8230;<br />
BOOL IsKindOf(const CRuntimeClass* pClass) const;<br />
};<br />
// in implementation file<br />
BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const<br />
{<br />
CRuntimeClass* pClassThis = GetRuntimeClass();<br />
while (pClassThis != NULL)<br />
{<br />
if (pClassThis == pClass)<br />
return TRUE;<br />
pClassThis = pClassThis-&gt;m_pBaseClass;<br />
}<br />
return FALSE; // walked to the top, no match</p>
<p>}</p>
<p>2.Dynamic Creatiion(动态生成)</p>
<p>如果能够把类别的大小记录在类别型录中，把构造函数（注意，这里并非指C++ 构造式，而是指即将出现CRuntimeClass::CreateObject）也记录在类别型录中，当程序在执行时期获得一个类别名称，它就可以在「类别型录网」中找出对应的元素，然后调用其构造函数（这里并非指C++ 构造式），产生出对象。</p>
<p>于是CRuntimeClass有了变化</p>
<p>struct CRuntimeClass<br />
{<br />
// Attributes<br />
LPCSTR m_lpszClassName;<br />
int m_nObjectSize;<br />
UINT m_wSchema; // schema number of the loaded class<br />
CObject* (PASCAL* m_pfnCreateObject)(); // NULL =&gt; abstract class<br />
CRuntimeClass* m_pBaseClass;<br />
CObject* CreateObject();<br />
static CRuntimeClass* PASCAL Load();<br />
// CRuntimeClass objects linked together in simple list<br />
static CRuntimeClass* pFirstClass; // start of class list<br />
CRuntimeClass* m_pNextClass; // linked list of registered classes</p>
<p>}</p>
<p>为了适应CRuntimeClass 中新增的成员变量， 再添两个宏如下：</p>
<p>#define DECLARE_DYNCREATE(class_name) \<br />
DECLARE_DYNAMIC(class_name) \<br />
static CObject* PASCAL CreateObject();</p>
<p>#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \<br />
CObject* PASCAL class_name::CreateObject() \<br />
{ return new class_name; } \<br />
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \<br />
class_name::CreateObject)</p>
<p>从宏的定义可以很清楚看出，拥有动态生成（Dynamic Creation）能力的类别库，<br />
必然亦拥有执行时期类型识别（RTTI）能力，因为_DYNCREATE 宏涵盖了_DYNAMIC<br />
宏。</p>
<p>现在，我们开始仿真动态生成。首先在main 函数中加上这一段码：</p>
<p>void main()<br />
{<br />
&#8230;<br />
//Test Dynamic Creation<br />
CRuntimeClass* pClassRef;<br />
CObject* pOb;<br />
while(1)<br />
{</p>
<p>if ((pClassRef = CRuntimeClass::Load()) == NULL)<br />
break;<br />
pOb = pClassRef-&gt;CreateObject();<br />
if (pOb != NULL)<br />
pOb-&gt;SayHello();<br />
}<br />
}<br />
CRuntimeClass::CreateObject 和 CRuntimeClass::Load 如㆘：<br />
// in implementation file<br />
CObject* CRuntimeClass::CreateObject()<br />
{<br />
if (m_pfnCreateObject == NULL)<br />
{<br />
TRACE1(&#8221;Error: Trying to create object which is not &#8221;<br />
&#8220;DECLARE_DYNCREATE \nor DECLARE_SERIAL: %hs.\n&#8221;,<br />
m_lpszClassName);<br />
return NULL;<br />
}<br />
CObject* pObject = NULL;<br />
pObject = (*m_pfnCreateObject)();<br />
return pObject;<br />
}<br />
CRuntimeClass* PASCAL CRuntimeClass::Load()<br />
{<br />
char szClassName[64];<br />
CRuntimeClass* pClass;<br />
// JJHOU : instead of Load from file, we Load from cin.<br />
cout &lt;&lt; &#8220;enter a class name&#8230; &#8220;;<br />
cin &gt;&gt; szClassName;<br />
for (pClass = pFirstClass; pClass != NULL; pClass = pClass-&gt;m_pNextClass)<br />
{<br />
if (strcmp(szClassName, pClass-&gt;m_lpszClassName) == 0)<br />
return pClass;<br />
}<br />
TRACE1(&#8221;Error: Class not found: %s \n&#8221;, szClassName);<br />
return NULL; // not found</p>
<p>}</p>
<p>3.Persistence(永续生存机制)</p>
<p>对象导向有一个术语：Persistence，意思就是把对象永久保留下来，一般是把对象写到文件里去。</p>
<p>MFC 有一套Serialize 机制，目的在于把档名的选择、文件的开关、缓冲区的建立、资<br />
料的读写、萃取运算子（&gt;&gt;）和嵌入运算子（&lt;&lt;）的多载（overload）、对象的动态生成<br />
&#8230;<br />
都包装起来。<br />
上述Serialize 的各部份工作，除了资料的读写和对象的动态生成，其余都是支节。动态<br />
生成的技术已经解决，让我们集中火力，分析资料的读写动作。</p>
<p>06 00 ;CObList elements count<br />
07 00 ;class name string length<br />
43 53 74 72 6F 6B 65 ;&#8221;CStroke&#8221;<br />
02 00 ;DWordArray size<br />
28 00 13 00 ;point<br />
28 00 13 00 ;point<br />
0A 00 ;class name string length<br />
43 52 65 63 74 61 6E 67 6C 65 ;&#8221;CRectangle&#8221;<br />
11 00 22 00 33 00 44 00 ;CRect<br />
07 00 ;class name string length<br />
43 43 69 72 63 6C 65 ;&#8221;CCircle&#8221;<br />
55 00 66 00 77 00 ;CPoint &amp; radius<br />
07 00 ;class name string length<br />
43 53 74 72 6F 6B 65 ;&#8221;CStroke&#8221;<br />
02 00 ;DWordArray size<br />
28 00 35 00 ;point<br />
28 00 35 00 ;point<br />
0A 00 ;class name string length<br />
43 52 65 63 74 61 6E 67 6C 65 ;&#8221;CRectangle&#8221;<br />
11 00 22 00 33 00 44 00 ;CRect<br />
07 00 ;class name string length<br />
43 43 69 72 63 6C 65 ;&#8221;CCircle&#8221;<br />
55 00 66 00 77 00 ;CPoint &amp; radius</p>
<p>我们可以在每次记录对象内容的时候，先写入一个代码，表示此对象之类别是否曾在档案中记录过了。如果是新类别，乖乖地记录其类别名称；如果是旧类别，则以代码表示。这样可以节省文件大小以及程序用于解析的时间。</p>
<p>还有一个问题。文件的「版本」如何控制？旧版程序读取新版文件，新版程序读取旧版文件，都可能出状况。为了防弊，最好把版本号码记录上去。最好是每个类别有自己的版本号码</p>
<p>20 03 84 03 ;Document Size<br />
06 00 ;CObList elements count<br />
FF FF ;new class tag<br />
02 00 ;schema<br />
07 00 ;class name string length<br />
43 53 74 72 6F 6B 65 ;&#8221;CStroke&#8221;<br />
02 00 ;DWordArray size<br />
28 00 13 00 ;point<br />
28 00 13 00 ;point<br />
FF FF ;new class tag<br />
01 00 ;schema<br />
0A 00 ;class name string length<br />
43 52 65 63 74 61 6E 67 6C 65 ;&#8221;CRectangle&#8221;<br />
11 00 22 00 33 00 44 00 ;CRect<br />
FF FF ;new class tag<br />
01 00 ;schema<br />
07 00 ;class name string length<br />
43 43 69 72 63 6C 65 ;&#8221;CCircle&#8221;<br />
55 00 66 00 77 00 ;CPoint &amp; radius<br />
01 80 ;old class tag<br />
02 00 ;DWordArray size<br />
28 00 35 00 ;point<br />
28 00 35 00 ;point<br />
03 80 ;old class tag<br />
11 00 22 00 33 00 44 00 ;CRect<br />
05 80 ;old class tag<br />
55 00 66 00 77 00 ;CPoint &amp; radius</p>
<p>类别之能够进行文件读写动作，前提是拥有动态生成的能力，所以，MFC 设计了两个宏<br />
DECLARE_SERIAL 和IMPLEMENT_SERIAL：</p>
<p>#define DECLARE_SERIAL(class_name) \<br />
DECLARE_DYNCREATE(class_name) \<br />
friend CArchive&amp; AFXAPI operator&gt;&gt;(CArchive&amp; ar, class_name* &amp;pOb);<br />
#define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \<br />
CObject* PASCAL class_name::CreateObject() \<br />
{ return new class_name; } \<br />
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \<br />
class_name::CreateObject) \<br />
CArchive&amp; AFXAPI operator&gt;&gt;(CArchive&amp; ar, class_name* &amp;pOb) \<br />
{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \<br />
return ar; } \</p>
<p>为了在每一个对象被处理（读或写）之前，能够处理琐屑的工作，诸如判断是否第一次<br />
出现、记录版本号码、记录文件名等工作，CRuntimeClass 需要两个函数Load 和Store</p>
<p>struct CRuntimeClass<br />
{<br />
// Attributes<br />
LPCSTR m_lpszClassName;<br />
int m_nObjectSize;<br />
UINT m_wSchema; // schema number of the loaded class<br />
CObject* (PASCAL* m_pfnCreateObject)(); // NULL =&gt; abstract class<br />
CRuntimeClass* m_pBaseClass;<br />
CObject* CreateObject();<br />
void Store(CArchive&amp; ar) const;<br />
static CRuntimeClass* PASCAL Load(CArchive&amp; ar, UINT* pwSchemaNum);<br />
// CRuntimeClass objects linked together in simple list<br />
static CRuntimeClass* pFirstClass; // start of class list<br />
CRuntimeClass* m_pNextClass; // linked list of registered classes</p>
<p>}；</p>
<p>// Runtime class serialization code<br />
CRuntimeClass* PASCAL CRuntimeClass::Load(CArchive&amp; ar, UINT* pwSchemaNum)<br />
{<br />
WORD nLen;<br />
char szClassName[64];<br />
CRuntimeClass* pClass;<br />
ar &gt;&gt; (WORD&amp;)(*pwSchemaNum) &gt;&gt; nLen;<br />
if (nLen &gt;= sizeof(szClassName) || ar.Read(szClassName, nLen) != nLen)<br />
return NULL;<br />
szClassName[nLen] = &#8221;;<br />
for (pClass = pFirstClass; pClass != NULL; pClass = pClass-&gt;m_pNextClass)<br />
{<br />
if (lstrcmp(szClassName, pClass-&gt;m_lpszClassName) == 0)<br />
return pClass;<br />
}<br />
return NULL; // not found<br />
}<br />
void CRuntimeClass::Store(CArchive&amp; ar) const<br />
// stores a runtime class description<br />
{<br />
WORD nLen = (WORD)lstrlenA(m_lpszClassName);<br />
ar &lt;&lt; (WORD)m_wSchema &lt;&lt; nLen;<br />
ar.Write(m_lpszClassName, nLen*sizeof(char));<br />
}</p>
<p>4.Message  Mapping(消息映射)</p>
<p>但是，MFC 之中用来处理消息的C++ 类别，并不呈单鞭发展。作为application framework<br />
的重要架构之一的document/view，也具有处理消息的能力（你现在可能还不清楚什么是<br />
document/view，没有关系）。因此，消息藉以攀爬的路线应该有横流的机会，如下图所示：</p>
<p><a href="http://robinhesky.blog.ubuntu.org.cn/files/2008/01/messageloop.JPG" title="消息路由" rel="lightbox"><img src="http://robinhesky.blog.ubuntu.org.cn/files/2008/01/messageloop.thumbnail.JPG" alt="消息路由" /></a></p>
<p>为了尽量降低对正常（一般）类别声明和定义的影响，我们希望，最好能够像RTTI 和<br />
Dynamic Creation 一样，用一两个宏就完成这巨大蜘蛛网的构造。最好能够像<br />
DECLARE_DYNAMIC 和IMPLEMENT_DYNAMIC 宏那么方便。</p>
<p>struct AFX_MSGMAP<br />
{<br />
AFX_MSGMAP* pBaseMessageMap;<br />
AFX_MSGMAP_ENTRY* lpEntries;<br />
};<br />
struct AFX_MSGMAP_ENTRY // MFC 4.0 format<br />
{<br />
UINT nMessage; // windows message<br />
UINT nCode; // control code or WM_NOTIFY code<br />
UINT nID; // control ID (or 0 for windows messages)<br />
UINT nLastID; // used for entries specifying a range of control id&#8217;s<br />
UINT nSig; // signature type (action) or pointer to message #<br />
AFX_PMSG pfn; // routine to call (or special value)<br />
};<br />
typedef void (CCmdTarget::*AFX_PMSG)(void);</p>
<p>然后我们定义一个宏：#define DECLARE_MESSAGE_MAP() \<br />
static AFX_MSGMAP_ENTRY _messageEntries[]; \<br />
static AFX_MSGMAP messageMap; \<br />
virtual AFX_MSGMAP* GetMessageMap() const</p>
<p>于是，DECLARE_MESSAGE_MAP 就相当于声明了这样一个数据结构：</p>
<p><a href="http://robinhesky.blog.ubuntu.org.cn/files/2008/01/messagemap.JPG" title="messagemap" rel="lightbox"><img src="http://robinhesky.blog.ubuntu.org.cn/files/2008/01/messagemap.thumbnail.JPG" alt="messagemap" /></a></p>
<p>于是，DECLARE_MESSAGE_MAP 就相当于声明了这样一个数据结构：<br />
这个数据结构的内容填塞工作由三个宏完成： #define BEGIN_MESSAGE_MAP(theClass, baseClass) \<br />
AFX_MSGMAP* theClass::GetMessageMap() const \<br />
{ return &amp;theClass::messageMap; } \<br />
AFX_MSGMAP theClass::messageMap = \<br />
{ &amp;(baseClass::messageMap), \<br />
(AFX_MSGMAP_ENTRY*) &amp;(theClass::_messageEntries) }; \<br />
AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \<br />
{<br />
#define ON_COMMAND(id, memberFxn) \<br />
{ WM_COMMAND, 0, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)memberFxn },<br />
#define END_MESSAGE_MAP() \<br />
{ 0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \<br />
};<br />
enum AfxSig<br />
{<br />
AfxSig_end = 0, // [marks end of message map]<br />
AfxSig_vv,<br />
};</p>
<p><a href="http://robinhesky.blog.ubuntu.org.cn/files/2008/01/messageflow.JPG" title="消息流动图" rel="lightbox"><img src="http://robinhesky.blog.ubuntu.org.cn/files/2008/01/messageflow.thumbnail.JPG" alt="消息流动图" /></a></p>
<p>5.Command Routing(命令绕行)</p>
<p>如果是一般的Windows 消息（WM_xxx），一定是由衍生类别流向基础类别，<br />
没有旁流的可能。<br />
如果是命令消息WM_COMMAND，就有奇特的路线了：</p>
<p>书上给出了个仿真例子，要理解它，必须理解虚函数!代码就不写了。</p>
<p>这并是我对这一章重要的地方的笔记，有些部分还不是很懂（特别是串行化部分） 。希望在以后几章的学习中，能加深对这几个重要技术的理解。</p>
]]></content:encoded>
			<wfw:commentRss>http://robinhesky.blog.ubuntu.org.cn/2008/01/20/%e6%b7%b1%e5%85%a5%e6%b5%85%e5%87%bamfc-mfc%e5%85%ad%e5%a4%a7%e5%85%b3%e9%94%ae%e6%8a%80%e6%9c%af%e7%ac%94%e8%ae%b0/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>看完了深入浅出C++第三章（MFC六大关键技术之模拟），thinking in java第六章的3/4！</title>
		<link>http://robinhesky.blog.ubuntu.org.cn/2008/01/19/%e7%9c%8b%e5%ae%8c%e4%ba%86%e4%ba%86%e6%b7%b1%e5%85%a5%e6%b5%85%e5%87%bac%e7%ac%ac%e4%b8%80%e7%ab%a0%ef%bc%8cthinking-in-java%e7%ac%ac%e5%85%ad%e7%ab%a0%e7%9a%8434%ef%bc%81/</link>
		<comments>http://robinhesky.blog.ubuntu.org.cn/2008/01/19/%e7%9c%8b%e5%ae%8c%e4%ba%86%e4%ba%86%e6%b7%b1%e5%85%a5%e6%b5%85%e5%87%bac%e7%ac%ac%e4%b8%80%e7%ab%a0%ef%bc%8cthinking-in-java%e7%ac%ac%e5%85%ad%e7%ab%a0%e7%9a%8434%ef%bc%81/#comments</comments>
		<pubDate>Sat, 19 Jan 2008 15:54:48 +0000</pubDate>
		<dc:creator>robinhesky</dc:creator>
				<category><![CDATA[VC]]></category>
		<category><![CDATA[java学习]]></category>

		<guid isPermaLink="false">http://robinhesky.blog.ubuntu.org.cn/2008/01/19/%e7%9c%8b%e5%ae%8c%e4%ba%86%e4%ba%86%e6%b7%b1%e5%85%a5%e6%b5%85%e5%87%bac%e7%ac%ac%e4%b8%80%e7%ab%a0%ef%bc%8cthinking-in-java%e7%ac%ac%e5%85%ad%e7%ab%a0%e7%9a%8434%ef%bc%81/</guid>
		<description><![CDATA[最近比较累，也晚了，要写的也比较多，就改天补上吧！
]]></description>
			<content:encoded><![CDATA[<p>最近比较累，也晚了，要写的也比较多，就改天补上吧！</p>
]]></content:encoded>
			<wfw:commentRss>http://robinhesky.blog.ubuntu.org.cn/2008/01/19/%e7%9c%8b%e5%ae%8c%e4%ba%86%e4%ba%86%e6%b7%b1%e5%85%a5%e6%b5%85%e5%87%bac%e7%ac%ac%e4%b8%80%e7%ab%a0%ef%bc%8cthinking-in-java%e7%ac%ac%e5%85%ad%e7%ab%a0%e7%9a%8434%ef%bc%81/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>thinking in java 第五章 隐藏具体实现</title>
		<link>http://robinhesky.blog.ubuntu.org.cn/2008/01/18/thinking-in-java-%e7%ac%ac%e4%ba%94%e7%ab%a0-%e9%9a%90%e8%97%8f%e5%85%b7%e4%bd%93%e5%ae%9e%e7%8e%b0/</link>
		<comments>http://robinhesky.blog.ubuntu.org.cn/2008/01/18/thinking-in-java-%e7%ac%ac%e4%ba%94%e7%ab%a0-%e9%9a%90%e8%97%8f%e5%85%b7%e4%bd%93%e5%ae%9e%e7%8e%b0/#comments</comments>
		<pubDate>Fri, 18 Jan 2008 11:17:15 +0000</pubDate>
		<dc:creator>robinhesky</dc:creator>
				<category><![CDATA[java学习]]></category>

		<guid isPermaLink="false">http://robinhesky.blog.ubuntu.org.cn/2008/01/18/thinking-in-java-%e7%ac%ac%e4%ba%94%e7%ab%a0-%e9%9a%90%e8%97%8f%e5%85%b7%e4%bd%93%e5%ae%9e%e7%8e%b0/</guid>
		<description><![CDATA[进行面向对象的设计时，一项基本的考虑是：如何将发生变化的东西与保持不变的东西分隔开
为解决这个问题，Java推出了“访问指示符”的概念，允许库创建者声明哪些东西是客户程序员可以使用的，哪些是不可使用的。这种访问控制的级别在“最大访问”和“最小访问”的范围之间，分别包括：public，protected，包访问权限（没有关键词），private。
1.库单元
我们用import关键字导入一个完整的库时，就会获得“包”（Package）。例如：
import java.util.*;
之所以要进行这样的导入，是为了提供一种特殊的机制，以便管理“命名空间”（Name  Space）。我们所有类成员的名字相互间都会隔离起来。位于类A内的一个方法f()不会与位于类B内的、拥有相同“签名”（自变量列表）的f()发生冲突
若在一个文件的开头使用下述代码：
package  mypackage;
那么package语句必须作为文件的第一个非注释语句出现。该语句的作用是指出这个编译单元属于名为mypackage的一个库的一部分。或者换句话说，它表明这个编译单元内的public类名位于mypackage这个名字的下面。如果其他人想使用这个名字，要么指出完整的名字，要么与mypackage联合使用import关键字（使用前面给出的选项）。注意根据Java包（封装）的约定，名字内的所有字母都应小写，甚至那些中间单词亦要如此。
2.创建独一无二的域名
由于一个包永远不会真的“封装”到单独一个文件里面，它可由多个.class文件构成，所以局面可能稍微有些混乱。为避免这个问题，最合理的一种做法就是将某个特定包使用的所有.class文件都置入单个目录里。也就是说，我们要利用操作系统的分级文件结构避免出现混乱局面。这正是Java所采取的方法。
它同时也解决了另两个问题：创建独一无二的包名以及找出那些可能深藏于目录结构某处的类。正如我们在第2章讲述的那样，为达到这个目的，需要将.class文件的位置路径编码到package的名字里。但根据约定，编译器强迫package名的第一部分是类创建者的因特网域名。由于因特网域名肯定是独一无二的（由InterNIC保证——注释②，它控制着域名的分配），所以假如按这一约定行事，package的名称就肯定不会重复，所以永远不会遇到名称冲突的问题。换句话说，除非将自己的域名转让给其他人，而且对方也按照相同的路径名编写Java代码，否则名字的冲突是永远不会出现的。
这个技巧的另一部分是将package名解析成自己机器上的一个目录。这样一来，Java程序运行并需要装载.class文件的时候（这是动态进行的，在程序需要创建属于那个类的一个对象，或者首次访问那个类的一个static成员时），它就可以找到.class文件驻留的那个目录。
 Java解释器的工作程序如下：首先，它找到环境变量CLASSPATH（将Java或者具有Java解释能力的工具——如浏览器——安装到机器中时，通过操作系统进行设定）。CLASSPATH包含了一个或多个目录，它们作为一种特殊的“根”使用，从这里展开对.class文件的搜索。从那个根开始，解释器会寻找包名，并将每个点号（句点）替换成一个斜杠，从而生成从CLASSPATH根开始的一个路径名（所以package  foo.bar.baz会变成foo\bar\baz或者foo/bar/baz；具体是正斜杠还是反斜杠由操作系统决定）。随后将它们连接到一起，成为CLASSPATH内的各个条目（入口）。以后搜索.class文件时，就可从这些地方开始查找与准备创建的类名对应的名字。此外，它也会搜索一些标准目录——这些目录与Java解释器驻留的地方有关。
对于如下的包名，编译器是这样做的：
package com.bruceeckel.util;
这两个文件都置于我自己系统的一个子目录中：
C:\DOC\JavaT\com\bruceeckel\util
若通过它往回走，就会发现包名com.bruceeckel.util，但路径的第一部分又是什么呢？这是由CLASSPATH环境变量决定的。在我的机器上，它是：
CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT
可以看出，CLASSPATH里能包含大量备用的搜索路径。然而，使用JAR文件时要注意一个问题：必须将JAR文件的名字置于类路径里，而不仅仅是它所在的路径。所以对一个名为grape.jar的JAR文件来说，我们的类路径需要包括：
CLASSPATH=.;D:\JAVA\LIB;C:\flavors\grape.jar
3.包访问权限
如果根本不指定访问指示符，这意味着当前包内的其他所有类都能访问成员，但对包外的所有类来说，这些成员却是“私有”（Private）的，外界不得访问。
为获得对一个访问权限，唯一的方法就是：
(1) 使成员成为“public”（公共的）。这样所有人从任何地方都可以访问它。
(2)  变成一个“友好”成员，方法是舍弃所有访问指示符，并将其类置于相同的包内。这样一来，其他类就可以访问成员。
(3)  正如以后引入“继承”概念后大家会知道的那样，一个继承的类既可以访问一个protected成员，也可以访问一个public成员（但不可访问private成员）。只有在两个类位于相同的包内时，它才可以访问友好成员。但现在不必关心这方面的问题。
(4)  提供“访问器／变化器”方法（亦称为“获取／设置”方法），以便读取和修改值。这是OOP环境中最正规的一种方法，也是Java  Beans的基础——具体情况会在第13章介绍。
下面举书上的两段程序加深对包访问权限的理解：
//: Cookie.java
// Creates a library
package c05.dessert;

public class Cookie {
  public Cookie() {
   System.out.println("Cookie constructor");
  }
  void foo() { System.out.println("foo"); }
} ///:~
 //: Dinner.java
// Uses the library
import c05.dessert.*;

public class Dinner {
  public [...]]]></description>
			<content:encoded><![CDATA[<p>进行面向对象的设计时，一项基本的考虑是：如何将发生变化的东西与保持不变的东西分隔开</p>
<p>为解决这个问题，Java推出了“访问指示符”的概念，允许库创建者声明哪些东西是客户程序员可以使用的，哪些是不可使用的。这种访问控制的级别在“最大访问”和“最小访问”的范围之间，分别包括：public，protected，包访问权限（没有关键词），private。</p>
<p>1.库单元</p>
<p>我们用import关键字导入一个完整的库时，就会获得“包”（Package）。例如：<br />
import java.util.*;</p>
<p>之所以要进行这样的导入，是为了提供一种特殊的机制，以便管理“命名空间”（Name  Space）。我们所有类成员的名字相互间都会隔离起来。位于类A内的一个方法f()不会与位于类B内的、拥有相同“签名”（自变量列表）的f()发生冲突</p>
<p>若在一个文件的开头使用下述代码：<br />
package  mypackage;<br />
那么package语句必须作为文件的第一个非注释语句出现。该语句的作用是指出这个编译单元属于名为mypackage的一个库的一部分。或者换句话说，它表明这个编译单元内的public类名位于mypackage这个名字的下面。如果其他人想使用这个名字，要么指出完整的名字，要么与mypackage联合使用import关键字（使用前面给出的选项）。注意根据Java包（封装）的约定，名字内的所有字母都应小写，甚至那些中间单词亦要如此。</p>
<p>2.创建独一无二的域名</p>
<p>由于一个包永远不会真的“封装”到单独一个文件里面，它可由多个.class文件构成，所以局面可能稍微有些混乱。为避免这个问题，最合理的一种做法就是将某个特定包使用的所有.class文件都置入单个目录里。也就是说，我们要利用操作系统的分级文件结构避免出现混乱局面。这正是Java所采取的方法。<br />
它同时也解决了另两个问题：创建独一无二的包名以及找出那些可能深藏于目录结构某处的类。正如我们在第2章讲述的那样，为达到这个目的，需要将.class文件的位置路径编码到package的名字里。但根据约定，编译器强迫package名的第一部分是类创建者的因特网域名。由于因特网域名肯定是独一无二的（由InterNIC保证——注释②，它控制着域名的分配），所以假如按这一约定行事，package的名称就肯定不会重复，所以永远不会遇到名称冲突的问题。换句话说，除非将自己的域名转让给其他人，而且对方也按照相同的路径名编写Java代码，否则名字的冲突是永远不会出现的。</p>
<p>这个技巧的另一部分是将package名解析成自己机器上的一个目录。这样一来，Java程序运行并需要装载.class文件的时候（这是动态进行的，在程序需要创建属于那个类的一个对象，或者首次访问那个类的一个static成员时），它就可以找到.class文件驻留的那个目录。<br />
<strong> Java解释器的工作程序如下：首先，它找到环境变量CLASSPATH（将Java或者具有Java解释能力的工具——如浏览器——安装到机器中时，通过操作系统进行设定）。CLASSPATH包含了一个或多个目录，它们作为一种特殊的“根”使用，从这里展开对.class文件的搜索。从那个根开始，解释器会寻找包名，并将每个点号（句点）替换成一个斜杠，从而生成从CLASSPATH根开始的一个路径名（所以package  foo.bar.baz会变成foo\bar\baz或者foo/bar/baz；具体是正斜杠还是反斜杠由操作系统决定）。随后将它们连接到一起，成为CLASSPATH内的各个条目（入口）。以后搜索.class文件时，就可从这些地方开始查找与准备创建的类名对应的名字。此外，它也会搜索一些标准目录——这些目录与Java解释器驻留的地方有关。</strong></p>
<p>对于如下的包名，编译器是这样做的：</p>
<p><font><font color="#0000ff">package</font> com.bruceeckel.util;</font></p>
<p><strong>这两个文件都置于我自己系统的一个子目录中：<br />
C:\DOC\JavaT\com\bruceeckel\util<br />
若通过它往回走，就会发现包名com.bruceeckel.util，但路径的第一部分又是什么呢？这是由CLASSPATH环境变量决定的。在我的机器上，它是：<br />
CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT<br />
可以看出，CLASSPATH里能包含大量备用的搜索路径。然而，使用JAR文件时要注意一个问题：必须将JAR文件的名字置于类路径里，而不仅仅是它所在的路径。所以对一个名为grape.jar的JAR文件来说，我们的类路径需要包括：<br />
CLASSPATH=.;D:\JAVA\LIB;C:\flavors\grape.jar</strong></p>
<p>3.包访问权限</p>
<p>如果根本不指定访问指示符，这意味着当前包内的其他所有类都能访问成员，但对包外的所有类来说，这些成员却是“私有”（Private）的，外界不得访问。</p>
<p><strong>为获得对一个访问权限，唯一的方法就是：<br />
(1) 使成员成为“public”（公共的）。这样所有人从任何地方都可以访问它。<br />
(2)  变成一个“友好”成员，方法是舍弃所有访问指示符，并将其类置于相同的包内。这样一来，其他类就可以访问成员。<br />
(3)  正如以后引入“继承”概念后大家会知道的那样，一个继承的类既可以访问一个protected成员，也可以访问一个public成员（但不可访问private成员）。只有在两个类位于相同的包内时，它才可以访问友好成员。但现在不必关心这方面的问题。<br />
(4)  提供“访问器／变化器”方法（亦称为“获取／设置”方法），以便读取和修改值。这是OOP环境中最正规的一种方法，也是Java  Beans的基础——具体情况会在第13章介绍。</strong></p>
<p>下面举书上的两段程序加深对包访问权限的理解：</p>
<pre><font face="times new roman,times" size="1"><font color="#009900">//: Cookie.java</font>
<font color="#009900">// Creates a library</font>
<font color="#0000ff">package</font> c05.dessert;

<font color="#0000ff">public</font> <font color="#0000ff">class</font> Cookie {
  <font color="#0000ff">public</font> Cookie() {
   System.out.println(<font color="#004488">"Cookie constructor"</font>);
  }
  <font color="#0000ff">void</font> foo() { System.out.println(<font color="#004488">"foo"</font>); }
} <font color="#009900">///:~</font></font></pre>
<pre><font face="times new roman,times" size="1"> </font><font face="times new roman,times" size="1"><font color="#009900">//: Dinner.java</font>
<font color="#009900">// Uses the library</font>
<font color="#0000ff">import</font> c05.dessert.*;

<font color="#0000ff">public</font> <font color="#0000ff">class</font> Dinner {
  <font color="#0000ff">public</font> Dinner() {
   System.out.println(<font color="#004488">"Dinner constructor"</font>);
  }
  <font color="#0000ff">public</font> <font color="#0000ff">static</font> <font color="#0000ff">void</font> main(String[] args) {
    Cookie x = <font color="#0000ff">new</font> Cookie();
    <font color="#009900">//! x.foo(); // Can't access</font>
  }</font></pre>
<pre><font face="times new roman,times" size="1">
} <font color="#009900">///:~</font></font></pre>
<p>以上程序可以创建一个Cookie对象，因为它的构建器是public的，而且类也是public的（公共类的概念稍后还会进行更详细的讲述）。然而，foo()成员不可在Dinner.java内访问，因为foo()只有在dessert包内才是“友好”的。</p>
<p>4.protected&#8211;包访问权限的一种（友好的一种）</p>
<p>为了便于理解，举例如下：</p>
<p><font><font color="#0000ff">import</font> c05.dessert.*;  </font></p>
<p><font><font color="#0000ff">public</font> <font color="#0000ff">class</font> ChocolateChip <font color="#0000ff">extends</font> Cookie {   </font></p>
<p><font><font color="#0000ff">public</font> ChocolateChip() {    System.out.println(      <font color="#004488">&#8220;ChocolateChip constructor&#8221;</font>);   }</font></p>
<p><font>   <font color="#0000ff">public</font> <font color="#0000ff">static</font> <font color="#0000ff">void</font> main(String[] args) {     </font></p>
<p><font>ChocolateChip x = <font color="#0000ff">new</font> ChocolateChip();     </font></p>
<p><font><font color="#009900">//! x.foo(); // Can&#8217;t access foo</font>   } } <font color="#009900">///:~</font></font></p>
<p>对于继承，值得注意的一件有趣的事情是倘若方法foo()存在于类Cookie中，那么它也会存在于从Cookie继承的所有类中。但由于foo()在外部的包里是“友好”的，所以我们不能使用它。当然，亦可将其变成public。但这样一来，由于所有人都能自由访问它，所以可能并非我们所希望的局面。若象下面这样修改类Cookie：</p>
<blockquote>
<pre><font size="4"><font color="#0000ff">public</font> <font color="#0000ff">class</font> Cookie {
  <font color="#0000ff">public</font> Cookie() {
    System.out.println(<font color="#004488">"Cookie constructor"</font>);
  }
  <font color="#0000ff">protected</font> <font color="#0000ff">void</font> foo() {
    System.out.println(<font color="#004488">"foo"</font>);
  }
}</font></pre>
</blockquote>
<p>那么仍然能在包dessert里“友好”地访问foo()，但从Cookie继承的其他东西亦可自由地访问它。然而，它并非公共的（public）。</p>
<p><strong>由上可见，java中的protected与C++的protected是由很大区别的，再c++中，如果再成员前加了关键词protected，那么由类生成的对象不能直接方问该成员（以a.member的形式访问,其中a为对象，member为protected），必须通过成员函数来访问，继承类也可以通过这样的方法访问，而java可以以a.member的形式访问。另外，再java中protected还提供包访问权限。</strong></p>
<p>5.类访问</p>
<p>注意不可将类设成private（那样会使除类之外的其他东西都不能访问它），也不能设成protected。</p>
<p>若构造器是私有的，该如何构造对象呢，示例如下：</p>
<blockquote>
<pre><font size="1"><font color="#009900">//: Lunch.java</font>
<font color="#009900">// Demonstrates class access specifiers.</font>
<font color="#009900">// Make a class effectively private</font>
<font color="#009900">// with private constructors:</font>

<font color="#0000ff">class</font> Soup {
  <font color="#0000ff">private</font> Soup() {}
  <font color="#009900">// (1) Allow creation via static method:</font>
  <font color="#0000ff">public</font> <font color="#0000ff">static</font> Soup makeSoup() {
    <font color="#0000ff">return</font> <font color="#0000ff">new</font> Soup();
  }
  <font color="#009900">// (2) Create a static object and</font>
  <font color="#009900">// return a reference upon request.</font>
  <font color="#009900">// (The "Singleton" pattern):</font>
  <font color="#0000ff">private</font> <font color="#0000ff">static</font> Soup ps1 = <font color="#0000ff">new</font> Soup();
  <font color="#0000ff">public</font> <font color="#0000ff">static</font> Soup access() {
    <font color="#0000ff">return</font> ps1;
  }
  <font color="#0000ff">public</font> <font color="#0000ff">void</font> f() {}
}

<font color="#0000ff">class</font> Sandwich { <font color="#009900">// Uses Lunch</font>
  <font color="#0000ff">void</font> f() { <font color="#0000ff">new</font> Lunch(); }
}

<font color="#009900">// Only one public class allowed per file:</font>
<font color="#0000ff">public</font> <font color="#0000ff">class</font> Lunch {
  <font color="#0000ff">void</font> test() {
    <font color="#009900">// Can't do this! Private constructor:</font>
    <font color="#009900">//! Soup priv1 = new Soup();</font>
    Soup priv2 = Soup.makeSoup();
    Sandwich f1 = <font color="#0000ff">new</font> Sandwich();
    Soup.access().f();
  }
} <font color="#009900">///:~</font></font></pre>
</blockquote>
<p><strong>第一个选择，我们可创建一个static方法，再通过它创建一个新的Soup，然后返回指向它的一个句柄。如果想在返回之前对Soup进行一些额外的操作，或者想了解准备创建多少个Soup对象（可能是为了限制它们的个数），这种方案无疑是特别有用的。<br />
第二个选择是采用“设计方案”（Design  Pattern）技术，本书后面会对此进行详细介绍。通常方案叫作“独子”，因为它仅允许创建一个对象。类Soup的对象被创建成Soup的一个static  private成员，所以有一个而且只能有一个。除非通过public方法access()，否则根本无法访问它。</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://robinhesky.blog.ubuntu.org.cn/2008/01/18/thinking-in-java-%e7%ac%ac%e4%ba%94%e7%ab%a0-%e9%9a%90%e8%97%8f%e5%85%b7%e4%bd%93%e5%ae%9e%e7%8e%b0/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Linux主要文件学习笔记1</title>
		<link>http://robinhesky.blog.ubuntu.org.cn/2008/01/16/linux%e4%b8%bb%e8%a6%81%e6%96%87%e4%bb%b6%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b01/</link>
		<comments>http://robinhesky.blog.ubuntu.org.cn/2008/01/16/linux%e4%b8%bb%e8%a6%81%e6%96%87%e4%bb%b6%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b01/#comments</comments>
		<pubDate>Wed, 16 Jan 2008 15:58:49 +0000</pubDate>
		<dc:creator>robinhesky</dc:creator>
				<category><![CDATA[ubuntu 学习笔记]]></category>

		<guid isPermaLink="false">http://robinhesky.blog.ubuntu.org.cn/2008/01/16/linux%e4%b8%bb%e8%a6%81%e6%96%87%e4%bb%b6%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b01/</guid>
		<description><![CDATA[字符设备和块设备文件。
字符设备是指设备发送和接收数据以字符的形式进行，没有缓冲；而块设备则以整个数据缓冲区的形式进行。字符设备的驱动相对比较简单。用ls -l 查文件，根据开头的字母可以来判断，以b开头的说明是块设备（如硬盘），以c开头的为字符设备文件（如打印机，终端，／dev/null）
管道设备文件
从字面上理解，管道设备文件就是FIF0文件（先进先出）。管道设备文件从一头流进，从另一头流出。可以用管道文件实现分区的镜像 。用ls -l 命令查，以p开头的就是管道文件。mknod命令可用来建立字符，块，管道设备文件。如
mknod 管道文件名 p
链接文件
linux存在软链接和硬链接
软链接又叫作符号链接，这个文件包含了被链接文件的路径名，可以是任意文件和目录， 可以链接不同文件系统的文件，类似于WINDOWS中的快捷方式，但由不是。链接文件还可以链接不存在的文件，这就产生一个断链的问题。链接文件还可以
链接自己，这就产生了个循环链接的问题，应尽量避免。删除链接文件时，只删除链接文件本身，而不删除文件。可用以下命令创建符号链接：
ln -s 源文件 链接文件
硬链接相当于给已经存在的 文件的别名。硬链接的命令是：
ln -d  exsiting file new file
硬链接有两个限制
1.不能给目录文件创建硬链接
2.不能在不同系统文间间创建硬链接
删除硬链接文件的源文件时，只删除文件本身，而不删除硬链接文件，而且还保留了原来的内容。这时系统就忘了它是一个硬链接文件，而把它看成普通文件看待。
&#160;
]]></description>
			<content:encoded><![CDATA[<p>字符设备和块设备文件。</p>
<p>字符设备是指设备发送和接收数据以字符的形式进行，没有缓冲；而块设备则以整个数据缓冲区的形式进行。字符设备的驱动相对比较简单。用ls -l 查文件，根据开头的字母可以来判断，以b开头的说明是块设备（如硬盘），以c开头的为字符设备文件（如打印机，终端，／dev/null）</p>
<p>管道设备文件</p>
<p>从字面上理解，管道设备文件就是FIF0文件（先进先出）。管道设备文件从一头流进，从另一头流出。可以用管道文件实现分区的镜像 。用ls -l 命令查，以p开头的就是管道文件。mknod命令可用来建立字符，块，管道设备文件。如</p>
<p align="center">mknod 管道文件名 p</p>
<div align="left">链接文件</div>
<div align="left">linux存在软链接和硬链接</div>
<div align="left">软链接又叫作符号链接，这个文件包含了被链接文件的路径名，可以是任意文件和目录， 可以链接不同文件系统的文件，类似于WINDOWS中的快捷方式，但由不是。链接文件还可以链接不存在的文件，这就产生一个断链的问题。链接文件还可以</div>
<p align="left">链接自己，这就产生了个循环链接的问题，应尽量避免。删除链接文件时，只删除链接文件本身，而不删除文件。可用以下命令创建符号链接：</p>
<p align="left">ln -s 源文件 链接文件</p>
<p align="left">硬链接相当于给已经存在的 文件的别名。硬链接的命令是：</p>
<p align="left">ln -d  exsiting file new file</p>
<p align="left">硬链接有两个限制</p>
<p align="left">1.不能给目录文件创建硬链接</p>
<p align="left">2.不能在不同系统文间间创建硬链接</p>
<p>删除硬链接文件的源文件时，只删除文件本身，而不删除硬链接文件，而且还保留了原来的内容。这时系统就忘了它是一个硬链接文件，而把它看成普通文件看待。</p>
<p align="left">&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://robinhesky.blog.ubuntu.org.cn/2008/01/16/linux%e4%b8%bb%e8%a6%81%e6%96%87%e4%bb%b6%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b01/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>thinking  in java 第四章 初始化与删除续</title>
		<link>http://robinhesky.blog.ubuntu.org.cn/2008/01/16/thinking-in-java-%e7%ac%ac%e5%9b%9b%e7%ab%a0-%e5%88%9d%e5%a7%8b%e5%8c%96%e4%b8%8e%e5%88%a0%e9%99%a4%e7%bb%ad/</link>
		<comments>http://robinhesky.blog.ubuntu.org.cn/2008/01/16/thinking-in-java-%e7%ac%ac%e5%9b%9b%e7%ab%a0-%e5%88%9d%e5%a7%8b%e5%8c%96%e4%b8%8e%e5%88%a0%e9%99%a4%e7%bb%ad/#comments</comments>
		<pubDate>Wed, 16 Jan 2008 13:32:29 +0000</pubDate>
		<dc:creator>robinhesky</dc:creator>
				<category><![CDATA[java学习]]></category>

		<guid isPermaLink="false">http://robinhesky.blog.ubuntu.org.cn/2008/01/16/thinking-in-java-%e7%ac%ac%e5%9b%9b%e7%ab%a0-%e5%88%9d%e5%a7%8b%e5%8c%96%e4%b8%8e%e5%88%a0%e9%99%a4%e7%bb%ad/</guid>
		<description><![CDATA[5.非静态实例的初始化
针对每个对象的非静态变量的初始化，它看起来与静态初始化从句极其相似，只是static关键字从里面消失了。为支持对“匿名内部类”的初始化，必须采用这一语法格式。
6.数组初始化
在C中初始化数组极易出错，而且相当麻烦。C++通过“集合初始化”使其更安全。Java则没有象C++那样的“集合”概念，因为Java中的所有东西都是对象。但它确实有自己的数组，通过数组初始化来提供支持。
数组代表一系列对象或者基本数据类型，所有相同的类型都封装到一起——采用一个统一的标识符名称。数组的定义和使用是通过方括号索引运算符进行的（[]）。为定义一个数组，只需在类型名后简单地跟随一对空方括号即可：
int[] al;
也可以将方括号置于标识符后面，获得完全一致的结果：
int al[];
基本数据类型的数组元素会自动初始化成“空”值（对于数值，空值就是零；对于char，它是null；而对于boolean，它却是false）。若操作的是一个非基本类型对象的数组，那么无论如何都要使用new,而且应该如下样式赋值，
Integer[] a = new Integer[pRand(20)];
prt(&#8221;length of a = &#8221; + a.length);
for(int i = 0; i &#60; a.length; i++) {
a[i] = new Integer(pRand(500));
prt(&#8221;a[" + i + "] = &#8221; + a[i]);
}
7.多维数组的赋值
int[][][] a3 = new int[pRand(7)][][];
for(int i = 0; i &#60; a3.length; i++) {
a3[i] = new int[pRand(5)][];
for(int j = 0; j &#60; a3[i].length; j++)
a3[i][j] = [...]]]></description>
			<content:encoded><![CDATA[<p>5.非静态实例的初始化</p>
<p>针对每个对象的非静态变量的初始化，它看起来与静态初始化从句极其相似，只是static关键字从里面消失了。为支持对“匿名内部类”的初始化，必须采用这一语法格式。</p>
<p>6.数组初始化</p>
<p>在C中初始化数组极易出错，而且相当麻烦。C++通过“集合初始化”使其更安全。Java则没有象C++那样的“集合”概念，因为Java中的所有东西都是对象。但它确实有自己的数组，通过数组初始化来提供支持。<br />
数组代表一系列对象或者基本数据类型，所有相同的类型都封装到一起——采用一个统一的标识符名称。数组的定义和使用是通过方括号索引运算符进行的（[]）。为定义一个数组，只需在类型名后简单地跟随一对空方括号即可：<br />
int[] al;<br />
也可以将方括号置于标识符后面，获得完全一致的结果：<br />
int al[];</p>
<p>基本数据类型的数组元素会自动初始化成“空”值（对于数值，空值就是零；对于char，它是null；而对于boolean，它却是false）。若操作的是一个非基本类型对象的数组，那么无论如何都要使用new,而且应该如下样式赋值，</p>
<p>Integer[] a = new Integer[pRand(20)];<br />
prt(&#8221;length of a = &#8221; + a.length);<br />
for(int i = 0; i &lt; a.length; i++) {<br />
a[i] = new Integer(pRand(500));<br />
prt(&#8221;a[" + i + "] = &#8221; + a[i]);<br />
}</p>
<p>7.多维数组的赋值</p>
<p>int[][][] a3 = new int[pRand(7)][][];<br />
for(int i = 0; i &lt; a3.length; i++) {<br />
a3[i] = new int[pRand(5)][];<br />
for(int j = 0; j &lt; a3[i].length; j++)<br />
a3[i][j] = new int[pRand(5)];<br />
}</p>
<p>对于第一个new创建的数组，它的第一个元素的长度是随机的，其他元素的长度则没有定义。for循环内的第二个new则会填写元素，但保持第三个索引的未定状态——直到碰到第三个new。<br />
根据输出结果，大家可以看到：假若没有明确指定初始化值，数组值就会自动初始化成零。</p>
<p>Integer[][] a5;<br />
a5 = new Integer[3][];<br />
for(int i = 0; i &lt; a5.length; i++) {<br />
a5[i] = new Integer[3];<br />
for(int j = 0; j &lt; a5[i].length; j++)<br />
a5[i][j] = new Integer(i*j);<br />
}</p>
]]></content:encoded>
			<wfw:commentRss>http://robinhesky.blog.ubuntu.org.cn/2008/01/16/thinking-in-java-%e7%ac%ac%e5%9b%9b%e7%ab%a0-%e5%88%9d%e5%a7%8b%e5%8c%96%e4%b8%8e%e5%88%a0%e9%99%a4%e7%bb%ad/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>thinking  in java 第四章 初始化与删除</title>
		<link>http://robinhesky.blog.ubuntu.org.cn/2008/01/16/thinking-in-java-%e7%ac%ac%e5%9b%9b%e7%ab%a0-%e5%88%9d%e5%a7%8b%e5%8c%96%e4%b8%8e%e5%88%a0%e9%99%a4/</link>
		<comments>http://robinhesky.blog.ubuntu.org.cn/2008/01/16/thinking-in-java-%e7%ac%ac%e5%9b%9b%e7%ab%a0-%e5%88%9d%e5%a7%8b%e5%8c%96%e4%b8%8e%e5%88%a0%e9%99%a4/#comments</comments>
		<pubDate>Tue, 15 Jan 2008 16:04:43 +0000</pubDate>
		<dc:creator>robinhesky</dc:creator>
				<category><![CDATA[java学习]]></category>

		<guid isPermaLink="false">http://robinhesky.blog.ubuntu.org.cn/2008/01/16/thinking-in-java-%e7%ac%ac%e5%9b%9b%e7%ab%a0-%e5%88%9d%e5%a7%8b%e5%8c%96%e4%b8%8e%e5%88%a0%e9%99%a4/</guid>
		<description><![CDATA[1.以构造器（constructor）确保初始化
2.方法重载（method overloading）
每个重载的方法都有一个独一无二的参数列表。重载的方法就靠不同的参数列表来区分。以返回值是不能区分重载的方法的，以参数的顺序来区分不同的重载方法也是不可取的，容易导致代码混乱，不可维护。
3.缺省构造器（无参构造器）
如果没有写构造器，编译器会自动为程序生成一个构造器。如果你写了构造器，编译器 将不在为程序自动生成一个编译器。构造器里调用构造器，可以通过this实现。但在构造器里只能调用一次构造器，且只能放在最前处。ge collection
4.清除（clean up）,终结（finalization），垃圾回收(garbage collection)
有时可能发现一个对象的存储空间永远都不会释放，因为自己的程序永远都接近于用光空间的临界点。若程序执行结束，而且垃圾收集器一直都没有释放我们创建的任何对象的存储空间，则随着程序的退出，那些资源会返回给操作系统。这是一件好事情，因为垃圾收集本身也要消耗一些开销。如永远都不用它，那么永远也不用支出这部分开销。
垃圾收集并不等于“破坏”！
若能时刻牢记这一点，踩到陷阱的可能性就会大大减少。它意味着在我们不再需要一个对象之前，有些行动是必须采取的，而且必须由自己来采取这些行动。Java并未提供“破坏器”或者类似的概念，所以必须创建一个原始的方法，用它来进行这种清除。例如，假设在对象创建过程中，它会将自己描绘到屏幕上。如果不从屏幕明确删除它的图像，那么它可能永远都不会被清除。若在finalize()里置入某种删除机制，那么假设对象被当作垃圾收掉了，图像首先会将自身从屏幕上移去。但若未被收掉，图像就会保留下来。所以要记住的第二个重点是：
我们的对象可能不会当作垃圾被收掉！
垃圾收集只跟内存有关！
垃圾收集器存在的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾收集有关的任何活动来说，其中最值得注意的是finalize()方法，它们也必须同内存以及它的回收有关。
但这是否意味着假如对象包含了其他对象，finalize()就应该明确释放那些对象呢？答案是否定的——垃圾收集器会负责释放所有对象占据的内存，无论这些对象是如何创建的。它将对finalize()的需求限制到特殊的情况。在这种情况下，我们的对象可采用与创建对象时不同的方法分配一些存储空间。但大家或许会注意到，Java中的所有东西都是对象，所以这到底是怎么一回事呢？
之所以要使用finalize()，看起来似乎是由于有时需要采取与Java的普通方法不同的一种方法，通过分配内存来做一些具有C风格的事情。这主要可以通过“固有方法”来进行，它是从Java里调用非Java方法的一种方式（固有方法的问题在附录A讨论）。C和C++是目前唯一获得固有方法支持的语言。但由于它们能调用通过其他语言编写的子程序，所以能够有效地调用任何东西。在非Java代码内部，也许能调用C的malloc()系列函数，用它分配存储空间。而且除非调用了free()，否则存储空间不会得到释放，从而造成内存“漏洞”的出现。当然，free()是一个C和C++函数，所以我们需要在finalize()内部的一个固有方法中调用它。
5.成员初始化
一个类的所有基本类型数据成员都会保证获得一个初始值。
在一个类的内部定义一个对象句柄时，如果不将其初始化成新对象，那个句柄就会获得一个空值。
6.构造器初始化
可以在运行期调用方法和采取行动，从而“现场”决定初始化值。但要注意这样一件事情：不可妨碍自动初始化的进行。
对于所有基本类型以及对象句柄，这种情况都是成立的。
7.初始化顺序
在一个类里，初始化的顺序是由变量在类内的定义顺序决定的。即使变量定义大量遍布于方法定义的中间，那些变量仍会在调用任何方法之前得到初始化——甚至在构造器调用之前。
8.静态数据的初始化
若数据是静态的（static），那么同样的事情就会发生；如果它属于一个基本类型（主类型），而且未对其初始化，就会自动获得自己的标准基本类型初始值；如果它是指向一个对象的句柄，那么除非新建一个对象，并将句柄同它连接起来，否则就会得到一个空值（NULL）。
9.初始化顺序的总结
初始化的顺序是首先static（如果它们尚未由前一次对象创建过程初始化），接着是非static对象。大家可从输出结果中找到相应的证据。
在这里有必要总结一下对象的创建过程。请考虑一个名为Dog的类：
(1) 类型为Dog的一个对象首次创建时，或者Dog类的static方法／static字段首次访问时，Java解释器必须找到Dog.class（在事先设好的类路径里搜索）。
(2) 找到Dog.class后（它会创建一个Class对象，这将在后面学到），它的所有static初始化模块都会运行。因此，static初始化仅发生一次——在Class对象首次载入的时候。
(3) 创建一个new Dog()时，Dog对象的构建进程首先会在内存堆（Heap）里为一个Dog对象分配足够多的存储空间。
(4) 这种存储空间会清为零，将Dog中的所有基本类型设为它们的默认值（零用于数字，以及boolean和char的等价设定）。
(5) 进行字段定义时发生的所有初始化都会执行。
(6) 执行构建器。
10.非静态实例初始化
还没有看，明天总结。
]]></description>
			<content:encoded><![CDATA[<p>1.以构造器（constructor）确保初始化</p>
<p>2.方法重载（method overloading）</p>
<p>每个重载的方法都有一个独一无二的参数列表。重载的方法就靠不同的参数列表来区分。以返回值是不能区分重载的方法的，以参数的顺序来区分不同的重载方法也是不可取的，容易导致代码混乱，不可维护。</p>
<p>3.缺省构造器（无参构造器）</p>
<p>如果没有写构造器，编译器会自动为程序生成一个构造器。如果你写了构造器，编译器 将不在为程序自动生成一个编译器。构造器里调用构造器，可以通过this实现。但在构造器里只能调用一次构造器，且只能放在最前处。<img src="http://robinhesky.blog.ubuntu.org.cn/wp-includes/js/tinymce/themes/advanced/images/separator.gif" height="20" width="2" /><a href="tinyMCE.execInstanceCommand('mce_editor_0','mceDirectionLTR');"><img src="http://robinhesky.blog.ubuntu.org.cn/wp-includes/js/tinymce/themes/advanced/images/spacer.gif" /></a><a href="tinyMCE.execInstanceCommand('mce_editor_0','mceDirectionRTL');"><img src="http://robinhesky.blog.ubuntu.org.cn/wp-includes/js/tinymce/themes/advanced/images/spacer.gif" /></a><img src="http://robinhesky.blog.ubuntu.org.cn/wp-includes/js/tinymce/themes/advanced/images/separator.gif" height="20" width="2" /><img src="http://robinhesky.blog.ubuntu.org.cn/wp-includes/js/tinymce/themes/advanced/images/separator.gif" height="20" width="2" /><img src="http://robinhesky.blog.ubuntu.org.cn/wp-includes/js/tinymce/themes/advanced/images/separator.gif" height="20" width="2" />ge collection</p>
<p>4.清除（clean up）,终结（finalization），垃圾回收(garbage collection)</p>
<p>有时可能发现一个对象的存储空间永远都不会释放，因为自己的程序永远都接近于用光空间的临界点。若程序执行结束，而且垃圾收集器一直都没有释放我们创建的任何对象的存储空间，则随着程序的退出，那些资源会返回给操作系统。这是一件好事情，因为垃圾收集本身也要消耗一些开销。如永远都不用它，那么永远也不用支出这部分开销。</p>
<p>垃圾收集并不等于“破坏”！</p>
<p>若能时刻牢记这一点，踩到陷阱的可能性就会大大减少。它意味着在我们不再需要一个对象之前，有些行动是必须采取的，而且必须由自己来采取这些行动。Java并未提供“破坏器”或者类似的概念，所以必须创建一个原始的方法，用它来进行这种清除。例如，假设在对象创建过程中，它会将自己描绘到屏幕上。如果不从屏幕明确删除它的图像，那么它可能永远都不会被清除。若在finalize()里置入某种删除机制，那么假设对象被当作垃圾收掉了，图像首先会将自身从屏幕上移去。但若未被收掉，图像就会保留下来。所以要记住的第二个重点是：</p>
<p>我们的对象可能不会当作垃圾被收掉！</p>
<p>垃圾收集只跟内存有关！</p>
<p>垃圾收集器存在的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾收集有关的任何活动来说，其中最值得注意的是finalize()方法，它们也必须同内存以及它的回收有关。<br />
但这是否意味着假如对象包含了其他对象，finalize()就应该明确释放那些对象呢？答案是否定的——垃圾收集器会负责释放所有对象占据的内存，无论这些对象是如何创建的。它将对finalize()的需求限制到特殊的情况。在这种情况下，我们的对象可采用与创建对象时不同的方法分配一些存储空间。但大家或许会注意到，Java中的所有东西都是对象，所以这到底是怎么一回事呢？<br />
之所以要使用finalize()，看起来似乎是由于有时需要采取与Java的普通方法不同的一种方法，通过分配内存来做一些具有C风格的事情。这主要可以通过“固有方法”来进行，它是从Java里调用非Java方法的一种方式（固有方法的问题在附录A讨论）。C和C++是目前唯一获得固有方法支持的语言。但由于它们能调用通过其他语言编写的子程序，所以能够有效地调用任何东西。在非Java代码内部，也许能调用C的malloc()系列函数，用它分配存储空间。而且除非调用了free()，否则存储空间不会得到释放，从而造成内存“漏洞”的出现。当然，free()是一个C和C++函数，所以我们需要在finalize()内部的一个固有方法中调用它。</p>
<p>5.成员初始化</p>
<p>一个类的所有基本类型数据成员都会保证获得一个初始值。</p>
<p>在一个类的内部定义一个对象句柄时，如果不将其初始化成新对象，那个句柄就会获得一个空值。</p>
<p>6.构造器初始化</p>
<p>可以在运行期调用方法和采取行动，从而“现场”决定初始化值。但要注意这样一件事情：不可妨碍自动初始化的进行。</p>
<p>对于所有基本类型以及对象句柄，这种情况都是成立的。</p>
<p>7.初始化顺序</p>
<p>在一个类里，初始化的顺序是由变量在类内的定义顺序决定的。即使变量定义大量遍布于方法定义的中间，那些变量仍会在调用任何方法之前得到初始化——甚至在构造器调用之前。</p>
<p>8.静态数据的初始化</p>
<p>若数据是静态的（static），那么同样的事情就会发生；如果它属于一个基本类型（主类型），而且未对其初始化，就会自动获得自己的标准基本类型初始值；如果它是指向一个对象的句柄，那么除非新建一个对象，并将句柄同它连接起来，否则就会得到一个空值（NULL）。</p>
<p>9.初始化顺序的总结</p>
<p>初始化的顺序是首先static（如果它们尚未由前一次对象创建过程初始化），接着是非static对象。大家可从输出结果中找到相应的证据。<br />
在这里有必要总结一下对象的创建过程。请考虑一个名为Dog的类：<br />
(1) 类型为Dog的一个对象首次创建时，或者Dog类的static方法／static字段首次访问时，Java解释器必须找到Dog.class（在事先设好的类路径里搜索）。<br />
(2) 找到Dog.class后（它会创建一个Class对象，这将在后面学到），它的所有static初始化模块都会运行。因此，static初始化仅发生一次——在Class对象首次载入的时候。<br />
(3) 创建一个new Dog()时，Dog对象的构建进程首先会在内存堆（Heap）里为一个Dog对象分配足够多的存储空间。<br />
(4) 这种存储空间会清为零，将Dog中的所有基本类型设为它们的默认值（零用于数字，以及boolean和char的等价设定）。<br />
(5) 进行字段定义时发生的所有初始化都会执行。<br />
(6) 执行构建器。</p>
<p>10.非静态实例初始化</p>
<p>还没有看，明天总结。</p>
]]></content:encoded>
			<wfw:commentRss>http://robinhesky.blog.ubuntu.org.cn/2008/01/16/thinking-in-java-%e7%ac%ac%e5%9b%9b%e7%ab%a0-%e5%88%9d%e5%a7%8b%e5%8c%96%e4%b8%8e%e5%88%a0%e9%99%a4/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>linux实用培训教程笔记1（by 红联）</title>
		<link>http://robinhesky.blog.ubuntu.org.cn/2008/01/14/linux%e5%ae%9e%e7%94%a8%e5%9f%b9%e8%ae%ad%e6%95%99%e7%a8%8b%e7%ac%94%e8%ae%b01%ef%bc%88by-%e7%ba%a2%e8%81%94%ef%bc%89/</link>
		<comments>http://robinhesky.blog.ubuntu.org.cn/2008/01/14/linux%e5%ae%9e%e7%94%a8%e5%9f%b9%e8%ae%ad%e6%95%99%e7%a8%8b%e7%ac%94%e8%ae%b01%ef%bc%88by-%e7%ba%a2%e8%81%94%ef%bc%89/#comments</comments>
		<pubDate>Mon, 14 Jan 2008 15:54:55 +0000</pubDate>
		<dc:creator>robinhesky</dc:creator>
				<category><![CDATA[ubuntu 学习笔记]]></category>

		<guid isPermaLink="false">http://robinhesky.blog.ubuntu.org.cn/2008/01/14/linux%e5%ae%9e%e7%94%a8%e5%9f%b9%e8%ae%ad%e6%95%99%e7%a8%8b%e7%ac%94%e8%ae%b01%ef%bc%88by-%e7%ba%a2%e8%81%94%ef%bc%89/</guid>
		<description><![CDATA[1.linux的7个运行级别
LINUX系统中，共有7个运行级别，含义如下：
0 停机。不要把系统的默认级别设置为0，否则系统不能正常启动。
1 但用户模式。用于root用户对系统进行维护，不允许其他用户使用主机。
2 多用户模式。在该模式下不能使用NFS。
3 完全多用户模式。主机作为服务器时通常在该模式下。
4 未分配使用。
5 图形登陆的多用户模式。用户在该模式下可以进行图形界面的登陆。
6 重新启动。不要把系统的默认级别设置为6，否则不能正常启动。
2.Linux的命令组成
Linux命令组成：shell内部命令+shell外部命令
shell内部命令：最简单最常用的命令，在shell启动时进入内存
shell外部命令：独立的可执行程序。是一些使用工具程序
3.Shell命令基本规则
Shell命令的一般格式如下：命令名【选项】【参数1】【参数2】&#8230;【选项】是对命令的特别定义，以减
号(-)开始，多个选项可以用一个减号(-)连起来，如ls -l -a 与ls -la 相同。【参数】提供命令运行的信息，或者是
命令执行过程中所使用的文件名。
使用分号(;)可以将两个命令隔开，这样可以实现一行中输入多个命令。命令的执行顺序和输入的顺序相同
。
4. 常用的Shell命令－目录和文件操作
重命名文件
mv [源文件名] [目标文件名]
例：mv /etc/rc.d/rc3.d/K50xinetd /etc/rc.d/rc3.d/S50xinetd
删除文件
rm [文件名]
不需确认地删除多个文件
rm -f [带通配符的文件名]
硬链接文件。不能对目录文件做硬链接，不能在不同的文件系统之间做硬链接。
ln [源文件名] [目标文件名]
例：mv /etc/rc.d/rc3.d/K50xinetd /etc/rc.d/rc3.d/S50xinetd
软链接文件。也就是符号链接。可用此法创建文件的快捷方式。
ln -s [源文件或文件夹名] [目标名]
按文件名查找文件。
find / -name nametofind -print
改变文件所有者。
例：chown workman：workgroup 文件名
改变文件访问权限
例：chmod -R 755 /usr/local/LumaQQ
查看一个文件有多少行
wc -l usr.bin
查看一个文件有多少字节
wc -c usr.bin
查看文本文件的内容
cat usr.bin
空
5 .管道
将一个程序的标准输出写道一个文件中去，再将这个文件的内容作为另一个命令的标准输入，等效于通过
临时文件将两个命令结合起来。这种情况很普遍，需要Linux系统提供一种功能：它不需要或不必使用临时文件
，就能将两条命令结合在一起。这种功能就是管道。管道的操作符是一个竖杠“&#124;”。管道是可以嵌套使用的，
因此可以把多个命令结合在一起。接上例，如果执行下面的命令将直接返回/usr/bin 中的文件列表的行数，而不
是列表的内容。
ls /usr/bin &#124; wc -l
6.命令补全
在送入命令的任何时刻，可以按&#60;Tab&#62;键，当这样做时，系统将试图补全此时已输入的命令。如果已经输
入的字符串不足以唯一地确定它应该使用的命令，系统将发出警告声。再次按&#60;Tab&#62;键，系统则会给出可用来
用来补全的字符串清单。使用命令补全功能，可以提高使用长命令或操作较长名字的文件或文件夹的都是非常
有意义的。
7.启动与关闭shell(现在终于知道怎么回到图形界面了！)
在启动Linux桌面系统后，Shell已经在后台运行起来了，但并没有显示出来。如果想让它显示出来，按如下
的组合键就可以：&#60;Ctrl&#62; + &#60;Alt&#62; + &#60;F2&#62;组合键中的F2可以替换为F3、F4、F5、F6。如果要回到图形界面，则按
如下组合键：&#60;Ctrl&#62; + &#60;Alt&#62; + [...]]]></description>
			<content:encoded><![CDATA[<p>1.linux的7个运行级别</p>
<p>LINUX系统中，共有7个运行级别，含义如下：<br />
0 停机。不要把系统的默认级别设置为0，否则系统不能正常启动。<br />
1 但用户模式。用于root用户对系统进行维护，不允许其他用户使用主机。<br />
2 多用户模式。在该模式下不能使用NFS。<br />
3 完全多用户模式。主机作为服务器时通常在该模式下。<br />
4 未分配使用。<br />
5 图形登陆的多用户模式。用户在该模式下可以进行图形界面的登陆。<br />
6 重新启动。不要把系统的默认级别设置为6，否则不能正常启动。</p>
<p>2.Linux的命令组成</p>
<p>Linux命令组成：shell内部命令+shell外部命令<br />
shell内部命令：最简单最常用的命令，在shell启动时进入内存<br />
shell外部命令：独立的可执行程序。是一些使用工具程序</p>
<p>3.Shell命令基本规则</p>
<p>Shell命令的一般格式如下：命令名【选项】【参数1】【参数2】&#8230;【选项】是对命令的特别定义，以减<br />
号(-)开始，多个选项可以用一个减号(-)连起来，如ls -l -a 与ls -la 相同。【参数】提供命令运行的信息，或者是<br />
命令执行过程中所使用的文件名。</p>
<p>使用分号(;)可以将两个命令隔开，这样可以实现一行中输入多个命令。命令的执行顺序和输入的顺序相同<br />
。</p>
<p>4. 常用的Shell命令－目录和文件操作</p>
<p>重命名文件<br />
mv [源文件名] [目标文件名]<br />
例：mv /etc/rc.d/rc3.d/K50xinetd /etc/rc.d/rc3.d/S50xinetd</p>
<p>删除文件<br />
rm [文件名]<br />
不需确认地删除多个文件<br />
rm -f [带通配符的文件名]<br />
硬链接文件。不能对目录文件做硬链接，不能在不同的文件系统之间做硬链接。<br />
ln [源文件名] [目标文件名]<br />
例：mv /etc/rc.d/rc3.d/K50xinetd /etc/rc.d/rc3.d/S50xinetd<br />
软链接文件。也就是符号链接。可用此法创建文件的快捷方式。<br />
ln -s [源文件或文件夹名] [目标名]<br />
按文件名查找文件。<br />
find / -name nametofind -print<br />
改变文件所有者。<br />
例：chown workman：workgroup 文件名<br />
改变文件访问权限<br />
例：chmod -R 755 /usr/local/LumaQQ<br />
查看一个文件有多少行<br />
wc -l usr.bin<br />
查看一个文件有多少字节<br />
wc -c usr.bin<br />
查看文本文件的内容<br />
cat usr.bin<br />
空</p>
<p>5 .管道</p>
<p>将一个程序的标准输出写道一个文件中去，再将这个文件的内容作为另一个命令的标准输入，等效于通过<br />
临时文件将两个命令结合起来。这种情况很普遍，需要Linux系统提供一种功能：它不需要或不必使用临时文件<br />
，就能将两条命令结合在一起。这种功能就是管道。管道的操作符是一个竖杠“|”。管道是可以嵌套使用的，<br />
因此可以把多个命令结合在一起。接上例，如果执行下面的命令将直接返回/usr/bin 中的文件列表的行数，而不<br />
是列表的内容。<br />
ls /usr/bin | wc -l</p>
<p>6.命令补全</p>
<p>在送入命令的任何时刻，可以按&lt;Tab&gt;键，当这样做时，系统将试图补全此时已输入的命令。如果已经输<br />
入的字符串不足以唯一地确定它应该使用的命令，系统将发出警告声。再次按&lt;Tab&gt;键，系统则会给出可用来<br />
用来补全的字符串清单。使用命令补全功能，可以提高使用长命令或操作较长名字的文件或文件夹的都是非常<br />
有意义的。</p>
<p>7.启动与关闭shell(现在终于知道怎么回到图形界面了！)</p>
<p>在启动Linux桌面系统后，Shell已经在后台运行起来了，但并没有显示出来。如果想让它显示出来，按如下<br />
的组合键就可以：&lt;Ctrl&gt; + &lt;Alt&gt; + &lt;F2&gt;组合键中的F2可以替换为F3、F4、F5、F6。如果要回到图形界面，则按<br />
如下组合键：&lt;Ctrl&gt; + &lt;Alt&gt; + &lt;F7&gt;。</p>
<p>8 .输出重定向</p>
<p>在默认的情况下，Linux从键盘接受输入，并将命令的输出送到屏幕。在有时候，这样做并不方便。比如<br />
，在一个目录里有很多文件，如果只用简单的ls命令，在屏幕上显示的输出结果可能上千行！为了得到我们需<br />
要的信息。我们或许需要把这些结果存储到一个文件中然后再查看这个文件，这就要用到系统的输出重定向功<br />
能。输出重定向的操作符为&gt;或&gt;&gt;。单个大于号(&gt;)后面紧跟文件名。<br />
如果指定的文件不存在，将建立这一文件。如果指定的文件存在，则文件原有的内容将被覆盖。如果使用两个<br />
大于号(&gt;&gt;)则会把输出内容追加到原来文件里面 。</p>
<p>9.几种常见shell简介</p>
<p>Linux系统提供多种不同的Shell以供选择。常用的有Bourne Shell（简称sh）、C-Shelll（简称csh）、Korn<br />
Shell（简称ksh）和Bourne Again Shell (简称bash)。</p>
<p>(1)Bourne Shell是AT&amp;T Bell实验室的 Steven Bourne为AT&amp;T的Unix开发的，它是Unix的默认Shell，也是其<br />
它Shell的开发基础。Bourne Shell在编程方面相当优秀，但在处理与用户的交互方面不如其它几种Shell。</p>
<p>(2)C Shell是加州伯克利大学的Bill Joy为BSD Unix开发的，与sh不同，它的语法与C语言很相似。它提供<br />
了Bourne Shell所不能处理的用户交互特征，如命令补全、命令别名、历史命令替换等。但是，C Shell<br />
与BourneShell并不兼容。</p>
<p>(3)Korn Shell是AT&amp;T Bell实验室的David Korn开发的，它集合了C Shell和Bourne Shell的优点，并且与Bourne<br />
Shell向下完全兼容。Korn Shell的效率很高，其命令交互界面和编程交互界面都很好。</p>
<p>(4)Bourne Again Shell (即bash)是自由软件基金会(GNU)开发的一个Shell，它是Linux系统中一个默认的Shell<br />
。Bash不但与Bourne Shell兼容，还继承了C Shell、Korn Shell等优点。</p>
]]></content:encoded>
			<wfw:commentRss>http://robinhesky.blog.ubuntu.org.cn/2008/01/14/linux%e5%ae%9e%e7%94%a8%e5%9f%b9%e8%ae%ad%e6%95%99%e7%a8%8b%e7%ac%94%e8%ae%b01%ef%bc%88by-%e7%ba%a2%e8%81%94%ef%bc%89/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>thinking in java 第三章－程序控制流</title>
		<link>http://robinhesky.blog.ubuntu.org.cn/2008/01/13/thinking-in-java-%e7%ac%ac%e4%b8%89%e7%ab%a0%ef%bc%8d%e7%a8%8b%e5%ba%8f%e6%8e%a7%e5%88%b6%e6%b5%81-2/</link>
		<comments>http://robinhesky.blog.ubuntu.org.cn/2008/01/13/thinking-in-java-%e7%ac%ac%e4%b8%89%e7%ab%a0%ef%bc%8d%e7%a8%8b%e5%ba%8f%e6%8e%a7%e5%88%b6%e6%b5%81-2/#comments</comments>
		<pubDate>Sun, 13 Jan 2008 14:13:39 +0000</pubDate>
		<dc:creator>robinhesky</dc:creator>
				<category><![CDATA[java学习]]></category>

		<guid isPermaLink="false">http://robinhesky.blog.ubuntu.org.cn/2008/01/13/thinking-in-java-%e7%ac%ac%e4%b8%89%e7%ab%a0%ef%bc%8d%e7%a8%8b%e5%ba%8f%e6%8e%a7%e5%88%b6%e6%b5%81-2/</guid>
		<description><![CDATA[今天终于顺利的完成了thinking in java第三章，看了位操作符，移位操作符，三元操作符，逗号操作符，字符操作符,此外讨论了使用操作符时程序员易犯的错误，对于while等控制语句，由于在java里只能用boolean来比较，而不像C或C＋＋那样可以拿数值量来当boolean，这样有效的避免了类似while（x=y），while(x&#124;y)等错误的发生。在java中c++中sizeof，因为sizeof是考虑c的移植问题而产生的，不同的机子数据类型的存储位数可能不一样，但java没有这个问题，也就不需要了。另外一个比较有意思的地方是，java使用了标签，类似于goto语句，但java中的标签主要用来跳出多重循环，有严格的限制。此外文章里总结了操作符的使用，给出了个很好的总结各种基本数据类型使用的程序。提到了优先级。总体今天比较轻松。
]]></description>
			<content:encoded><![CDATA[<p>今天终于顺利的完成了thinking in java第三章，看了位操作符，移位操作符，三元操作符，逗号操作符，字符操作符,此外讨论了使用操作符时程序员易犯的错误，对于while等控制语句，由于在java里只能用boolean来比较，而不像C或C＋＋那样可以拿数值量来当boolean，这样有效的避免了类似while（x=y），while(x|y)等错误的发生。在java中c++中sizeof，因为sizeof是考虑c的移植问题而产生的，不同的机子数据类型的存储位数可能不一样，但java没有这个问题，也就不需要了。另外一个比较有意思的地方是，java使用了标签，类似于goto语句，但java中的标签主要用来跳出多重循环，有严格的限制。此外文章里总结了操作符的使用，给出了个很好的总结各种基本数据类型使用的程序。提到了优先级。总体今天比较轻松。</p>
]]></content:encoded>
			<wfw:commentRss>http://robinhesky.blog.ubuntu.org.cn/2008/01/13/thinking-in-java-%e7%ac%ac%e4%b8%89%e7%ab%a0%ef%bc%8d%e7%a8%8b%e5%ba%8f%e6%8e%a7%e5%88%b6%e6%b5%81-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
