建议只是建议而已。
1. 你们的项目组使用源代码管理工具了么?
应该用。VSS、CVS、PVCS、ClearCase、CCC/Harvest、FireFly都可以。我的选择是VSS。
2. 你们的项目组使用缺陷管理系统了么?
应该用。ClearQuest太复杂,我的推荐是BugZilla。
3. 你们的测试组还在用Word写测试用例么?
不要用Word写测试用例(Test Case)。应该用一个专门的系统,可以是Test Manager,也可以是自己开发一个ASP.NET的小网站。主要目的是Track和Browse。
4. 你们的项目组有没有建立一个门户网站?
要有一个门户网站,用来放Contact Info、Baselined Schedule、News等等。推荐Sharepoint Portal Server 2003来实现,15分钟就搞定。买不起SPS 2003可以用WSS (Windows Sharepoint Service)。
5. 你们的项目组用了你能买到最好的工具么?
应该用尽量好的工具来工作。比如,应该用VS.NET而不是Notepad来写C#。用Notepad写程序多半只是一种炫耀。但也要考虑到经费,所以说是"你能买到最好的"。
6. 你们的程序员工作在安静的环境里么?
需要安静环境。这点极端重要,而且要保证每个人的空间大于一定面积。
7. 你们的员工每个人都有一部电话么?
需要每人一部电话。而且电话最好是带留言功能的。当然,上这么一套带留言电话系统开销不小。不过至少每人一部电话要有,千万别搞得经常有人站起来喊:"某某某电话"。《人件》里面就强烈谴责这种做法。
8. 你们每个人都知道出了问题应该找谁么?
应该知道。任何一个Feature至少都应该有一个Owner,当然,Owner可以继续Dispatch给其他人。
9. 你遇到过有人说"我以为…"么?
要消灭"我以为"。Never assume anything。
10. 你们的项目组中所有的人都坐在一起么?
需要。我反对Virtual Team,也反对Dev在美国、Test在中国这种开发方式。能坐在一起就最好坐在一起,好处多得不得了。
11. 你们的进度表是否反映最新开发进展情况?
应该反映。但是,应该用Baseline的方法来管理进度表:维护一份稳定的Schedule,再维护一份最新更改。Baseline的方法也应该用于其它的Spec。Baseline是变更管理里面的一个重要手段。
12. 你们的工作量是先由每个人自己估算的么?
应该让每个人自己估算。要从下而上估算工作量,而不是从上往下分派。除非有其他原因,比如政治任务工期固定等。
13. 你们的开发人员从项目一开始就加班么?
不要这样。不要一开始就搞疲劳战。从项目一开始就加班,只能说明项目进度不合理。当然,一些对日软件外包必须天天加班,那属于剥削的范畴。
14. 你们的项目计划中Buffer Time是加在每个小任务后面的么?
不要。Buffer Time加在每个小任务后面,很容易轻易的就被消耗掉。Buffer Time要整段的加在一个Milestone或者checkpoint前面。
15. 值得再多花一些时间,从95%做到100%好值得,非常值得。
尤其当项目后期人困马乏的时候,要坚持。这会给产品带来质的区别。
16. 登记新缺陷时,是否写清了重现步骤?
要。这属于Dev和Test之间的沟通手段。面对面沟通需要,详细填写Repro Steps也需要。
17. 写新代码前会把已知缺陷解决么?
要。每个人的缺陷不能超过10个或15个,否则必须先解决老的bug才能继续写新代码。
18. 你们对缺陷的轻重缓急有事先的约定么?
必须有定义。Severity要分1、2、3,约定好:蓝屏和Data Lost算Sev 1,Function Error算Sev 2,界面上的算Sev 3。但这种约定可以根据产品质量现状适当进行调整。
19. 你们对意见不一的缺陷有三国会议么?
必须要有。要有一个明确的决策过程。这类似于CCB (Change Control Board)的概念。
20. 所有的缺陷都是由登记的人最后关闭的么?
Bug应该由Opener关闭。Dev不能私自关闭Bug。
21. 你们的程序员厌恶修改老的代码么?
厌恶是正常的。解决方法是组织Code Review,单独留出时间来。XP也是一个方法。
22. 你们项目组有Team Morale Activity么?
每个月都要搞一次,吃饭、唱歌、Outing、打球、开卡丁车等等,一定要有。不要剩这些钱。
23. 你们项目组有自己的Logo么?
要有自己的Logo。至少应该有自己的Codename。
24. 你们的员工有印有公司Logo的T-Shirt么?
要有。能增强归属感。当然,T-Shirt要做的好看一些,最好用80支的棉来做。别没穿几次就破破烂烂的。
25. 总经理至少每月参加次项目组会议要的。
要让team member觉得高层关注这个项目。
26. 你们是给每个Dev开一个分支么?
反对。Branch的管理以及Merge的工作量太大,而且容易出错。
27. 有人长期不Check-In代码么?
不可以。对大部分项目来说,最多两三天就应该Check-In。
28. 在Check-In代码时都填写注释了么?
要写的,至少一两句话,比如"解决了Bug No.225(给bug编号)"。如果往高处拔,这也算做"配置审计"的一部分。
29. 有没有设定每天Check-In的最后期限?
要的,要明确Check-In Deadline。否则会Build Break。
30. 你们能把所有源码一下子编译成安装文件吗?
要的。这是每日编译(Daily Build)的基础。而且必须要能够做成自动的。
31. 你们的项目组做每日编译么?
当然要做。有三样东西是软件项目/产品开发必备的:1. bug management; 2. source control; 3. daily build。
32. 你们公司有没有积累一个项目风险列表?
要。Risk Inventory。否则,下个项目开始的时候,又只能拍脑袋分析Risk了。
33. 设计越简单越好越简单越好。
设计时候多一句话,将来可能就带来无穷无尽的烦恼。应该从一开始就勇敢的砍。这叫scope management。
34. 尽量利用现有的产品、技术、代码千万别什么东西都自己Coding。
BizTalk和Sharepoint就是最好的例子,有这两个作为基础,可以把起点提高很多。或者可以尽量多用现成的Control之类的。或者尽量用XML,而不是自己去Parse一个文本文件;尽量用RegExp,而不是自己从头操作字符串,等等等等。这就是"软件复用"的体现。
35. 你们会隔一段时间就停下来夯实代码么?
要。最好一个月左右一次。传言去年年初Windows组在Stevb的命令下停过一个月增强安全。Btw,"夯"这个字念"hang",第一声。
36. 你们的项目组每个人都写Daily Report么?
要写。五分钟就够了,写10句话左右,告诉自己小组的人今天我干了什么。一则为了沟通,二则鞭策自己(要是游手好闲一天,自己都会不好意思写的)。
37. 你们的项目经理会发出Weekly Report么?
要。也是为了沟通。内容包括目前进度,可能的风险,质量状况,各种工作的进展等。
38. 你们项目组是否至少每周全体开会一次?
要。一定要开会。程序员讨厌开会,但每个礼拜开会时间加起来至少应该有4小时。包括team meeting, spec review meeting, bug triage meeting。千万别大家闷头写code。
39. 你们项目组的会议、讨论都有记录么?
会前发meeting request和agenda,会中有人负责主持和记录,会后有人负责发meeting minutes,这都是effective meeting的要点。而且,每个会议都要形成agreements和action items。
40. 其他部门知道你们项目组在干什么么?
要发一些Newsflash给整个大组织。Show your team's value。否则,当你坐在电梯里面,其他部门的人问:"你们在干嘛",你回答"ABC项目"的时候,别人全然不知,那种感觉不太好。
41. 通过Email进行所有正式沟通
Email的好处是免得抵赖。但也要避免矫枉过正,最好的方法是先用电话和当面说,然后Email来确认。
42. 为项目组建立多个Mailing Group
如果在AD+Exchange里面,就建Distribution List。比如,我会建ABC Project Core Team,ABC Project Dev Team,ABC Project All Testers,ABC Project Extended Team等等。这样发起Email来方便,而且能让该收到email的人都收到、不该收到不被骚扰。
43. 每个人都知道哪里可以找到全部的文档么?
应该每个人都知道。这叫做知识管理(Knowledge Management)。最方便的就是把文档放在一个集中的File Share,更好的方法是用Sharepoint。
44. 你做决定、做变化时,告诉大家原因了么?
要告诉大家原因。Empower team member的手段之一是提供足够的information,这是MSF一开篇的几个原则之一。的确如此,tell me why是人之常情,tell me why了才能有understanding。中国人做事喜欢搞限制,限制信息,似乎能够看到某一份文件的人就是有身份的人。大错特错。权威、权力,不在于是不是能access information/data,而在于是不是掌握资源。
45. Stay agile and expect change 要这样。
需求一定会变的,已经写好的代码一定会被要求修改的。做好心理准备,对change不要抗拒,而是expect change。
46. 你们有没有专职的软件测试人员?
要有专职测试。如果人手不够,可以peer test,交换了测试。千万别自己测试自己的。
47. 你们的测试有一份总的计划来规定做什么和怎么做么?
这就是Test Plan。要不要做性能测试?要不要做Usability测试?什么时候开始测试性能?测试通过的标准是什么?用什么手段,自动的还是手动的?这些问题需要用Test Plan来回答。
48. 你是先写Test Case然后再测试的么?
应该如此。应该先设计再编程、先test case再测试。当然,事情是灵活的。我有时候在做第一遍测试的同时补上test case。至于先test case再开发,我不喜欢,因为不习惯,太麻烦,至于别人推荐,那试试看也无妨。
49. 你是否会为各种输入组合创建测试用例?
不要,不要搞边界条件组合。当心组合爆炸。有很多test case工具能够自动生成各种边界条件的组合--但要想清楚,你是否有时间去运行那么多test case。
50. 你们的程序员能看到测试用例么?
要。让Dev看到Test Case吧。我们都是为了同一个目的走到一起来的:提高质量。
51. 你们是否随便抓一些人来做易用性测试?
要这么做。自己看自己写的程序界面,怎么看都是顺眼的。这叫做审美疲劳--臭的看久了也就不臭了,不方便的永久了也就习惯了。
52. 你对自动测试的期望正确么?
别期望太高。依我看,除了性能测试以外,还是暂时先忘掉"自动测试"吧,忘掉WinRunner和LoadRunner吧。对于国内的软件测试的现状来说,只能"矫枉必须过正"了。
53. 你们的性能测试是等所有功能都开发完才做的么?
不能这样。性能测试不能被归到所谓的"系统测试"阶段。早测早改正,早死早升天。
54. 你注意到测试中的杀虫剂效应了么?
虫子有抗药性,Bug也有。发现的新Bug越来越少是正常的。这时候,最好大家交换一下测试的area,或者用用看其他工具和手法,就又会发现一些新bug了。
55. 你们项目组中有人能说出产品的当前整体质量情况么?
要有。当老板问起这个产品目前质量如何,Test Lead/Manager应该负责回答。
56. 你们有单元测试么?
单元测试要有的。不过没有单元测试也不是不可以,我做过没有单元测试的项目,也做成功了--可能是侥幸,可能是大家都是熟手的关系。还是那句话,软件工程是非常实践、非常工程、非常灵活的一套方法,某些方法在某些情况下会比另一些方法好,反之亦然。
57. 你们的程序员是写完代码就扔过墙的么?
大忌。写好一块程序以后,即便不做单元测试,也应该自己先跑一跑。虽然有了专门的测试人员,做开发的人也不可以一点测试都不做。微软还有Test Release Document的说法,程序太烂的话,测试有权踢回去。
58. 你们的程序中所有的函数都有输入检查么?
不要。虽然说做输入检查是write secure code的要点,但不要做太多的输入检查,有些内部函数之间的参数传递就不必检查输入了,省点功夫。同样的道理,未必要给所有的函数都写注释。写一部分主要的就够了。
59. 产品有统一的错误处理机制和报错界面么?
要有。最好能有统一的error message,然后每个error message都带一个error number。这样,用户可以自己根据error number到user manual里面去看看错误的具体描述和可能原因,就像SQL Server的错误那样。同样,ASP.NET也要有统一的Exception处理。可以参考有关的Application Block。
60. 你们有统一的代码书写规范么?
要有。Code Convention很多,搞一份来发给大家就可以了。当然,要是有FxCop这种工具来检查代码就更好了。
61. 你们的每个人都了解项目的商业意义么?
要。这是Vision的意思。别把项目只当成工作。有时候要想着自己是在为中国某某行业的信息化作先驱者,或者时不时的告诉team member,这个项目能够为某某某国家部门每年节省多少多少百万的纳税人的钱,这样就有动力了。平凡的事情也是可以有个崇高的目标的。
62. 产品各部分的界面和操作习惯一致么?
要这样。要让用户觉得整个程序好像是一个人写出来的那样。
63. 有可以作为宣传亮点的Cool Feature么?
要。这是增强团队凝聚力、信心的。而且,"一俊遮百丑",有亮点就可以掩盖一些问题。这样,对于客户来说,会感觉产品从质量角度来说还是acceptable的。或者说,cool feature或者说亮点可以作为质量问题的一个事后弥补措施。
64. 尽可能缩短产品的启动时间要这样。
软件启动时间(Start-Up time)是客户对性能好坏的第一印象。
65. 不要过于注重内在品质而忽视了第一眼的外在印象程序员容易犯这个错误:太看重性能、稳定性、存储效率,但忽视了外在感受。而高层经理、客户正相反。这两方面要兼顾,协调这些是PM的工作。
66. 你们根据详细产品功能说明书做开发么?
要这样。要有设计才能开发,这是必须的。设计文档,应该说清楚这个产品会怎么运行,应该采取一些讲故事的方法。设计的时候千万别钻细节,别钻到数据库、代码等具体实现里面去,那些是后面的事情,一步步来不能着急。
67. 开始开发和测试之前每个人都仔细审阅功能设计么?
要做。Function Spec review是用来统一思想的。而且,review过以后形成了一致意见,将来再也没有人可以说"你看,当初我就是反对这么设计的,现在吃苦头了吧"
68. 所有人都始终想着The Whole Image么?
要这样。项目里面每个人虽然都只是在制造一片叶子,但每个人都应该知道自己在制造的那片叶子所在的树是怎么样子的。我反对软件蓝领,反对过分的把软件制造看成流水线、车间。参见第61条。
69. Dev工作的划分是单纯纵向或横向的么?
不能单纯的根据功能模块分,或者单纯根据表现层、中间层、数据库层分。我推荐这么做:首先根据功能模块分,然后每个"层"都有一个Owner来Review所有人的设计和代码,保证consistency。
70. 你们的程序员写程序设计说明文档么?
要。不过我听说微软的程序员1999年以前也不写。所以说,写不写也不是绝对的,偷懒有时候也是可以的。参见第56条。
71. 你在招人面试时让他写一段程序么?
要的。我最喜欢让人做字符串和链表一类的题目。这种题目有很多循环、判断、指针、递归等,既不偏向过于考算法,也不偏向过于考特定的API。
72. 你们有没有技术交流讲座?
要的。每一两个礼拜搞一次内部的Tech Talk或者Chalk Talk吧。让组员之间分享技术心得,这笔花钱送到外面去培训划算。
73. 你们的程序员都能专注于一件事情么?
要让程序员专注一件事。例如说,一个部门有两个项目和10个人,一种方法是让10个人同时参加两个项目,每个项目上每个人都花50%时间;另一种方法是5个人去项目A,5个人去项目B,每个人都100%在某一个项目上。我一定选后面一种。这个道理很多人都懂,但很多领导实践起来就把属下当成可以任意拆分的资源了。
74. 你们的程序员会夸大完成某项工作所需要的时间么?
会的,这是常见的,尤其会在项目后期夸大做某个change所需要的时间,以次来抵制change。解决的方法是坐下来慢慢磨,磨掉程序员的逆反心理,一起分析,并把估算时间的颗粒度变小。
75. 尽量不要用Virtual Heads 最好不要用Virtual Heads。 Virtual heads意味着resource is not secure,shared resource会降低resource的工作效率,容易增加出错的机会,会让一心二用的人没有太多时间去review spec、review design。一个dedicated的人,要强过两个只能投入50%时间和精力的人。我是吃过亏的:7个part time的tester,发现的Bug和干的活,加起来还不如两个full-time的。参见第73条。73条是针对程序员的,75条是针对Resource Manager的。
记住建议就是建议!!!
posted @
2006-07-02 19:30 安德尔斯 阅读(594) |
评论 (0) |
编辑 收藏
我们在www.sync4j.org网站上下载Sync4j的源代码,解压到任一目录,其中只有sync4j这个子目录的元源码是我们需要的,其他的目录中的代码是一些工具代码什么的,暂时用不到,用到的时候在拷贝进工程就可以了。
我们的目的是在eclipse中创建一个工程,把Sync4j主要的代码放进去,能够编译通过,哈哈其实这个没什么,只是有一些注意事项:
1.在eclipse中新建一个工程,通过模板“java project”创建;
2.在这个新的工程中,新建一个“source folder”,通过文件系统导入Sync4j的源代码,并把工程的源代码目录指向Sync4j/src/java子目录;
3.定义一个新的“external tool”生成ant build.xml file;
4.在工程的“build path”中添加所有需要的jar包到lib目录;
5.添加ant.jar和j2ee.jar包到lib中;
6.定义一个新的jre并且配置好assert,assert(俗称断言)是在JDK1.4引入的关键字,用于判断值是否为真,当不为真时,就抛出异常,eclipse默认时assert是关闭的,你需要在eclipse中的设置界面里的java->compiler里把assert打开;
7.编辑sync4j-build.properties文件从你的home目录;
文件的内容可能这样:
sunj2eesdk-rootdir=c:/Sun/AppServer
jdom.jar=c:/jar/jdom.jar
junit.jar=c:/jar/junit.jar
cactus.home=c:/jar/jakarta-cactus
dbms=mysql
jdbc.classpath=c:/jar/mysql-connector-java-3.0.14-production-bin.jar
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/sync4j
jdbc.user=sync4j
jdbc.password=sync4j
server=jboss
server.jboss.deploy=c:/dev/jboss-3.0.8/server/default/deploy
最后你可以用ant编译并部署到你的应用服务器上,就可以进行手机,pda,windows mbile等移动设备的数据同步开发。
posted @
2005-11-26 00:33 安德尔斯 阅读(1273) |
评论 (7) |
编辑 收藏
前言:
在我们学习Java的过程中,掌握其中的基本概念对我们的学习无论是J2SE,J2EE,J2ME都是很重要的,J2SE是Java的基础,所以有必要对其中的基本概念做以归纳,以便大家在以后的学习过程中更好的理解java的精髓,在此我总结了30条基本的概念.
Java概述:
目前Java主要应用于中间件的开发(middleware)---处理客户机于服务器之间的通信技术,早期的实践证明,Java不适合pc应用程序的开发,其发展逐渐变成在开发手持设备,互联网信息站,及车载计算机的开发.Java于其他语言所不同的是程序运行时提供了平台的独立性,称许可以在windows,solaris,linux其他操作系统上使用完全相同的代码.Java的语法与C++语法类似,C++/C程序员很容易掌握,而且Java是完全的彻底的面向对象的,其中提出了很好的GC(Garbage Collector)垃圾处理机制,防止内存溢出.
Java的白皮书为我们提出了Java语言的关键特性.
(1)Easy:Java的语法比C++的相对简单,另一个方面就是Java能使软件在很小的机器上运行,基础解释其和类库的支持的大小约为40kb,增加基本的标准库和线程支持的内存需要增加125kb.
(2)分布式:Java带有很强大的TCP/IP协议族的例程库,Java应用程序能够通过URL来穿过网络来访问远程对象,由于servlet机制的出现,使Java编程非常的高效,现在许多的大的web server都支持servlet.
(3)OO:面向对象设计是把重点放在对象及对象的接口上的一个编程技术.其面向对象和C++有很多不同,在与多重继承的处理及Java的原类模型.
(4)健壮特性:Java采取了一个安全指针模型,能减小重写内存和数据崩溃的可能型
(5)安全:Java用来设计网路和分布系统,这带来了新的安全问题,Java可以用来构建防病毒和防攻击的System.事实证明Java在防毒这一方面做的比较好.
(6)中立体系结构:Java编译其生成体系结构中立的目标文件格式可以在很多处理器上执行,编译器产生的指令字节码(Javabytecode)实现此特性,此字节码可以在任何机器上解释执行.
(7)可移植性:Java中对基本数据结构类型的大小和算法都有严格的规定所以可移植性很好.
(8)多线程:Java处理多线程的过程很简单,Java把多线程实现交给底下操作系统或线程程序完成.所以多线程是Java作为服务器端开发语言的流行原因之一
(9)Applet和servlet:能够在网页上执行的程序叫Applet,需要支持Java的浏览器很多,而applet支持动态的网页,这是很多其他语言所不能做到的.
基本概念:
1.OOP中惟一关系的是对象的接口是什么,就像计算机的销售商她不管电源内部结构是怎样的,他只关系能否给你提供电就行了,也就是只要知道can or not而不是how and why.所有的程序是由一定的属性和行为对象组成的,不同的对象的访问通过函数调用来完成,对象间所有的交流都是通过方法调用,通过对封装对象数据,很大限度上提高复用率.
2.OOP中最重要的思想是类,类是模板是蓝图,从类中构造一个对象,即创建了这个类的一个实例(instance)
3.封装:就是把数据和行为结合起在一个包中)并对对象使用者隐藏数据的实现过程,一个对象中的数据叫他的实例字段(instance field)
4.通过扩展一个类来获得一个新类叫继承(inheritance),而所有的类都是由Object根超类扩展而得,根超类下文会做介绍.
5.对象的3个主要特性
behavior---说明这个对象能做什么.
state---当对象施加方法时对象的反映.
identity---与其他相似行为对象的区分标志.
每个对象有惟一的indentity 而这3者之间相互影响.
6.类之间的关系:
use-a :依赖关系
has-a :聚合关系
is-a :继承关系--例:A类继承了B类,此时A类不仅有了B类的方法,还有其自己的方法.(个性存在于共性中)
7.构造对象使用构造器:构造器的提出,构造器是一种特殊的方法,构造对象并对其初始化.
例:Data类的构造器叫Data
new Data()---构造一个新对象,且初始化当前时间.
Data happyday=new Data()---把一个对象赋值给一个变量happyday,从而使该对象能够多次使用,此处要声明的使变量与对象变量二者是不同的.new返回的值是一个引用.
构造器特点:构造器可以有0个,一个或多个参数构造器和类有相同的名字一个类可以有多个构造器,构造器没有返回值,构造器总是和new运算符一起使用.
8.重载:当多个方法具有相同的名字而含有不同的参数时,便发生重载.编译器必须挑选出调用哪个方法.
9.包(package)Java允许把一个或多个类收集在一起成为一组,称作包,以便于组织任务,标准Java库分为许多包.java.lang java.util java,net等,包是分层次的所有的java包都在java和javax包层次内.
10.继承思想:允许在已经存在的类的基础上构建新的类,当你继承一个已经存在的类时,那么你就复用了这个类的方法和字段,同时你可以在新类中添加新的方法和字段.
11.扩展类:扩展类充分体现了is-a的继承关系. 形式为:class (子类) extends (基类).
12.多态:在java中,对象变量是多态的.而java中不支持多重继承.
13.动态绑定:调用对象方法的机制.
(1)编译器检查对象声明的类型和方法名.
(2)编译器检查方法调用的参数类型.
(3)静态绑定:若方法类型为priavte static final 编译器会准确知道该调用哪个方法.
(4)当程序运行并且使用动态绑定来调用一个方法时,那么虚拟机必须调用x所指向的对象的实际类型相匹配的方法版本.
(5)动态绑定:是很重要的特性,它能使程序变得可扩展而不需要重编译已存代码.
14.final类:为防止他人从你的类上派生新类,此类是不可扩展的.
15.动态调用比静态调用花费的时间要长,
16.抽象类:规定一个或多个抽象方法的类本身必须定义为abstract
例: public abstract string getDescripition
17.Java中的每一个类都是从Object类扩展而来的.
18.object类中的equal和toString方法.equal用于测试一个对象是否同另一个对象相等.toString返回一个代表该对象的字符串,几乎每一个类都会重载该方法,以便返回当前状态的正确表示.(toString 方法是一个很重要的方法)
19.通用编程:任何类类型的所有值都可以同object类性的变量来代替.
20.数组列表:ArrayList动态数组列表,是一个类库,定义在java.uitl包中,可自动调节数组的大小.
21.class类 object类中的getClass方法返回class类型的一个实例,程序启动时包含在main方法的类会被加载,虚拟机要加载他需要的所有类,每一个加载的类都要加载它需要的类.
22.class类为编写可动态操纵java代码的程序提供了强大的功能:反射,这项功能为JavaBeans特别有用,使用反射Java能支持VB程序员习惯使用的工具.
能够分析类能力的程序叫反射器,Java中提供此功能的包叫Java.lang.reflect反射机制十分强大.
1.在运行时分析类的能力.
2.在运行时探察类的对象.
3.实现通用数组操纵代码.
4.提供方法对象.
而此机制主要针对是工具者而不是应用及程序.
反射机制中的最重要的部分是允许你检查类的结构.用到的API有:
java.lang.reflect.Field 返回字段.
java.reflect.Method 返回方法.
java.lang.reflect.Constructor 返回参数.
方法指针:java没有方法指针,把一个方法的地址传给另一个方法,可以在后面调用它,而接口是更好的解决方案.
23.接口(Interface)说明类该做什么而不指定如何去做,一个类可以实现一个或多个interface.
24.接口不是一个类,而是对符合接口要求的类的一套规范.
若实现一个接口需要2个步骤:
1.声明类需要实现的指定接口.
2.提供接口中的所有方法的定义.
声明一个类实现一个接口需要使用implements 关键字
class actionB implements Comparable 其actionb需要提供CompareTo方法,接口不是类,不能用new实例化一个接口.
25.一个类只有一个超类,但一个类能实现多个接口.Java中的一个重要接口Cloneable
26.接口和回调.编程一个常用的模式是回调模式,在这种模式中你可以指定当一个特定时间发生时回调对象上的方法.
例:ActionListener 接口监听.
类似的API有:java.swing.JOptionPane
java.swing.Timer
java.awt.Tookit
27.对象clone:clone方法是object一个保护方法,这意味着你的代码不能简单的调用它.
28.内部类:一个内部类的定义是定义在另一个类的内部,原因是:1.一个内部类的对象能够访问创建它的对象的实现,包括私有数据
2.对于同一个包中的其他类来说,内部类能够隐藏起来.
3.匿名内部类可以很方便的定义回调.
4.使用内部类可以非常方便的编写事件驱动程序.
29.代理类(proxy):1.指定接口要求所有代码
2.object类定义的所有的方法(toString equals)
30.数据类型:Java是强调类型的语言,每个变量都必须先申明它都类型,java中总共有8个基本类型.4种是整型,2种是浮点型,一种是字符型,被用于Unicode编码中的字符,布尔型.(T111)
posted @
2005-11-17 10:51 安德尔斯 阅读(268) |
评论 (0) |
编辑 收藏
文件I/O:文件流→序列化
★文件流
文件操作是最简单最直接也是最容易想到的一种方式,我们说的文件操作不仅仅是通过FileInputStream/FileOutputStream这么“裸”的方式直接把数据写入到本地文件(像我以前写的一个扫雷的小游戏JavaMine就是这样保存一局的状态的),这样就比较“底层”了。
主要类与方法和描述
FileInputStream.read() //从本地文件读取二进制格式的数据
FileReader.read() //从本地文件读取字符(文本)数据
FileOutputStream.write() //保存二进制数据到本地文件
FileWriter.write() //保存字符数据到本地文件
★XML
和上面的单纯的I/O方式相比,XML就显得“高档”得多,以至于成为一种数据交换的标准。以DOM方式为例,它关心的是首先在内存中构造文档树,数据保存在某个结点上(可以是叶子结点,也可以是标签结点的属性),构造好了以后一次性的写入到外部文件,但我们只需要知道文件的位置,并不知道I/O是怎么操作的,XML操作方式可能多数人也实践过,所以这里也只列出相关的方法,供初学者预先了解一下。主要的包是javax.xml.parsers,org.w3c.dom,javax.xml.transform。
主要类与方法和描述
DocumentBuilderFactory.newDocumentBuilder().parse() //解析一个外部的XML文件,得到一个Document对象的DOM树
DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() //初始化一棵DOM树
Document.getDocumentElement().appendChild() //为一个标签结点添加一个子结点
Document.createTextNode() //生成一个字符串结点
Node.getChildNodes() //取得某个结点的所有下一层子结点
Node.removeChild() //删除某个结点的子结点
Document.getElementsByTagName() 查找所有指定名称的标签结点
Document.getElementById() //查找指定名称的一个标签结点,如果有多个符合,则返回某一个,通常是第一个
Element.getAttribute() //取得一个标签的某个属性的的值
Element.setAttribute() //设置一个标签的某个属性的的值
Element.removeAttribute() //删除一个标签的某个属性
TransformerFactory.newInstance().newTransformer().transform() //将一棵DOM树写入到外部XML文件
★序列化
使用基本的文件读写方式存取数据,如果我们仅仅保存相同类型的数据,则可以用同一种格式保存,譬如在我的JavaMine中保存一个盘局时,需要保存每一个方格的坐标、是否有地雷,是否被翻开等,这些信息组合成一个“复合类型”;相反,如果有多种不同类型的数据,那我们要么把它分解成若干部分,以相同类型(譬如String)保存,要么我们需要在程序中添加解析不同类型数据格式的逻辑,这就很不方便。于是我们期望用一种比较“高”的层次上处理数据,程序员应该花尽可能少的时间和代码对数据进行解析,事实上,序列化操作为我们提供了这样一条途径。
序列化(Serialization)大家可能都有所接触,它可以把对象以某种特定的编码格式写入或从外部字节流(即ObjectInputStream/ObjectOutputStream)中读取。序列化一个对象非常之简单,仅仅实现一下Serializable接口即可,甚至都不用为它专门添加任何方法:
public class MySerial implements java.io.Serializable
{
//...
}
但有一个条件:即你要序列化的类当中,它的每个属性都必须是是“可序列化”的。这句话说起来有点拗口,其实所有基本类型(就是int,char,boolean之类的)都是“可序列化”的,而你可以看看JDK文档,会发现很多类其实已经实现了Serializable(即已经是“可序列化”的了),于是这些类的对象以及基本数据类型都可以直接作为你需要序列化的那个类的内部属性。如果碰到了不是“可序列化”的属性怎么办?对不起,那这个属性的类还需要事先实现Serializable接口,如此递归,直到所有属性都是“可序列化”的。
主要类与方法和描述
ObjectOutputStream.writeObject() //将一个对象序列化到外部字节流
ObjectInputStream.readObject() //从外部字节流读取并重新构造对象
从实际应用上看来,“Serializable”这个接口并没有定义任何方法,仿佛它只是一个标记(或者说像是Java的关键字)而已,一旦虚拟机看到这个“标记”,就会尝试调用自身预定义的序列化机制,除非你在实现Serializable接口的同时还定义了私有的readObject()或writeObject()方法。这一点很奇怪。不过你要是不愿意让系统使用缺省的方式进行序列化,那就必须定义上面提到的两个方法:
public class MySerial implements java.io.Serializable
{
private void writeObject(java.io.ObjectOutputStream out) throws IOException
{
//...
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
{
//...
}
//...
}
譬如你可以在上面的writeObject()里调用默认的序列化方法ObjectOutputStream.defaultWriteObject();譬如你不愿意将某些敏感的属性和信息序列化,你也可以调用ObjectOutputStream.writeObject()方法明确指定需要序列化那些属性。关于用户可定制的序列化方法,我们将在后面提到。
★Bean
上面的序列化只是一种基本应用,你把一个对象序列化到外部文件以后,用notepad打开那个文件,只能从为数不多的一些可读字符中猜到这是有关这个类的信息文件,这需要你熟悉序列化文件的字节编码方式,那将是比较痛苦的(在《Core Java 2》第一卷里提到了相关编码方式,有兴趣的话可以查看参考资料),某些情况下我们可能需要被序列化的文件具有更好的可读性。另一方面,作为Java组件的核心概念“JavaBeans”,从JDK 1.4开始,其规范里也要求支持文本方式的“长期的持久化”(long-term persistence)。
打开JDK文档,java.beans包里的有一个名为“Encoder”的类,这就是一个可以序列化bean的实用类。和它相关的两个主要类有XMLEcoder和XMLDecoder,显然,这是以XML文件的格式保存和读取bean的工具。他们的用法也很简单,和上面ObjectOutputStream/ObjectInputStream比较类似。
主要类与方法和描述
XMLEncoder.writeObject() //将一个对象序列化到外部字节流
XMLDecoder.readObject() //从外部字节流读取并重新构造对象
如果一个bean是如下格式:
public class MyBean
{
int i;
char[] c;
String s;
//...(get和set操作省略)...
}
那么通过XMLEcoder序列化出来的XML文件具有这样的形式:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.4.0" class="java.beans.XMLDecoder">
<object class="MyBean">
<void property="i">
<int>1</int>
</void>
<void property="c">
<array class="char" length="3">
<void index="0">
<int>a</int>
</void>
<void index="1">
<int>b</int>
</void>
<void index="2">
<int>c</int>
</void>
</array>
</void>
<void property="s">
<string>fox jump!</string>
</void>
</object>
</java>
像AWT和Swing中很多可视化组件都是bean,当然也是可以用这种方式序列化的,下面就是从JDK文档中摘录的一个JFrame序列化以后的XML文件:
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.0" class="java.beans.XMLDecoder">
<object class="javax.swing.JFrame">
<void property="name">
<string>frame1</string>
</void>
<void property="bounds">
<object class="java.awt.Rectangle">
<int>0</int>
<int>0</int>
<int>200</int>
<int>200</int>
</object>
</void>
<void property="contentPane">
<void method="add">
<object class="javax.swing.JButton">
<void property="label">
<string>Hello</string>
</void>
</object>
</void>
</void>
<void property="visible">
<boolean>true</boolean>
</void>
</object>
</java>
因此但你想要保存的数据是一些不是太复杂的类型的话,把它做成bean再序列化也不失为一种方便的选择。
★Properties
在以前我总结的一篇关于集合框架的小文章里提到过,Properties是历史集合类的一个典型的例子,这里主要不是介绍它的集合特性。大家可能都经常接触一些配置文件,如Windows的ini文件,Apache的conf文件,还有Java里的properties文件等,这些文件当中的数据以“关键字-值”对的方式保存。“环境变量”这个概念都知道吧,它也是一种“key-value”对,以前也常常看到版上问“如何取得系统某某信息”之类的问题,其实很多都保存在环境变量里,只要用一条
System.getProperties().list(System.out);
就能获得全部环境变量的列表:
-- listing properties --
java.runtime.name=Java(TM) 2 Runtime Environment, Stand...
sun.boot.library.path=C:\Program Files\Java\j2re1.4.2_05\bin
java.vm.version=1.4.2_05-b04
java.vm.vendor=Sun Microsystems Inc.
java.vendor.url=http://java.sun.com/
path.separator=;
java.vm.name=Java HotSpot(TM) Client VM
file.encoding.pkg=sun.io
user.country=CN
sun.os.patch.level=Service Pack 1
java.vm.specification.name=Java Virtual Machine Specification
user.dir=d:\my documents\项目\eclipse\SWTDemo
java.runtime.version=1.4.2_05-b04
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
java.endorsed.dirs=C:\Program Files\Java\j2re1.4.2_05\li...
os.arch=x86
java.io.tmpdir=C:\DOCUME~1\cn2lx0q0\LOCALS~1\Temp\
line.separator=
java.vm.specification.vendor=Sun Microsystems Inc.
user.variant=
os.name=Windows XP
sun.java2d.fontpath=
java.library.path=C:\Program Files\Java\j2re1.4.2_05\bi...
java.specification.name=Java Platform API Specification
java.class.version=48.0
java.util.prefs.PreferencesFactory=java.util.prefs.WindowsPreferencesFac...
os.version=5.1
user.home=D:\Users\cn2lx0q0
user.timezone=
java.awt.printerjob=sun.awt.windows.WPrinterJob
file.encoding=GBK
java.specification.version=1.4
user.name=cn2lx0q0
java.class.path=d:\my documents\项目\eclipse\SWTDemo\bi...
java.vm.specification.version=1.0
sun.arch.data.model=32
java.home=C:\Program Files\Java\j2re1.4.2_05
java.specification.vendor=Sun Microsystems Inc.
user.language=zh
awt.toolkit=sun.awt.windows.WToolkit
java.vm.info=mixed mode
java.version=1.4.2_05
java.ext.dirs=C:\Program Files\Java\j2re1.4.2_05\li...
sun.boot.class.path=C:\Program Files\Java\j2re1.4.2_05\li...
java.vendor=Sun Microsystems Inc.
file.separator=\
java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport...
sun.cpu.endian=little
sun.io.unicode.encoding=UnicodeLittle
sun.cpu.isalist=pentium i486 i386
主要类与方法和描述
load() //从一个外部流读取属性
store() //将属性保存到外部流(特别是文件)
getProperty() //取得一个指定的属性
setProperty() //设置一个指定的属性
list() //列出这个Properties对象包含的全部“key-value”对
System.getProperties() //取得系统当前的环境变量
你可以这样保存一个properties文件:
Properties prop = new Properties();
prop.setProperty("key1", "value1");
...
FileOutputStream out = new FileOutputStream("config.properties");
prop.store(out, "--这里是文件头,可以加入注释--");
★Preferences
如果我说Java里面可以不使用JNI的手段操作Windows的注册表你信不信?很多软件的菜单里都有“Setting”或“Preferences”这样的选项用来设定或修改软件的配置,这些配置信息可以保存到一个像上面所述的配置文件当中,如果是Windows平台下,也可能会保存到系统注册表中。从JDK 1.4开始,Java在java.util下加入了一个专门处理用户和系统配置信息的java.util.prefs包,其中一个类Preferences是一种比较“高级”的玩意。从本质上讲,Preferences本身是一个与平台无关的东西,但不同的OS对它的SPI(Service Provider Interface)的实现却是与平台相关的,因此,在不同的系统中你可能看到首选项保存为本地文件、LDAP目录项、数据库条目等,像在Windows平台下,它就保存到了系统注册表中。不仅如此,你还可以把首选项导出为XML文件或从XML文件导入。
主要类与方法和描述
systemNodeForPackage() //根据指定的Class对象得到一个Preferences对象,这个对象的注册表路径是从“HKEY_LOCAL_MACHINE\”开始的
systemRoot() //得到以注册表路径HKEY_LOCAL_MACHINE\SOFTWARE\Javasoft\Prefs 为根结点的Preferences对象
userNodeForPackage() //根据指定的Class对象得到一个Preferences对象,这个对象的注册表路径是从“HKEY_CURRENT_USER\”开始的
userRoot() //得到以注册表路径HKEY_CURRENT_USER\SOFTWARE\Javasoft\Prefs 为根结点的Preferences对象
putXXX() //设置一个属性的值,这里XXX可以为基本数值型类型,如int、long等,但首字母大写,表示参数为相应的类型,也可以不写而直接用put,参数则为字符串
getXXX() //得到一个属性的值
exportNode() //将全部首选项导出为一个XML文件
exportSubtree() //将部分首选项导出为一个XML文件
importPreferences() //从XML文件导入首选项
你可以按如下步骤保存数据:
Preferences myPrefs1 = Preferences.userNodeForPackage(this);// 这种方法是在“HKEY_CURRENT_USER\”下按当前类的路径建立一个注册表项
Preferences myPrefs2 = Preferences.systemNodeForPackage(this);// 这种方法是在“HKEY_LOCAL_MACHINE\”下按当前类的路径建立一个注册表项
Preferences myPrefs3 = Preferences.userRoot().node("com.jungleford.demo");// 这种方法是在“HKEY_CURRENT_USER\SOFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路径建立一个注册表项
Preferences myPrefs4 = Preferences.systemRoot().node("com.jungleford.demo");// 这种方法是在“HKEY_LOCAL_MACHINE\SOFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路径建立一个注册表项
myPrefs1.putInt("key1", 10);
myPrefs1.putDouble("key2", -7.15);
myPrefs1.put("key3", "value3");
FileOutputStream out = new FileOutputStream("prefs.xml");
myPrefs1.exportNode(out);
网络I/O:Socket→RMI
★Socket
Socket编程可能大家都很熟,所以就不多讨论了,只是说通过socket把数据保存到远端服务器或从网络socket读取数据也不失为一种值得考虑的方式。
★RMI
RMI机制其实就是RPC(远程过程调用)的Java版本,它使用socket作为基本传输手段,同时也是序列化最重要的一个应用。现在网络传输从编程的角度来看基本上都是以流的方式操作,socket就是一个例子,将对象转换成字节流的一个重要目标就是为了方便网络传输。
想象一下传统的单机环境下的程序设计,对于Java语言的函数(方法)调用(注意与C语言函数调用的区别)的参数传递,会有两种情况:如果是基本数据类型,这种情况下和C语言是一样的,采用值传递方式;如果是对象,则传递的是对象的引用,包括返回值也是引用,而不是一个完整的对象拷贝!试想一下在不同的虚拟机之间进行方法调用,即使是两个完全同名同类型的对象他们也很可能是不同的引用!此外对于方法调用过程,由于被调用过程的压栈,内存“现场”完全被被调用者占有,当被调用方法返回时,才将调用者的地址写回到程序计数器(PC),恢复调用者的状态,如果是两个虚拟机,根本不可能用简单压栈的方式来保存调用者的状态。因为种种原因,我们才需要建立RMI通信实体之间的“代理”对象,譬如“存根”就相当于远程服务器对象在客户机上的代理,stub就是这么来的,当然这是后话了。
本地对象与远程对象(未必是物理位置上的不同机器,只要不是在同一个虚拟机内皆为“远程”)之间传递参数和返回值,可能有这么几种情形:
值传递:这又包括两种子情形:如果是基本数据类型,那么都是“可序列化”的,统统序列化成可传输的字节流;如果是对象,而且不是“远程对象”(所谓“远程对象”是实现了java.rmi.Remote接口的对象),本来对象传递的应该是引用,但由于上述原因,引用是不足以证明对象身份的,所以传递的仍然是一个序列化的拷贝(当然这个对象也必须满足上述“可序列化”的条件)。
引用传递:可以引用传递的只能是“远程对象”。这里所谓的“引用”不要理解成了真的只是一个符号,它其实是一个留在(客户机)本地stub中的,和远端服务器上那个真实的对象张得一模一样的镜像而已!只是因为它有点“特权”(不需要经过序列化),在本地内存里已经有了一个实例,真正引用的其实是这个“孪生子”。
由此可见,序列化在RMI当中占有多么重要的地位。
数据库I/O:CMP、Hibernate
★什么是“Persistence”
用过VMWare的朋友大概都知道当一个guest OS正在运行的时候点击“Suspend”将虚拟OS挂起,它会把整个虚拟内存的内容保存到磁盘上,譬如你为虚拟OS分配了128M的运行内存,那挂起以后你会在虚拟OS所在的目录下找到一个同样是128M的文件,这就是虚拟OS内存的完整镜像!这种内存的镜像手段其实就是“Persistence”(持久化)概念的由来。
★CMP和Hibernate
因为我对J2EE的东西不是太熟悉,随便找了点材料看看,所以担心说的不到位,这次就不作具体总结了,人要学习……真是一件痛苦的事情
序列化再探讨
从以上技术的讨论中我们不难体会到,序列化是Java之所以能够出色地实现其鼓吹的两大卖点??分布式(distributed)和跨平台(OS independent)的一个重要基础。TIJ(即“Thinking in Java”)谈到I/O系统时,把序列化称为“lightweight persistence”??“轻量级的持久化”,这确实很有意思。
★为什么叫做“序列”化?
开场白里我说更习惯于把“Serialization”称为“序列化”而不是“串行化”,这是有原因的。介绍这个原因之前先回顾一些计算机基本的知识,我们知道现代计算机的内存空间都是线性编址的(什么是“线性”知道吧,就是一个元素只有一个唯一的“前驱”和唯一的“后继”,当然头尾元素是个例外;对于地址来说,它的下一个地址当然不可能有两个,否则就乱套了),“地址”这个概念推广到数据结构,就相当于“指针”,这个在本科低年级大概就知道了。注意了,既然是线性的,那“地址”就可以看作是内存空间的“序号”,说明它的组织是有顺序的,“序号”或者说“序列号”正是“Serialization”机制的一种体现。为什么这么说呢?譬如我们有两个对象a和b,分别是类A和B的实例,它们都是可序列化的,而A和B都有一个类型为C的属性,根据前面我们说过的原则,C当然也必须是可序列化的。
import java.io.*;
...
class A implements Serializable
{
C c;
...
}
class B implements Serializable
{
C c;
...
}
class C implements Serializable
{
...
}
A a;
B b;
C c1;
...
注意,这里我们在实例化a和b的时候,有意让他们的c属性使用同一个C类型对象的引用,譬如c1,那么请试想一下,但我们序列化a和b的时候,它们的c属性在外部字节流(当然可以不仅仅是文件)里保存的是一份拷贝还是两份拷贝呢?序列化在这里使用的是一种类似于“指针”的方案:它为每个被序列化的对象标上一个“序列号”(serial number),但序列化一个对象的时候,如果其某个属性对象是已经被序列化的,那么这里只向输出流写入该属性的序列号;从字节流恢复被序列化的对象时,也根据序列号找到对应的流来恢复。这就是“序列化”名称的由来!这里我们看到“序列化”和“指针”是极相似的,只不过“指针”是内存空间的地址链,而序列化用的是外部流中的“序列号链”。
使用“序列号”而不是内存地址来标识一个被序列化的对象,是因为从流中恢复对象到内存,其地址可能就未必是原来的地址了??我们需要的只是这些对象之间的引用关系,而不是死板的原始位置,这在RMI中就更是必要,在两台不同的机器之间传递对象(流),根本就不可能指望它们在两台机器上都具有相同的内存地址。
★更灵活的“序列化”:transient属性和Externalizable
Serializable确实很方便,方便到你几乎不需要做任何额外的工作就可以轻松将内存中的对象保存到外部。但有两个问题使得Serializable的威力收到束缚:
一个是效率问题,《Core Java 2》中指出,Serializable使用系统默认的序列化机制会影响软件的运行速度,因为需要为每个属性的引用编号和查号,再加上I/O操作的时间(I/O和内存读写差的可是一个数量级的大小),其代价当然是可观的。
另一个困扰是“裸”的Serializable不可定制,傻乎乎地什么都给你序列化了,不管你是不是想这么做。其实你可以有至少三种定制序列化的选择。其中一种前面已经提到了,就是在implements Serializable的类里面添加私有的writeObject()和readObject()方法(这种Serializable就不裸了,),在这两个方法里,该序列化什么,不该序列化什么,那就由你说了算了,你当然可以在这两个方法体里面分别调用ObjectOutputStream.defaultWriteObject()和ObjectInputStream.defaultReadObject()仍然执行默认的序列化动作(那你在代码上不就做无用功了?呵呵),也可以用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()方法对你中意的属性进行序列化。但虚拟机一看到你定义了这两个方法,它就不再用默认的机制了。
如果仅仅为了跳过某些属性不让它序列化,上面的动作似乎显得麻烦,更简单的方法是对不想序列化的属性加上transient关键字,说明它是个“暂态变量”,默认序列化的时候就不会把这些属性也塞到外部流里了。当然,你如果定义writeObject()和readObject()方法的化,仍然可以把暂态变量进行序列化。题外话,像transient、violate、finally这样的关键字初学者可能会不太重视,而现在有的公司招聘就偏偏喜欢问这样的问题 :(
再一个方案就是不实现Serializable而改成实现Externalizable接口。我们研究一下这两个接口的源代码,发现它们很类似,甚至容易混淆。我们要记住的是:Externalizable默认并不保存任何对象相关信息!任何保存和恢复对象的动作都是你自己定义的。Externalizable包含两个public的方法:
public void writeExternal(ObjectOutput out) throws IOException;
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
乍一看这和上面的writeObject()和readObject()几乎差不多,但Serializable和Externalizable走的是两个不同的流程:Serializable在对象不存在的情况下,就可以仅凭外部的字节序列把整个对象重建出来;但Externalizable在重建对象时,先是调用该类的默认构造函数(即不含参数的那个构造函数)使得内存中先有这么一个实例,然后再调用readExternal方法对实例中的属性进行恢复,因此,如果默认构造函数中和readExternal方法中都没有赋值的那些属性,特别他们是非基本类型的话,将会是空(null)。在这里需要注意的是,transient只能用在对Serializable而不是Externalizable的实现里面。
★序列化与克隆
从“可序列化”的递归定义来看,一个序列化的对象貌似对象内存映象的外部克隆,如果没有共享引用的属性的化,那么应该是一个深度克隆。关于克隆的话题有可以谈很多,这里就不细说了,有兴趣的话可以参考IBM developerWorks上的一篇文章:JAVA中的指针,引用及对象的clone
一点启示
作为一个实际的应用,我在写那个简易的邮件客户端JExp的时候曾经对比过好几种保存Message对象(主要是几个关键属性和邮件的内容)到本地的方法,譬如XML、Properties等,最后还是选择了用序列化的方式,因为这种方法最简单, 大约可算是“学以致用”罢。这里“存取程序状态”其实只是一个引子话题罢了,我想说的是??就如同前面我们讨论的关于logging的话题一样??在Java面前对同一个问题你可以有很多种solution:熟悉文件操作的,你可能会觉得Properties、XML或Bean比较方便,然后又发现了还有Preferences这么一个东东,大概又会感慨“天外有天”了,等到你接触了很多种新方法以后,结果又会“殊途同归”,重新反省Serialization机制本身。这不仅是Java,科学也是同样的道理。
参考资料
Core Java 2. by Cay S. Horstmann, Gary Cornell
J2SE进阶. by JavaResearch.org
Thinking in Java. by Bruce Eckel
J2SE 1.4.2 Documentation. by java.sun.com
Java Network Programming. by Elliotte R. Harold
Java分布式对象:RMI和CORBA. by IBM developerWorks
posted @
2005-11-17 10:37 安德尔斯 阅读(291) |
评论 (0) |
编辑 收藏
1.document.write(""); 输出语句
2.JS中的注释为//
3.传统的HTML文档顺序是:document->html->(head,body)
4.一个浏览器窗口中的DOM顺序是:window->(navigator,screen,history,location,document)
5.得到表单中元素的名称和值:document.getElementById("表单中元素的ID号").name(或value)
6.一个小写转大写的JS: document.getElementById("output").value = document.getElementById("input").value.toUpperCase();
7.JS中的值类型:String,Number,Boolean,Null,Object,Function
8.JS中的字符型转换成数值型:parseInt(),parseFloat()
9.JS中的数字转换成字符型:(""+变量)
10.JS中的取字符串长度是:(length)
11.JS中的字符与字符相连接使用+号.
12.JS中的比较操作符有:==等于,!=不等于,>,>=,<.<=
13.JS中声明变量使用:var来进行声明
14.JS中的判断语句结构:if(condition){}else{}
15.JS中的循环结构:for([initial expression];[condition];[upadte expression]) {inside loop}
16.循环中止的命令是:break
17.JS中的函数定义:function functionName([parameter],...){statement[s]}
18.当文件中出现多个form表单时.可以用document.forms[0],document.forms[1]来代替.
19.窗口:打开窗口window.open(), 关闭一个窗口:window.close(), 窗口本身:self
20.状态栏的设置:window.status="字符";
21.弹出提示信息:window.alert("字符");
22.弹出确认框:window.confirm();
23.弹出输入提示框:window.prompt();
24.指定当前显示链接的位置:window.location.href="URL"
25.取出窗体中的所有表单的数量:document.forms.length
26.关闭文档的输出流:document.close();
27.字符串追加连接符:+=
28.创建一个文档元素:document.createElement(),document.createTextNode()
29.得到元素的方法:document.getElementById()
30.设置表单中所有文本型的成员的值为空:
var form = window.document.forms[0]
for (var i = 0; i<form.elements.length;i++){
if (form.elements.type == "text"){
form.elements.value = "";
}
}
31.复选按钮在JS中判断是否选中:document.forms[0].checkThis.checked (checked属性代表为是否选中返回TRUE或FALSE)
32.单选按钮组(单选按钮的名称必须相同):取单选按钮组的长度document.forms[0].groupName.length
33.单选按钮组判断是否被选中也是用checked.
34.下拉列表框的值:document.forms[0].selectName.options[n].value (n有时用下拉列表框名称加上.selectedIndex来确定被选中的值)
35.字符串的定义:var myString = new String("This is lightsword");
36.字符串转成大写:string.toUpperCase(); 字符串转成小写:string.toLowerCase();
37.返回字符串2在字符串1中出现的位置:String1.indexOf("String2")!=-1则说明没找到.
38.取字符串中指定位置的一个字符:StringA.charAt(9);
39.取出字符串中指定起点和终点的子字符串:stringA.substring(2,6);
40.数学函数:Math.PI(返回圆周率),Math.SQRT2(返回开方),Math.max(value1,value2)返回两个数中的最在值,Math.pow(value1,10)返回value1的十次方,Math.round(value1)四舍五入函数,Math.floor(Math.random()*(n+1))返回随机数
41.定义日期型变量:var today = new Date();
42.日期函数列表:dateObj.getTime()得到时间,dateObj.getYear()得到年份,dateObj.getFullYear()得到四位的年份,dateObj.getMonth()得到月份,dateObj.getDate()得到日,dateObj.getDay()得到日期几,dateObj.getHours()得到小时,dateObj.getMinutes()得到分,dateObj.getSeconds()得到秒,dateObj.setTime(value)设置时间,dateObj.setYear(val)设置年,dateObj.setMonth(val)设置月,dateObj.setDate(val)设置日,dateObj.setDay(val)设置星期几,dateObj.setHours设置小时,dateObj.setMinutes(val)设置分,dateObj.setSeconds(val)设置秒 [注意:此日期时间从0开始计]
43.FRAME的表示方式: [window.]frames[n].ObjFuncVarName,frames["frameName"].ObjFuncVarName,frameName.ObjFuncVarName
44.parent代表父亲对象,top代表最顶端对象
45.打开子窗口的父窗口为:opener
46.表示当前所属的位置:this
47.当在超链接中调用JS函数时用:(javascript :)来开头后面加函数名
48.在老的浏览器中不执行此JS:<!-- //-->
49.引用一个文件式的JS:<script type="text/javascript" src="aaa.js"></script>
50.指定在不支持脚本的浏览器显示的HTML:<noscript></noscript>
51.当超链和onCLICK事件都有时,则老版本的浏览器转向a.html,否则转向b.html.例:<a href="a.html" onclick="location.href='b.html';return false">dfsadf</a>
52.JS的内建对象有:Array,Boolean,Date,Error,EvalError,Function,Math,Number,Object,RangeError,ReferenceError,RegExp,String,SyntaxError,TypeError,URIError
53.JS中的换行:\n
54.窗口全屏大小:<script>function fullScreen(){ this.moveTo(0,0);this.outerWidth=screen.availWidth;this.outerHeight=screen.availHeight;}window.maximize=fullScreen;</script>
55.JS中的all代表其下层的全部元素
56.JS中的焦点顺序:document.getElementByid("表单元素").tabIndex = 1
57.innerHTML的值是表单元素的值:如<p id="para">"how are <em>you</em>"</p>,则innerHTML的值就是:how are <em>you</em>
58.innerTEXT的值和上面的一样,只不过不会把<em>这种标记显示出来.
59.contentEditable可设置元素是否可被修改,isContentEditable返回是否可修改的状态.
60.isDisabled判断是否为禁止状态.disabled设置禁止状态
61.length取得长度,返回整型数值
62.addBehavior()是一种JS调用的外部函数文件其扩展名为.htc
63.window.focus()使当前的窗口在所有窗口之前.
64.blur()指失去焦点.与FOCUS()相反.
65.select()指元素为选中状态.
66.防止用户对文本框中输入文本:onfocus="this.blur()"
67.取出该元素在页面中出现的数量:document.all.tags("div(或其它HTML标记符)").length
68.JS中分为两种窗体输出:模态和非模态.window.showModaldialog(),window.showModeless()
69.状态栏文字的设置:window.status='文字',默认的状态栏文字设置:window.defaultStatus = '文字.';
70.添加到收藏夹:external.AddFavorite("http://www.dannyg.com";,"jaskdlf");
71.JS中遇到脚本错误时不做任何操作:window.onerror = doNothing; 指定错误句柄的语法为:window.onerror = handleError;
72.JS中指定当前打开窗口的父窗口:window.opener,支持opener.opener...的多重继续.
73.JS中的self指的是当前的窗口
74.JS中状态栏显示内容:window.status="内容"
75.JS中的top指的是框架集中最顶层的框架
76.JS中关闭当前的窗口:window.close();
77.JS中提出是否确认的框:if(confirm("Are you sure?")){alert("ok");}else{alert("Not Ok");}
78.JS中的窗口重定向:window.navigate("http://www.sina.com.cn";);
79.JS中的打印:window.print()
80.JS中的提示输入框:window.prompt("message","defaultReply");
81.JS中的窗口滚动条:window.scroll(x,y)
82.JS中的窗口滚动到位置:window.scrollby
83.JS中设置时间间隔:setInterval("expr",msecDelay)或setInterval(funcRef,msecDelay)或setTimeout
84.JS中的模态显示在IE4+行,在NN中不行:showModalDialog("URL"[,arguments][,features]);
85.JS中的退出之前使用的句柄:function verifyClose(){event.returnValue="we really like you and hope you will stay longer.";}} window.onbeforeunload=verifyClose;
86.当窗体第一次调用时使用的文件句柄:onload()
87.当窗体关闭时调用的文件句柄:onunload()
88.window.location的属性: protocol(http:),hostname(www.example.com),port(80),host(www.example.com:80),pathname("/a/a.html"),hash("#giantGizmo",指跳转到相应的锚记),href(全部的信息)
89.window.location.reload()刷新当前页面.
90.window.history.back()返回上一页,window.history.forward()返回下一页,window.history.go(返回第几页,也可以使用访问过的URL)
91.document.write()不换行的输出,document.writeln()换行输出
92.document.body.noWrap=true;防止链接文字折行.
93.变量名.charAt(第几位),取该变量的第几位的字符.
94."abc".charCodeAt(第几个),返回第几个字符的ASCii码值.
95.字符串连接:string.concat(string2),或用+=进行连接
96.变量.indexOf("字符",起始位置),返回第一个出现的位置(从0开始计算)
97.string.lastIndexOf(searchString[,startIndex])最后一次出现的位置.
98.string.match(regExpression),判断字符是否匹配.
99.string.replace(regExpression,replaceString)替换现有字符串.
100.string.split(分隔符)返回一个数组存储值.
101.string.substr(start[,length])取从第几位到指定长度的字符串.
102.string.toLowerCase()使字符串全部变为小写.
103.string.toUpperCase()使全部字符变为大写.
104.parseInt(string[,radix(代表进制)])强制转换成整型.
105.parseFloat(string[,radix])强制转换成浮点型.
106.isNaN(变量):测试是否为数值型.
107.定义常量的关键字:const,定义变量的关键字:var
posted @
2005-11-17 09:54 安德尔斯 阅读(463) |
评论 (0) |
编辑 收藏
Java Reflection (JAVA反射)
Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性。例如,使用它能获得 Java 类中各成员的名称并显示出来。
Java 的这一能力在实际应用中也许用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性。例如,Pascal、C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息。
JavaBean 是 reflection 的实际应用之一,它能让一些工具可视化的操作软件组件。这些工具通过 reflection 动态的载入并取得 Java 组件(类) 的属性。
1. 一个简单的例子
考虑下面这个简单的例子,让我们看看 reflection 是如何工作的。
import java.lang.reflect.*;
public class DumpMethods {
public static void main(String args[]) {
try {
Class c = Class.forName(args[0]);
Method m[] = c.getDeclaredMethods();
for (int i = 0; i < m.length; i++)
System.out.println(m[i].toString());
} catch (Throwable e) {
System.err.println(e);
}
}
}
按如下语句执行:
java DumpMethods java.util.Stack
它的结果输出为:
public java.lang.Object java.util.Stack.push(java.lang.Object)
public synchronized java.lang.Object java.util.Stack.pop()
public synchronized java.lang.Object java.util.Stack.peek()
public boolean java.util.Stack.empty()
public synchronized int java.util.Stack.search(java.lang.Object)
这样就列出了java.util.Stack 类的各方法名以及它们的限制符和返回类型。
这个程序使用 Class.forName 载入指定的类,然后调用 getDeclaredMethods 来获取这个类中定义了的方法列表。java.lang.reflect.Methods 是用来描述某个类中单个方法的一个类。
2.开始使用 Reflection
用于 reflection 的类,如 Method,可以在 java.lang.relfect 包中找到。使用这些类的时候必须要遵循三个步骤:第一步是获得你想操作的类的 java.lang.Class 对象。在运行中的 Java 程序中,用 java.lang.Class 类来描述类和接口等。
下面就是获得一个 Class 对象的方法之一:
Class c = Class.forName("java.lang.String");
这条语句得到一个 String 类的类对象。还有另一种方法,如下面的语句:
Class c = int.class;
或者
Class c = Integer.TYPE;
它们可获得基本类型的类信息。其中后一种方法中访问的是基本类型的封装类 (如 Integer) 中预先定义好的 TYPE 字段。
第二步是调用诸如 getDeclaredMethods 的方法,以取得该类中定义的所有方法的列表。
一旦取得这个信息,就可以进行第三步了??使用 reflection API 来操作这些信息,如下面这段代码:
Class c = Class.forName("java.lang.String");
Method m[] = c.getDeclaredMethods();
System.out.println(m[0].toString());
它将以文本方式打印出 String 中定义的第一个方法的原型。
在下面的例子中,这三个步骤将为使用 reflection 处理特殊应用程序提供例证。
模拟 instanceof 操作符
得到类信息之后,通常下一个步骤就是解决关于 Class 对象的一些基本的问题。例如,Class.isInstance 方法可以用于模拟 instanceof 操作符:
class A {
}
public class instance1 {
public static void main(String args[]) {
try {
Class cls = Class.forName("A");
boolean b1 = cls.isInstance(new Integer(37));
System.out.println(b1);
boolean b2 = cls.isInstance(new A());
System.out.println(b2);
} catch (Throwable e) {
System.err.println(e);
}
}
}
在这个例子中创建了一个 A 类的 Class 对象,然后检查一些对象是否是 A 的实例。Integer(37) 不是,但 new A() 是。
3.找出类的方法
找出一个类中定义了些什么方法,这是一个非常有价值也非常基础的 reflection 用法。下面的代码就实现了这一用法:
import java.lang.reflect.*;
public class method1 {
private int f1(Object p, int x) throws NullPointerException {
if (p == null)
throw new NullPointerException();
return x;
}
public static void main(String args[]) {
try {
Class cls = Class.forName("method1");
Method methlist[] = cls.getDeclaredMethods();
for (int i = 0; i < methlist.length; i++) {
Method m = methlist[i];
System.out.println("name = " + m.getName());
System.out.println("decl class = " + m.getDeclaringClass());
Class pvec[] = m.getParameterTypes();
for (int j = 0; j < pvec.length; j++)
System.out.println("param #" + j + " " + pvec[j]);
Class evec[] = m.getExceptionTypes();
for (int j = 0; j < evec.length; j++)
System.out.println("exc #" + j + " " + evec[j]);
System.out.println("return type = " + m.getReturnType());
System.out.println("-----");
}
} catch (Throwable e) {
System.err.println(e);
}
}
}
这个程序首先取得 method1 类的描述,然后调用 getDeclaredMethods 来获取一系列的 Method 对象,它们分别描述了定义在类中的每一个方法,包括 public 方法、protected 方法、package 方法和 private 方法等。如果你在程序中使用 getMethods 来代替 getDeclaredMethods,你还能获得继承来的各个方法的信息。
取得了 Method 对象列表之后,要显示这些方法的参数类型、异常类型和返回值类型等就不难了。这些类型是基本类型还是类类型,都可以由描述类的对象按顺序给出。
输出的结果如下:
name = f1
decl class = class method1
param #0 class java.lang.Object
param #1 int
exc #0 class java.lang.NullPointerException
return type = int
-----
name = main
decl class = class method1
param #0 class [Ljava.lang.String;
return type = void
-----
4.获取构造器信息
获取类构造器的用法与上述获取方法的用法类似,如:
import java.lang.reflect.*;
public class constructor1 {
public constructor1() {
}
protected constructor1(int i, double d) {
}
public static void main(String args[]) {
try {
Class cls = Class.forName("constructor1");
Constructor ctorlist[] = cls.getDeclaredConstructors();
for (int i = 0; i < ctorlist.length; i++) {
Constructor ct = ctorlist[i];
System.out.println("name = " + ct.getName());
System.out.println("decl class = " + ct.getDeclaringClass());
Class pvec[] = ct.getParameterTypes();
for (int j = 0; j < pvec.length; j++)
System.out.println("param #" + j + " " + pvec[j]);
Class evec[] = ct.getExceptionTypes();
for (int j = 0; j < evec.length; j++)
System.out.println("exc #" + j + " " + evec[j]);
System.out.println("-----");
}
} catch (Throwable e) {
System.err.println(e);
}
}
}
这个例子中没能获得返回类型的相关信息,那是因为构造器没有返回类型。
这个程序运行的结果是:
name = constructor1
decl class = class constructor1
-----
name = constructor1
decl class = class constructor1
param #0 int
param #1 double
-----
5.获取类的字段(域)
找出一个类中定义了哪些数据字段也是可能的,下面的代码就在干这个事情:
import java.lang.reflect.*;
public class field1 {
private double d;
public static final int i = 37;
String s = "testing";
public static void main(String args[]) {
try {
Class cls = Class.forName("field1");
Field fieldlist[] = cls.getDeclaredFields();
for (int i = 0; i < fieldlist.length; i++) {
Field fld = fieldlist[i];
System.out.println("name = " + fld.getName());
System.out.println("decl class = " + fld.getDeclaringClass());
System.out.println("type = " + fld.getType());
int mod = fld.getModifiers();
System.out.println("modifiers = " + Modifier.toString(mod));
System.out.println("-----");
}
} catch (Throwable e) {
System.err.println(e);
}
}
}
这个例子和前面那个例子非常相似。例中使用了一个新东西 Modifier,它也是一个 reflection 类,用来描述字段成员的修饰语,如“private int”。这些修饰语自身由整数描述,而且使用 Modifier.toString 来返回以“官方”顺序排列的字符串描述 (如“static”在“final”之前)。这个程序的输出是:
name = d
decl class = class field1
type = double
modifiers = private
-----
name = i
decl class = class field1
type = int
modifiers = public static final
-----
name = s
decl class = class field1
type = class java.lang.String
modifiers =
-----
和获取方法的情况一下,获取字段的时候也可以只取得在当前类中申明了的字段信息 (getDeclaredFields),或者也可以取得父类中定义的字段 (getFields) 。
6.根据方法的名称来执行方法
文本到这里,所举的例子无一例外都与如何获取类的信息有关。我们也可以用 reflection 来做一些其它的事情,比如执行一个指定了名称的方法。下面的示例演示了这一操作:
import java.lang.reflect.*;
public class method2 {
public int add(int a, int b) {
return a + b;
}
public static void main(String args[]) {
try {
Class cls = Class.forName("method2");
Class partypes[] = new Class[2];
partypes[0] = Integer.TYPE;
partypes[1] = Integer.TYPE;
Method meth = cls.getMethod("add", partypes);
method2 methobj = new method2();
Object arglist[] = new Object[2];
arglist[0] = new Integer(37);
arglist[1] = new Integer(47);
Object retobj = meth.invoke(methobj, arglist);
Integer retval = (Integer) retobj;
System.out.println(retval.intvalue());
} catch (Throwable e) {
System.err.println(e);
}
}
}
假如一个程序在执行的某处的时候才知道需要执行某个方法,这个方法的名称是在程序的运行过程中指定的 (例如,JavaBean 开发环境中就会做这样的事),那么上面的程序演示了如何做到。
上例中,getMethod 用于查找一个具有两个整型参数且名为 add 的方法。找到该方法并创建了相应的 Method 对象之后,在正确的对象实例中执行它。执行该方法的时候,需要提供一个参数列表,这在上例中是分别包装了整数 37 和 47 的两个 Integer 对象。执行方法的返回的同样是一个 Integer 对象,它封装了返回值 84。
7.创建新的对象
对于构造器,则不能像执行方法那样进行,因为执行一个构造器就意味着创建了一个新的对象 (准确的说,创建一个对象的过程包括分配内存和构造对象)。所以,与上例最相似的例子如下:
import java.lang.reflect.*;
public class constructor2 {
public constructor2() {
}
public constructor2(int a, int b) {
System.out.println("a = " + a + " b = " + b);
}
public static void main(String args[]) {
try {
Class cls = Class.forName("constructor2");
Class partypes[] = new Class[2];
partypes[0] = Integer.TYPE;
partypes[1] = Integer.TYPE;
Constructor ct = cls.getConstructor(partypes);
Object arglist[] = new Object[2];
arglist[0] = new Integer(37);
arglist[1] = new Integer(47);
Object retobj = ct.newInstance(arglist);
} catch (Throwable e) {
System.err.println(e);
}
}
}
根据指定的参数类型找到相应的构造函数并执行它,以创建一个新的对象实例。使用这种方法可以在程序运行时动态地创建对象,而不是在编译的时候创建对象,这一点非常有价值。
8.改变字段(域)的值
reflection 的还有一个用处就是改变对象数据字段的值。reflection 可以从正在运行的程序中根据名称找到对象的字段并改变它,下面的例子可以说明这一点:
import java.lang.reflect.*;
public class field2 {
public double d;
public static void main(String args[]) {
try {
Class cls = Class.forName("field2");
Field fld = cls.getField("d");
field2 f2obj = new field2();
System.out.println("d = " + f2obj.d);
fld.setDouble(f2obj, 12.34);
System.out.println("d = " + f2obj.d);
} catch (Throwable e) {
System.err.println(e);
}
}
}
这个例子中,字段 d 的值被变为了 12.34。
9.使用数组
本文介绍的 reflection 的最后一种用法是创建的操作数组。数组在 Java 语言中是一种特殊的类类型,一个数组的引用可以赋给 Object 引用。观察下面的例子看看数组是怎么工作的:
import java.lang.reflect.*;
public class array1 {
public static void main(String args[]) {
try {
Class cls = Class.forName("java.lang.String");
Object arr = Array.newInstance(cls, 10);
Array.set(arr, 5, "this is a test");
String s = (String) Array.get(arr, 5);
System.out.println(s);
} catch (Throwable e) {
System.err.println(e);
}
}
}
例中创建了 10 个单位长度的 String 数组,为第 5 个位置的字符串赋了值,最后将这个字符串从数组中取得并打印了出来。
下面这段代码提供了一个更复杂的例子:
import java.lang.reflect.*;
public class array2 {
public static void main(String args[]) {
int dims[] = new int[]{5, 10, 15};
Object arr = Array.newInstance(Integer.TYPE, dims);
Object arrobj = Array.get(arr, 3);
Class cls = arrobj.getClass().getComponentType();
System.out.println(cls);
arrobj = Array.get(arrobj, 5);
Array.setInt(arrobj, 10, 37);
int arrcast[][][] = (int[][][]) arr;
System.out.println(arrcast[3][5][10]);
}
}
例中创建了一个 5 x 10 x 15 的整型数组,并为处于 [3][5][10] 的元素赋了值为 37。注意,多维数组实际上就是数组的数组,例如,第一个 Array.get 之后,arrobj 是一个 10 x 15 的数组。进而取得其中的一个元素,即长度为 15 的数组,并使用 Array.setInt 为它的第 10 个元素赋值。
注意创建数组时的类型是动态的,在编译时并不知道其类型。
posted @
2005-11-17 09:53 安德尔斯 阅读(634) |
评论 (2) |
编辑 收藏
1.对象的复制
2.clone()的使用
3.对象实例的比较
////////////////////////////
1.对象的复制
-
- String str1 = "This is a string!" //这里是 "对象引用" 的复制
- String str2 = new String(str1); //这里是 "对象实例" 的复制
浅复制: 只复制复合对象本身.
深复制: 除了复制复合对象本身, 还复制了复合对象的引用的对象实例.
例如:
-
- class Pupil{
- public Pupil(String sno, String name, int age){
- this.sno = new String(sno);
- this.name = new String(name);
- this.age = age;
- }
-
- public String getSno() {
- return sno;
- }
-
- public String getName() {
- return name;
- }
-
- public int getAge() {
- return age;
- }
-
- public void setAge(int age) {
- this.age = age;
- }
-
- private String sno;
- private String name;
- private int age;
- }
-
- public class CopyDemo {
- public static Pupil[] shallowCopy(Pupil[] aClass) {
- Pupil[] newClass = new Pupil[aClass.length];
-
- //此时newClass 与aClass 指向同一块内存
- for(int i=0; i<aClass.length; i++)
- newClass[i] = aClass[i];
- return newClass;
- }
-
- public static Pupil[] deepCopy(Pupil[] aClass) {
- Pupil[] newClass = new Pupil[aClass.length];
-
- //此时newClass 与aClass 的相应sno , name 指向同一块内存
- for(int i=0; i<aClass.length; i++) {
- String sno = aClass[i].getSno();
- String name = aClass[i].getName();
- int age = aClass[i].getAge();
- newClass[i] = new Pupil(sno, name, age);
- }
-
- return newClass;
- }
-
- public static Pupil[] deeperCopy(Pupil[] aClass) {
- Pupil[] newClass = new Pupil[aClass.length];
-
- //完全的复制
- for(int i=0; i<aClass.length; i++) {
- String sno = new String(aClass[i].getSno());
- String name = new String(aClass[i].getName());
- int age = aClass[i].getAge();
- newClass[i] = new Pupil(sno, name, age);
- }
-
- return newClass;
- }
- }
2.clone()的使用
* Object.clone()
* Cloneable 接口
* CloneNotSupportedException
a. 使用Object.clone 进行复制
两个必须条件:
1.一定要将重定义后的clone() 方法定义为公有方法(在Object 类中, 它是受保护的成员, 不能直接使用)
2.该后代类声明实现接口 Cloneable 接口(当类实现该接口, 其任何子类也会继承该接口), 该接口实际上没有任何
内容, 只是一个标识, 标志实现该接口的类提供clone() 方法.(这是接口的一种非典型用法)
b.重写Object.clone()
例如对 private char[] cb; character buffer 进行复制
c.复制数组
数组是在方法调用重以引用的形式传递的对象. 下述情况下非常适合引用来传递数组:
*正在接收的方法不修改数组
*正在调用的方法不必关心是否修改数组
*正在调用的方法想要得到数组中的修改结果
否则, 就应该在方法调用中传递数组对象的副本. 只需调用 arrObj.clone() 方法即可完成数组arrObj 的复制操作. 随后将该数组副本强制转换为其正确类型:
(type[])arrObj.clone();
System.arraycopy 方法提供一种用于在数组间复制多个元素的有效方式.
System.arraycopy(source, i, target, j, len)
3.对象实例的比较
例如:
-
- Pupil p1 = new Pupil("99184001", "zhang3", 18);
- Pupil p2 = new Pupil("99184001", "zhang3", 18);
a. "=="
if(p1 == p2)...
此次测试的是对象引用, 其结果肯定是false, 只要两个对象引用不是互为别名就不会相等.
b. 浅比较 false
-
- if(p1.getSno() == p2.getSno() && p1.getName() == p2.getName()
- && p1.getAge() == p2.getAge()) ...;
c. 深比较 true[/code]
if(p1.getSno().equals(p2.getSno()) && p1.getName().equals(p2.getName())
&& p1.getAge() == p2.getAge()) ...;[/code]
JAVA API 的跟类Object 也提供了equals() 方法, 但它只是比较两个对象引用, 而非比较两个对象实例.
不管怎样, 如果需要比较Pupil 类的对象(例如要将它们放入对象容器), 应该为Pupil 类重定义equals() 方法:
-
- public boolean equals(Object otherobj) {
- //检查otherobj 是否为空
- if(otherobj == null) return false;
-
- //检查otherobj 是否就是当前对象
- if(otherobj == this) return true;
-
- //检查otherobj 是否具有正确的类型, 即检查是否可与当前对象比较
- if(!(otherobj instanceof Pupil)) return false;
-
- //将otherobj 转换为Pupil 类的对象引用
- Pupil tmpObj = (Pupil)otherobj;
- //关于学生是否相等的逻辑检查
- if(sno.equals(tmpObj.sno) && name.equals(tmpObj.name)
- && age == tmpObj.age) return true;
-
- return false;
- }
JAVA API 所提供的每个类几乎都提供了采用深比较策略的equals() 方法, 例如String 类equals() 方法. 一般来说, 用户自己定义的类也应当提供合适的equals() 方法, 特别是当程序要将其对象放入JAVA API 所提供的对象容器类的时候.
按照约定, 任何类所提供的equals() 方法所实现的相等比较应该是等价关系, 即满足自反性, 对称性和传递性. 另外一个类重定义了equals() 方法, 也应该重定义相应hashCode() 方法, 否则将这个类的对象放入映射对象容器时也会发生以外.
posted @
2005-11-17 09:47 安德尔斯 阅读(954) |
评论 (1) |
编辑 收藏
??Java反射机制
侯捷观点
Java反射机制
摘要
Reflection 是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时
透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如
public, static 等等)、superclass(例如Object)、实现之interfaces(例如
Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起
methods。本文借由实例,大面积示范Reflection APIs。
关于本文:
读者基础:具备Java 语言基础。
本文适用工具:JDK1.5
关键词:
Introspection(内省、内观)
Reflection(反射)
有时候我们说某个语言具有很强的动态性,有时候我们会区分动态和静态的不同技术与作法。我们
朗朗上口动态绑定(dynamic binding)、动态链接(dynamic linking)、动态加载
(dynamic loading)等。然而“动态”一词其实没有绝对而普遍适用的严格定义,有时候甚至
像对象导向当初被导入编程领域一样,一人一把号,各吹各的调。
一般而言,开发者社群说到动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构
或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C+
+,Java,C#不是动态语言。
尽管在这样的定义与分类下Java不是动态语言,它却有着一个非常突出的动态相关机制:
Reflection。这个字的意思是“反射、映象、倒影”,用在Java身上指的是我们可以于运行时加
载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名
称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设
值、或唤起其methods1。这种“看透class”的能力(the ability of the program to
examine itself)被称为introspection(内省、内观、反省)。Reflection和
introspection是常被并提的两个术语。
Java如何能够做出上述的动态特性呢?这是一个深远话题,本文对此只简单介绍一些概念。整个篇
幅最主要还是介绍Reflection APIs,也就是让读者知道如何探索class的结构、如何对某
个“运行时才获知名称的class”生成一份实体、为其fields设值、调用其methods。本文将谈到
file:///H|/download/806.html(第 1/12 页)2005-9-8 12:03:22
侯捷观点??Java反射机制
java.lang.Class,以及java.lang.reflect中的Method、Field、Constructor等等
classes。
“Class”class
众所周知Java有个Object class,是所有Java classes的继承根源,其内声明了数个应该在所
有Java class中被改写的methods:hashCode()、equals()、clone()、toString()、
getClass()等。其中getClass()返回一个Class object。
Class class十分特殊。它和一般classes一样继承自Object,其实体用以表达Java程序运行时
的classes和interfaces,也用来表达enum、array、primitive Java types
(boolean, byte, char, short, int, long, float, double)以及关键词void。当一
个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM 便自动产
生一个Class object。如果您想借由“修改Java标准库源码”来观察Class object的实际生成
时机(例如在Class的constructor内添加一个println()),不能够!因为Class并没有
public constructor(见图1)。本文最后我会拨一小块篇幅顺带谈谈Java标准库源码的改动办
法。
Class是Reflection故事起源。针对任何您想探勘的class,唯有先为它产生一个Class
object,接下来才能经由后者唤起为数十多个的Reflection APIs。这些APIs将在稍后的探险
活动中一一亮相。
#001 public final
#002 class Class<T> implements java.io.Serializable,
#003 java.lang.reflect.GenericDeclaration,
#004 java.lang.reflect.Type,
#005 java.lang.reflect.AnnotatedElement {
#006 private Class() {}
#007 public String toString() {
#008 return ( isInterface() ? "interface " :
#009 (isPrimitive() ? "" : "class "))
#010 + getName();
#011 }
...
图1:Class class片段。注意它的private empty ctor,意指不允许任何人经由编程方式产生Class object。是的,其object 只能由
JVM 产生。
“Class” object的取得途径
Java允许我们从多种管道为一个class生成对应的Class object。图2是一份整理。
Class object 诞生管道示例
运用getClass()
注:每个class 都有此函数
String str = "abc";
Class c1 = str.getClass();
file:///H|/download/806.html(第 2/12 页)2005-9-8 12:03:22
侯捷观点??Java反射机制
运用
Class.getSuperclass()2
Button b = new Button();
Class c1 = b.getClass();
Class c2 = c1.getSuperclass();
运用static method
Class.forName()
(最常被使用)
Class c1 = Class.forName ("java.lang.
String");
Class c2 = Class.forName ("java.awt.Button");
Class c3 = Class.forName ("java.util.
LinkedList$Entry");
Class c4 = Class.forName ("I");
Class c5 = Class.forName ("[I");
运用
.class 语法
Class c1 = String.class;
Class c2 = java.awt.Button.class;
Class c3 = Main.InnerClass.class;
Class c4 = int.class;
Class c5 = int[].class;
运用
primitive wrapper
classes
的TYPE 语法
Class c1 = Boolean.TYPE;
Class c2 = Byte.TYPE;
Class c3 = Character.TYPE;
Class c4 = Short.TYPE;
Class c5 = Integer.TYPE;
Class c6 = Long.TYPE;
Class c7 = Float.TYPE;
Class c8 = Double.TYPE;
Class c9 = Void.TYPE;
图2:Java 允许多种管道生成Class object。
Java classes 组成分析
首先容我以图3的java.util.LinkedList为例,将Java class的定义大卸八块,每一块分别对
应图4所示的Reflection API。图5则是“获得class各区块信息”的程序示例及执行结果,它们
都取自本文示例程序的对应片段。
package java.util; //(1)
import java.lang.*; //(2)
public class LinkedList<E> //(3)(4)(5)
extends AbstractSequentialList<E> //(6)
implements List<E>, Queue<E>,
Cloneable, java.io.Serializable //(7)
{
private static class Entry<E> { … }//(8)
public LinkedList() { … } //(9)
public LinkedList(Collection<? extends E> c) { … }
public E getFirst() { … } //(10)
public E getLast() { … }
private transient Entry<E> header = …; //(11)
private transient int size = 0;
}
file:///H|/download/806.html(第 3/12 页)2005-9-8 12:03:22
侯捷观点??Java反射机制
图3:将一个Java class 大卸八块,每块相应于一个或一组Reflection APIs(图4)。
Java classes 各成份所对应的Reflection APIs
图3的各个Java class成份,分别对应于图4的Reflection API,其中出现的Package、
Method、Constructor、Field等等classes,都定义于java.lang.reflect。
Java class 内
部模块(参见图
3)
Java class 内部模块说明相应之Reflection
API,多半为Class
methods。
返回值类型
(return type)
(1) package class隶属哪个package getPackage() Package
(2) import class导入哪些classes 无直接对应之API。
解决办法见图5-2。
(3) modifier class(或methods,
fields)的属性
int getModifiers()
Modifier.toString
(int)
Modifier.
isInterface(int)
int
String
bool
(4) class
name or
interface
name
class/interface 名称getName() String
(5) type
parameters
参数化类型的名称getTypeParameters
()
TypeVariable
<Class>[]
(6) base
class
base class(只可能一个) getSuperClass() Class
(7)
implemented
interfaces
实现有哪些interfaces getInterfaces() Class[]
(8) inner
classes
内部classes getDeclaredClasses
()
Class[]
(8') outer
class
如果我们观察的class 本身是
inner classes,那么相对它
就会有个outer class。
getDeclaringClass() Class
(9)
constructors
构造函数
getDeclaredConstructors
()
不论 public 或
private 或其它
access level,皆可获
得。另有功能近似之取得
函数。
Constructor[]
file:///H|/download/806.html(第 4/12 页)2005-9-8 12:03:22
侯捷观点??Java反射机制
(10) methods 操作函数
getDeclaredMethods()
不论 public 或
private 或其它
access level,皆可获
得。另有功能近似之取得
函数。
Method[]
(11) fields 字段(成员变量) getDeclaredFields()
不论 public 或
private 或其它
access level,皆可获
得。另有功能近似之取得
函数。
Field[]
图4:Java class大卸八块后(如图3),每一块所对应的Reflection API。本表并非
Reflection APIs 的全部。
Java Reflection API 运用示例
图5示范图4提过的每一个Reflection API,及其执行结果。程序中出现的tName()是个辅助函
数,可将其第一自变量所代表的“Java class完整路径字符串”剥除路径部分,留下class名
称,储存到第二自变量所代表的一个hashtable去并返回(如果第二自变量为null,就不储存而只
是返回)。
#001 Class c = null;
#002 c = Class.forName(args[0]);
#003
#004 Package p;
#005 p = c.getPackage();
#006
#007 if (p != null)
#008 System.out.println("package "+p.getName()+";");
执行结果(例):
package java.util;
图5-1:找出class 隶属的package。其中的c将继续沿用于以下各程序片段。
#001 ff = c.getDeclaredFields();
#002 for (int i = 0; i < ff.length; i++)
#003 x = tName(ff[i].getType().getName(), classRef);
#004
#005 cn = c.getDeclaredConstructors();
#006 for (int i = 0; i < cn.length; i++) {
#007 Class cx[] = cn[i].getParameterTypes();
#008 for (int j = 0; j < cx.length; j++)
#009 x = tName(cx[j].getName(), classRef);
#010 }
#011
#012 mm = c.getDeclaredMethods();
#013 for (int i = 0; i < mm.length; i++) {
#014 x = tName(mm[i].getReturnType().getName(), classRef);
#015 Class cx[] = mm[i].getParameterTypes();
file:///H|/download/806.html(第 5/12 页)2005-9-8 12:03:22
侯捷观点??Java反射机制
#016 for (int j = 0; j < cx.length; j++)
#017 x = tName(cx[j].getName(), classRef);
#018 }
#019 classRef.remove(c.getName()); //不必记录自己(不需import 自己)
执行结果(例):
import java.util.ListIterator;
import java.lang.Object;
import java.util.LinkedList$Entry;
import java.util.Collection;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
图5-2:找出导入的classes,动作细节详见内文说明。
#001 int mod = c.getModifiers();
#002 System.out.print(Modifier.toString(mod)); //整个modifier
#003
#004 if (Modifier.isInterface(mod))
#005 System.out.print(" "); //关键词 "interface" 已含于modifier
#006 else
#007 System.out.print(" class "); //关键词 "class"
#008 System.out.print(tName(c.getName(), null)); //class 名称
执行结果(例):
public class LinkedList
图5-3:找出class或interface 的名称,及其属性(modifiers)。
#001 TypeVariable<Class>[] tv;
#002 tv = c.getTypeParameters(); //warning: unchecked conversion
#003 for (int i = 0; i < tv.length; i++) {
#004 x = tName(tv[i].getName(), null); //例如 E,K,V...
#005 if (i == 0) //第一个
#006 System.out.print("<" + x);
#007 else //非第一个
#008 System.out.print("," + x);
#009 if (i == tv.length-1) //最后一个
#010 System.out.println(">");
#011 }
执行结果(例):
public abstract interface Map<K,V>
或 public class LinkedList<E>
图5-4:找出parameterized types 的名称
#001 Class supClass;
#002 supClass = c.getSuperclass();
#003 if (supClass != null) //如果有super class
#004 System.out.print(" extends" +
#005 tName(supClass.getName(),classRef));
执行结果(例):
public class LinkedList<E>
extends AbstractSequentialList,
file:///H|/download/806.html(第 6/12 页)2005-9-8 12:03:22
侯捷观点??Java反射机制
图5-5:找出base class。执行结果多出一个不该有的逗号于尾端。此非本处重点,为简化计,不多做处理。
#001 Class cc[];
#002 Class ctmp;
#003 //找出所有被实现的interfaces
#004 cc = c.getInterfaces();
#005 if (cc.length != 0)
#006 System.out.print(", \r\n" + " implements "); //关键词
#007 for (Class cite : cc) //JDK1.5 新式循环写法
#008 System.out.print(tName(cite.getName(), null)+", ");
执行结果(例):
public class LinkedList<E>
extends AbstractSequentialList,
implements List, Queue, Cloneable, Serializable,
图5-6:找出implemented interfaces。执行结果多出一个不该有的逗号于尾端。此非本处重点,为简化计,不多做处
理。
#001 cc = c.getDeclaredClasses(); //找出inner classes
#002 for (Class cite : cc)
#003 System.out.println(tName(cite.getName(), null));
#004
#005 ctmp = c.getDeclaringClass(); //找出outer classes
#006 if (ctmp != null)
#007 System.out.println(ctmp.getName());
执行结果(例):
LinkedList$Entry
LinkedList$ListItr
图5-7:找出inner classes 和outer class
#001 Constructor cn[];
#002 cn = c.getDeclaredConstructors();
#003 for (int i = 0; i < cn.length; i++) {
#004 int md = cn[i].getModifiers();
#005 System.out.print(" " + Modifier.toString(md) + " " +
#006 cn[i].getName());
#007 Class cx[] = cn[i].getParameterTypes();
#008 System.out.print("(");
#009 for (int j = 0; j < cx.length; j++) {
#010 System.out.print(tName(cx[j].getName(), null));
#011 if (j < (cx.length - 1)) System.out.print(", ");
#012 }
#013 System.out.print(")");
#014 }
执行结果(例):
public java.util.LinkedList(Collection)
public java.util.LinkedList()
图5-8a:找出所有constructors
#004 System.out.println(cn[i].toGenericString());
执行结果(例):
file:///H|/download/806.html(第 7/12 页)2005-9-8 12:03:22
侯捷观点??Java反射机制
public java.util.LinkedList(java.util.Collection<? extends E>)
public java.util.LinkedList()
图5-8b:找出所有constructors。本例在for 循环内使用toGenericString(),省事。
#001 Method mm[];
#002 mm = c.getDeclaredMethods();
#003 for (int i = 0; i < mm.length; i++) {
#004 int md = mm[i].getModifiers();
#005 System.out.print(" "+Modifier.toString(md)+" "+
#006 tName(mm[i].getReturnType().getName(), null)+" "+
#007 mm[i].getName());
#008 Class cx[] = mm[i].getParameterTypes();
#009 System.out.print("(");
#010 for (int j = 0; j < cx.length; j++) {
#011 System.out.print(tName(cx[j].getName(), null));
#012 if (j < (cx.length - 1)) System.out.print(", ");
#013 }
#014 System.out.print(")");
#015 }
执行结果(例):
public Object get(int)
public int size()
图5-9a:找出所有methods
#004 System.out.println(mm[i].toGenericString());
public E java.util.LinkedList.get(int)
public int java.util.LinkedList.size()
图5-9b:找出所有methods。本例在for 循环内使用toGenericString(),省事。
#001 Field ff[];
#002 ff = c.getDeclaredFields();
#003 for (int i = 0; i < ff.length; i++) {
#004 int md = ff[i].getModifiers();
#005 System.out.println(" "+Modifier.toString(md)+" "+
#006 tName(ff[i].getType().getName(), null) +" "+
#007 ff[i].getName()+";");
#008 }
执行结果(例):
private transient LinkedList$Entry header;
private transient int size;
图5-10a:找出所有fields
#004 System.out.println("G: " + ff[i].toGenericString());
private transient java.util.LinkedList.java.util.LinkedList$Entry<E> ??
java.util.LinkedList.header
private transient int java.util.LinkedList.size
图5-10b:找出所有fields。本例在for 循环内使用toGenericString(),省事。
找出class参用(导入)的所有classes
file:///H|/download/806.html(第 8/12 页)2005-9-8 12:03:22
侯捷观点??Java反射机制
没有直接可用的Reflection API可以为我们找出某个class参用的所有其它classes。要获得这
项信息,必须做苦工,一步一脚印逐一记录。我们必须观察所有fields的类型、所有methods(包
括constructors)的参数类型和回返类型,剔除重复,留下唯一。这正是为什么图5-2程序代码
要为tName()指定一个hashtable(而非一个null)做为第二自变量的缘故:hashtable可为我
们储存元素(本例为字符串),又保证不重复。
本文讨论至此,几乎可以还原一个class的原貌(唯有methods 和ctors的定义无法取得)。接下
来讨论Reflection 的另三个动态性质:(1) 运行时生成instances,(2) 执
行期唤起methods,(3) 运行时改动fields。
运行时生成instances
欲生成对象实体,在Reflection 动态机制中有两种作法,一个针对“无自变量ctor”,
一个针对“带参数ctor”。图6是面对“无自变量ctor”的例子。如果欲调用的是“带参数
ctor“就比较麻烦些,图7是个例子,其中不再调用Class的newInstance(),而是调用
Constructor 的newInstance()。图7首先准备一个Class[]做为ctor的参数类型(本例指定
为一个double和一个int),然后以此为自变量调用getConstructor(),获得一个专属ctor。
接下来再准备一个Object[] 做为ctor实参值(本例指定3.14159和125),调用上述专属ctor
的newInstance()。
#001 Class c = Class.forName("DynTest");
#002 Object obj = null;
#003 obj = c.newInstance(); //不带自变量
#004 System.out.println(obj);
图6:动态生成“Class object 所对应之class”的对象实体;无自变量。
#001 Class c = Class.forName("DynTest");
#002 Class[] pTypes = new Class[] { double.class, int.class };
#003 Constructor ctor = c.getConstructor(pTypes);
#004 //指定parameter list,便可获得特定之ctor
#005
#006 Object obj = null;
#007 Object[] arg = new Object[] {3.14159, 125}; //自变量
#008 obj = ctor.newInstance(arg);
#009 System.out.println(obj);
图7:动态生成“Class object 对应之class”的对象实体;自变量以Object[]表示。
运行时调用methods
这个动作和上述调用“带参数之ctor”相当类似。首先准备一个Class[]做为ctor的参数类型
(本例指定其中一个是String,另一个是Hashtable),然后以此为自变量调用getMethod(),
获得特定的Method object。接下来准备一个Object[]放置自变量,然后调用上述所得之特定
Method object的invoke(),如图8。知道为什么索取Method object时不需指定回返类型
吗?因为method overloading机制要求signature(署名式)必须唯一,而回返类型并非
signature的一个成份。换句话说,只要指定了method名称和参数列,就一定指出了一个独一无
二的method。
file:///H|/download/806.html(第 9/12 页)2005-9-8 12:03:22
侯捷观点??Java反射机制
#001 public String func(String s, Hashtable ht)
#002 {
#003 …System.out.println("func invoked"); return s;
#004 }
#005 public static void main(String args[])
#006 {
#007 Class c = Class.forName("Test");
#008 Class ptypes[] = new Class[2];
#009 ptypes[0] = Class.forName("java.lang.String");
#010 ptypes[1] = Class.forName("java.util.Hashtable");
#011 Method m = c.getMethod("func",ptypes);
#012 Test obj = new Test();
#013 Object args[] = new Object[2];
#014 arg[0] = new String("Hello,world");
#015 arg[1] = null;
#016 Object r = m.invoke(obj, arg);
#017 Integer rval = (String)r;
#018 System.out.println(rval);
#019 }
图8:动态唤起method
运行时变更fields内容
与先前两个动作相比,“变更field内容”轻松多了,因为它不需要参数和自变量。首先调用
Class的getField()并指定field名称。获得特定的Field object之后便可直接调用Field的
get()和set(),如图9。
#001 public class Test {
#002 public double d;
#003
#004 public static void main(String args[])
#005 {
#006 Class c = Class.forName("Test");
#007 Field f = c.getField("d"); //指定field 名称
#008 Test obj = new Test();
#009 System.out.println("d= " + (Double)f.get(obj));
#010 f.set(obj, 12.34);
#011 System.out.println("d= " + obj.d);
#012 }
#013 }
图9:动态变更field 内容
Java 源码改动办法
先前我曾提到,原本想借由“改动Java标准库源码”来测知Class object的生成,但由于其
ctor原始设计为private,也就是说不可能透过这个管道生成Class object(而是由class
loader负责生成),因此“在ctor中打印出某种信息”的企图也就失去了意义。
这里我要谈点题外话:如何修改Java标准库源码并让它反应到我们的应用程序来。假设我想修改
java.lang.Class,让它在某些情况下打印某种信息。首先必须找出标准源码!当你下载JDK 套
file:///H|/download/806.html(第 10/12 页)2005-9-8 12:03:22
侯捷观点??Java反射机制
件并安装妥当,你会发现jdk150\src\java\lang 目录(见图10)之中有Class.java,这就是
我们此次行动的标准源码。备份后加以修改,编译获得Class.class。接下来准备将.class 搬移
到jdk150\jre\lib\endorsed(见图10)。
这是一个十分特别的目录,class loader将优先从该处读取内含classes的.jar文件??成功的
条件是.jar内的classes压缩路径必须和Java标准库的路径完全相同。为此,我们可以将刚才做
出的Class.class先搬到一个为此目的而刻意做出来的\java\lang目录中,压缩为foo.zip(任
意命名,唯需夹带路径java\lang),再将这个foo.zip搬到jdk150\jre\lib\endorsed并改
名为foo.jar。此后你的应用程序便会优先用上这里的java.lang.Class。整个过程可写成一个
批处理文件(batch file),如图11,在DOS Box中使用。
图10
图10:JDK1.5 安装后的目录组织。其中的endorsed 是我新建。
del e:\java\lang\*.class //清理干净
del c:\jdk150\jre\lib\endorsed\foo.jar //清理干净
c:
cd c:\jdk150\src\java\lang
javac -Xlint:unchecked Class.java //编译源码
javac -Xlint:unchecked ClassLoader.java //编译另一个源码(如有必要)
move *.class e:\java\lang //搬移至刻意制造的目录中
e:
cd e:\java\lang //以下压缩至适当目录
pkzipc -add -path=root c:\jdk150\jre\lib\endorsed\foo.jar *.class
cd e:\test //进入测试目录
javac -Xlint:unchecked Test.java //编译测试程序
java Test //执行测试程序
图11:一个可在DOS Box中使用的批处理文件(batch file),用以自动化java.lang.Class
的修改动作。Pkzipc(.exe)是个命令列压缩工具,add和path都是其命令。
更多信息
以下是视野所及与本文主题相关的更多讨论。这些信息可以弥补因文章篇幅限制而带来的不足,或
带给您更多视野。
l "Take an in-depth look at the Java Reflection API -- Learn about
the new Java 1.1 tools forfinding out information about classes", by
Chuck McManis。此篇文章所附程序代码是本文示例程序的主要依据(本文示例程序示范了更
多Reflection APIs,并采用JDK1.5 新式的for-loop 写法)。
l "Take a look inside Java classes -- Learn to deduce properties of
a Java class from inside aJava program", by Chuck McManis。
l "The basics of Java class loaders -- The fundamentals of this key
component of the Javaarchitecture", by Chuck McManis。
l 《The Java Tutorial Continued》, Sun microsystems. Lesson58-61,
"Reflection".
file:///H|/download/806.html(第 11/12 页)2005-9-8 12:03:22
侯捷观点??Java反射机制
注1用过诸如MFC这类所谓 Application Framework的程序员也许知道,MFC有所谓的
dynamic creation。但它并不等同于Java的动态加载或动态辨识;所有能够在MFC程序中起作用
的classes,都必须先在编译期被编译器“看见”。
注2如果操作对象是Object,Class.getSuperClass()会返回null。
本文程序源码可至侯捷网站下载:
http://www.jjhou.com/javatwo-2004-reflection-and-generics-in-jdk15-sample.ZIP发表于 2004年10月27日 11:30 AM
posted @
2005-11-17 09:45 安德尔斯 阅读(775) |
评论 (0) |
编辑 收藏
传值?还是传引用?
(Wang hailong)
关于编程的参数传递问题,总是存在着这样的争论。传值?还是传引用?(还是传指针?还是传地址?)这些提法,经常出现在C++, java, C#的编程技术文档里面。这个问题也经常引起开发人员的争论,徒耗人力物力。实际上,这根本不成为问题,只是由于人为加入的概念,混淆了人们的视听。
从程序运行的角度来看,参数传递,只有传值,从不传递其它的东西。只不过,值的内容有可能是数据,也有可能是一个内存地址。
开发人员应该了解程序的编译结果是怎样在计算机中运行的。程序运行的时候,使用的空间可以分为两个部分,栈和堆。栈是指运行栈,局部变量,参数,都分配在栈上。程序运行的时候,新生成的对象,都分配在堆里,堆里分配的对象,栈里的数据参数,或局部变量。
下面举一个C++的例子。
public class Object{
int i;
public Object(int i){
this.i = i;
}
public int getValue(){
return i;
}
public void setValue(int i){
this.i = i;
}
};
class A {
Void func1(int a, Object b){
Object * c = new Object( a );
b = c;
}
public void main(){
Object * param = new Object( 1 );
func1( 2, param );
// what is value of parram now ?
// it is still 1.
}
};
我们来看一下,当调用到func1函数时,运行到Object * c = new Object( a ); 栈和堆的状态。不同编译器生成的代码运行的结果可能会稍有不同。但参数和局部变量的大致排放顺序都是相同的。
这时候,我们来看,param变量被压入运行栈的时候,只是进行了简单的复制。把param里面的内容拷贝到b里面。这时候,b就指向了Object(1)。这里的参数传递,是把param的值传递给b。
下面我们来看,程序执行到b = c;时候的堆栈状态。
我们可以看到,b现在指向了Object(2)。但是对param的值毫无影响。param的值还是Object(1)。
所以,我们说,对参数的赋值不会影响到外层函数的数据,但是,调用参数的操作方法,却等于直接操作外层函数的数据。比如,如果我们在func1()函数中,不调用b=c;而调用b.setValue(3),那么Object(1)的数据就会变为3,param的数据也会改变为3。
在java和C#中的情况,也都是一样。
C++还有一种变量定义方法,表面上看起来,不符合上面的说明,这里进行说明。
Object * a = new Object(1);
Object & * b = a;
这里的b就等于是a的另外一个别名,b就是a。对b赋值就等于对a赋值。甚至作为参数传递时,也是如此。对这种类型的参数的赋值,就等于对外层函数数据的赋值。
public class B{
void func1(Object & * b){
b = new Object(4);
}
public void main(){
Object * a = new Object(1);
func1(a);
// a is changed to Object(4) now.
}
}
当运行完func1(a);时,a的值变化为Object(4)。这是因为编译器实际把参数Object & * b编译为Object ** b_addr,b_addr的值是b的地址,也就是a的地址。
当调用func1()的时候,实际上是把b_addr作为参数压到栈里,b_addr的值是a的地址。
当执行b = new Object(4); 时,实际执行了 b_addr->b = new Object(4); 也就是执行了 b_addr->a = new Object(4); a的值当然变化了。
还有一点需要说明,当使用COM,CORBA等中间件规范进行开发时,我们需要定义IDL语言。参数的类型分为,[in],[out],[in, out],其中的RPC远程调用的参数打包规范,就更复杂了,但原理却是一样的。
posted @
2005-11-17 09:43 安德尔斯 阅读(379) |
评论 (0) |
编辑 收藏
/*
* Created on 2004-8-4
*
* To change the template for this generated file go to
* Window>Preferences>Java>Code Generation>Code and Comments
*/
package myclass.test;
import java.awt.*;
import java.awt.image.*;
import java.util.*;
/**
* @author
*
* To change the template for this generated type comment go to
* Window>Preferences>Java>Code Generation>Code and Comments
*/
public class Image {
public String sRand="";
public Color getRandColor(int fc,int bc){//给定范围获得随机颜色
Random random = new Random();
if(fc>255) fc=255;
if(bc>255) bc=255;
int r=fc+random.nextInt(bc-fc);
int g=fc+random.nextInt(bc-fc);
int b=fc+random.nextInt(bc-fc);
return new Color(r,g,b);
}
public BufferedImage creatImage(){
// 在内存中创建图象
int width=60, height=20;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 获取图形上下文
Graphics g = image.getGraphics();
//生成随机类
Random random = new Random();
// 设定背景色
g.setColor(getRandColor(200,250));
g.fillRect(0, 0, width, height);
//设定字体
g.setFont(new Font("Times New Roman",Font.PLAIN,18));
//画边框
//g.setColor(new Color());
//g.drawRect(0,0,width-1,height-1);
// 随机产生155条干扰线,使图象中的认证码不易被其它程序探测到
g.setColor(getRandColor(160,200));
for (int i=0;i<155;i++)
{
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x,y,x+xl,y+yl);
}
// 取随机产生的认证码(4位数字)
//String rand = request.getParameter("rand");
//rand = rand.substring(0,rand.indexOf("."));
for (int i=0;i<4;i++){
String rand=String.valueOf(random.nextInt(10));
sRand+=rand;
// 将认证码显示到图象中
g.setColor(new Color(20+random.nextInt(110),20+random.nextInt(110),20+random.nextInt(110)));//调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成
g.drawString(rand,13*i+6,16);
}
// 图象生效
g.dispose();
return image;
}
}
======================================================================
image.jsp(对bean的引用)
<%@ page contentType="image/jpeg" import="javax.imageio.*" %>
<jsp:useBean id="image" scope="session" class="myclass.test.Image"/>
<%
//设置页面不缓存
response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);
// 将认证码存入SESSION
session.setAttribute("rand",image.sRand);
// 输出图象到页面
ImageIO.write(image.creatImage(), "JPEG", response.getOutputStream());
%>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
大家经常在网上登陆的时候经常会看到让你输入验证码,有的是文字的,有的呢是图片,比如chinaren.com校友录中留言的时候,我们就会看到数字图片验证码;网上关于数字文字验证码实现方法的相关资料很多,而我们这里介绍的是数字和字母随机组成的并且生成图片的验证码的实现方法。看起来很复杂、其实很简单的,大家跟着我往下看:
首先,我们先介绍一下设计思路,数字和字母的随机组合生成验证码,然后将验证码生成图片,这里“数字和字母的组合”应该是随机取出来的;如果是专门的数字验证码,我们可以这样实现:
ycodenum=4 '验证码的位数,或者说成个数
for i=1 to ycodenum
Randomize '初始化随机数发生器
ycode=ycode&Int((9*Rnd)) 'rnd是随机数,从0到1之间的任意实数,这里获得0到9之间的整数
next
response.write ycode '就可以输出数字验证码(4位)
然而,我们要让数字和字母同样随机生成,这里我们可以用到数组来实现这种效果,如下:
ychar="0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z" '将数字和大写字母组成一个字符串
yc=split(char,",") '将字符串生成数组
ycodenum=4
for i=1 to ycodenum
Randomize
ycode=ycode&yc(Int((35*Rnd))) '数组一般从0开始读取,所以这里为35*Rnd
next
response.write ycode
现在看看输出结果是不是数字和字母随机组合的呢?
下面看看怎样生成图片,这个也许有些朋友知道:asp不能生成图片,必须使用asp组件。不错,我们这里使用的是ASP图象组件shotgraph。有一点大家注意,服务器不是自己的不能用哦,因为你装不了这组件。
组件的下载地址:
http://www.csdn.com.cn/download/ShotGraph.rar,至于怎么注册,这里就不多说了,网上有很多资料
我们看看生成图片的代码:
ychar="0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z" '将数字和大写字母组成一个字符串
yc=split(char,",") '将字符串生成数组
ycodenum=4
for i=1 to ycodenum
Randomize
ycode=ycode&yc(Int((35*Rnd))) '数组一般从0开始读取,所以这里为35*Rnd
next
Response.Clear
Response.ContentType="image/gif"
set obj=Server.CreateObject("shotgraph.image")
x=55 '图片的宽
y=26 '图片的高
obj.CreateImage x,y,8 '8是图片的颜色8位
obj.SetColor 0,55,126,222
obj.SetColor 1,255,255,255
obj.CreatePen "PS_SOLID",1,0
obj.SetBgColor 0
obj.Rectangle 0,0,x-1,y-1
obj.SetBkMode "TRANSPARENT"
obj.CreateFont "Arial",136,18,1,False,False,False,False
obj.SetTextColor 1
obj.TextOut 5,4,ycode&" "
img=obj.GifImage(-1,1,"")
Response.BinaryWrite (img)
针对以上代码也就是说shotgraph普通的画图的原理请参考:
http://www.pconline.com.cn/pcedu/empolder/wz/asp/10204/45207.html
posted @
2005-11-17 09:40 安德尔斯 阅读(1746) |
评论 (3) |
编辑 收藏
1.关于在静态方法中访问非静态内部类的问题
public class Outer{
public String name = "Outer";
public static void main(String argv[]){
//Inner myinner = new Inner(); //直接用这句话创建会编译错误
Outer myouter=new Outer(); //先创建外部类的对象
Outer.Inner myinner=myouter.new Inner();
myinner.showName();
}//End of main
//下面这段代码用来测试这种n烦的办法
public void amethod(){
Outer myouter=new Outer();
Outer.Inner myinner=myouter.new Inner();
myinner.showName();
}
//非静态方法访问非静态内部类
private class Inner{
String name =new String("Inner");
void showName(){
System.out.println(name);
}
}//End of Inner class
}
在非静态方法访问非静态内部类直接创建该内部类的对象:new Inner().showName();当然也可以采取这种n烦的办法假设private class Inner改成static private class Inner, 那么在静态方法中访问静态内部类也是直接创建该内部类的对象,即Inner myinner = new Inner(),或者Outer.Inner myinner = new Outer.Inner()也行得通,可见这种n烦的方法在上面三种情况下都是可以用的。
2.Abstract方法不能用final,static修饰非abstract方法在abstract类中可以用final,static
抽象类中的抽象方法不能是final,但是非抽象方法前加final可以编译通过因为abstract和final相互排斥,前者专用于继承,后者禁止继承
抽象类中的抽象方法不能为static
非抽象方法可以为static
包裹类Integer、 String 、Float、 Double等都是final类,不能被继承!Integer i=new Integer(“6”);如果字符串不是数字,会产生运行异常(不会出现编译错误)但是对于boolean,这个规则不适用。当字符串时(大小写无关),Boolean对象代表的数值为true,其他字符串均为false如:
Boolean b = new Boolean(“afiwou”); 代表false
Boolean b = new Boolean(“tRue”); 是true
3.多态性、虚拟方法调用
public class Test8 {
public static void main(String [] args){
Base b = new Subclass();
System.out.println(b.x);
System.out.println(b.method());
}
}
class Base{
int x = 2;
int method(){
return x;
}
}
class Subclass extends Base{
int x = 3;
int method(){
return x;
}
}
结果是2,3,而不是3,3
Employee e = new Manager();
e.department = " Finance " ;
//department 是Manager的一个特殊属性
声明变量e后,你能访问的对象部分只是Employee的部分;Manager的特殊部分是隐藏的。这是因为编译器应意识到,e 是一个Employee,而不是一个Manager。但重写的方法除外
在你接收父类的一个引用时,你可以通过使用instanceof运算符判定该对象实际上是你所要的子类,并可以用类型转换该引用的办法来恢复对象的全部功能。为什么说“恢复对象的全部功能”,就是因为上一格所描述的,子类对象赋给父类句柄后,该句柄不能访问子类的那些特殊属性和方法,要用就要重新造型。这其实是多态参数的后续应用,形成这样一个链条:传入多态参数??instanceof判断类型??casting??恢复功能
Employee e = new Manager();
e.getDetails();
在此例中,Manager 重写了Employee的getDetail()方法。被执行的e.getDetails()方法来自对象的真实类型:Manager。事实上,执行了与变量的运行时类型(即,变量所引用的对象的类型)相关的行为,而不是与变量的编译时类型相关的行为。这是面向对象语言的一个重要特征。它也是多态性的一个特征,并通常被称作虚拟方法调用??“动态绑定”
写了这么多也不知道对你有没有帮助呢?
posted @
2005-11-17 09:34 安德尔斯 阅读(316) |
评论 (2) |
编辑 收藏
1.关于参数的传递
class ValHold{
public int i = 10;
}
public class ObParm{
public void amethod(){
ValHold v = new ValHold();
another(v);
System.out.println(v.i);
}
public void another(ValHold v){
v.i = 20;
ValHold vh = new ValHold();
v =vh;
System.out.println(v.i);
}
public static void main(String[] argv){
ObParm o = new ObParm();
o.amethod();
}
}
此题的答案是10,20,为什么不是10,10呢?
这样解释吧,按照sun官方的说法:当一个引用变量作为参数传递给一个方法时, 在这个方法内可以改变变量的值,即改变引用指向的对象,(本题中将vh赋给v)但是方法的调用结束后,改变量恢复原来的值,即变量仍然指向原来的对象。 (即another(v)调用结束之后,v又回复到第一次ValHold v = new ValHold();时指向的地址空间。) 但是如果在方法内改变了引用指向的对象的数据(属性),那么当方法的调用结束后,尽管引用仍然指向原来的对象,这个对象的某个属性已经被改变了(v的i值在 执行v.i=20的时候就已经被改变了,所以调用another结束后,v.i已经变成了20) .
2.关于内部类
public class InOut{
String s= new String("Between");
public void amethod(final int iArgs) {
int iam;
class Bicycle{
Bicycle() {
System.out.println(s); //这两句话可以,也就是说可以访问s
System.out.println(iArgs); //和final int 常量
//System.out.println(iOther);
}
}
new Bicycle();
}
public void another(){
int iOther;
}
public static void main(String[] args) {
InOut inout= new InOut();
inout.amethod(22);
}
}
Inner class能够存取外部类的所有实例变量----无论这些实例变量有什么样的存取控制符(比如private),就像类中的方法能够存取方法所在类的所有变量一样;如果inner class定义在方法中,则inner class能够存取方法所在的类中的实例变量,也能存取该方法中的局部变量,但该局部变量必须是final的,也就是只能访问方法中的常量.(上面所说的都是普通内部类,不是静态内部类的情况).
public class Testinner {
int t=10;
public void a() {
final int u =90;
class InMethod { //方法中内部类
InMethod() { //内部类的构造方法
System.out.println("u="+u); //封装方法内的变量必须是final才能访问到!
System.out.println("t="+t); //外部类的变量可以任意访问!
}
}
new InMethod();//必须在方法a()中创建内部类对象之后,Testinner对象才能通过 a()访问到InMethod类
}
public static void main (String[] args) {
Testinner t= new Testinner();
t.a();
}
}
输出:u=90 ,t=10
方法中的内部类不可以是static的!如果一个内部类是静态的(当然只能是类中的内部类啦),那么这个类就自动的成为顶级(top-level)类即普通的类。静态内部类中的方法(无论是静态的方法还是非静态的方法)只能直接访问外部类中的静态成员,要访问外部类中的非静态成员,则必须创建外部类的对象。
posted @
2005-11-17 09:33 安德尔斯 阅读(204) |
评论 (0) |
编辑 收藏