2007年10月10日星期三

《.NET设计规范》——学习笔记(3) 命名规范

一组一致的的命名约定对框架的可用性及其重要。 名字要易于理解,同时必须传达每个元素的功能。

大小写约定

标识符的大小写规则

PascalCasing:如HtmlTag  IOStream

camelCasing:如htmlTag  ioStream

  • 要把PascalCasing用于由多个单词构成的名字空间、类型、以及成员的名字;
  • 要把camelCasing用于参数的名字。

通用命名约定

单词的选择

对框架中标识符的名字来说,很重要的一点是一目了然。

名字的意思清楚比长度短更重要。名字应该与场景、系统的逻辑组成或物理组成以及为人熟知的概念相对应,而不应该与技术或框架相对应。

  • 要为标识符选择易于阅读的名字;
  • 要更看重可读性,而不是更看重简短性;
  • 不要使用下划线、连字符以及其他任何既非字母也非数字的字符;
  • 不要使用匈牙利命名法;
  • 避免使用与广泛使用的编程语言的关键字有冲突的标识符。

使用单词缩写和首字母缩写词

一般来说,不要在标识符中使用单词缩写或首字母缩写:宁可名字长一点,也不要别人看不懂。  尤其不要使用未被广泛接受的单词缩写和首字母缩写词。

  • 不要使用缩写词或缩略词作为标识符名字的一部分
    用GetWindow  不用GetWin
  • 不要使用未被广泛接受的首字母缩写词
    多谓广泛接受:用搜索引擎在网上搜索该首字母缩写词,如果返回的前几个结果与期望相符,那么该首字母缩写词才有资格被称为众所周知。

避免使用语言特有的名字

  • 要给类型名使用语义上有意义的名字,而不要使用特有的关键字
    GetLength比GetInt要好
  • 要使用CLR的通用类型名,而不要使用语言特有的别名
  • 要使用常见的名字,比如value或item,而不要重复类型的名字

为已有API的新版本命名

当用新类型和新成员接替或取代已有的类型或成员时,如何选择名字:

  • 使用与旧API相似的名字
  • 要优先使用后缀而不是前缀来表示已有API的新版本
    这样有助于在浏览文档或使用Intellisense时发现新版本:按字母排序
  • 可以考虑使用全新但有意义的标识符
  • 要使用数字后缀来表示已有API的新版本
    有些名字(或工业标准)不宜添加后缀或改名
  • 不要在标识符中使用“Ex”“New”等类似的后缀来区分相同API的不同版本
  • 要在引入64位整数(long)而非32位整数进行操作的新版本API时使用“64”后缀,反之亦然。

程序集和DLL的命名

程序集是一个部署单元,同时还代表托管代码程序的身份。虽然程序集可以分布一个或多个文件中,但一般来说一个程序集仅与一个DLL相对应。

名字空间与DLL程序集的区别:

名字空间:一组逻辑实体

DLL和程序集:打包和部署时的一个单

  • 要为程序集和DLL选择提示性的名字,比如System.Data,这样很容易就知道它的大致功能。
  • DLL命名:<Company>.<Component>.dll

名字空间的命名

<Company>.(<Product>|<Technology>)[.<Feature>][.<Subnamespace>]

  • 要用公司名称作为名字空间的前缀,不要用缩写
  • 要用稳定的与版本无关的产品名称作为名字空间的第二层
  • 不要根据公司的组织架构来决定名字空间的层次结构,因为公司内部组织的名称一般来说不会持续太长的时间
  • 要使用PascalCasing大小写风格,并用点号来分隔名字空间的各部分。
    如Microsoft.Office.PowerPoint
  • 考虑适当的时候在名字空间中使用复数形式。  首字母缩写词例外
    System.Collections
    System.IO
  • 不要用相同的名字来命名名字空间与位于该名字空间中的类型
    如:不要将名字空间命名为Debug,然后又在该名字空间中提供一个名为Debug的类。

名字空间和类型冲突

  • 不要引入太一般化的类型名,比如Element、Node、Log以及Message。

不同类型的名字空间,有不同的规范来避免类型名的冲突:

  • 应用程序模型名字空间(application model namespace)
    属于单个应用程序模型的名字空间经常一起使用,但是它们几乎不合属于其他应用程序模型的名字空间一起使用
    System.Windows*
    System.UI*
  • 基础设施名字空间(infrastructure namespace)
    此类别包含一些在开发常用应用程序时很少会导入的名字空间
  • 核心名字空间(core namespace)
    包含了所有的System名字空间,但应用程序模块名字空间和基础设施名字空间除外。  包括System、System.IO、System.Xml以及System.Net等等
  • 技术名字空间组(technology namespace group)
    此类别包含所有那些以相同的两个前缀(<Company>.<Technology>*)开始的名字空间。

类、结构和接口的命名

一般来说类型名应该是名词词组。如果无法为类型找到一个名词词组,那么应该重新考虑类型的总体设计。

另一个中重要的考虑因素:最易于识别的名字应该用于最常用的类型。

最常用的类型名应该反映出使用场景,而不是继承层次。

  • 要用名词词组来给类型命名。使用PascalCasing风格
  • 不要给类名加前缀
    只有接口才能(可以)被加前缀“I”,那是因为.NET框架受到COM及Java的影响
  • 考虑让派生类的名字以其基类结尾
    public class FileStream : Stream {...}
  • 要确保一对类/接口的名字只差一个“I”前缀,如果该类是该接口的标准实现。
    public interface IComponent {...}
    public class Component : IComponent {...}

泛型类型参数的命名

  • 要用描述性的名字来命名
  • 考虑用T来命名参数类型
  • 要给描述性的类型参数名加上T前缀
  • 考虑在类型参数中显示出施加于该类型参数上的限制

枚举类型的命名

  • 要用单数名词来命名枚举类型,除非它表示的是位域(bit field)
  • 不要给枚举类型的名字添加“Enum”后缀,也不要添加“Flag”、“Flags”等后缀
  • 不要给枚举类型值的名字添加前缀
    此规范与C++中通常所使用的恰好相反。在C++中给枚举的每个成员加上完成的限定符是很重要的,因为它们可能在枚举名的作用域之外被访问。但是在托管代码中,枚举成员只能通过枚举名的作用域来访问。

类型成员的命名

类型:方法、属性、事件、构造函数、字段

方法的命名

要尽量根据方法所对应的任务来给它们命名,而不要根据一些实现细节。

  • 要用动词或动词词组来命名方法

属性的命名

  • 要用名词、名词词组或形容词来命名属性
  • 不要让属性名看起来与“Get”方法的名字相似
  • 要用肯定性的短语(CanSeek而不是CantSeek)来命名布尔属性
  • 考虑用属性的类型名来命名属性
    public enum Color {...}
    public class Control
    {
        public Color Color
        {
            get {...}
            set {...}
        }
    }

事件的命名

  • 要用动词或动词短语来命名事件
    事件总是表示一些动作,要么正在发生,要么已经发生
  • 要用现在时和过去时来赋予事件名之前和之后的概念
    窗口关闭之前发生的close事件:Closing
    窗口关闭之后发生的close时间:Closed
  • 不要用Before和After前缀或后缀来区分前置和后置事件
  • 要在命名事件处理函数(用作事件类型的委托)时加上“EventHandler”后缀
  • 要在事件处理函数中用sender和e作为两个参数的名字
    sender:触发事件的对象,在整个.NET框架中,sender为object类型
  • 要在命名事件的参数类型时加上“EventArgs”后缀

字段的命名

  • PascalCasing风格
  • 名词或名词短语
  • 不要给字段名添加前缀

参数的命名

  • camelCasing风格
  • 要使用描述性的参数名
    参数名要具备足够的描述性,使得在大多数情况下,用户根据参数的名字和类型就能够确定它的意思
  • 考虑根据参数的意思而不是参数的类型来命名参数

资源的命名

本地化的资源就好比是属性,可以通过特定的对象来引用。因此,它的命名规范与属性的相似。

  • 要在命名属性关键字(resource key)时使用PascalCasing大小写风格
  • 要使标识符的名字具有描述性而不是使名字变短
  • 不要使用各主要CLR编程语言特有的关键字
  • 要在命名资源时使用字母、数字和下划线
  • 要用点号来给标识符清楚地划分层次
  • 要在为异常消息资源命名是遵循下面的命名约定:
    资源标识符应该是异常的类型名加一个简短的异常标识符,之间以点号分隔

2007年9月20日星期四

Adobe Flex 3最有趣的特征之一 :Web设计者和开发者的相遇

 

著:Yakov Fain 译:叶进

Adobe将于2008年二月左右发布Flex 3.它有增加了一些新的改进和特色,尤其是它将使Flash设计者和Flex开发者结合在一起。CS3可以很容易的使Flex融入到Flash IDE的发布时间线上。Flash中的容器创建在Flex中将会得到足够多的发展。为了开发Flash CS3,你必须要有个一个Flex组件工具包。这就使得Flash艺术拥护者必须去学习至少一点点Flex的基础知识,那会使他们获得在接下来的几年中获得更多的市场。

在今年的早些时候,我看到MS发布了Sliver流。我对这种可以被一个对于Web设计一窍不通的人开发的绚丽的GUI应用印象非常深刻。他(用时间线)创建了一个绚丽的画面,并且为Sillverlight开发者增加了添加他们自己写代码的地方。现在,Flash设计者也可以非常简单地就整合Flex为他们的作品添加代码了。

只要看看这段5分钟的youtube视频,你就知道我在说什么了。新一代的用户体验专家将获得更好的工具和动机去获得、学习新的技能。但是,针对艺术家的编程介绍应该更优雅一点——他们是不一样的人群,同时如果你用编程术语的话,可能会把他们吓跑。我对于这个有切身的体会,因为我的儿子,一个接受过专业培训的漫画家和漫画创作者对Flash IDE非常精通,却拒绝对于编程做任何事情。在很多场合,我都试图使他确信通过学习一些编程知识赚取更多的钱是一件好事。但是,到现在位置,我还没有成功,当然我会继续劝说他的,特别是当Flex 3发布的时候。那些认识到Web设计和编程开发是未来的人将会有一个无边的发展(People who realize that knowing of Web design and programming is the future will have an edge.)!

漫谈C#之关键字(1)

    每一种语言都有非常多的关键字,而且这些关键字也都大同小异,不过毕竟还是有些许的不一样。有些关键字大家碰到的多了,自然就熟悉了,但是有些关键字用得不大多,或者是新引入的,所以就不大熟悉了。我平常在用的时候,就是会碰到一些关键字,感觉有点生疏,平常也会把这些我不懂的关键字的用法了解一下并记录下来。想到应该也有很多跟我同样的人,所以就把我的记录跟大家分享一下。请各位tx多多指正!
访问关键字
    base:用于派生类中访问基类的成员

  • 调用基类上已被其他方法重写的方法

    1 public override void GetInfo()
    2 {
    3 base.GetInfo(); // 调用基类上的GetInfo方法
    4 }

  • 指定创建派生类实例时应用的基类构造函数

    1 public MyDerived() : base() // 调用基类的构造函数
    2 {}

    从静态方法中使用base关键字是错误的。
转换关键字
explicit:用于声明用户定义的显式类型转换运算符

1 class MyType
2 {
3 public static explicit operator MyType(int i)
4     {
5 // 从int转换到MyType类型的代码
6     }
7 }

显式转换运算符必须通过类型转换调用

1 int i;
2 MyType x = (MyType)i; // int到MyType类型的转换需要进行类型转换

    如果转换操作可能导致异常或信息丢失,则应用explicit关键字标记它。
    implicit:用于声明用户定义的隐式转换运算符 

1 class MyType
2 {
3 public static implicit operator int(MyType m)
4     {
5 // 从MyType转换到int类型的代码
6     }
7 }

1 MyType x;
2 int i = x;  // 隐式地调用MyType的MyType到int类型的转换运算符

隐式转换可以通过消除不必要的类型转换来提高源代码的可读性。
    一般情况下,调用某一个隐式转换时,应当绝不会引发异常,并且不会造成信息丢失。否则,应将其标记为explicit。
方法参数关键字
    如果声明方法的参数时没有指明ref或out,该参数将具有与该方法相关的值。这个值在方法中能被更改,但是当程序返回到调用过程时,这种改动不会被保留。

    params:用于指定在参数数目可变时带有参数的方法参数
    在方法声明中的params关键字之后不允许引入任何其他参数,但在其前面可以有其他参数。而且在方法声明中只允许使用一个params关键字。

 1 public static void UseParams(params int[] list)
 2 {
 3 for(int i = 0; i < list.Length; i++)
 4     {
 5         Console.WriteLine(list[i]);
 6     }
 7 }
 8
 9 public static void Main()
10 {
11     UseParams(1,2,3);
12 int[] myArray = new int[3] { 10,11,12 };
13     UseParams(myArray);
14 }

    ref、out  使方法可以引用传递到该方法的那一个变量,当程序转至调用方法时,在方法中对参数所做的任何改动都将传给该变量。
    ref参数的值将被传递到ref参数,故必须首先初始化;而out参数不然,它的值不会被传递到该out参数,故不必首先初始化,但它必须在方法返回以前为out参数赋值。
    属性不是变量,不能作为ref/out参数。

2007年9月5日星期三

《.NET设计规范》——学习笔记(2.5)框架设计基础

这篇文章在一定程度上是对前面几篇文章的一个总结。

一个成功的通用框架必须是为广大具有不同的需求、技能和背景的开发人员而设计的。框架设计师面临的最大挑战是为这些多样化的用户群提供即简单又功能强大的框架。

  • 要设计即功能强大又易于使用的框架。
    80/20原则。 要把精力集中在框架中使用最为频繁的部分(20%)
  • 要明确地为具有不同编程风格、需求、技能以及使用不同编程语言的开发人员设计框架。
  • 要了解哪些使用多语言框架的广大开发人员。
    我们往往会只为自己设计API,而没有清楚地考虑用户的真正需求。

渐进框架

针对不同的使用场景,为不同的开发团体提供不同的产品,这种多框架的方法在某种程度上说是成功的,比如MS有Visual Basic程序库,有Win32程序库,也有MFC和ATL,但它也存在严重的缺点:多框架使得使用某个框架的开发人员难以将他们的知识转移到下一个技能等级或使用场景(这通常需要另一个框架)。

  • .NET框架所做的是把VB、MFC、ATL、ASP等这些模型统一起来。无论开发人员使用何种编程语言或者选择何种编程模型,可供使用的API始终都是一致的。
    一个更好的方法是提供渐进框架(Progresive framework)。  从无到有,慢慢积累知识,并应用到以后更高级的使用场景中去。
  • .NET框架就是一个渐进框架。
    渐进框架的目标是覆盖广大的开发人员,但并不是所有可能的开发人员。
    这也应了没有十全十美这句话:不可能满足每一个开发人员的需求。

框架设计的基本原则:

对用户而言,真正的开发效率来自能够轻易地创造非凡的产品,而并非来自能够轻易地创造垃圾。

  1. 场景驱动设计原则
  2. 低门栏原则
  3. 自说明对象原则
  4. 分层架构原则

《.NET设计规范》——学习笔记(2.4)分层架构原则

分层设计使得在单个框架中同时提供强大的功能和易用性成为可能。

  • 考虑对框架进行分层,使高层API能提供最佳的开发效率,低层API能提供最强大的功能和最丰富的表现力。
    通俗地讲,象我这样的菜鸟只能用高层API,太低层都不懂,而牛人们都是想用也更愿意用低层API的强大功能的(个人意见)。ps:这边的高层跟低层不是指高深的意思。而是从易用性方面考虑的!
  • 避免把低层API和高层API混在同一名字空间中,如果低层API非常复杂的话(即包含了许多类型)。
  • 要确保单个特性域中不同的层能很好的集成在一起。

2007年9月4日星期二

《.NET设计规范》——学习笔记(2.3)自说明对象原则

在简单的使用场景中,一定要让框架无需文档就能使用。

  • 要确保API是直观的,无需查阅参考文档就能用于基本场景
    你总不希望写个“Hello World”都去查阅API文档吧。
  • 要为所有的API提供优秀的文档。
    一方面,并非所有的API都能自说明。不同的人会认为不同的API是自说明的;
    另一方面,有些人想在开始使用API之前完全理解它们。

设计自说明API时最重要的一些考虑因素:

  1. 命名
    要在规范检查中重视标识符名称的选择;
    不要担心标识符的名字太冗长;
           一眼就能看出相应的方法是做什么的,类型和参数是表示什么意思。
           而且类型的名字如果足够好,那么用到这些类型的代码会更易于立即和维护。
    要在设计过程的初期就让用户教育专家参与;
    考虑把最好的名字留给最常用的类型。
  2. 异常
    要通过异常消息来清楚地告诉开发人员对框架的误用。
  3. 强类型
    很明显,调用Customer.Name要比调用Customer.Properties["Name"]容易。
    要尽可能提供强类型API。
           如果必须使用属性包,那么应该为包中最常用的属性提供相应的强类型属性。
  4. 一致性
    要确保与.NET框架以及客户可能会使用的其他框架保持一致。
  5. 限制抽象的数量
    标准面向对象设计方法会产生大量抽象。其目的是使代码易于维护。然而如果框架中存在太多的抽象,则会要求用户对框架的架构有深入的了解,不易于用户使用。
    避免在主要场景的API中使用太多的抽象。

2007年9月2日星期日

做礼拜

又是一周过去了,星期一又来了!早上,在公司里偷个空,写点东东:

昨天是星期日:基督徒做礼拜的日子!

自从呆子去年去了英国,也有一年多没见到她了。昨天我们越好见面的,还有冲哥和晓丹!昨天也是她要做礼拜的日子:她在英国受洗了!我以前也从来没有见识过基督教做礼拜的场景,所以跟呆子说,也想看看。我到富阳的时候他们的仪式已经开始了,我到的时候,上面是长老在做报告类似的,因为讲得是富阳话,我不大听得懂;不过这个很快也就结束了(大概是在我来之前已经讲了很久了:-P)。然后,他们就开始唱圣歌,唱了很多,我个人感觉那个旋律还不错,也很容易上口,也跟着唱了几首。圣歌中都是些赞颂耶稣,赞颂主的,也有一些是说希望主不要摒弃他们的,还有就是希望主收留他们。呆子,一直都在非常虔诚的唱,而且每次低头祷告的时候都非常虔诚(我作为外人,自然体会不到其中的滋味)。唱完圣歌,过后是讲了一段圣经上的内容,我们四个人,除了呆子,其他的都没有认真的在听,所以也不知道具体地讲了些什么。不过我听到一个故事印象非常深刻:有一对夫妻,妻子是基督徒。故事就讲到那个丈夫,如果打他妻子,她绝对不会还手,而且还是笑脸相迎! 这个也许就是常说的那个故事:如果有人打你左脸,你要把右脸让他打!这个礼拜好像还有圣餐,不过呆子说我们没有受洗过,不能接受的!

后来,也跟呆子聊了很多。也大致了解了基督教的一些东西。她也说她自己变了很多,在英国最重要的事就是找到了自己的主。在我看来她在英国最重要的的确是找到了自己的主,找到了自己一生的寄托。

我从来不会觉得因为有宗教信仰,呆子就是另类了。在我看来呆子还是呆子,唯独变的只是她找到了一个可以倾诉的对象!

《.NET设计规范》——学习笔记(2.2)低门栏原则

框架必须以易于使用的方式来为普通用户提供一个低门栏。

每个人在第一次接触一个新框架时,都希望其是简单而功能强大的。如果他一开始就感觉其很复杂,则会望而却步。

  • 要确保每个特性域的名字空间只包含哪些用于最常见场景的类型。应该把用于更高级的场景的类型放在子名字空间中。
    例如:System.Net命名空间提供了有关网络的主要API,而更高级的socket API则位于System.Net.Socket子命名空间。
  • 要为构造函数和方法提供简单的重载函数。参数少,且都是基本类型。
  • 不要在为主要的使用场景而设计类型中包含用于高级场景(这里我个人感觉是不常用的意思)的方法。
    通过从框架中减少(或至少不增加)特性,能够使开发人员更有效率。因为需要处理的概念减少了。CLR把多重继承排除在外就是这个原因。
  • 不要要求用户在最基本的场景中显示地实例化一个以上的类型。
  • 不要要求用户在为基本使用场景编写代码之前就进行大量的初始化。
    如果一些初始化是必需的,那么当用户未执行初始化而引起异常时,应该在异常消息中清楚的告诉用户需要做些什么。
  • 要尽可能地(用便利的重载函数)为所有的属性和参数提供合适的默认值。
    当然不要盲目采用,如果默认值会引起错误,自然不可取。
  • 要通过异常来传达API的误用。
    显示错误的原因、如何修正等信息。

2007年8月29日星期三

《.NET设计规范》——学习笔记(2)框架设计的基本原则

由于我怕文章的篇幅过长会使人看了头痛,所以,我打算分几篇文章把《.NET设计规范》第二章的学习笔记写出来,这样大家看着不至于太累!大概是接下去总共五篇文章是说“框架设计基础”的......

 

对用户而言,真正的开发效率来自能够轻易地创造出非凡的产品,而并非来自能够轻易地创造垃圾。

场景驱动设计的原则

框架通常包含非常大的一组API。但在开发过程中,真正用到的只是其中较小的一个子集,只会涉及一小部分常用场景。

在设计框架时,使用场景来驱动。从用户的角度,先自己编写一些对主要场景来说必不可少的代码,然后再设计对象模型(object model)来支持这些样例代码。

于功能性规范之前,先撰写一份场景驱动的API规范,应该列出一个给定的技术领域中最常用的5—10个使用场景,并列出实现这些场景的样例代码,至少用两种语言编写。

  • 要确保对任何包含公用API的特性设计来说,其核心部分都是API设计规范。
  • 要为每个主要的特性域(feature area)定义一些最常用的场景。
  • 要确保使用场景与适当的抽象层次相对应。场景应该大致与最终用户的用例相对应。
  • 先为主要的使用场景编写样例代码,然后再定义对象模型来支持这些样例代码。
  • 要用至少两种不同的编程语言来为主要场景编写样例代码。
    最好能保证所选编程语言的语法和风格差异很大。
  • 不要在设计框架的公用API时完全依赖于标准的设计方法。
    标准的设计方法(包括面向对象的方法)是为了使设计的具体实现容易维护,而不是为了使得到的API易于使用。
    以场景驱动设计为主,辅以原型制作、可用性研究以及一定数量的迭代,这种方法要比标准的设计方法好得多。
    可用性研究是为了确定开发人员真正的需求。这跟需求获取一样,设计师此时化身为一名需求分析师,而开发人员则变成了客户。需求分析师不能想当然的认为客户的真正需求是什么,一定要通过跟客户交流才行,站在客户的角度考虑问题。跟需求获取类似,可用性研究宜早不宜迟。
  • 要安排可用性研究来测试用于主要场景的API。
    如果开发人员在为主要场景编写代码时,遇到较大问题,则说明API需要重新设计。在原有API的基础上修改,开销反而大,而且是很大。

2007年8月27日星期一

《.NET设计规范》——学习笔记(1)

第一章 概述

如果框架的设计者能够站在使用者背后解释应该如何使用框架,那么就不需要同规范了。

 

精心设计的框架所具备的品质

  1. 简单性
    在设计框架时,宁可不要某个特性比较复杂的设计,也不能破坏整体的简单性。
  2. 设计代价高
    框架设计需要耗费大量的资源。框架设计应该是开发过程中明确而独立的一部分。
  3. 精心设计的框架充满利弊权衡
    没有十全十美的东西。
  4. 应该借鉴过去
    站在前人的肩膀上。
  5. 要考虑未来的发展
    考虑将来发展框架是一把“双刃剑”。一方面,它以“万一”的名义增加复杂性;另一方面,它可以避免让设计随着时间的流逝而贬值,或避免产生无法向后兼容的设计。
  6. 应具有良好的集成性
  7. 一致性
    一致性是精心设计的框架的关键特征,它是影响开发效率的最重要因素之一。一致的框架可以是开发人员从框架中已了解的部分推知不了解的部分。一致性同时还可以帮助开发人员很快地认识到,设计的哪些部分是某个特定区域所独有的,需要特别加以注意,而哪些部分仅仅是常用的既有设计模式和惯用法。

java垃圾收集算法(转)

很长的一篇文章,不过看看应该有所助益!转自:java垃圾收集算法

1.垃圾收集算法的核心思想
Java语言建立了垃圾收集机制,用以跟踪正在使用的对象和发现并回收不再使用(引用)的对象。该机制可以有效防范动态内存分配中可能发生的两个危险:因内存垃圾过多而引发的内存耗尽,以及不恰当的内存释放所造成的内存非法引用。
垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能,因此需要开发人员做比较深入的了解。
2.触发主GC(Garbage Collector)的条件
JVM进行次GC的频率很高,但因为这种GC占用时间极短,所以对系统产生的影响不大。更值得关注的是主GC的触发条件,因为它对系统影响很明显。总的来说,有两个条件会触发主GC:
①当应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就不会被调用,但以下条件除外。
②Java 堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满足要求,则 JVM将报“out of memory”的错误,Java应用将停止。
由于是否进行主GC由JVM根据系统环境决定,而系统环境在不断的变化当中,所以主GC的运行具有不确定性,无法预计它何时必然出现,但可以确定的是对一个长期运行的应用来说,其主GC是反复进行的。
3.减少GC开销的措施
根据上述GC的机制,程序的运行会直接影响系统环境的变化,从而影响GC的触发。若不针对GC的特点进行设计和编码,就会出现内存驻留等一系列负面影响。为了避免这些影响,基本的原则就是尽可能地减少垃圾和减少GC过程中的开销。具体措施包括以下几个方面:
(1)不要显式调用System.gc()
此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。
(2)尽量减少临时对象的使用
临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。
(3)对象不用时最好显式置为Null
一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。
(4)尽量使用StringBuffer,而不用String来累加字符串(详见blog另一篇文章JAVA中String与StringBuffer)
由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5= Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
(5)能用基本类型如Int,Long,就不用Integer,Long对象
基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。
(6)尽量少用静态对象变量
静态变量属于全局变量,不会被GC回收,它们会一直占用内存。
(7)分散对象创建或删除的时间
集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主 GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。
4.gc与finalize方法
⑴gc方法请求垃圾回收
使用System.gc ()可以不管JVM使用的是哪一种垃圾回收的算法,都可以请求Java的垃圾回收。需要注意的是,调用System.gc()也仅仅是一个请求。JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。
⑵finalize方法透视垃圾收集器的运行
在JVM垃圾收集器收集一个对象之前 ,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java提供了缺省机制来终止化该对象释放资源,这个方法就是finalize()。它的原型为:
protected void finalize() throws Throwable
在finalize()方法返回之后,对象消失,垃圾收集开始执行。原型中的throws Throwable表示它可以抛出任何类型的异常。
因此,当对象即将被销毁时,有时需要做一些善后工作。可以把这些操作写在finalize()方法里。
protected void finalize()
{
// finalization code here
}
⑶代码示例
class Garbage
{
int index;
static int count;
Garbage()
{
count++;
System.out.println("object "+count+" construct");
setID(count);
}
void setID(int id)
{
index=id;
}
protected void finalize() //重写finalize方法
{
System.out.println("object "+index+" is reclaimed");
}
public static void main(String[] args)
{
new Garbage();
new Garbage();
new Garbage();
new Garbage();
System.gc(); //请求运行垃圾收集器
}
}
5.Java 内存泄漏
由于采用了垃圾回收机制,任何不可达对象(对象不再被引用)都可以由垃圾收集线程回收。因此通常说的Java 内存泄漏其实是指无意识的、非故意的对象引用,或者无意识的对象保持。无意识的对象引用是指代码的开发人员本来已经对对象使用完毕,却因为编码的错误而意外地保存了对该对象的引用(这个引用的存在并不是编码人员的主观意愿),从而使得该对象一直无法被垃圾回收器回收掉,这种本来以为可以释放掉的却最终未能被释放的空间可以认为是被“泄漏了”。
考虑下面的程序,在ObjStack类中,使用push和pop方法来管理堆栈中的对象。两个方法中的索引(index)用于指示堆栈中下一个可用位置。push方法存储对新对象的引用并增加索引值,而pop方法减小索引值并返回堆栈最上面的元素。在main方法中,创建了容量为64的栈,并64次调用push方法向它添加对象,此时index的值为64,随后又32次调用pop方法,则 index的值变为32,出栈意味着在堆栈中的空间应该被收集。但事实上,pop方法只是减小了索引值,堆栈仍然保持着对那些对象的引用。故32个无用对象不会被GC回收,造成了内存渗漏。
public class ObjStack {
private Object[] stack;
private int index;
ObjStack(int indexcount) {
stack = new Object[indexcount];
index = 0;
}
public void push(Object obj) {
stack[index] = obj;
index++;
}
public Object pop() {
index--;
return stack[index];
}
}
public class Pushpop {
public static void main(String[] args) {
int i = 0;
Object tempobj;
ObjStack stack1 = new ObjStack(64);//new一个ObjStack对象,并调用有参构造函数。分配stack Obj数组的空间大小为64,可以存64个对象,从0开始存储。
while (i < 64)
{
tempobj = new Object();//循环new Obj对象,把每次循环的对象一一存放在stack Obj数组中。
stack1.push(tempobj);
i++;
System.out.println("第" + i + "次进栈" + "\t");
}
while (i > 32)
{
tempobj = stack1.pop();//这里造成了空间的浪费。
//正确的pop方法可改成如下所指示,当引用被返回后,堆栈删除对他们的引用,因此垃圾收集器在以后可以回收他们。
/*
* public Object pop() {index - -;Object temp = stack [index];stack [index]=null;return temp;}
*/
i--;
System.out.println("第" + (64 - i) + "次出栈" + "\t");
}
}
}
如何消除内存泄漏
  虽然Java虚拟机(JVM)及其垃圾收集器(garbage collector,GC)负责管理大多数的内存任务,Java软件程序中还是有可能出现内存泄漏。实际上,这在大型项目中是一个常见的问题。避免内存泄漏的第一步是要弄清楚它是如何发生的。本文介绍了编写Java代码的一些常见的内存泄漏陷阱,以及编写不泄漏代码的一些最佳实践。一旦发生了内存泄漏,要指出造成泄漏的代码是非常困难的。因此本文还介绍了一种新工具,用来诊断泄漏并指出根本原因。该工具的开销非常小,因此可以使用它来寻找处于生产中的系统的内存泄漏。
垃圾收集器的作用
  虽然垃圾收集器处理了大多数内存管理问题,从而使编程人员的生活变得更轻松了,但是编程人员还是可能犯错而导致出现内存问题。简单地说,GC循环地跟踪所有来自“根”对象(堆栈对象、静态对象、JNI句柄指向的对象,诸如此类)的引用,并将所有它所能到达的对象标记为活动的。程序只可以操纵这些对象;其他的对象都被删除了。因为GC使程序不可能到达已被删除的对象,这么做就是安全的。
  虽然内存管理可以说是自动化的,但是这并不能使编程人员免受思考内存管理问题之苦。例如,分配(以及释放)内存总会有开销,虽然这种开销对编程人员来说是不可见的。创建了太多对象的程序将会比完成同样的功能而创建的对象却比较少的程序更慢一些(在其他条件相同的情况下)。
  而且,与本文更为密切相关的是,如果忘记“释放”先前分配的内存,就可能造成内存泄漏。如果程序保留对永远不再使用的对象的引用,这些对象将会占用并耗尽内存,这是因为自动化的垃圾收集器无法证明这些对象将不再使用。正如我们先前所说的,如果存在一个对对象的引用,对象就被定义为活动的,因此不能删除。为了确保能回收对象占用的内存,编程人员必须确保该对象不能到达。这通常是通过将对象字段设置为null或者从集合(collection)中移除对象而完成的。但是,注意,当局部变量不再使用时,没有必要将其显式地设置为null。对这些变量的引用将随着方法的退出而自动清除。
  概括地说,这就是内存托管语言中的内存泄漏产生的主要原因:保留下来却永远不再使用的对象引用。
典型泄漏
  既然我们知道了在Java中确实有可能发生内存泄漏,就让我们来看一些典型的内存泄漏及其原因。
全局集合
  在大的应用程序中有某种全局的数据储存库是很常见的,例如一个JNDI树或一个会话表。在这些情况下,必须注意管理储存库的大小。必须有某种机制从储存库中移除不再需要的数据。
  这可能有多种方法,但是最常见的一种是周期性运行的某种清除任务。该任务将验证储存库中的数据,并移除任何不再需要的数据。
  另一种管理储存库的方法是使用反向链接(referrer)计数。然后集合负责统计集合中每个入口的反向链接的数目。这要求反向链接告诉集合何时会退出入口。当反向链接数目为零时,该元素就可以从集合中移除了。
缓存
  缓存是一种数据结构,用于快速查找已经执行的操作的结果。因此,如果一个操作执行起来很慢,对于常用的输入数据,就可以将操作的结果缓存,并在下次调用该操作时使用缓存的数据。
  缓存通常都是以动态方式实现的,其中新的结果是在执行时添加到缓存中的。典型的算法是:
检查结果是否在缓存中,如果在,就返回结果。
如果结果不在缓存中,就进行计算。
将计算出来的结果添加到缓存中,以便以后对该操作的调用可以使用。
  该算法的问题(或者说是潜在的内存泄漏)出在最后一步。如果调用该操作时有相当多的不同输入,就将有相当多的结果存储在缓存中。很明显这不是正确的方法。
  为了预防这种具有潜在破坏性的设计,程序必须确保对于缓存所使用的内存容量有一个上限。因此,更好的算法是:
检查结果是否在缓存中,如果在,就返回结果。
如果结果不在缓存中,就进行计算。
如果缓存所占的空间过大,就移除缓存最久的结果。
将计算出来的结果添加到缓存中,以便以后对该操作的调用可以使用。
  通过始终移除缓存最久的结果,我们实际上进行了这样的假设:在将来,比起缓存最久的数据,最近输入的数据更有可能用到。这通常是一个不错的假设。
  新算法将确保缓存的容量处于预定义的内存范围之内。确切的范围可能很难计算,因为缓存中的对象在不断变化,而且它们的引用包罗万象。为缓存设置正确的大小是一项非常复杂的任务,需要将所使用的内存容量与检索数据的速度加以平衡。
  解决这个问题的另一种方法是使用java.lang.ref.SoftReference类跟踪缓存中的对象。这种方法保证这些引用能够被移除,如果虚拟机的内存用尽而需要更多堆的话。
ClassLoader
  Java ClassLoader结构的使用为内存泄漏提供了许多可乘之机。正是该结构本身的复杂性使ClassLoader在内存泄漏方面存在如此多的问题。 ClassLoader的特别之处在于它不仅涉及“常规”的对象引用,还涉及元对象引用,比如:字段、方法和类。这意味着只要有对字段、方法、类或 ClassLoader的对象的引用,ClassLoader就会驻留在JVM中。因为ClassLoader本身可以关联许多类及其静态字段,所以就有许多内存被泄漏了。
确定泄漏的位置
  通常发生内存泄漏的第一个迹象是:在应用程序中出现了 OutOfMemoryError。这通常发生在您最不愿意它发生的生产环境中,此时几乎不能进行调试。有可能是因为测试环境运行应用程序的方式与生产系统不完全相同,因而导致泄漏只出现在生产中。在这种情况下,需要使用一些开销较低的工具来监控和查找内存泄漏。还需要能够无需重启系统或修改代码就可以将这些工具连接到正在运行的系统上。可能最重要的是,当进行分析时,需要能够断开工具而保持系统不受干扰。
  虽然 OutOfMemoryError通常都是内存泄漏的信号,但是也有可能应用程序确实正在使用这么多的内存;对于后者,或者必须增加JVM可用的堆的数量,或者对应用程序进行某种更改,使它使用较少的内存。但是,在许多情况下,OutOfMemoryError都是内存泄漏的信号。一种查明方法是不间断地监控GC的活动,确定内存使用量是否随着时间增加。如果确实如此,就可能发生了内存泄漏。

2007年8月26日星期日

接口和抽象类的定义方式举例说明(转)

本位转自中国IT实验室

原文地址:接口和抽象类的定义方式举例说明

接口定义
关于java的接口定义方式,以下三种情况下可以采用接口定义方式:
1.    接口中声明的变量全部为final 和static类型的,并且这个接口的作用在于定义一些值不能改变的变量。
举个例子:
public interface ObjectConstants{
public static final String SPACE = new String(" ");
public static final char FORMFEED = '\f';
}
2.    接口中只定义可供实现的抽象方法
EventListener.java
    public interface EventListener {
    public void handleEvent(Event evt);
    }
Runnable.java
package java.lang;
    public interface Runnable {   
    public abstract void run();
    }
3.    还有一种方式是上述两种方式的组合,如非必要一般会将这样一个接口定义拆分成两个接口定义
抽象类的定义
1.    如果一个类包含一个接口但是不完全实现接口定义的方法,那么该类必须定义成abstract型
例如InputStream.java类的定义方式:
package java.io;
public abstract class InputStream implements Closeable {
    // SKIP_BUFFER_SIZE is used to determine the size of skipBuffer
    private static final int SKIP_BUFFER_SIZE = 2048;
    // skipBuffer is initialized in skip(long), if needed.
    private static byte[] skipBuffer;   
    public abstract int read() throws IOException;
    public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
    }   
    public int read(byte b[], int off, int len) throws IOException {
    if (b == null) {
        throw new NullPointerException();
    } else if ((off < 0) || (off > b.length) || (len < 0) ||
           ((off + len) > b.length) || ((off + len) < 0)) {
        throw new IndexOutOfBoundsException();
    } else if (len == 0) {
        return 0;
    }
    int c = read();
    if (c == -1) {
        return -1;
    }
    b[off] = (byte)c;
    int i = 1;
    try {
        for (; i < len ; i++) {
        c = read();
        if (c == -1) {
            break;
        }
        if (b != null) {
            b[off + i] = (byte)c;
        }
        }
    } catch (IOException ee) {
    }
    return i;
    }
 public long skip(long n) throws IOException {
    long remaining = n;
    int nr;
    if (skipBuffer == null)
        skipBuffer = new byte[SKIP_BUFFER_SIZE];
    byte[] localSkipBuffer = skipBuffer;        
    if (n <= 0) {
        return 0;
    }
    while (remaining > 0) {
        nr = read(localSkipBuffer, 0,
              (int) Math.min(SKIP_BUFFER_SIZE, remaining));
        if (nr < 0) {
        break;
        }
   remaining -= nr;
    }    
    return n - remaining;
    } 
    public int available() throws IOException {
    return 0;
    }   
    public void close() throws IOException {}  
    public synchronized void mark(int readlimit) {}    
    public synchronized void reset() throws IOException {
    throw new IOException("mark/reset not supported");
    }
    public boolean markSupported() {
    return false;
    }
}
2.    抽象类的方法体中只定义抽象的方法,例如AbstractMethodError.java
    package java.lang;
    public class AbstractMethodError extends IncompatibleClassChangeError {
    public AbstractMethodError() {
    super();}
    public AbstractMethodError(String s) {
    super(s); }
}

2007年8月24日星期五

如何获得数据库里所有表的名字(转)

在CSDN上看到这篇文章,感觉很有用,不敢独享,转过来大家一起看看!

平时我们操作比较多的都是表里的数据,也许突然有一天会需要把所有表的名字都列出来看一看——比如,你的论坛是按每个版块一个表来管理的,这时候你要在首页列出各版块的名字。应该怎么办呢?

肯定得用SELECT吧……但我们平时使用SELECT操作的数据都是表里的数据,表的名字并不是表的数据,这可怎么办呢?

......

原文是用C#实现的,不过我感觉用其他的语言(比如Java)也是很简单的。 

下面是这篇文章的链接:

如何获得数据库里所有表的名字

Java中堆和栈的区别(转)

     本文摘自天极网     作者:dave     原文出处:Java中堆和栈的区别

      栈与堆都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
     Java的堆是一个运行时数据区,类的对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
     栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。
栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
int a = 3;
int b = 3;
      编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。
      这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。
      要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
String是一个特殊的包装类数据。可以用:
String str = new String("abc");
String str = "abc";
      两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。而第二种是先在栈中创建一个对String类的对象引用变量str,然后查找栈中有没有存放"abc",如果没有,则将"abc"存放进栈,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”。
比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
可以看出str1和str2是指向同一个对象的。
String str1 =new String ("abc");
String str2 =new String ("abc");
System.out.println(str1==str2); // false
用new的方式是生成不同的对象。每一次生成一个。
     因此用第二种方式创建多个”abc”字符串,在内存中其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。
      另一方面, 要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象。
由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

2007年8月21日星期二

规范?!

在学校里,一直都知道,做事情(开发)要有规范,应该先怎样,然后怎样,最后做什么,这些都是知道的,而且还是学的重点。可是,真真到了岗位上,真真要自己动手的时候,什么都抛掉了!

只图方便,只图快捷,根本就不管什么可读性,什么可维护性,连注释都没有,更不用说什么文档的。昨晚也想到要好好的写写文档,可是今天到了公司,又不愿意写了。其实,也是另有内情:我实在是不知道,到底该如何下手。

所以,我也请晓鸣能够把他以前写的文档,借我看看,让我好有个参考,看看到底大致该怎么写,应该写些什么内容。

当然,这次,我也暂且先不写,一方面,考虑到,目前为在做的只是一个试用版,日后一定会再做过的;另一方面,钱好像急着想用它,就先完成它再说吧(也许完成后,再来写文档也好,我知道,这个不可取,可总比没有好)。

刚刚,我也想到,等这个完成,给钱之后,让他先去跑跑看;然后,我再抛掉所有的东西,重头做过。到时候要好好的做!

可是,我把这个想法跟晓鸣说了之后,问他我有没有可能重新做过,他却说不大可能。郁闷......看不起我!呵呵......也以此为鉴吧!希望我日后,不要如晓鸣所言,而且,这个算是我的第一个项目,一定要好好做才行。

写此文以记之,望日后自省,望同仁提点!

 

PS:我也深刻认识到,没有写文档所带来的麻烦:尽管大致的结构、功能,心理都清楚,可是很多小地方,细节的东西,都是到了临时才会想起来,这样的开发,不仅没有质量保证,而且效率也不高!很多时候,还要仔细地想清楚,到底有没有什么细节没有考虑进去。

2007年8月18日星期六

Core Java2 6th 摘要(8)

每个组件都有三要素:

  • 内容,例如,按钮的状态(是否按下)或者文本框中的文本
  • 外观显示(颜色、尺寸等)
  • 行为(对事件的反应)

 

模型——视图——控制器(MVC)模式实现三个独立的类:

  • 模型——存储内容
  • 视图——显示内容
  • 控制器——处理用户输入

模型——视图——控制器模式的一点优点是一个模型可以有多个视图,其中每个视图可以显示完整内容的不同部分或不同方面。

 

使用setColumns方法改变了一个文本框的大小以后,以需要调用包容该文本框的容器的validate方法。

textField.setColumns(10);

panel.validate();

validate()方法会重新计算容器内所有组件的大小,并且对它们重新布局。使用完validate()方法以后,布局管理器会重新绘制容器,然后就可以看到改变尺寸后的文本框。

 

通常,不可能通过过滤器避免所有的无效字符串。 

过滤器的另外一个用途就是把字符串中的所有字符变成大写。这样的过滤器很容易编写,在过滤器的insertString和replace方法中,把要被插入的字符串转换为大写的,然后调用超类方法。

 

你可以给任何JComponent附加检验器。如果组件失去焦点,那么就询问检验器。如果检验器报告说组件的内容无效,该组件立即重新获得焦点,用户在提供其他输入前强迫修改内容。

 

在Swing中,组件增加滚动条的通用机制是:把组件添加进一个滚动窗格中。

 

标签是容纳文本的组件。这种组件没有修饰(例如,没有边界),它们也不响应用户输入,你可以使用一个标签来标识组件。给组件设置标签的方法如下:

  1. 使用正确的文本构造一个JLabel组件。
  2. 把该标签组件放置到同需要标识的组件足够近的地方,这样用户能看到该标签标识了哪个组件。

从J2SE1.3开始,按钮、标签以及菜单项中不仅可以无格式的文本,还可以使用HTML文本。注意,第一个使用HTML标签的组件需要延迟一段时间才能显示出来,这是因为必须加载相当复杂的HTML翻译代码。

2007年8月17日星期五

Core Java2 6th 摘要(7)

任何支持GUI的操作都会不断地监视敲打键盘、单击鼠标等事件,操作环境会把这些事件报告给正在运行的程序,程序会决定如何响应这些事件。

 

事件源拥有自己的方法,允许我们向其注册事件监听器。当事件源产生某个事件时,事件源会向注册在那个事件上的所有事件监听器对象发送通知。

在像Java这样的面向对象语言中,关于事件的信息被封装在一个事件对象中。所有的事件对象都是从Java.util.EventObject派生出来的。

 

AWT中事件处理机制的概览:

  • 一个监听器对象是一个实现了专门的监听器接口的类的实例。
  • 一个事件源是一个能够注册监听器对象并向它们发送事件对象的对象。
  • 事件发生时,事件源会把事件对象发生给所有的注册监听器。
  • 监听器对象随后会使用事件对象中的信息来决定对事件的反应。

 

对于ActionListener接口,它可用于多种情况:

  •  当通过双击来选择列表框的一项时。
  • 当选择一菜单项时。
  • 当在文本域中按下ENTER键时。
  • 对于Timer组件来说,当指定时间达到时。

使用ActionListener接口的方法在所有的情况下都一样:actionPerformed方法(ActionListener接口中唯一的方法)只有一个参数,是一个ActionEvent类型的对象。该事件对象给出了所发生事件的详细信息。

 

在Java中,类实现一个接口意味着它要实现该接口中所有方法。

每个具有不止一个方法的AWT监听器接口都有一个实现其所有方法,但方法中什么也不做的适配器类。

 

一个想接收某一事件的类必须实现一个监听器接口。这个类要在事件源中注册自己,然后,接收所需的事件对象。并通过监听器接口中的方法做出相应的处理。

 

AWT明确区分语义事件和底层事件。  语义事件是用于表达用户动作(如“点击按钮”)的事件;底层事件是使这些成为可能的事件。类似地,调整滚动条是个语义事件,而拖动鼠标则是一个底层事件。

 

事件源是用户界面组件、窗口和菜单。操作系统会把用户的动作(如鼠标移动和击键操作)通知给感兴趣的事件源。事件源在一个事件对象中描述事件的特征。事件源还保持有一组监听器——当事件发生时需要调用的对象。当事件发生时,事件源调用监听器接口中的适当方法把事件信息传递给多个监听器。事件源是通过把适当的事件对象传递给监听器类的方法来实现这一点的。监听器通过分析事件对象以找出关于这个事件更多的信息。

 

Swing包提供了一个非常有用的机制,用来封装命令,并把它们连接到多个事件源。这种机制就是Action接口,一个动作是一个对象。它封装了:

  • 命令的说明(用字符串或可选图标表示)。
  • 执行命令需要的参数。

 

所有的AWT事件源都支持一种对应监听器的多点传递模型。这意味着同一事件被传递到不止一个监听器对象。

API不对向事件源注册的一组监听器传递事件的次序作任何保证。因此,不要编写依赖传递次序的逻辑。

 

当操作环境响应用户动作(如鼠标点击)生成事件时,同操作环境通信的AWT部分会收到一个通知,并把它转化为一个AWT事件。接着,AWT把这个事件添加到一个事件队列中。AWT中把事件分派到监听器的部分会:

  • 从事件队列中提取事件。
  • 为事件定位监听器对象。
  • 为事件调用正确的监听器过程。

2007年8月16日星期四

挫折?鞭策!

昨天,有提到关于餐馆的事,今天写了一下概要设计(以前好像从来没有写过),还有就是确定了数据的表。在做概要设计的时候(我是按照国标写的),发现,很多项都很烦的样子。主要是我都看不懂这些条目到底要写些什么,所以都搁在那边,不高兴写了。

我知道这是因为我以前都没有做过项目,我也提过了,原来课程上的那些项目只要写些文档就搞定了,而且还是很多人一起写文档,所以并不能真真的理解这个文档的作用,当然也不知道如何下手喽!我也知道文档的重要性,至少学校里的老师和书都是这么说的......呵呵!

下午,草草地写完了概要设计(其实很多条目还空着),然后完成了数据库表的设计,还有点时间多,我就继续了下前几天写的访问数据库的操作。

因为我是一个超级菜鸟,而且是那种没有飞过的菜鸟,所以所有的东西都是从头开始的。关于那个数据库操作,也是前几天我请晓鸣教我的,那天晚上是顺利的去出了menu表中的数据。今天,我想自己依着那天的思路,写一个函数:getDishByName。居然出了个错,于是我马上就僵死在那边了,郁闷啊!只能回头再请教晓鸣了!一遍又在感慨自己的没用......

不过,我还是感觉自己收获颇丰的,特别是那天晓鸣教我具体的数据库操作的时候。因为,我也知道,其实这些都不难,平常在看代码的时候也基本上能懂,但自己却不知道怎么下手写自己的数据库操作函数.......呵呵。可是,那天晓鸣教了下后,感觉豁然开朗......不过今天马上受挫!无奈......

看来,还得不断的努力啊!  呵呵,没有急功近利的意思,只是鞭策一下自己!

各位,多给我提提意见啊,希望知无不言,言无不尽!  谢谢......

Core Java2 6th 摘要(6)

内部类是定义在其他类内部的类。使用内部类的原因有以下四个:

  • 内部类对象能够访问创建它的对象的实现——包括那些私有数据;
  • 内部类能够隐藏起来,不为同一包中的其他类所见;
  • 匿名内部类可以方便地定义运行时回调;
  • 使用内部类在编写事件驱动的程序时用起来很方便。

局部类不会使用访问指示符(如public或者private)来声明。它们的范围总是限定在声明它们的程序快中。

 

你可以在运行时使用代理创建实现了一组给定接口的新类。只有在编译时无法确定要实现哪些接口时,才有必要使用代理。

 

Java中的顶层窗口(即那些没有包含在其他窗口中的窗口)被称为框架。

框架是一个容器。这意味着一个框架可以容纳按钮、文本域等其他用户界面组件。

 

JFrame类中几个可能最为重要的方法:

  • dispose方法:关闭窗口并收回用于创建窗口的任何系统资源;
  • setIconImage方法:当窗口最小化(在Java中常常称作图标化)时,把一个Image对象作为图标;
  • setTitle方法:改变标题栏中的文字;
  • setResizable方法:使用boolean参数来决定框架大小是否能被用户改变。