用"堆栈区数据复制"理解Java赋值和参数传递机制的心得

提纲:

1、版权声明

2、前言

3、正文

4、结论

5、附注

6、参考资料

===========================================================================================================================

1、版权声明:

  本文作者:littlebat,原始出处:用"堆栈区数据复制"理解Java赋值和参数传递机制的心得 (http://java.learndiary.com/disDiaryContentAction.do?goalID=2716),邮件:mdx-xx@tom.com。如有任何反馈意见请联系作者,作者会在本文原始出处随时更新此文。转载及引用请保留此版权声明,谢谢。

2、前言:

  在所有一切之前,先让我们看一个例子,如果您能得出这个例子的正确答案,下面的内容您应该基本上领会了。当然,请注意,本文不光只是阐述Java方法的参数传递问题,站在一个统一的角度还包括Java的赋值操作。如果您有兴趣,不妨把它看完:)

  例子1源码:Tester.java(摘改自:Java参数传递方式)


public class Tester {

    public static void main(String[] args) {

        int primitive = 2;

        changePrimitive(primitive);

        System.out.println("Step 1, primitive = " + primitive); //Step 1

        //问题1:请问,Step 1这里primitive的值是多少?

        MyClass myClass = new MyClass(); // Step 2

        System.out.println("Step 3, myClass = " + myClass); //Step 3

        changeObject(myClass);

        System.out.println("Step 8, myClass = " + myClass); //Step 8

        System.out.println("Step 9, myClass.i = " + myClass.i); //Step 9

        //问题2:请问,Step 8这里myClass指向的是执行方法changeObject(MyClass)之前的Step 3的那个对象,

        //还是在方法changeObject(MyClass)中的Step 5新生成的那个对象?

        //问题3:现在,Step 9中,myClass.i等于多少了?

    }

    public static void changePrimitive(int primitive) {

        primitive = 3;

    }

    public static void changeObject(MyClass myClass) {

        System.out.println("Step 4, myClass = " + myClass); //Step 4

        myClass.i = 3;

        myClass = new MyClass(); //Step 5

        System.out.println("Step 6, myClass = " + myClass); //Step 6

        System.out.println("Step 7, myClass.i = " + myClass.i); //Step 7

    }

}

class MyClass {

    int i = 0;

}

请认真想好,例子中3个问题您的答案是什么?

这里是标准答案:

问题1:primitive = 2

问题2:myClass仍然指向的是执行方法changeObject(MyClass)之前的Step 2的那个对象。

问题3:myClass.i = 3

您做对了吗(首先坦白,在写这篇文章之前,我得出了错误的答案:(,见:Java参数传递方式(转帖))?怎么,像我一样,没做对?那么您该好好读一下下面的内容了,读了之后还做不对的话就把我这篇帖子扔进垃圾堆吧。不过,扔之前能给我回复一下为什么我将感激不尽:)

3、正文:

  关于Java的赋值和参数传递是按值(by value)进行的还是按引用(by reference)进行的,这个问题曾经迷惑了很多人,包括我。而且,我想,这个问题还将继续迷惑一些人,包括那些C++的高手。

  在这里,我不准备用“按值(by value)”和“按引用(by value)”这样的术语来阐述这个问题。因为,从字面的理解来看,这样的术语在不同的人头脑里有不同的含义。我试图从Java数据(包括原始类型(primitive type)和对象(ojbect))在内存中的存储这个角度,用一个自创的“术语”来阐述我对这个问题的理解。这个术语就是:“堆栈区数据复制(Stack Data Copy,简称SDC)”。详细一点就是:在Java中,不管是赋值操作还是参数传递操作--针对原始类型(primitive type),是对堆栈区的原始类型的值进行复制;针对对象,是对储存在堆栈区的,对象的引用中所储存的_对象的值被存储的地址_进行复制。

  像上面抠字眼的句子读起来比较费力,我在后面将用两个例子并结合一些示意图来阐述我对这个问题的理解。希望各位朋友帮助纠正错误。

1)、赋值操作:

例子2源码:(Assign.java)


public class Assign{

  public static void main(String[] args){

    int i = 1;

    Object o = new Object();

    System.out.println("i = " + i + " ; o = " + o ); // Step 1 (示意图:3-1-1)

    int j = i;  

    Object p = o;

    System.out.println("i = " + i + " ; j = " + j + " ; o = " + o + " ; p = " + p); //Step 2 (示意图:3-1-2)

    j++;

    p = new Object();

    System.out.println("i = " + i + " ; j = " + j + " ; o = " + o + " ; p = " + p); //Step 3 (示意图:3-1-3)

  }

}

对上面例子的说明:

(1),Step 1中,整数i和对象o得到赋值。

示意图3-1-1

从示意图3-1-1中可以看出:整数i存储在堆栈区(Stack);对象o的引用存储在了堆栈区,但是对象o的值却存储在了内存堆中(Heap),对象o的引用存储了对象o的地址。

Step 1在我的机器上的一次输出结果:


i = 1 ; o = java.lang.Object@a90653

至于对象o的值输出来怎么会是那个样子,我只能告诉您:在java程序的一次运行过程中,每个Object对象输出这样的值是唯一的,因此可以借此来判断对象的引用指向的对象是否发生了改变。详情请参考Java API 文档(下同,这里给出的是:J2SE 1.5.0 API 中文版):

http://gceclub.sun.com.cn/Java_Docs/html/zh_CN/api/java/io/PrintStream.html#println(java.lang.Object)

http://gceclub.sun.com.cn/Java_Docs/html/zh_CN/api/java/lang/Object.html#toString()

(2),Step 2中,把整数i赋值给了整数j,把对象o赋值给了对象p。

示意图3-1-2

从示意图3-1-2中可以看出:整数i的值复制给了整数j,整数j同样存储在堆栈区;存储在堆栈区的对象o的引用中存储的_对象o的值的地址C_复制给了对象p的引用,对象p的引用同样在堆栈区中。因为对象p的引用_得到了对象o的引用复制过来的_对象o的值的存储地址C,所以对象p的引用_和对象o的引用_都指向了在堆(heap)中的同一个对象,并且,这个对象的地址是地址C。

Step 2在我的机器上的一次输出结果:


i = 1 ; j = 1 ; o = java.lang.Object@a90653 ; p = java.lang.Object@a90653

(3),Step 3中,整数j的值加1,赋给了对象p新的对象值。

示意图3-1-3

从示意图3-1-3中可以看出:整数i的值不变,整数j的值加1变为2,整数在堆栈区中;新生成的对象的值存储在了堆(Heap)中,地址为F。新生成对象的值的地址F_存储在了堆栈区p的引用中,替换了原来存储在其中的地址C。于是,p的引用就指向了新生成的对象,这个新生成的对象的值的地址_是地址F。而整数i和对象o的(包括对象o的引用)没有改变也不曾有任何改变(除了初次赋值)。

Step 3在我的机器上的一次输出结果:


i = 1 ; j = 2 ; o = java.lang.Object@a90653 ; p = java.lang.Object@de6ced

至此,通过上面的例子及其示意图和说明,我得到一个结论:

在Java赋值操作中,针对原始类型(primitive type),是对堆栈区的原始类型的值进行复制;针对对象,是对储存在堆栈区的,对象的引用中所储存的_对象的值被存储的地址进行复制。这就是术语:“堆栈区数据复制(Stack Data Copy,简称SDC)”在Java赋值操作中的阐述。

2)、方法中的参数传递操作:

例子2源码:(PassParameter.java)


public class PassParameter{

  static void showMe(int pi, Object po){

    System.out.println("pi = " + pi + " ; po = " + po); // Step 2 (示意图:3-2-2)

    pi++;

    po = new Object();

    System.out.println("pi = " + pi + " ; po = " + po); // Step 3 (示意图:3-2-3)

  }

  public static void main(String[] args){

    int i = 1;

    Object o = new Object();

    System.out.println("i = " + i + " ; o = " + o); // Step 1 (示意图:3-1-1)

    showMe(i, o);

    System.out.println("i = " + i + " ; o = " + o); // Step 4 (示意图:3-2-3)

  }

}

对上面例子的说明:

(1),Step 1中,与上面Assign.java中的Step 1相同,略,下面重复其示意图3-1-1。

示意图3-1-1

Step 1在我的机器上的一次输出结果:


i = 1 ; o = java.lang.Object@a90653

(2),Step 2中,与上面Assign.java中的Step 2类似,只是Assign.java中的整数j和对象p变成了这里的方法showMe()中的参数:整数pi和对象po。并且,由于这里是参数传递,把Assign.java示意图3-1-2中的“=”替换成PassParameter.java示意图3-2-2中的“<--”,以此表示是参数传递。据我的理解,它们是一回事。

示意图3-2-2

Step 2在我的机器上的一次输出结果:


pi = 1 ; po = java.lang.Object@a90653

(3),Step 3和Step 4合并起来,见示意图3-2-3同样,与上面Assign.java中的Step 3类似。

示意图3-2-3

Step 3和Step 4在我的机器上的一次输出结果:


pi = 2 ; po = java.lang.Object@de6ced

i = 1 ; o = java.lang.Object@a90653

至此,通过上面的例子及其示意图和说明,我得到一个结论:

在Java方法参数传递操作中,针对原始类型(primitive type),是对堆栈区的原始类型的值进行复制;针对对象,是对储存在堆栈区的,对象的引用中所储存的_对象的值被存储的地址进行复制。这就是术语:“堆栈区数据复制(Stack Data Copy,简称SDC)”在Java方法参数传递操作中的阐述。

4,结论

综上所述:在Java中,不管是赋值操作还是方法的参数传递操作--针对原始类型(primitive type),是对堆栈区的原始类型的值进行复制;针对对象,是对储存在堆栈区的,对象的引用中所储存的_对象的值被存储的地址进行复制。

所以,据我的理解,术语:“堆栈区数据复制(Stack Data Copy,简称SDC)”能够有助于理解在Java中进行赋值和传递参数的机制,能够有助于在一定程度上消除“传值”、“传引用”等语义上的多变性的负面影响,可以提出来供大家交流。

5,附注:

由于本人水平有限,上面的一切全是基于实践进行的带有一些推测成分在内的个人心得总结。我也以上面的自创术语去成功解释过一些文章中的有关问题(如下面参考资料中的例程)。谨希望在能为部分Java初学者提供一个理解Java赋值和参数传递的手段的同时,更能得到各位朋友的斧正,以便能够对这个问题形成更加正确和准确的认识。在我提高认识的同时,我会在本文原始出处:用"堆栈区数据复制"理解Java赋值和参数传递机制的心得 (http://java.learndiary.com/disDiaryContentAction.do?goalID=2716)中随时更新此文。再次贴出我的邮件:mdx-xx@tom.com。谢谢。

6,参考资料:

1),Java参数传递方式 (http://www.jiehoo.com/java-pass-parameter.htm)

2),破除java神话之二:参数是传址的 (http://www.javaresearch.org/article/showarticle.jsp?column=544&thread=443)

3),Java 应用程序中的按值传递语义 (http://www.javaresearch.org/article/showarticle.jsp?column=1&thread=706)

4),我对《Java 应用程序中的按值传递语义》的理解 (http://www.javaresearch.org/article/showarticle.jsp?column=1&thread=3156)

5),Thinking in Java, 3rd Edition in Java (http://www.mindviewinc.com/downloads/TIJ-3rd-edition4.0.zip)

                                                              全文完

                                                              2006年11月22日午首稿

                                                              2006年11月24日下午第一次更新

                                                              2006年11月28日早晨第二次更新(局部小更新)

Java参数传递方式(转帖)

转自:http://www.jiehoo.com/java-pass-parameter.htm

Java参数传递方式

其实这个问题我原来翻译(破除java神话之二:参数是传址的 )、转帖别人的详细解释(Java 应用程序中的按值传递语义 )和专门解释( 我对《Java 应用程序中的按值传递语义》的理解)过,不过现在看来,原来翻译或者解释的角度是有问题的,从底层的角度解释并不直观,在交流的时候也容易引起误解,最终不能达成一致意见。下面以最终的效果来解释参数的传递方式:

1、对于原始数据类型,也就是int、 long、char之类的类型,是传值的,如果你在方法中修改了值,方法调用结束后,那个变量的值没用改变。

2、对于对象类型,也就是Object的子类,如果你在方法中修改了它的成员的值,那个修改是生效的,方法调用结束后,它的成员是新的值,但是如果你把它指向一个其它的对象,方法调用结束后,原来对它的引用并没用指向新的对象。

代码如下:

public class Tester {

    public static void main(String[] args) {

        int primitive = 2;

        changePrimitive(primitive);

        //primitive的值依然是2

        MyClass myClass = new MyClass();

        changeObject(myClass);

        //myClass仍然指向的是执行changeObject之前的那个对象

        //但是myClass.i等于3了

    }

    public static void changePrimitive(int primitive) {

        primitive = 3;

    }

    public static void changeObject(MyClass myClass) {

        myClass.i = 3;

        myClass = new MyClass();

    }

}

class MyClass {

    int i;

}

对于远程调用,无论是什么类型,调用结束后,传入的参数和以前没用任何变化(当然前途是直接调用远程方法,如果中间经过其它的Proxy类或者Facade类,不能保证那些类对对象没用修改)。至于是通过Locale接口进行调用的,我不太清楚是否属于远程调用。以后确定了再来更新。

作者: Cherami

原载: Java参数传递方式

版权所有。转载时必须以链接形式注明作者和原始出处及本声明。

相关日志

    * 修复不能评论的问题

    * 使用参数方式还是页面配置方式

    * 我使用的WordPress插件

    * 解析XML的时候完全忽略DTD

随机日志

    * 机场也可以种树

    * Response被关闭

    * Java面试中的经典对比问题

    * 在目录中查找类位于哪个jar包中

添加到网摘

[del.icio.us]  [新浪 VIVI]  [365key]  [YouNote]  [博采中心]  [Poco]  [SOHU狐摘]  [天极网摘]  [和讯网摘]

喜欢这个插件?

当前日志信息

标题:

    Java参数传递方式

发表:

    2006-11-15

分类:

    Java

标签:

    java, 参数传递, 传值, 传引用

点击:

    28

评价:

    Votes | Average: 0 out of 5 Votes | Average: 0 out of 5 Votes | Average: 0 out of 5 Votes | Average: 0 out of 5 Votes | Average: 0 out of 5 (暂无评价)

    Loading ... Loading ...

该日志共有 3 条评论

发表评论 | RSS订阅 | 反向链接

   1. oneal 2006-11-15

      local 调用相当于同一个虚拟机上的对普通对象方法的调用。

      上面那段代码眼熟啊,上个月做过,哈哈。

   2. Cherami 2006-11-16

      local调用可能是本地调用,但是感觉有可能会换成远程调用,这样如果没有意识到,可能会有潜在的bug。因为本地调用和远程调用的特性是不一样的。可能会误用特性。

   3. English Study and Sharing » 博客文章 » Java参数传递方式 2006-11-19

      […] 对于远程调用,无论是什么类型,调用结束后,传入的参数和以前没用任何变化(当然前途是直接调用远程方法,如果中间经过其它的Proxy类或者 Facade类,不能保证那些类对对象没用修改)。至于是通过Locale接口进行调用的,我不太清楚是否属于远程调用。以后确定了再来更新。 作者: Cherami 原载: Java参数传递方式 […]

Eclipse使用技巧(转)

转自:(http://www.javaresearch.org/article/57437.htm

用了eclipse有一段时间了,就是没有系统的学习过。下面这篇转帖不错,看了下觉得有收藏价值就贴在这里了。

正文

××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

Eclipse使用技巧

jiqimiao1982 转贴   更新:2006-11-04 12:45:09  版本: 1.0  

热键篇:

Template:Alt + .

修改处:Window->Preference->Workbench->Keys->Command->Edit->Content Assist。

个人习惯:Shift+SPACE(空白)。

简易说明:编辑程序代码时,打sysout +Template启动键,就

会自动出现:System.out.println(); 。

设定Template的格式:窗口->喜好设定->Java->编辑器->模板。

程序代码自动排版:Ctrl+Shift+F

修改处:窗口->喜好设定->工作台->按键->程序代码->格式。

个人习惯:Alt+Z。

自动排版设定:窗口->喜好设定->Java->程序代码格式制作程序。

样式页面->将插入tab(而非空格键)以内缩,该选项取消勾选

,下面空格数目填4,这样在自动编排时会以空格4作缩排。

快速执行程序:Ctrl + F11

个人习惯:ALT+X

修改处:窗口->喜好设定->工作台->按键->执行->启动前一次的启动作业。

简易说明:第一次执行时,它会询问您执行模式,

设置好后,以后只要按这个热键,它就会快速执行。

<ALT+Z(排版完)、ATL+X(执行)>..我觉得很顺手^___^

自动汇入所需要的类别:Ctrl+Shift+O

简易说明:

假设我们没有Import任何类别时,当我们在程序里打入:

BufferedReader buf =

new BufferedReader(new InputStreamReader(System.in));

此时Eclipse会警示说没有汇入类别,这时我们只要按下Ctrl+Shift+O

,它就会自动帮我们Import类别。

查看使用类别的原始码:Ctrl+鼠标左键点击

简易说明:可以看到您所使用类别的原始码。

将选取的文字批注起来:Ctrl+/

简易说明:Debug时很方便。

修改处:窗口->喜好设定->工作台->按键->程序代码->批注

将选取的文字取消批注:Ctrl+简易说明:同上。

修改处:窗口->喜好设定->工作台->按键->程序代码->取消批注

视景切换:Ctrl+F8

个人习惯:Alt+S。

修改处:窗口->喜好设定->工作台->按键->窗口->下一个视景。

简易说明:可以方便我们快速切换编辑、除错等视景。

3.0里Ctrl+Alt+H可以看到调用当前member的方法,而且可以一层一层上去.

Ctrl+O可以快速切到其他方法.

密技篇:

一套Eclipse可同时切换,英文、繁体、简体显示:

1.首先要先安装完中文化包。

2.在桌面的快捷方式后面加上参数即可,

英文-> -nl "zh_US"

繁体-> -nl "zh_TW"

简体-> -nl "zh_CN"。

(其它语系以此类推)

像我2.1.2中文化后,我在我桌面的Eclipse快捷方式加入参数-n1 "zh_US"。

"C:\Program Files\eclipse\eclipse.exe" -n "zh_US"

接口就会变回英文语系噜。

利用Eclipse,在Word编辑文书时可不必将程序代码重新编排:

将Eclipse程序编辑区的程序代码整个复制下来(Ctrl+C),直接贴(Ctrl+V)到

Word或WordPad上,您将会发现在Word里的程序代码格式,跟Eclipse

所设定的完全一样,包括字型、缩排、关键词颜色。我曾试过JBuilder

、GEL、NetBeans...使用复制贴上时,只有缩排格式一样,字型、颜

色等都不会改变。

外挂篇:

外挂安装:将外挂包下载回来后,将其解压缩后,您会发现features、

plugins这2个数据夹,将里面的东西都复制或移动到Eclipse的features

、plugins数据夹内后,重新启动Eclipse即可。

让Eclipse可以像JBuilderX一样使用拖拉方式建构GUI的外挂:

1.Jigloo SWT/Swing GUI Builder :

http://cloudgarden.com/jigloo/index.html

下载此版本:Jigloo plugin for Eclipse (using Java 1.4 or 1.5)

安装后即可由档案->新建->其它->GUI Form选取要建构的GUI类型。

2.Eclipse Visual Editor Project:

http://www.eclipse.org/vep/

点选下方Download Page,再点选Latest Release 0.5.0进入下载。

除了VE-runtime-0.5.0.zip要下载外,以下这2个也要:

EMF build 1.1.1: (build page) (download zip)

GEF Build 2.1.2: (build page) (download zip)

我只测试过Eclipse 2.1.2版本,使用上是OK的!

3.0版本以上的使用者,请下载:

Eclipse build I20040324:

1.0.0 Stream Integration Build I20040325 Thu, 25 Mar 2004 -- 12:09 (-0500)

1.0.0 Stream Nightly Build N20040323a Tue, 23 Mar 2004 -- 13:53 (-0500)

注意:3.0以上版本,仅build I20040324可正常使用。

安装成功后,即可由新建->Java->AWT与Swing里选择

所要建构的GUI类型开始进行设计。VE必须配合着对应

版本,才能正常使用,否则即使安装成功,使用上仍会

有问题。

使用Eclipse来开发JSP程序:

外挂名称:lomboz(下载页面)

http://forge.objectweb.org/project/showfiles.php?group_id=97

请选择适合自己版本的lomboz下载,lomboz.212.p1.zip表示2.1.2版,

lomboz.3m7.zip表示M7版本....以此类推。

lomboz安装以及设置教学:

Eclipse开发JSP-教学文件

Java转exe篇:

实现方式:Eclipse搭配JSmooth(免费)。

1.先由Eclipse制作包含Manifest的JAR。

制作教学

2.使用JSmooth将做好的JAR包装成EXE。

JSmooth下载页面:

http://jsmooth.sourceforge.net/index.php

3.制作完成的exe文件,可在有装置JRE的Windows上执行。

Eclipse-Java编辑器最佳设定:

编辑器字型设定:工作台->字型->Java编辑器文字字型。

(建议设定Courier New -regular 10)

编辑器相关设定:窗口->喜好设定->Java->编辑器

外观:显示行号、强调对称显示的方括号、强调显示现行行、

显示打印边距,将其勾选,Tab宽度设4,打印编距字段设80。

程序代码协助:采预设即可。

语法:可设定关键词、字符串等等的显示颜色。

附注:采预设即可。

输入:全部字段都勾选。

浮动说明:采预设即可。

导览:采预设即可。

使自动排版排出来的效果,最符合Java设计惯例的设定:

自动排版设定:窗口->喜好设定->Java->程序代码制作格式。

换行:全部不勾选。

分行:行长度上限设:80。

样式:只将强制转型后插入空白勾选。

内缩空格数目:设为4。

Eclipse的教学文件:

扩充Eclipse的Java 开发工具(中文)

使用Eclipse开发J2EE 应用程序(中文)

使用Eclipse平台进行除错(中文)

用Eclipse进行XML 开发(中文)

开发Eclipse外挂程序(中文)

国际化您的Eclipse外挂程序(英文)

将Swing编辑器加入Eclipse(英文)

如何测试你的Eclipse plug-in符合国际市场需求(英文)

Eclipse的相关网站:

http://eclipse-plugins.2y.net/eclipse/index.jsp

http://www.eclipseplugincentral.com/

Eclipse相关教学[简体]

                                                             转帖完毕

在eclipse中javabean中的boolean型变量不要用"is"开头

在eclipse中(2.* 和 3.*)都试过,如果选定属性用eclipse的自动生成getter和setter 的方法,则在boolean类型中不能以is开头。如下面的

boolean b 和 boolean isB 自动产生的getter和setter都为:


public boolean isB() {

return b;

}

public void setB(boolean b) {

this.b = b;

}

下面试了在eclipse中多种类型的数据的自动生成getter和setter方法,方法是用鼠标选定要生成getter和setter的属性,再选择菜单中的:Source->Generate Getters and Setters...:


public class T {

private int isI;

private String isS;

private boolean b;

// private boolean isB;

private Boolean isBool;

public boolean isB() {

return b;

}

public void setB(boolean b) {

this.b = b;

}

public Boolean getIsBool() {

return isBool;

}

public void setIsBool(Boolean isBool) {

this.isBool = isBool;

}

public int getIsI() {

return isI;

}

public void setIsI(int isI) {

this.isI = isI;

}

public String getIsS() {

return isS;

}

public void setIsS(String isS) {

this.isS = isS;

}

}

搭建环境

一、安装Ruby

http://rubyinstaller.rubyforge.org/上下载最新的Ruby one-click Installer for windows。

所有选项都选择。一顿经典的next之后,我们就安装了Ruby。

二、使用Rubygems安装Rails

因为刚才经典的next,我们已经成功的安装了Rubygems,利用它可以安装Rails。

gem包含很多子命令和相应的选项,感兴趣的可以在dos窗口下输入gem -h来查看帮助。

好了,让我们用一条语句来安装Rails。

首先确保你的计算机连接到了internet。

在dos窗口下输入:gem install rails --remote

等待吧。。。

有yes or no的地方按“y”

三、数据库的选择

要说Rails对数据库的连接,用mysql吧,这可是官方推荐的。至于mysql安装与配置,再次不赘诉了。

现在,我们已经搭建了开发Rails应用的环境,马上就要开始Rails应用的编写了。呵呵

Ruby简介

Ruby 是由日本人松本行弘发明的一种面向对象的脚本语言。虽然很多语言都宣称自己是面向对象的,但是每种语言的解释都不一样,大多是以它们自己特有的方式来解释什么是面向对象,而实际情况中,这些面向对象语言又都采用了很多非面向对象的做法。以 Java 为例:如果你想对一个数字取绝对值,你会怎么做呢? java 的做法是

int c = Math.abs(-166);

也就是将一个数值传递给 Math 类的一个静态函数 abs 处理。为什么这么做?因为在 java 中, 数值是基本类型不是类。

而在 Ruby 中,任何事物都是类, 也就是说,数字 –166 就是对象,取绝对值这样的操作应该属于数字本身,所以”Ruby 方式”的做法就是:

c = -166.abs

这种做法是不是更直观一点。

接着我们来介绍一下 ruby 语言的一些简单特点。

如何定义函数

def hello(name)

result = "hello, " + name

return result

end

这就是一个最简单的函数定义。Ruby 以 def 开始一个函数定义,后跟着 函数名, 然后是 参数,但是参数不必非要放在括号中, 你可以这样定义 def hello name ,之所以要用括号是为了更清晰。在 ruby 中你可以用多种方式来完成同一件事情,这也是 ruby 的设计思想。

Hello 函数很简单, 将参数和 “hello, “字符串组合在一起,赋值给临时变量 result ,然后再返回 result。 你会注意到 result 变量并没有申明,因为在 ruby 中无需申明,变量在赋值中自动使用。 另外 ruby 中不需要使用 ; 来结束每个语言,只要保持每条语句在单独一行就可以。 Ruby 也不包含 {} 来定义块结构, if , when 函数都以一个 end 关键字来结束。缩进不是语法(python 程序员要失望了)。第一次使用ruby 我也有点不习惯,因为我认为python 语言的缩进语法可以使得阅读程序代码更容易一些。但是学习了一段时间后,我发现缩进语法并不是那么重要,Ruby 本身带来的帮助远比这个更重要。而且代码的易读性也不是由缩进来改善的.此外上面的代码还可以更简化。

def hello (name)

" hello, #{name}"

end

在 ruby 中,函数的最后一条语句如果是表达式,那么它就作为返回值。在上面的代码中,用到了另外一个概念,就是表达式插入,字符串可以用单引号和双引号来括起来。但是两者还是有一点区别。区别在于处理时间上的不同。如果使用单引号,那么处理的时间很短。如果使用双引号,那么你可以插入变量,表达式,还有就是转意字符的替换,最常见的是 \n,\t 等。 以上面的代码为例 字符串中插入了变量 name ,字符串中插入变量以 # 开始,变量放在{}中。 但是特殊变量可以不用 {}. 那么什么是特殊变量呢。这个也是 ruby 特别的地方。在 Ruby 中,全局变量以 $开头,静态变量,也就是类变量以 @@ 开头,实例变量以 @ 开头。如果在字符串中引用 实例变量,你可以这么写。

def hello

" hello, #@name"

end

关于变量和基本结构我们就简单介绍这些,下面我们要介绍一个重要的Ruby 概念。就是 interator (迭代器)。 这个概念遍布在很多 ruby 代码中,也是最常见的 ruby way 。正是因为 interator ,在 ruby 程序中,你很少会看到循环的使用。 举个最简单的例子,如何打印从 1 到 6 的结果。

在 java 等语言中,我们的实现方法是:

for(int i = 1; i<6;i++) {

System.out.println(i);

}

而在 ruby 中,一切皆为对象,数字本身就是对象(我们在前面已经介绍过了),数字本身有它自己的迭代器 times 。那么让我们看看它的实现。

6.times {|i| p i }

是不是很简单。

迭代器的概念很有趣,它是如何在 ruby 中实现的呢。每个函数可以不光带有参数,还可以在参数后面带一个代码块。代码块在ruby 中是以 {} 和 do end 来包围起来的。通常如果是单行使用 {} ,如果是多行这使用do end 。这并非强制,只是习惯性用法。

代码块和参数并不相同,它和函数的执行可以说是并行的,确切的说是接替进行。如果在函数内部如何在运行过程中碰到了 yield ,它就会将函数执行过程交给函数附带的代码块来执行,代码快执行完了,执行流程转到函数内部继续运行。似乎很难理解,以下面这段代码来理解可以会容易一些。

def callBlock

yield

yield

end

callBlock { puts "In the block" }

输出:

In the block

In the block

CallBlock 函数中只有两个 yield 语句,它在执行过程中将执行权交给 函数附带的 block 运行,block 输出字符串 "In the block" ,这样最终结果就是输出两行 "In the block" 。 在函数内部使用 yield 还可以带任意多参数。比如

yield a, b, c

这三个参数对应代码块中的三个参数 {|x,y,z| p x y z }

也许是永远也搞不懂了的"bean:write"怪事

学习日记里有3个jsp文件,接受action送来的一个帖子列表,然后循环的用struts中<bean:write/>标记显示出来。

帖子中有一个在3个jsp文件中都有的属性名为"myLastUpdate",在文件:processGoal.jsp中一切正常,但在finishedGoal.jsp和quitedGoal.jsp中却在网站空间中报告帖子对象的一个myLastUpdate的getter方法抛出异常。而同样的文件在本地用Tomcat5.0正常。空间用的是resin2.*。

具体是这3个文件都有一段如下的代码在文件:processGoal.jsp中一切正常,但在finishedGoal.jsp和quitedGoal.jsp出问题:


...

      <td width = "10%" align="center">

        <bean:write name="anGoal" property="finishDate" scope="page" filter="false"/>

      </td>

 

      <td width = "10%" align="center">

        <bean:write name="anGoal" property="myLastUpdate" scope="page" filter="false"/>

      </td>

...

最后,实在找不到原因了,用了jstl中的<c:out/>标记来输出这个对象的"myLastUpdate"属性才全部正常。

如下所示:


...

      <td width = "10%" align="center">

        <bean:write name="anGoal" property="finishDate" scope="page" filter="false"/>

      </td>

 

      <td width = "10%" align="center">

        <c:out value="${anGoal.myLastUpdate}"/>

      </td>

...

这个问题实在是不应该,可就是出现了。也许是Struts中的<bean:write/>加上属性名myLastUpdate加上resin服务器这三者的偶然巧合出现了这个错误?如果有知道答案的朋友指点一下。

如果,你碰巧碰到这样的情况,不妨在jsp文件中改一下取得对象属性的方法,也许就解决了你磨破脑袋也找不到原因的问题。

JDOM与XML的案例(转帖)

JDOM与XML的案例转自:http://www.j2medev.com/code/j2se/language/200606/2559.html

××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

import java.io.IOException;

import java.util.Iterator;

import java.util.List;

import org.jdom.Comment;

import org.jdom.Document;

import org.jdom.Element;

import org.jdom.JDOMException;

import org.jdom.ProcessingInstruction;

import org.jdom.input.DOMBuilder;

import org.jdom.input.SAXBuilder;

import org.jdom.output.DOMOutputter;

/*

 * Simple demo of JDOM

 */

public class JDOMDemo {

    public static void main(String[] args) {

    // Must be at least one file or URL argument

        if (args.length == 0) {

            System.out.println("Usage: java JDOMDemo URL [...]");

        }

        SAXBuilder saxBuilder = new SAXBuilder();

        DOMBuilder domBuilder = new DOMBuilder();

        for (int i = 0; i < args.length; i++) {

            try {

                Document jdomDocument = saxBuilder.build(args[i]);

                DOMOutputter domOutputter = new DOMOutputter();

                /*

                 * Test getting DOM Document from JDOM Document

                org.w3c.dom.Document domDocument = domOutputter.output(doc);

                 */

                /*

                 * Test getting DOM Element from JDOM Element

                 */

                org.w3c.dom.Element domElement =

                  domOutputter.output(jdomDocument.getRootElement());

                /*

                 * Test getting JDOM Element from DOM Element

                 */

                org.jdom.Element jdomElement = domBuilder.build(domElement);

                demo(jdomElement);

            } catch (JDOMException e) { // indicates a well-formedness or other error

                System.out.println(args[i] + " is not a well formed XML document.");

                System.out.println(e.getMessage());

            } catch (IOException ex) {

        System.out.println("Input or Output error:" +

          args[i] + ": " + ex);

      }    

        }

    }

    public static void demo(Document doc) {

        List children = doc.getContent();

        Iterator iterator = children.iterator();

        while (iterator.hasNext()) {

            Object o = iterator.next();

            if (o instanceof Element) {

                demo((Element) o);

            }

            else if (o instanceof Comment)

        doComment((Comment) o);

            else if (o instanceof ProcessingInstruction)

        doPI((ProcessingInstruction) o);

        }

    }    

    public static void demo(Element element) {

    System.out.println("Element " + element);

        List attributes = element.getAttributes();

        List children = element.getContent();

        Iterator iterator = children.iterator();

        while (iterator.hasNext()) {

            Object o = iterator.next();

            if (o instanceof Element) {

                demo((Element) o);

            }

            else if (o instanceof Comment)

        doComment((Comment)o);

            else if (o instanceof ProcessingInstruction)

        doPI((ProcessingInstruction)o);

            else if (o instanceof String) {

                System.out.println("String: " + o);

            }  

        }

    } 

  public static void doComment(Comment c) {

    System.out.println("Comment: " + c);

  }

  public static void doPI(ProcessingInstruction pi) {

    System.out.println("PI: " + pi);

  }

}

// demo xml file

/*

<?xml version="1.0"?>

<people>

<person>

  <name>Ian Darwin</name>

  <email>http://www.darwinsys.com/</email>

  <country>Canada</country>

</person>

<person>

  <name>Another Darwin</name>

  <email type="intranet">afd@node1</email>

  <country>Canada</country>

</person>

</people>

*/

JSP/servlet中<JSP:FORWORD>和response.sendRedirect("")重定向

JAVA学习日记现有绑定了3个域名:www.learndiary.com, www.123go.org.cn, java.learndiary.com,

搜索引擎优化排名SEO每天一贴的zac老师讲:http://www.chinamyhosting.com/seoblog/2006/08/13/some-seo-questions/


如果这些域名都返回的是200,那么恐怕就有复制内容问题。应该把其中一个域名做主域名,服务器返回200,其他的所有域名都要做301转向,检查服务器状态码时,应该返回301,这样才能避免复制内容网页。

但是,我还没有解决本站现在要面临的首页重定向问题,即怎样在输入http://java.learndiary.com后返回200状态码,这主要是告诉搜索引擎:这个域名是我们的主域名。

具体问题是:


#  littlebat说:

2006年10月21日 at 5:22 pm

尊敬的zac老师,

你好。

我的网站是个jsp的网站,现有三个绑定的域名,按时间顺序分别是www.learndiary.com(pr=4),www.123go.org.cn (pr=4),当前由于网站定位调整,准备主要启用域名java.learndiary.com(pr=0)(pr值用www.123cha.com上查的)。

根据你写的文章和jsp的重定向技术,我在程序中实现了网站下面各页面内容的301转向,请求原来域名下的内容返回301码,请求新域名下的内容返回200码。

但是,我的首页通过程序无法实现301重定向。访问http://www.learndiary.com和http: //www.123go.org.cn返回301,重定向到http://java.learndiary.com ,访问http://java.learndiary.com返回302,重定向到http: //java.learndiary.com/indexAction.do ,

访问http://java.learndiary.com/indexAction.do返回200码。

因为我的网站是租的虚拟主机,主机只能够自动把http://java.learndiary.com定向到index.jsp, index.html, index.htm三种主页上,而我的网站的主页是index.do (使用struts做的),于是我在index.jsp做了一些重定向代码。但是始终不能达到输入我现在主用的域名http: //java.learndiary.com就返回200码的效果。

请问我这样情况的主页对搜索引擎友好吗?如果不友好我应该作些什么变动?

谢谢。

littlebat

http://java.learndiary.com JAVA学习日记,分享目标,分享快乐。

# Zac说:

2006年10月22日 at 6:12 am

littlebat: 抱歉,具体怎么实现301在各种服务器上方法不一样。我对JSP等一无所知,也帮不上忙了。

# littlebat说:

2006年10月22日 at 2:25 pm

谢谢您的回答,我主要是看了您的文章

回答几个SEO相关问题:http://www.chinamyhosting.com/seoblog/2006/08/13/some-seo- questions/中的这段:“如果这些域名都返回的是200,那么恐怕就有复制内容问题。应该把其中一个域名做主域名,服务器返回200,其他的所有域名都要做301转向,检查服务器状态码时,应该返回301,这样才能避免复制内容网页。”

我的重点是想问:我输入首域名http://java.learndiary.com的返回状态码是302,不返回200码;输入其它两个辅助域名的返回状态码是301。

请问这样的情况对搜索引擎友好吗?对网站的搜索排名等有多大的负面影响呢?

# littlebat说:

2006年10月23日 at 6:39 am

zac老师,

您好。我在jsp文件里用meta的重定向标记使输入主域名正确的返回了200码,输入辅助域名返回301码并重定向到主域名。

但是,我听说使用meta的跳转会被怀疑作弊,甚至会被搜索引擎封掉,就没敢用。是真的吗?

我的用meta重定向的首页index.jsp:


<%

  String newLocn = "http://java.learndiary.com";

  if (!(request.getHeader("host").equalsIgnoreCase("java.learndiary.com"))){

    response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);

    response.setHeader("Location",newLocn);

  }else{

    //response.setStatus(HttpServletResponse.SC_OK);

%>

<html>

        <head>

                <title>login...</title>

                <meta http-equiv="Refresh" content="0; URL=http://java.learndiary.com/indexAction.do">

        </head>

        <body>

        </body>

</html>

<%

  }

%>

当前正在运行,“输入首域名http://java.learndiary.com的返回状态码是302,不返回200码”的首页index.jsp:


<%@ include file="/common/predirective.jsp" %>

<html:html locale="true">

<head>

<title>login...</title>

<!--<meta http-equiv="Refresh" content="0; URL=bulletinAction.do">-->

</head>

<body>

<%

  String newLocn = "http://java.learndiary.com";

  if (!(request.getHeader("host").equalsIgnoreCase("java.learndiary.com"))){

    response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);

    response.setHeader("Location",newLocn);

  }else{

    //response.setStatus(HttpServletResponse.SC_OK);

%>

  <logic:redirect page="/indexAction.do" />

<%

  }

%>

</body>

</html:html>

这个问题正在向zac老师请教一些问题,见转向和网址规范化。zac老师在搜索引擎优化排名SEO方面写了不少好文章。我也需要自己来探索在jsp中怎么样来具体实现。

另一篇转自:http://www.javalaw.cn/forum/read.php?tid=333&page=e的文章:servlet的两种重定向方法的区别及应用。

这篇文章还对HTTP的各种状态代码作了简要介绍。转帖正文:

×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××××

servlet的两种重定向方法的区别及应用

一 问题:在servlet/JSP编程学习中,发现有两种方法可以实现服务端输出重定向,一种是通过forward方法(例如JSP中的<jsp: forward page=”OtherPage.jsp”/>),另一种则是通过运用 javax.servlet.http.HttpServletResponse接口的sendRedirect方法(例如 response.sendRedirect(“OtherPage.jsp”);这两种方法有什么区别和联系呢?让我们看下面的分析。

  二 分析:(1)<JSP:FORWORD>

  该方法是利用服务器端先将数据输出到缓冲区的机制,在把缓冲区(buffer)的内容发送到客户端之前,原来的不发送,改为发送该页面的内容,如果在 <JSP:FORWORD>之前有很多输出,前面的输出已使缓冲区满,将自动输出到客户端,那么该语句将不起作用,这一点应该特别注意.

  补充知识:输出缓冲区

  缺省情况下:服务端要输出到客户端的内容,不直接写到客户端,而是先写到一个输出缓冲区中.只有在下面三中情况下,才会把该缓冲区的内容输出到客户端上:

  1该JSP网页已完成信息的输出

  2输出缓冲区已满

  3JSP中调用了out.flush()或response.flushbuffer()

  输出缓冲区的大小可以用:<%@page buffer="none"|"nkb"%>或response.setBufferSize()设置,如下:

  1设置输出缓冲区的大小为1KB。<%@page buffer="1kb"%>或response.setBufferSize(1);

  2设置输出缓冲区的大小为0,即不缓冲。<%@page buffer="none" %>或response.setBufferSize(0);

  用response.getBufferSize()或out.getBufferSize()可取的输出缓冲区的大小,单位为字节.

  用response.isCommitted()可检查看服务端是否已将数据输出到客户端. 如果返回值是TRUE则已将数据输出到客户端,是FALSE则还没有。

  (2)response.sendRedirect(“OtherPage.jsp”)

  该方法通过修改HTTP协议的HEADER部分,对浏览器下达重定向指令的,让浏览器对在location中指定的URL提出请求,使浏览器显示重定向网页的内容。该方法可以接受绝对的或相对的URLs。如果传递到该方法的参数是一个相对的URL,那么Web container在将它发送到客户端前会把它转换成一个绝对的URL。如果地址是相对的,没有一个’/’,那么Web container就认为它是相对于当前的请求URI的。

  因为这个方法是通过修改HTTP协议的HEADER实现的重定义功能,而下面的方法也能改变HTTP HEADER属性,他们的原理是一样的.

  <%

  response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);

  String newLocn="/index.html";

  response.setHeader("Location",newLocn);

  %>

  补充知识:HTTP应答头

   Web服务器响应浏览器或其他客户程序的请求时,其应答一般由以下几个部分组成:一个状态行,几个应答头,一个空行,内容文档。下面是一个最简单的应答:

  HTTP/1.1 200 OK

  Content-Type: text/plain

  Hello World

  1设置状态信息

  状态行包含HTTP版本、状态代码、与状态代码对应的简短说明信息。在大多数情况下,除了Content-Type之外的所有应答头都是可选的。 Servlet可以利用状态代码来实现许多功能。例如,可以把用户重定向到另一个网站,就像我们上边所看到的那个例子。下面我们就通过这个机会具体讨论各种状态代码的含义以及利用这些代码可以做些什么。

  如前所述,HTTP应答状态行包含HTTP版本、状态代码和对应的状态信息。由于状态信息直接和状态代码相关,而HTTP版本又由服务器确定,因此需要Servlet设置的只有一个状态代码。

  先给出常见的HTTP 1.1状态代码以及它们对应的状态信息和含义,具体的使用方法我们接下来再做详细的介绍。

  100 Continue 初始的请求已经接受,客户应当继续发送请求的其余部分。(HTTP 1.1新)

  101 Switching Protocols 服务器将遵从客户的请求转换到另外一种协议(HTTP 1.1新)

  200 OK 一切正常,对GET和POST请求的应答文档跟在后面。如果不用setStatus设置状态代码,Servlet默认使用202状态代码。

  201 Created 服务器已经创建了文档,Location头给出了它的URL。

  202 Accepted 已经接受请求,但处理尚未完成。

  203 Non-Authoritative Information 文档已经正常地返回,但一些应答头可能不正确,因为使用的是文档的拷贝(HTTP 1.1新)。

  204 No Content 没有新文档,浏览器应该继续显示原来的文档。如果用户定期地刷新页面,而Servlet可以确定用户文档足够新,这个状态代码是很有用的。

  205 Reset Content 没有新的内容,但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容(HTTP 1.1新)。

  206 Partial Content 客户发送了一个带有Range头的GET请求,服务器完成了它(HTTP 1.1新)。

  300 Multiple Choices 客户请求的文档可以在多个位置找到,这些位置已经在返回的文档内列出。如果服务器要提出优先选择,则应该在Location应答头指明。

  301 Moved Permanently 客户请求的文档在其他地方,新的URL在Location头中给出,浏览器应该自动地访问新的URL。

  302 Found 类似于301,但新的URL应该被视为临时性的替代,而不是永久性的。注意,在HTTP1.0中对应的状态信息是“Moved Temporatily”,而HttpServletResponse中相应的常量是SC_MOVED_TEMPORARILY,而不是SC_FOUND。

  出现该状态代码时,浏览器能够自动访问新的URL,因此它是一个很有用的状态代码。为此,Servlet提供了一个专用的方法,即 sendRedirect。使用response.sendRedirect(url)比使用response.setStatus (response.SC_MOVED_TEMPORARILY)和response.setHeader("Location",url)更好。这是因为:

  首先,代码更加简洁。

  第二,使用sendRedirect,Servlet会自动构造一个包含新链接的页面(用于那些不能自动重定向的老式浏览器)。

  最后,sendRedirect能够处理相对URL,自动把它们转换成绝对URL。

  注意这个状态代码有时候可以和301替换使用。例如,如果浏览器错误地请求http://host/~user(缺少了后面的斜杠),有的服务器返回301,有的则返回302。

  严格地说,我们只能假定只有当原来的请求是GET时浏览器才会自动重定向。请参见307。

  303 See Other 类似于301/302,不同之处在于,如果原来的请求是POST,Location头指定的重定向目标文档应该通过GET提取(HTTP 1.1新)。

  304 Not Modified 客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。

  305 Use Proxy 客户请求的文档应该通过Location头所指明的代理服务器提取(HTTP 1.1新)。

  307 Temporary Redirect 和302(Found)相同。许多浏览器会错误地响应302应答进行重定向,即使原来的请求是POST,即使它实际上只能在POST请求的应答是303时才能重定向。由于这个原因,HTTP 1.1新增了307,以便更加清除地区分几个状态代码:当出现303应答时,浏览器可以跟随重定向的GET和POST请求;如果是307应答,则浏览器只能跟随对GET请求的重定向。

  注意,HttpServletResponse中没有为该状态代码提供相应的常量。(HTTP 1.1新)

  400 Bad Request 请求出现语法错误。

  401 Unauthorized 客户试图未经授权访问受密码保护的页面。应答中会包含一个WWW-Authenticate头,浏览器据此显示用户名字/密码对话框,然后在填写合适的Authorization头后再次发出请求。

  403 Forbidden 资源不可用。服务器理解客户的请求,但拒绝处理它。通常由于服务器上文件或目录的权限设置导致。

  404 Not Found 无法找到指定位置的资源。这也是一个常用的应答,HttpServletResponse专门提供了相应的方法:sendError(message)。

  405 Method Not Allowed 请求方法(GET、POST、HEAD、DELETE、PUT、TRACE等)对指定的资源不适用。(HTTP 1.1新)

  406 Not Acceptable 指定的资源已经找到,但它的MIME类型和客户在Accpet头中所指定的不兼容(HTTP 1.1新)。

  407 Proxy Authentication Required 类似于401,表示客户必须先经过代理服务器的授权。(HTTP 1.1新)

  408 Request Timeout 在服务器许可的等待时间内,客户一直没有发出任何请求。客户可以在以后重复同一请求。(HTTP 1.1新)

  409 Conflict 通常和PUT请求有关。由于请求和资源的当前状态相冲突,因此请求不能成功。(HTTP 1.1新)

  410 Gone 所请求的文档已经不再可用,而且服务器不知道应该重定向到哪一个地址。它和404的不同在于,返回407表示文档永久地离开了指定的位置,而404表示由于未知的原因文档不可用。(HTTP 1.1新)

  411 Length Required 服务器不能处理请求,除非客户发送一个Content-Length头。(HTTP 1.1新)

  412 Precondition Failed 请求头中指定的一些前提条件失败(HTTP 1.1新)。

  413 Request Entity Too Large 目标文档的大小超过服务器当前愿意处理的大小。如果服务器认为自己能够稍后再处理该请求,则应该提供一个Retry-After头(HTTP 1.1新)。

  414 Request URI Too Long URI太长(HTTP 1.1新)。

  416 Requested Range Not Satisfiable 服务器不能满足客户在请求中指定的Range头。(HTTP 1.1新)

  500 Internal Server Error 服务器遇到了意料不到的情况,不能完成客户的请求。

  501 Not Implemented 服务器不支持实现请求所需要的功能。例如,客户发出了一个服务器不支持的PUT请求。

  502 Bad Gateway 服务器作为网关或者代理时,为了完成请求访问下一个服务器,但该服务器返回了非法的应答。

  503 Service Unavailable 服务器由于维护或者负载过重未能应答。例如,Servlet可能在数据库连接池已满的情况下返回503。服务器返回503时可以提供一个Retry-After头。

  504 Gateway Timeout 由作为代理或网关的服务器使用,表示不能及时地从远程服务器获得应答。(HTTP 1.1新)

  505 HTTP Version Not Supported 服务器不支持请求中所指明的HTTP版本。(HTTP 1.1新)

  如前所述,HTTP应答状态行包含HTTP版本、状态代码和对应的状态信息。由于状态信息直接和状态代码相关,而HTTP版本又由服务器确定,因此需要 Servlet设置的只有一个状态代码。拿我们上边的例子来看。其中有一句就是设置HTTP应答头的状态代码的,是:

  response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);

  Servlet设置状态代码一般使用HttpServletResponse的setStatus方法。setStatus方法的参数是一个整数(即状态代码),不过为了使得代码具有更好的可读性,可以用HttpServletResponse中定义的常量来避免直接使用整数。这些常量根据HTTP 1.1中的标准状态信息命名,所有的名字都加上了SC前缀(Status Code的缩写)并大写,同时把空格转换成了下划线。也就是说,与状态代码301对应的状态信息是“Moved Permanently”,则HttpServletResponse中的对应常量名字为SC_MOVED_PERMANENTLY。但有两个例外:和状态代码302对应的常量根据HTTP 1.0命名为SC_MOVED_TEMPORARILY,而不是SC_FOUND,而307没有对应的常量。

  虽然设置状态代码一般使用的是response.setStauts(int)方法,但为了简单起见,HttpServletResponse为两种常见的情形提供了专用方法:sendError方法生成一个404应答,同时生成一个简短的HTML错误信息文档;sendRedirect方法生成一个 302应答,同时在Location头中指示新文档的URL。这种方法就是我们前边提到的response.sendRedirect (“OtherPage.jsp”)实现重定向的方法的原理。它与301应答,既response.setStatus (HttpServletResponse.SC_MOVED_PERMANENTLY)的区别请看上边状态列表的红色标记部分,这里不再重复。

  2设置HTTP应答头

  设置HTTP应答头往往和设置状态行中的状态代码结合起来。例如,有好几个表示“文档位置已经改变”的状态代码都伴随着一个Location头,而 401(Unauthorized)状态代码则必须伴随一个WWW-Authenticate头。这些都在相应的状态代码的状态信息说明中都提到了。

  然而,即使在没有设置特殊含义的状态代码时,指定应答头也是很有用的。应答头可以用来完成:设置Cookie,指定修改日期,指示浏览器按照指定的间隔刷新页面,声明文档的长度以便利用持久HTTP连接,……等等许多其他任务。还拿我们上边的例子来看,其中有一句就是设置HTTP应答头的,是:

   response.setHeader("Location",newLocn);

  设置应答头最常用的方法是HttpServletResponse的setHeader,该方法有两个参数,分别表示应答头的名字和值。和设置状态代码相似,设置应答头应该在发送任何文档内容之前进行。

  应答头 说明

  Allow 服务器支持哪些请求方法(如GET、POST等)。

  Content-Encoding 文档的编码(Encode)方法。只有在解码之后才可以得到Content-Type头指定的内容类型。利用gzip压缩文档能够显著地减少HTML文档的下载时间。Java的GZIPOutputStream可以很方便地进行gzip压缩,但只有Unix上的Netscape和Windows上的IE 4、IE 5才支持它。因此,Servlet应该通过查看Accept-Encoding头(即request.getHeader("Accept- Encoding"))检查浏览器是否支持gzip,为支持gzip的浏览器返回经gzip压缩的HTML页面,为其他浏览器返回普通页面。

  Content-Length 表示内容长度。只有当浏览器使用持久HTTP连接时才需要这个数据。如果你想要利用持久连接的优势,可以把输出文档写入 ByteArrayOutputStram,完成后查看其大小,然后把该值放入Content-Length头,最后通过 byteArrayStream.writeTo(response.getOutputStream()发送内容。

  Content- Type 表示后面的文档属于什么MIME类型。Servlet默认为text/plain,但通常需要显式地指定为text/html。由于经常要设置 Content-Type,因此HttpServletResponse提供了一个专用的方法setContentTyep。

  Date 当前的GMT时间。你可以用setDateHeader来设置这个头以避免转换时间格式的麻烦。

  Expires 应该在什么时候认为文档已经过期,从而不再缓存它?

  Last-Modified 文档的最后改动时间。客户可以通过If-Modified-Since请求头提供一个日期,该请求将被视为一个条件GET,只有改动时间迟于指定时间的文档才会返回,否则返回一个304(Not Modified)状态。Last-Modified也可用setDateHeader方法来设置。

  Location 表示客户应当到哪里去提取文档。Location通常不是直接设置的,而是通过HttpServletResponse的sendRedirect方法,该方法同时设置状态代码为302。

  Refresh 表示浏览器应该在多少时间之后刷新文档,以秒计。除了刷新当前文档之外,你还可以通过setHeader("Refresh", "5; URL=http://host/path")让浏览器读取指定的页面。

  注意这种功能通常是通过设置HTML页面HEAD区的<META HTTP-EQUIV="Refresh" CONTENT="5;URL=http://host/path">实现,这是因为,自动刷新或重定向对于那些不能使用CGI或Servlet的 HTML编写者十分重要。但是,对于Servlet来说,直接设置Refresh头更加方便。

  注意Refresh的意义是“N秒之后刷新本页面或访问指定页面”,而不是“每隔N秒刷新本页面或访问指定页面”。因此,连续刷新要求每次都发送一个Refresh头,而发送204状态代码则可以阻止浏览器继续刷新,不管是使用Refresh头还是<META HTTP-EQUIV="Refresh" ...>。

  注意Refresh头不属于HTTP 1.1正式规范的一部分,而是一个扩展,但Netscape和IE都支持它。

  Server 服务器名字。Servlet一般不设置这个值,而是由Web服务器自己设置。

  Set-Cookie 设置和页面关联的Cookie。Servlet不应使用response.setHeader("Set-Cookie", ...),而是应使用HttpServletResponse提供的专用方法addCookie。参见下文有关Cookie设置的讨论。

   WWW-Authenticate 客户应该在Authorization头中提供什么类型的授权信息?在包含401(Unauthorized)状态行的应答中这个头是必需的。例如, response.setHeader("WWW-Authenticate", "BASIC realm=\"executives\"")。

  注意Servlet一般不进行这方面的处理,而是让Web服务器的专门机制来控制受密码保护页面的访问(例如.htaccess)。

   到此我们应该可以理解用下面代码实现重定向的原理:修改HTTP协议的HEADER部分,对浏览器下达重定向指令的,让浏览器对在location中指定的URL提出请求,使浏览器显示重定向网页的内容。

  <%

  response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); 设置HTTP应答头状态代码

  String newLocn="/index.html";

  response.setHeader("Location",newLocn); 设置HTTP应答头

  %>

  三 总结:

  通过上边的分析,我们清楚了这两种服务器端重定向方法的原理,<jsp:forward>是利用服务器端先将数据输出到缓冲区的机制,在把缓冲区的内容发送到客户端之前,通过停止调用页的处理,将执行转向响应的页面,从而实现的重定位功能,原调用页的输出缓冲区中任何尚未在浏览器中显示(刷新)的内容将被清楚,不再显示。

   而response.sendRedirect(“OtherPage.jsp”)是通过修改HTTP协议的HEADER部分,对浏览器下达重定向指令的,让浏览器对在location中指定的URL提出请求,使浏览器显示重定向网页的内容的。

  四 对比:

  1 forward方法是在Web container内部工作的。SendRedirect方法需要到客户端的一个往返。所以forward方法要比sendreRirect要快

  2 forward方法只能重定向到同一个Web应用程序中的一个资源。而sendRedirect方法可以重定向到任何URL

  3 forward方法还将原始的HTTP请求对象(request)转到目录页。而sendRedirect方法的功能是触发浏览器转向指定的URL,并不会将原始请求对象转发。

  在例:C:\Tomcat 5.0\webapps\test\regiester10中我们可以看到在WELCOME。JSP中有:

  <tr><td>姓名:<%=request.getParameter("userName")%></td></tr>

  当在LOGIN.JSP中用response.sendRedirect("welcome.jsp");重定向到WELCOME.JSP时,输出为:

   姓名:null

  当在LOGIN.JSP中用<jsp:forward page = "welcome.jsp"/>重定向到WELCOME.JSP时,输出为:

   姓名:wangjian

  4.sendRedirect能够处理相对URL,自动把它们转换成绝对URL。

                                               --转帖完毕

OS中复制文件后eclipse3.*不能识别包下面的合法类文件!

在eclipse3.1和3.2中写学习日记的程序。我发现,如果把一个已有的工程文件在操作系统中复制成另一个工程(把.project文件中的文件名改了)。再编译复制的工程文件时会出现。报告一个明明正确引用的类文件不存在,全是小红叉!我初步试了一下,可以把工程顶层目录中的那些前面有.的隐藏删除,然后重启eclipse好像就可以了(还没完全证实)。

另外,如果要把一个工程的文件复制到另一个工程去。我发现只要在eclipse系统中进行就没事。

我的操作系统用的是Redhat linux9.0,不知道在windows下eclipse3.*有没有这个问题?没试过。