做过JAVA编程的都知道,在JAVA中有一种垃圾收集器的机制,当它运行时(通常在系统内
存低到一定限度时自动运行),会回收不再使用的对象所占用的内存,所以,在JAVA程序
中,我们通常只考虑创建对象,而从不关心对象的清除。Finalize()是JAVA为类提供的
一种特殊方法。垃圾收集器的工作过程大致是这样的:一旦垃圾收集器准备好释放无用对象
占用的存储空间,它首先调用那些对象的finalize()方法,然后才真正回收对象的内存。
通过使用finalize(),就可以在垃圾收集器运行期间进行一些特殊的工作。下面一例就说
明了finalize()的一种巧妙用法。
现在的商业应用系统越来越多的采用WEB形式。在WEB形式应用中,每一次页面访问是
独立的,前后不相关联,哪怕多个用户在同一时刻访问应用的同一个页面,用户相互之间也
是不知道的。如果想要检查当前有哪些用户正在使用系统(如准备恢复数据备份或进行系统
升级时,系统管理员都很希望知道这些信息),该怎么办呢?基于Servlet、Jsp技术的
WEB服务器提供了隐含的Session、Application对象,利用它可以帮开发者实现一些信息
的持续保存和共享。当用户访问一个WEB应用时,WEB服务器会自动创建一个Session对象,
该对象可以供用户在会话期内在应用的所有页面中共享数据;Application是WEB应用的
一个全局对象。利用Session、Application对象,可以达到跟踪所有用户信息的目的。
当用户打开浏览器开始请求WEB应用的登录页面时,WEB服务即为该客户创建一个
ssion,此后,在ssion的timeout时间内,该客户都使用这个ssion(timeout时间
可设置,如Tomcat服务器是在各应用的文件中设置)。如果使用IE浏览器,Session
与客户IP地址、客户程序进程ID所共同标识的连接有对应关系,相同IP地址、相同进程
的窗口(如通过IE-文件-新建-窗口打开的新窗口)具有同一个ssion,所以ssion
可用于标识各个独立的客户应用连接。
下面是一个样例
为了方便处理,先建一个简单类(ur)用来表达用户信息及存放ssionId:
packagecom;
publicclassur{
publicStringname="";
publicStringssionId="";
}
另一个类(testSession)用于处理用户的login、logout等动作信息,使系统可以跟
踪当前连接的用户信息。
packagecom;
;
;
publicclasstestSession{
publicurUr;
privateVectorvsid;
publictestSession()
{
Ur=newur();
}
publicbooleanverify(Stringurname,Stringpassword)
throwsException//验证用户/密码
{
returntrue;
}
publicvoidtSessionVar(Stringsid,Vectorsid){
nId=sid;
=sid;
}
privatestaticsynchronizedvoidaddapps(urpur,
Vectorpvsid){//记录一个新连接的用户
intpos=-1;
url_ur;
if(pur==null||pvsid==null)
return;
for(inti=0;i<();i++){
l_ur=(ur)(i);
if(l_(nId)){
pos=i;
break;
}
}
if(pos==-1){
(pur);
}
el{
(pos,pur);
}
}
privatestaticsynchronizedvoidremoveapps(urpur,
Vectorpvsid){//移除一个退出的用户
intpos=-1;
url_ur;
if(pur==null||pvsid==null)
return;
for(inti=0;i<();i++){
l_ur=(ur)(i);
if(l_(nId)){
pos=i;
break;
}
}
if(pos!=-1){
(pos);
}
}
protectedvoidfinalize(){
apps(,);
}
publicbooleanlogin(Stringurname)throwsException
{//处理登录
=urname;
s(,);
returntrue;
}
publicbooleanlogout()throwsException
{//处理注销
ze();
=null;
=null;
returntrue;
}
}
每一个用户均建立一个testSession对象,来保存该用户的信息。为了对类testSession
进行说明,必须同时引人另一个文件。这个用于示例的JSP文件提供一个简
单的界面进行登录、注销处理。文件内容如下:
<%@pageimport="ssion,
"%>
<%@pagecontentType="text/html;chart=GBK"%>
<%racterEncoding(respon.
getCharacterEncoding());%>
<%
StringactionType=ameter("actiontype");
StringactionResult="";
if(actionType!=null){
if(("login")){//-1-
StringurName=ameter("urname");
if(urName==null||("")){
;
}
el{
Stringpassword=ameter("password");
if(password==null)
password="";
testSessionts=
(testSession)ribute("testSession");
if(ts!=null){//-1.1-
Attribute("testSession");
if(!(""))
();
}
ts=newtestSession();
if(!(urName,password)){
//验证用户与密码,看是否合法用户
actionResult="loginfail";
//非法用户,显示错误信息
}
el{//验证成功
ribute("testSession",ts);
Vectorapp_vts=
(Vector)ribute("app_vts");
if(app_vts==null){
app_vts=newVector();
ribute("app_vts",app_vts);
}
sionVar((),app_vts);
(urName);
actionResult=urName+"loginsuccess";
}
}
}
if(("logout")){
testSessionts=
(testSession)ribute("testSession");
if(ts!=null){
Attribute("testSession");
if(!("")){//-2-
actionResult=;
();
}
date();
}
actionResult=actionResult+"logoutsuccess";
}
}
el
actionResult="null";
%>
<head>
<scriptLANGUAGE="Javascript"></script>
<script>
functiondoAction(actionType)
{
=actionType;
();
}
</script>
</head>
<body>
<tablewidth="80%"border="1"align="center">
<formmethod="POST"action=""
name="test">
<tr>
<tdheight="33"align="right">用户:</td>
<tdwidth="70%"><inputname="urname"
type="text"value=""size="20">
</td>
</tr>
<tr>
<tdwidth="27%"height="22"align="right">密码:</td>
<tdwidth="73%">
<inputname="password"type="password"size="20">
</td>
</tr>
<tr>
<tdheight="32"colspan="2"align="right">
<divalign="center">
<inputname="B1"type="button"value="登录"
onclick="doAction('login')">
<inputname="B2"type="ret"
value="重写">
<inputname="B3"type="button"value="注销"
onclick="doAction('logout')">
</div></td>
</tr>
<tr>
<tdwidth="27%"height="22"align="right">
<inputname="actiontype"type="hidden"
value=""></td>
<tdwidth="73%"><inputname="info"type="text"
size="20"value="<%=actionResult%>">
</td>
</tr>
</form>
</table>
</body>
[-1-]:程序的if(("login")){„}部分处理login。[-1.1-]前后部
分先通过ribute("testSession");取得ssion中保存的会话变量ts(一
个testSession对象实例)。如果ts为空,表示当前用户还没有login,否则用户已经login
了,则先logout再重新login,并将新testSession对象保存到ssion里。
ribute("app_vts");所取得的变量app_vts中保存了所有当前登录用
户的ur信息。每个用户login成功时,即往app_vts中添加一个ur对象,这是通过
testSession的addapps方法完成的。而当用户注销时,先从
ribute("testSession")中取到当前用户的testSession对象,该对象已含
有ribute("app_vts")对象的引用,通过testSession的logout方法
进行注销处理(见[-2-]标记前后)。最终是通过调用removeapps
方法从全局对象app_vts中移除用户信息的。总结来说,程序利用应用全局对象application
来保存跟踪用户连接信息的对象(例中为app_vts),该对象记录着应用中所有用户的连接、
退出信息。
JSP页面运行的界面如图:
当我们输入用户testur_1并按<登录>按钮,按钮下面的文本框显示"testur_1
loginsuccess"。我们利用来观察结果,显示如下:
testur_1ssionId=A16DCE950C2C664D0AA93E05B27D8E00
文件的内容如下:
<%@pageimport="ssion,,
"%>
<%@pagecontentType="text/html;chart=GBK"%>
<%racterEncoding(respon.
getCharacterEncoding());%>
<%
Vectorl_vts=(Vector)ribute("app_vts");
url_us;
if(l_vts!=null){
for(inti=0;i<l_();i++){
l_us=(ur)l_(i);
n(l_+"ssionId="+l_nId);
n("<br>");
}
}
%>
文件的作用是将app_vts中的用户信息显示出来。
当我们从桌面再启动一个IE程序,输入用户testur_2并按<登录>按钮,按钮下面
的文本框显示"testur_2loginsuccess"。我们利用来观察结果,显
示如下:
testur_1ssionId=A16DCE950C2C664D0AA93E05B27D8E00
testur_2ssionId=34B0AF3F1F2573F1C1DD12D62DF06F91
而当我们在第一个IE中按下按钮<注销>,的显示为:
刷新来观察结果,显示如下:
testur_2ssionId=BC487C6A9FD663EA27E797A420B41051
我们在第二个IE中按下按钮<注销>,按钮下面的文本框显示"testur_2login
success",刷新来观察结果,显示出已经没有连接的用户信息。
上面演示中,用户信息的移除是通过调用类的logout()方法来实现的。但假如用户没有点
按<注销>按钮而直接关闭IE或转到其他网站,该用户信息不是就一直存留在系统中吗?
让我们看看类testSession中的一个方法:
protectedvoidfinalize(){
apps(,);
}
用户访问应用,只有一个入口:login。应用的所有用户登录都可以被观察到。用户离
开应用,有三种可能:注销、转到其他网站、直接关闭浏览器。选择注销离开应用,可以被
程序观察到(logout),而后两种方式的离开应用,却不会调用logout。要观察到后两种
方式,就需要使用对象的finalize()方法。
用户通过转到其他网站、直接关闭浏览器两种方式离开应用超过Session的timeout
时间时,用户的Session对象会自动失效,即变为无用对象,而列入了垃圾收集器的回收范
围;关联的,"寄存"在Session中的testSession对象会同时变为无用对象(在其生命期,
仅存在Session对它的引用,Session失效了,它的唯一引用者不存在了,也就变成了无用
对象)。垃圾收集器运行时,首先会调用testSession的finalize(),testSession就通过
在finalize()方法中清除app_vts中存储的本用户信息。在testSession类代码中可看到,
finalize()调用类的removeapps()方法执行实际的清除操作。垃圾收集器的运行,除了
让其根据需要自动启动外,也可通过程序调用来启动它,比如:()就直接启动系
统垃圾收集动作。
可以想见,本例如果不利用类的finalize()方法,我们很难找到另一种简便的途径来
达到清除用户信息的目的,因为用户非正常离开应用的事件对WEB服务端来说是无法感知
的。
在testSession类代码中还有一个比较特殊的地方,实现用户信息加入和清除的两个方
法addapps和removeapps都被定义为staticsynchronized类型。为什么呢?这是同
步的需要。
App_vts是个应用级的全局可共享对象,在同一时刻连接到WEB上的有多个用户,这些
用户都可以操作对象app_vts。如果有两个或以上的用户同时调用testSession的addapps
或removeapps方法(这是完全可能的,因为WEBSERVER是多线程服务),将带来不可预
料的结果。
为了防止两个或以上的客户程序(属于不同线程)同时访问一个资源,Java提供了一
种内建的机制来解决冲突。这种机制就是synchronized(同步)。在一个类中将一个特定
的方法设为synchronized(同步的),便可有效地防止冲突,在任何时刻,只能有一个线
程调用特定对象的一个synchronized方法(尽管那个线程可以调用多个对象的同步方法),
另一个线程只有等上一线程对该方法的调用执行完毕后才能获得该方法的调用权。大致的工
作机制可以这样认为:每个对象都包含了一把锁(也叫作"监视器"),它自动成为对象的一
部分;调用任何synchronized方法时,对象就会被锁定,不可再调用那个对象的其他任何
synchronized方法,除非第一个方法完成了自己的工作,并解除锁定。在类testSession
中,将addapps和removeapps这两个方法设为了synchronized,当调用testSession
对象的addapps方法时,便不能再同时调用testSession对象的removeapps方法,反
之亦然。
Synchronized又有两个级别。当我们将一个方法仅仅设为synchronized时,那是对象
级的"锁",虽然一个对象的synchronized方法不可同时调用,却可以同时调用不同对象的
同一个synchronized方法。所以这样做还没完全解决问题,因为两个用户(各有自己的
testSession对象)可以同时调用addapps方法同时操作app_vts。当我们将一个方法设
为staticsynchronized时,则是类级的"锁"。类包含的"锁"(自动作为类的Class对象的
一部分),可在一个类的范围内被相互间锁定起来,从那个类创建的所有对象都共享一把"
锁"。就如testSession实际所做的那样,addapps和removeapps被定义为static
synchronized类型,这样,任一时候,所有线程用户中肯定只能有一个用户调用addapps
和removeapps两者中的一个方法,达到防止冲突的目的。
以上样例在WINDOWS2000、TOMCAT40、JDK13中通过。
理解finalize()-析构函数替代者
在许多方面,Java类似于C++。Java的语法非常类似于C++,Java有类、方法和数据成
员;Java的类有构造函数;Java有异常处理。
但是,如果你使用过C++会发现Java也丢掉一些可能是你熟悉的特性。这些特性之
一就是析构函数。取代使用析构函数,Java支持finalize()方法。
在本文中,我们将描述finalize()与C++析构函数的区别。另外,我们将创建一个
简单的Applet来演示finalize()是如何工作的。
最终的界限
与Java不同,C++支持局部对象(基于栈)和全局对象(基于堆)。因为这一双重支
持,C++也提供了自动构造和析构,这导致了对构造函数和析构函数的调用,(对于堆对象)
就是内存的分配和释放。
在Java中,所有对象都驻留在堆内存,因此局部对象就不存在。结果,Java的设计
者觉得不需要析构函数(象C++中所实现的)。
取而代之,Java定义了一个特殊的方法叫做finalize(),它提供了C++析构函数的
一些功能。但是,finalize()并不完全与C++的析构函数一样,并可以假设它会导致一系
列的问题。finalize()方法作用的一个关键元素是Java的垃圾回收器。
垃圾回收器
在C/C++、Pascal和其他几种多种用途的编程语言中,开发者有责任在内存管理上发
挥积极的作用。例如,如果你为一个对象或数据结构分配了内存,那么当你不再使用它时必
须释放掉该内存。
在Java中,当你创建一个对象时,Java虚拟机(JVM)为该对象分配内存、调用构造
函数并开始跟踪你使用的对象。当你停止使用一个对象(就是说,当没有对该对象有效的引
用时),JVM通过垃圾回收器将该对象标记为释放状态。
当垃圾回收器将要释放一个对象的内存时,它调用该对象的finalize()方法(如果该
对象定义了此方法)。垃圾回收器以独立的低优先级的方式运行,只有当其他线程挂起等待
该内存释放的情况出现时,它才开始运行释放对象的内存。(事实上,你可以调用()
方法强制垃圾回收器来释放这些对象的内存。)
在以上的描述中,有一些重要的事情需要注意。首先,只有当垃圾回收器释放该对象的
内存时,才会执行finalize()。如果在Applet或应用程序退出之前垃圾回收器没有释放
内存,垃圾回收器将不会调用finalize()。
其次,除非垃圾回收器认为你的Applet或应用程序需要额外的内存,否则它不会试图
释放不再使用的对象的内存。换句话说,这是完全可能的:一个Applet给少量的对象分配
内存,没有造成严重的内存需求,于是垃圾回收器没有释放这些对象的内存就退出了。
显然,如果你为某个对象定义了finalize()方法,JVM可能不会调用它,因为垃圾回
收器不曾释放过那些对象的内存。调用()也不会起作用,因为它仅仅是给JVM
一个建议而不是命令。
finalize()有什么优点呢?
如果finalize()不是析构函数,JVM不一定会调用它,你可能会疑惑它是否在任何情
况下都有好处。事实上,在Java1.0中它并没有太多的优点。
根据Java文档,finalize()是一个用于释放非Java资源的方法。但是,JVM有很
大的可能不调用对象的finalize()方法,因此很难证明使用该方法释放资源是有效的。
Java1.1通过提供一个alizersOnExit()方法部分地解决了这个问
题。(不要将这个方法与Java1.0中的alizations()方法相混淆。)不
象()方法那样,alizersOnExit()方法并不立即试图启动垃圾回
收器。而是当应用程序或Applet退出时,它调用每个对象的finalize()方法。
正如你可能猜测的那样,通过调用alizersOnExit()方法强制垃圾回收
器清除所有独立对象的内存,当清除代码执行时可能会引起明显的延迟。现在建立一个示例
Applet来演示Java垃圾回收器和finalize()方法是如何相互作用的。
回收垃圾
通过使用JavaAppletWizard创建一个新的Applet开始。当提示这样做时,输入
final_things作为Applet名,并选择不要生成源文件注释。
接下来,在JavaAppletWizard进行第三步,不要选择多线程选项。在第五步之前,
根据需要修改Applet的描述。
当你单击Finish后,AppletWizard将生成一个新的工作空间,并为该项目创建缺省
的Java文件。从列表A中选择适当的代码输入(我们已经突出显示了你需要输入的代码)。
当你完成代码的输入后,配置Internet浏览器将的输出信息写到
文件中。(在IE选项对话框的高级页面中选择起用JavaLogging。)
编译并运行该Applet。然后,等待Applet运行(你将在状态栏中看到Applet已启动的
信息),退出浏览器,并打开文件。你将会发现类似于下列行的信息:
1000thingsconstructed
0thingsfinalized
正如你能够看到的那样,建立了1,000个对象仍然没有迫使垃圾回收器开始回收空间,
即使在Applet退出时也没有对象被使用。
现在,删除在stop()方法第一行中的注释符以起用()方法。再次编译并运
行该Applet,等待Applet完成运行,并退出浏览器。当你再次打开文件,
你将看到下列行:
1000thingsconstructed
963thingsfinalized
这次,垃圾回收器认为大多数对象未被使用,并将它们回收。按顺序,当垃圾回收器开
始释放这些对象的内存时,JVM调用它们的finalize()方法。
继承finalize()?
顺便,如果你在类中定义了finalize(),它将不会自动调用基类中的方法。在我们讨
论了finalize()与C++的析构函数的不同点后,对这个结论不会惊讶,因为为某个类定
制的清除代码另一个类不一定会需要。
如果你决定要通过派生一个类的finalize()方法来调用基类中的finalize()方法,
你可以象其他继承方法一样处理。
protectedvoidfinalize()
{
ze();
//otherfinalizationcode...
}
除了允许你控制是否执行清除操作外,这个技术还使你可以控制当前类的finalize()
方法何时执行。
结论
然而有益的是,Java的自动垃圾回收器不会失去平衡。作为便利的代价,你不得不放
弃对系统资源释放的控制。不象C++中的析构函数,JavaApplet不会自动执行你的类中
的finalize()方法。事实上,如果你正在使用Java1.0,即使你试图强制它调用finalize()
方法,也不能确保将调用它。
因此,你不应当依靠finalize()来执行你的Applet和应用程序的资源清除工作。取
而代之,你应当明确的清除那些资源或创建一个try...finally块(或类似的机制)来实
现。
列表A:final_
.*;
.*;
classthing
{
publicstaticintthingcount=0;
publicstaticintthingfinal=0;
publicthing()
{
++thingcount;
}
protectedvoidfinalize()
{
++thingfinal;
}
}
publicclassfinal_thingxtendsApplet
{
publicfinal_things()
{
}
publicStringgetAppletInfo()
{
return"Name:final_thingrn"+
"Author:TimGoochrn"+
"CreatedwithMicrosoft"+
"VisualJ++Version1.1";
}
publicvoidinit()
{
resize(320,240);
}
publicvoiddestroy()
{
}
publicvoidpaint(Graphicsg)
{
ring("CreatedwithMicrosoft"+
"VisualJ++Version1.1",10,20);
}
publicvoidstart()
{
while(inal<1)
{
newthing();
}
}
publicvoidstop()
{
//();
n(ount+
"thingsconstructed");
n(inal+
"thingsfinalized");
}
}
Thinking:Java中static、this、super、
final用法
本篇旨在帮助准备学习Java以及刚接触Java的朋友认识、掌握和使用static、this、super、
final这几个关键字的使用。Java博大精深,我也是一位正在学习和使用Java的爱好者,
文中难免有不妥之处,欢迎指正。
一、static
请先看下面这段程序:
publicclassHello{
publicstaticvoidmain(String[]args){//(1)
n("Hello,world!");//(2)
}
}
看过这段程序,对于大多数学过Java的从来说,都不陌生。即使没有学过Java,而学
过其它的高级语言,例如C,那你也应该能看懂这段代码的意思。它只是简单的输出
“Hello,world”,一点别的用处都没有,然而,它却展示了static关键字的主要用法。
在1处,我们定义了一个静态的方法名为main,这就意味着告诉Java编译器,我这个
方法不需要创建一个此类的对象即可使用。你还得你是怎么运行这个程序吗?一般,我们都
是在命令行下,打入如下的命令(加下划线为手动输入):
javaHello
Hello,world!
这就是你运行的过程,第一行用来编译这个文件,执行完后,如果你查看
当前,会发现多了一个文件,那就是第一行产生的Java二进制字节码。第二
行就是执行一个Java程序的最普遍做法。执行结果如你所料。在2中,你可能会想,为什
么要这样才能输出。好,我们来分解一下这条语句。(如果没有安装Java文档,请到Sun
的官方网站浏览J2SEAPI)首先,System是位于包中的一个核心类,如果你查
看它的定义,你会发现有这样一行:publicstaticfinalPrintStreamout;接着在进一步,
点击PrintStream这个超链接,在METHOD页面,你会看到大量定义的方法,查找println,
会有这样一行:
publicvoidprintln(Stringx)。
好了,现在你应该明白为什么我们要那样调用了,out是System的一个静态变量,所
以可以直接使用,而out所属的类有一个println方法。
静态方法
通常,在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方
法。如下所示:
classSimple{
staticvoidgo(){
n("Go...");
}
}
publicclassCal{
publicstaticvoidmain(String[]args){
();
}
}
调用一个静态方法就是“类名.方法名”,静态方法的使用很简单如上所示。一般来说,
静态方法常常为应用程序中的其它类提供一些实用工具所用,在Java的类库中大量的静态
方法正是出于此目的而定义的。
静态变量
静态变量与静态方法类似。所有此类实例共享此静态变量,也就是说在类装载时,只分
配一块存储空间,所有此类的对象都可以操控此块存储空间,当然对于final则另当别论了。
看下面这段代码:
classValue{
staticintc=0;
staticvoidinc(){
c++;
}
}
classCount{
publicstaticvoidprt(Strings){
n(s);
}
publicstaticvoidmain(String[]args){
Valuev1,v2;
v1=newValue();
v2=newValue();
prt("v1.c="+v1.c+"v2.c="+v2.c);
();
prt("v1.c="+v1.c+"v2.c="+v2.c);
}
}
结果如下:
v1.c=0v2.c=0
v1.c=1v2.c=1
由此可以证明它们共享一块存储区。static变量有点类似于C中的全局变量的概念。值得
探讨的是静态变量的初始化问题。我们修改上面的程序:
classValue{
staticintc=0;
Value(){
c=15;
}
Value(inti){
c=i;
}
staticvoidinc(){
c++;
}
}
classCount{
publicstaticvoidprt(Strings){
n(s);
}
Valuev=newValue(10);
staticValuev1,v2;
static{
prt("v1.c="+v1.c+"v2.c="+v2.c);
v1=newValue(27);
prt("v1.c="+v1.c+"v2.c="+v2.c);
v2=newValue(15);
prt("v1.c="+v1.c+"v2.c="+v2.c);
}
publicstaticvoidmain(String[]args){
Countct=newCount();
prt("ct.c="+ct.v.c);
prt("v1.c="+v1.c+"v2.c="+v2.c);
();
prt("v1.c="+v1.c+"v2.c="+v2.c);
prt("ct.c="+ct.v.c);
}
}
运行结果如下:
v1.c=0v2.c=0
v1.c=27v2.c=27
v1.c=15v2.c=15
ct.c=10
v1.c=10v2.c=10
v1.c=11v2.c=11
ct.c=11
这个程序展示了静态初始化的各种特性。如果你初次接触Java,结果可能令你吃惊。
可能会对static后加大括号感到困惑。首先要告诉你的是,static定义的变量会优先于任
何其它非static变量,不论其出现的顺序如何。正如在程序中所表现的,虽然v出现在v1
和v2的前面,但是结果却是v1和v2的初始化在v的前面。在static{后面跟着一段代码,
这是用来进行显式的静态变量初始化,这段代码只会初始化一次,且在类被第一次装载时。
如果你能读懂并理解这段代码,会帮助你对static关键字的认识。在涉及到继承的时候,
会先初始化父类的static变量,然后是子类的,依次类推。非静态变量不是本文的主题,
在此不做详细讨论,请参考ThinkinJava中的讲解。
静态类
通常一个普通类不允许声明为静态的,只有一个内部类才可以。这时这个声明为静态的
内部类可以直接作为一个普通类来使用,而不需实例一个外部类。如下代码所示:
publicclassStaticCls{
publicstaticvoidmain(String[]args){
lsoi=ls();
}
}
classOuterCls{
publicstaticclassInnerCls{
InnerCls(){
n("InnerCls");
}
}
}
输出结果会如你所料:
InnerCls
和普通类一样。内部类的其它用法请参阅ThinkinJava中的相关章节,此处不作详解。
二、this&super
在上一篇拙作中,我们讨论了static的种种用法,通过用static来定义方法或成员,
为我们编程提供了某种便利,从某种程度上可以说它类似于C语言中的全局函数和全局变
量。但是,并不是说有了这种便利,你便可以随处使用,如果那样的话,你便需要认真考虑
一下自己是否在用面向对象的思想编程,自己的程序是否是面向对象的。好了,现在开始讨
论this&super这两个关键字的意义和用法。
在Java中,this通常指当前对象,super则指父类的。当你想要引用当前对象的某种
东西,比如当前对象的某个方法,或当前对象的某个成员,你便可以利用this来实现这个
目的,当然,this的另一个用途是调用当前对象的另一个构造函数,这些马上就要讨论。
如果你想引用父类的某种东西,则非super莫属。由于this与super有如此相似的一些特
性和与生俱来的某种关系,所以我们在这一块儿来讨论,希望能帮助你区分和掌握它们两个。
在一般方法中
最普遍的情况就是,在你的方法中的某个形参名与当前对象的某个成员有相同的名字,
这时为了不至于混淆,你便需要明确使用this关键字来指明你要使用某个成员,使用方法
是“this.成员名”,而不带this的那个便是形参。另外,还可以用“this.方法名”来引
用当前对象的某个方法,但这时this就不是必须的了,你可以直接用方法名来访问那个方
法,编译器会知道你要调用的是那一个。下面的代码演示了上面的用法:
publicclassDemoThis{
privateStringname;
privateintage;
DemoThis(Stringname,intage){
tName(name);//你可以加上this来调用方法,像这样:e(name);但这
并不是必须的
tAge(age);
();
}
publicvoidtName(Stringname){
=name;//此处必须指明你要引用成员变量
}
publicvoidtAge(intage){
=age;
}
publicvoidprint(){
n("Name="+name+"Age="+age);//在此行中并不需要用this,因为
没有会导致混淆的东西
}
publicstaticvoidmain(String[]args){
DemoThisdt=newDemoThis("Kevin","22");
}
}
这段代码很简单,不用解释你也应该能看明白。在构造函数中你看到用(),
你完全可以用print()来代替它,两者效果一样。下面我们修改这个程序,来演示super的
用法。
classPerson{
publicintc;
privateStringname;
privateintage;
protectedvoidtName(Stringname){
=name;
}
protectedvoidtAge(intage){
=age;
}
protectedvoidprint(){
n("Name="+name+"Age="+age);
}
}
publicclassDemoSuperextendsPerson{
publicvoidprint(){
n("DemoSuper:");
();
}
publicstaticvoidmain(String[]args){
DemoSuperds=newDemoSuper();
e("kevin");
(22);
();
}
}
在DemoSuper中,重新定义的print方法覆写了父类的print方法,它首先做一些自己
的事情,然后调用父类的那个被覆写了的方法。输出结果说明了这一点:
DemoSuper:
Name=kevinAge=22
这样的使用方法是比较常用的。另外如果父类的成员可以被子类访问,那你可以像使用
this一样使用它,用“super.父类中的成员名”的方式,但常常你并不是这样来访问父类
中的成员名的。
在构造函数中
构造函数是一种特殊的方法,在对象初始化的时候自动调用。在构造函数中,this和
super也有上面说的种种使用方式,并且它还有特殊的地方,请看下面的例子:
classPerson{
publicstaticvoidprt(Strings){
n(s);
}
Person(){
prt("APerson.");
}
Person(Stringname){
prt("Apersonnameis:"+name);
}
}
publicclassChineextendsPerson{
Chine(){
super();//调用父类构造函数(1)
prt("Achine.");//(4)
}
Chine(Stringname){
super(name);//调用父类具有相同形参的构造函数(2)
prt("hisnameis:"+name);
}
Chine(Stringname,intage){
this(name);//调用当前具有相同形参的构造函数(3)
prt("hisageis:"+age);
}
publicstaticvoidmain(String[]args){
Chinecn=newChine();
cn=newChine("kevin");
cn=newChine("kevin",22);
}
}
在这段程序中,this和super不再是像以前那样用“.”连接一个方法或成员,而是直
接在其后跟上适当的参数,因此它的意义也就有了变化。super后加参数的是用来调用父类
中具有相同形式的构造函数,如1和2处。this后加参数则调用的是当前具有相同参数的
构造函数,如3处。当然,在Chine的各个重载构造函数中,this和super在一般方法
中的各种用法也仍可使用,比如4处,你可以将它替换为“”(因为它继承了父类
中的那个方法)或者是“”(因为它是父类中的方法且可被子类访问),它照样
可以正确运行。但这样似乎就有点画蛇添足的味道了。
最后,写了这么多,如果你能对“this通常指代当前对象,super通常指代父类”这句
话牢记在心,那么本篇便达到了目的,其它的你自会在以后的编程实践当中慢慢体会、掌握。
另外关于本篇中提到的继承,请参阅相关Java教程。
三、final
final在Java中并不常用,然而它却为我们提供了诸如在C语言中定义常量的功能,
不仅如此,final还可以让你控制你的成员、方法或者是一个类是否可被覆写或继承等功能,
这些特点使final在Java中拥有了一个不可或缺的地位,也是学习Java时必须要知道和掌
握的关键字之一。
final成员
当你在类中定义变量时,在其前面加上final关键字,那便是说,这个变量一旦被初始
化便不可改变,这里不可改变的意思对基本类型来说是其值不可变,而对于对象变量来说其
引用不可再变。其初始化可以在两个地方,一是其定义处,也就是说在final变量定义时直
接给其赋值,二是在构造函数中。这两个地方只能选其一,要么在定义时给值,要么在构造
函数中给值,不能同时既在定义时给了值,又在构造函数中给另外的值。下面这段代码演示
了这一点:
;
ist;
List;
publicclassBat{
finalPI=3.14;//在定义时便给址值
finalinti;//因为要在构造函数中进行初始化,所以此处便不可再给值
finalListlist;//此变量也与上面的一样
Bat(){
i=100;
list=newLinkedList();
}
Bat(intii,Listl){
i=ii;
list=l;
}
publicstaticvoidmain(String[]args){
Batb=newBat();
(newBat());
//b.i=25;
//=newArrayList();
n("I="+b.i+"ListType:"+ss());
b=newBat(23,newArrayList());
(newBat());
n("I="+b.i+"ListType:"+ss());
}
}
此程序很简单的演示了final的常规用法。在这里使用在构造函数中进行初始化的方
法,这使你有了一点灵活性。如Bat的两个重载构造函数所示,第一个缺省构造函数会为你
提供默认的值,重载的那个构造函数会根据你所提供的值或类型为final变量初始化。然而
有时你并不需要这种灵活性,你只需要在定义时便给定其值并永不变化,这时就不要再用这
种方法。在main方法中有两行语句注释掉了,如果你去掉注释,程序便无法通过编译,这
便是说,不论是i的值或是list的类型,一旦初始化,确实无法再更改。然而b可以通过
重新初始化来指定i的值或list的类型,输出结果中显示了这一点:
I=100ListType:List
I=23ListType:ist
还有一种用法是定义方法中的参数为final,对于基本类型的变量,这样做并没有什么
实际意义,因为基本类型的变量在调用方法时是传值的,也就是说你可以在方法中更改这个
参数变量而不会影响到调用语句,然而对于对象变量,却显得很实用,因为对象变量在传递
时是传递其引用,这样你在方法中对对象变量的修改也会影响到调用语句中的对象变量,当
你在方法中不需要改变作为参数的对象变量时,明确使用final进行声明,会防止你无意的
修改而影响到调用方法。
另外方法中的内部类在用到方法中的参变量时,此参变也必须声明为final才可使用,如下
代码所示:
publicclassINClass{
voidinnerClass(finalStringstr){
classIClass{
IClass(){
n(str);
}
}
IClassic=newIClass();
}
publicstaticvoidmain(String[]args){
INClassinc=newINClass();
lass("Hello");
}
}
final方法
将方法声明为final,那就说明你已经知道这个方法提供的功能已经满足你要求,不需
要进行扩展,并且也不允许任何从此类继承的类来覆写这个方法,但是继承仍然可以继承这
个方法,也就是说可以直接使用。另外有一种被称为inline的机制,它会使你在调用final
方法时,直接将方法主体插入到调用处,而不是进行例行的方法调用,例如保存断点,压栈
等,这样可能会使你的程序效率有所提高,然而当你的方法主体非常庞大时,或你在多处调
用此方法,那么你的调用主体代码便会迅速膨胀,可能反而会影响效率,所以你要慎用final
进行方法定义。
final类
当你将final用于类身上时,你就需要仔细考虑,因为一个final类是无法被任何人继
承的,那也就意味着此类在一个继承树中是一个叶子类,并且此类的设计已被认为很完美而
不需要进行修改或扩展。对于final类中的成员,你可以定义其为final,也可以不是final。
而对于方法,由于所属类为final的关系,自然也就成了final型的。你也可以明确的给
final类中的方法加上一个final,但这显然没有意义。
下面的程序演示了final方法和final类的用法:
finalclassfinal{
finalStringstr="finalData";
publicStringstr1="nonfinaldata";
finalpublicvoidprint(){
n("finalmethod.");
}
publicvoidwhat(){
n(str+"n"+str1);
}
}
publicclassFinalDemo{//extendsfinal无法继承
publicstaticvoidmain(String[]args){
finalf=newfinal();
();
();
}
}
从程序中可以看出,final类与普通类的使用几乎没有差别,只是它失去了被继承的特
性。final方法与非final方法的区别也很难从程序行看出,只是记住慎用。
final在设计模式中的应用
在设计模式中有一种模式叫做不变模式,在Java中通过final关键字可以很容易的实
现这个模式,在讲解final成员时用到的程序就是一个不变模式的例子。如果你
对此感兴趣,可以参考阎宏博士编写的《Java与模式》一书中的讲解。
到此为止,this,static,supert和final的使用已经说完了,如果你对这四个关键字
已经能够大致说出它们的区别与用法,那便说明你基本已经掌握。然而,世界上的任何东西
都不是完美无缺的,Java提供这四个关键字,给程序员的编程带来了很大的便利,但并不
是说要让你到处使用,一旦达到滥用的程序,便适得其反,所以在使用时请一定要认真考虑。
本文发布于:2023-01-02 15:11:35,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/78467.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |