serialVersionUID 用来表明类的不同版本间的兼容性.如果你修改了此类, 要修改此值. 否则以前用老版本的类序列化的类恢复时会出错.
可以利用JDK的bin目录下的serialver.exe工具产生这个serialVersionUID
对于Test.class,执行命令: serialver Test
为了在反序列化时,确保类版本的兼容性,最好在每个要序列化的类中加入private static final long serialVersionUID这个属性,具体数值自己定义。这样,即使某个类在与之对应的对象已经序列化出去后做了修改,该对象依然可以被正确反序列化。否则,如果不显示定义该属性,这个属性值将由JVM根据类的相关信息计算,而修改后的类的计算结果与修改前的类的计算结果往往不同,从而造成对象的反序列化因为类版本不兼容而失败。
不显示定义这个属性值的另一个坏处是,不利于程序在不同的JVM之间的移植。因为不同的编译器实现的该属性值的计算策略可能不同,从而造成虽然类没有改变,但是因为JVM不同,依然会有因类版本不兼容而无法正确反序列化的现象出现。
因为我做的系统不太会经常需要序列化类,所以为了去掉这些警告,做如下设置:
Window-Preferences-Java,如图所示,将serializable class without serialVersionUID的设置由warning改为Ignore。然后Eclipse会重新编译程序,那些警告信息也就会消失了。
小结:如果我们开发大量需要序列化的类的时候,我们最好还是还原为原来的设置。这样可以保证系统的性能和健壮。
-------------------------------------------------------------------
java对象序列化问题研究(转自
http://www.54bk.com/user1/2064/archives/2005/1864.html)
序列化的过程就是对象写入字节流和从字节流中读取对象。将对象状态转换成字节流之后,可以用java.io包中的各种字节流类将其保存到文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机。对象序列化功能非常简单、强大,在RMI、Socket、JMS、EJB都有应用。对象序列化问题在网络编程中并不是最激动人心的课题,但却相当重要,具有许多实用意义。
一:对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。
二:java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。
从上面的叙述中,我们知道了对象序列化是java编程中的必备武器,那么让我们从基础开始,好好学习一下它的机制和用法。
java序列化比较简单,通常不需要编写保存和恢复对象状态的定制代码。实现java.io.Serializable接口的类对象可以转换成字节流或从字节流恢复,不需要在类中增加任何代码。只有极少数情况下才需要定制代码保存或恢复对象状态。这里要注意:不是每个类都可序列化,有些类是不能序列化的,例如涉及线程的类与特定JVM有非常复杂的关系。
序列化机制:
序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。ObjectOutputStream中的序列化过程与字节流连接,包括对象类型和版本信息。反序列化时,JVM用头信息生成对象实例,然后将对象字节流中的数据复制到对象数据成员中。下面我们分两大部分来阐述:
处理对象流:
(序列化过程和反序列化过程)
java.io包有两个序列化对象的类。ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象。
我们先了解ObjectOutputStream类吧。ObjectOutputStream类扩展DataOutput接口。
writeObject()方法是最重要的方法,用于对象序列化。如果对象包含其他对象的引用,则writeObject()方法递归序列化这些对象。每个ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。(这点很重要)由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。
下面,让我们从例子中来了解ObjectOutputStream这个类吧。
// 序列化 today's date 到一个文件中.
FileOutputStream f = new FileOutputStream("tmp");
ObjectOutputStream s = new ObjectOutputStream(f);
s.writeObject("Today");
s.writeObject(new Date());
s.flush();
现在,让我们来了解ObjectInputStream这个类。它与ObjectOutputStream相似。它扩展DataInput接口。ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。
例子如下:
//从文件中反序列化 string 对象和 date 对象
FileInputStream in = new FileInputStream("tmp");
ObjectInputStream s = new ObjectInputStream(in);
String today = (String)s.readObject();
Date date = (Date)s.readObject();
定制序列化过程:
序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为serializable,但仍可手工控制声明为static或transient的数据成员。
例子:一个非常简单的序列化类。
public class simpleSerializableClass implements Serializable{
String sToday="Today:";
transient Date dtToday=new Date();
}
序列化时,类的所有数据成员应可序列化除了声明为transient或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员。
关于如何使用定制序列化的部分代码如下:
//重写writeObject()方法以便处理transient的成员。
public void writeObject(ObjectOutputStream outputStream) throws IOException{
outputStream.defaultWriteObject();//使定制的writeObject()方法可以
利用自动序列化中内置的逻辑。
outputStream.writeObject(oSocket.getInetAddress());
outputStream.writeInt(oSocket.getPort());
}
//重写readObject()方法以便接收transient的成员。
private void readObject(ObjectInputStream inputStream) throws IOException,ClassNotFoundException{
inputStream.defaultReadObject();//defaultReadObject()补充自动序列化
InetAddress oAddress=(InetAddress)inputStream.readObject();
int iPort =inputStream.readInt();
oSocket = new Socket(oAddress,iPort);
iID=getID();
dtToday =new Date();
}
完全定制序列化过程:
如果一个类要完全负责自己的序列化,则实现Externalizable接口而不是Serializable接口。Externalizable接口定义包括两个方法writeExternal()与readExternal()。利用这些方法可以控制对象数据成员如何写入字节流.类实现Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。到此为至,我们学习了序列化的基础部分知识。关于序
列化的高级教程,以后再述。
posted @
2006-12-12 11:12 保尔任 阅读(352) |
评论 (0) |
编辑 收藏
Java FAQ
目录:
Q1.1 什么是Java、Java2、JDK?JDK后面的1.3、1.4版本号又是怎么回事?
Q1.2 什么是JRE/J2RE?
Q1.3 学习Java用什么工具比较好?
Q1.4 学习Java有哪些好的参考书?
Q1.5 Java和C++哪个更好?
Q1.6 什么是J2SE/J2EE/J2ME?
Q2.1 我写了第一个Java程序,应该如何编译/运行?
Q2.2 我照你说的做了,但是出现什么“'javac' 不是内部或外部命令,也不是可运行的
程序或批处理文件。”。
Q2.3 环境变量怎么设置?
Q2.4 我在javac xxx.java的时候显示什么“unreported exception java.io.IOExcepti
on;”。
Q2.5 javac xxx.java顺利通过了,但是java xxx的时候显示什么“NoClassDefFoundErr
or”。
Q2.6 我在java xxx的时候显示“Exception in thread "main" java.lang.NoSuchMetho
dError: main”。
Q2.7 在java xxx的时候显示“Exception in thread "main" java.lang.NullPointerEx
ception”。
Q2.8 package是什么意思?怎么用?
Q2.9 我没有声明任何package会怎么样?
Q2.10 在一个类中怎么使用其他类?
Q2.11 我用了package的时候显示"NoClassDefFoundError",但是我把所有package去掉的
时候能正常运行。
Q2.12 我想把java编译成exe文件,该怎么做?
Q2.13 我在编译的时候遇到什么"deprecated API",是什么意思?
Q3.1 我怎么给java程序加启动参数,就像dir /p/w那样?
Q3.2 我怎么从键盘输入一个int/double/字符串?
Q3.3 我怎么输出一个int/double/字符串?
Q3.4 我发现有些书上直接用System.in输入,比你要简单得多。
Q3.5 我怎么从文件输入一个int/double/字符串?
Q3.6 我想读写文件的指定位置,该怎么办?
Q3.7 怎么判断要读的文件已经到了尽头?
Q4.1 java里面怎么定义宏?
Q4.2 java里面没法用const。
Q4.3 java里面也不能用goto。
Q4.4 java里面能不能重载操作符?
Q4.5 我new了一个对象,但是没法delete掉它。
Q4.6 我想知道为什么main方法必须被声明为public static?为什么在main方法中不能调
用非static成员?
Q4.7 throw和throws有什么不同?
Q4.8 什么是异常?
Q4.9 final和finally有什么不同?
Q5.1 extends和implements有什么不同?
Q5.2 java怎么实现多继承?
Q5.3 abstract是什么?
Q5.4 public,protected,private有什么不同?
Q5.5 Override和Overload有什么不同?
Q5.6 我继承了一个方法,但现在我想调用在父类中定义的方法。
Q5.7 我想在子类的构造方法中调用父类的构造方法,该怎么办?
Q5.8 我在同一个类中定义了好几个构造方法并且想在一个构造方法中调用另一个。
Q5.9 我没有定义构造方法会怎么样?
Q5.10 我调用无参数的构造方法失败了。
Q5.11 我该怎么定义类似于C++中的析构方法(destructor)?
Q5.12 我想将一个父类对象转换成一个子类对象该怎么做?
Q5.13 其实我不确定a是不是B的实例,能不能分情况处理?
Q5.14 我在方法里修改了一个对象的值,但是退出方法后我发现这个对象的值没变!
Q6.1 java能动态分配数组吗?
Q6.2 我怎么知道数组的长度?
Q6.3 我还想让数组的长度能自动改变,能够增加/删除元素。
Q 什么是链表?为什么要有ArrayList和LinkedList两种List?
Q6.5 我想用队列/栈。
Q6.6 我希望不要有重复的元素。
Q6.7 我想遍历集合/Map。
Q6.8 我还要能够排序。
Q6.9 但是我想给数组排序。
Q6.10 我想按不同方式排序。
Q6.11 Map有什么用?
Q6.12 set方法没问题,但是get方法返回的是Object。
Q6.13 ArrayList和Vector有什么不同?HashMap和Hashtable有什么不同?
Q6.14 我要获得一个随机数。
Q6.15 我比较两个String总是false,但是它们明明都是"abc" !
Q6.16 我想修改一个String但是在String类中没找到编辑方法。
Q6.17 我想处理日期/时间。
一、准备篇
Q1.1 什么是Java、Java2、JDK?JDK后面的1.3、1.4版本号又是怎么回事?
答:Java是一种通用的,并发的,强类型的,面向对象的编程语言(摘自Java规范第二版
)。
JDK是Sun公司分发的免费Java开发工具包,正式名称为J2SDK(Java2 Software Develop K
it)。
包括基本的java工具包和标准类库。
到目前(2003年7月)为止,Java有3个主要版本,即1.0,1.1,2.0;
JDK有1.0,1.1,1.2,1.3,1.4五个版本。
从JDK1.2起,Sun公司觉得Java改变足够大而将java语言版本号提升为2.0。
不同的JDK主要在于提供的类库不同。作为学习你可以下载最新的JDK1.4.2。
真正开发时则应考虑向前兼容,比如1.3。下载请去http://java.sun.com。
JDK1.5预计将在2004年推出,届时其中将包含若干崭新的特性。
Q1.2 什么是JRE/J2RE?
答:J2RE是Java2 Runtime Environment,即Java运行环境,有时简称JRE。
如果你只需要运行Java程序或Applet,下载并安装它即可。
如果你要自行开发Java软件,请下载JDK。在JDK中附带有JRE。
注意由于Microsoft对Java的支持不完全,请不要使用IE自带的虚拟机来运行Applet,务必
安装一个JRE或JDK。
Q1.3 学习Java用什么工具比较好?
答:作者建议首先使用JDK+文本编辑器,这有助你理解下列几个基础概念:path,classp
ath,package
并熟悉基本命令:javac和java。并且下载和你的JDK版本一致的API帮助。
如果你不确定类或函数的用法,请先查阅API而不是发贴求助。
当你熟悉Java之后,你可以考虑开始使用一个IDE。
作者推荐eclipse,下载网址http://www.eclipse.org。因为eclispe是免费的,插件化的
。
eclispe的主要缺点是缺乏一个可视化的桌面程序开发工具,
幸运的是IBM在2003年11月已经将部分代码捐给eclipse组织,可以预计这个缺点很快就会
得到弥补。
无论如何,请不要使用Microsoft的VJ++!众所周知Microsoft从来就没有认真支持过Java
。
最后但并非最不重要,要有一本好的参考书,并且英文要过关。
Q1.4 学习Java有哪些好的参考书?
答:作者首先推荐Thinking in Java,中文名《Java编程思想》,有中文版。
目前的最新版本是第三版。
在http://64.78.49.204可以免费下载英文版。
该书第一章介绍了很多面向对象的编程思想,作为新手应当认真阅读。
除此以外,O'relly出版社和Wrox出版社的书也不错。作者本人不喜欢大陆作者的书。
也许你觉得英文太难,但是网上大多数资料都是英文的。另外,你需要经常查阅API,而那
也是英文的。
Q1.5 Java和C++哪个更好?
答:这个问题是一个很不恰当的问题。你应该问:Java和C++哪个更适用于我的项目?
Java的优点和缺点一样明显。
跨平台是Java的主要优点,但代价是运行速度的下降。
VC和Windows平台有良好的集成和足够快的速度,但是也只能局限在Windows平台上。
和C++相比,Java学起来更快,开发人员不会碰到很多容易出错的特性。
但是VB程序员甚至只需要拼装模块就可以了。
Q1.6 什么是J2SE/J2EE/J2ME?
答:J2SE就是一般的Java。
J2ME是针对嵌入式设备的,比如支持Java的手机,它有自己的JRE和SDK。
J2EE是一组用于企业级程序开发的规范和类库,它使用J2SE的JRE。
二、命令篇
Q2.1 我写了第一个Java程序,应该如何编译/运行?
答:首先请将程序保存为xxx.java文件,注意你可能需要修改文件后缀名。
然后在dos窗口下使用javac xxx.java命令,你会发现该目录下多了一个xxx.class文件,
再使用java xxx命令,你的java程序就开始运行了。
Q2.2 我照你说的做了,但是出现什么“'javac' 不是内部或外部命令,也不是可运行的
程序或批处理文件。”。
答:你遇到了path问题。操作系统在一定的范围(path)内搜索javac.exe,但是没能找到。
请编辑你的操作系统环境变量,新增一个JAVA_HOME变量,设为你JDK的安装目录,
再编辑Path变量,加上一项 %JAVA_HOME%\bin。
然后保存并新开一个dos窗口,你就可以使用javac和java命令了。
Q2.3 环境变量怎么设置?
答:请向身边会设的人咨询。
Q2.4 我在javac xxx.java的时候显示什么“unreported exception java.io.IOExcepti
on;”。
答:参见Q4.8以了解java中的异常机制。
Q2.5 javac xxx.java顺利通过了,但是java xxx的时候显示什么“NoClassDefFoundErr
or”。
答:1. 你遇到了classpath问题。java命令在一定的范围(classpath)内搜索你直接或间接
使用的class文件,但是未能找到。
首先请确认你没有错敲成java xxx.class,
其次,检查你的CLASSPATH环境变量,其实你并不需要设置该变量,
但如果你设置了该变量又没有包含.(代表当前目录)的项,
你就会遇到这个问题。请在你的CLASSPATH环境变量中加入一项. 或干脆删掉这个变量。
2. 如果你使用了并非JDK自带的标准包,比如javax.servlet.*包,也会遇到这个问题,请
将相应的jar文件加入classpath。
3. 如果你在java源文件中定义了package,请参见Q2.11。
Q2.6 我在java xxx的时候显示“Exception in thread "main" java.lang.NoSuchMetho
dError: main”。
答:首先,在你的程序中每个java文件有且只能有一个public类,
这个类的类名必须和文件名的大小写完全一样。
其次,在你要运行的类中有且只能有一个public static void main(String[] args)方法
,
这个方法就是你的主程序。
Q2.7 在java xxx的时候显示“Exception in thread "main" java.lang.NullPointerEx
ception”。
答:在程序中你试图在值为null的对象变量上调用方法,请检查你的程序确保你的对象被恰当的初始化。
参见Q4.8以了解java中的异常机制。
Q2.8 package是什么意思?怎么用?
答:为了唯一标识每个类并分组,java使用了package的概念。
每个类都有一个全名,例如String的全名是java.lang.String,其中java.lang是包名,S
tring是短名。按照java命名惯例,包名是全部小写的,而类名的第一个字母是大写的。
这样,如果你自行定义了同样名字的类String,你可以把它放在mypackage中,
通过使用全名mypackage.String和java.lang.String来区分这两个类。
同时,将逻辑上相关的类放在同一个包中,可以使程序结构更为清楚。
为了定义包,你要做的就是在java文件开头加一行“package mypackage;”。
注意包没有嵌套或包含关系,mypackage包和mypackage.mysubpackage包对JRE来说是并列的两个包(虽然开发者可
能暗示包含关系)。
Q2.9 我没有声明任何package会怎么样?
答:你的类被认为放在默认包中。这时全名和短名是一致的。
Q2.10 在一个类中怎么使用其他类?
答:如果你使用java.lang包或者默认包中的类,不用做任何事。
如果你的类位于mypackage包中,并且要调用同一包中的其他类,也不用做任何事。
如果你使用其他包中的类,在package声明之后,类声明之前使用import otherpackage1.Class
1; 或 import otherpackage2.*;
这里.*表示引入这个包中的所有类。然后在程序中你可以使用其他类的短名。
如果短名间有重名冲突,必须使用全名来区分。
注意在使用其他包中的类时,你只能使用public的类和接口,参见Q5.4。
Q2.11 我用了package的时候显示"NoClassDefFoundError",但是我把所有package去掉的
时候能正常运行。
答:将你的java文件按包名组织存放。
比如你的工作目录是/work,你的类是package1.Class1,那么将它存放为/work/package1
/Class1.java。
如果没有声明包,那么直接放在/work下。
在/work下执行javac package1/class1.java,再执行java package1.class1,你会发现一
切正常。
另外,如果你的类的个数已经多到了你需要使用包来组织的话,你可以考虑开始使用IDE。
Q2.12 我想把java编译成exe文件,该怎么做?
答:JDK只能将java源文件编译为class文件。
class文件是一种跨平台的字节码,必须依赖平台相关的JRE来运行。Java以此来实现跨平
台性。
有些开发工具可以将java文件编译为exe文件。作者反对这种做法,因为这样就取消了跨平
台性。
如果你确信你的软件只在Windows平台上运行,你可以考虑使用C++/C#来编程。
Q2.13 我在编译的时候遇到什么"deprecated API",是什么意思?
答:所谓deprecated是指已经过时,但是为了向前兼容起见仍然保留的方法。
这些方法可能会在以后取消支持。你应当改用较新的方法。
在API里面会说明你应当用什么方法来代替之。
三、I/O篇
Q3.1 我怎么给java程序加启动参数,就像dir /p/w那样?
答:还记得public static void main(String[] args)吗?这里的args就是你的启动参数
。
在运行时你输入java package1.class1 arg1 arg2,args中就会有两个String,第一个是
arg1,第二个是arg2。
Q3.2 我怎么从键盘输入一个int/double/字符串?
答:java的I/O操作比C++要复杂一点。如果要从键盘输入,样例代码如下:
BufferedReader cin = new BufferedReader( new InputStreamReader( System.in ) );
String s = cin.readLine();
这样你就获得了一个字符串,如果你需要数字的话再使用:
int n = Integer.parseInt( s ); 或者 double d = Double.parseDouble( s );
来将字符串"534"转换成int或double。
Q3.3 我怎么输出一个int/double/字符串?
答:使用System.out.println(n)或者System.out.println("Hello")等等。
Q3.4 我发现有些书上直接用System.in输入,比你要简单得多。
答:java使用unicode,是双字节。而System.in是单字节的stream。
如果你要输入双字节文字比如中文,请使用作者的做法。
Q3.5 我怎么从文件输入/输出一个int/double/字符串?
答:类似于从键盘输入,只不过换成
BufferedReader fin = new BufferedReader( new FileReader(" myFileName " ) );
PrintWriter fout = new PrintWriter( new FileWriter(" myFileName " ) );
另外如果你还没下载API,请开始下载并阅读java.io包中的内容。
Q3.6 我想读写文件的指定位置,该怎么办?
答:java.io.RandomAccessFile可以满足你的需要。
Q3.7 怎么判断要读的文件已经到了尽头?
答:在Reader的read方法中明确说明返回-1表示流的结尾。
四、 关键字篇
Q4.1 java里面怎么定义宏?
答:java不支持宏,因为宏代换不能保证类型安全。
如果你需要定义常量,可以将它定义为某个类的static final成员。参见Q4.2和Q4.6。
Q4.2 java里面没法用const。
答:你可以用final关键字。例如 final int m = 9。被声明为final的变量不能被再次赋
值。唯一的例外是所谓blank final,如下例所示:
public class MyClass1 {
private final int a = 3;
private final int b; // blank final
public MyClass1() {
a = 5; // 不合法,final变量不能被再次赋值。
b = 4; // 合法,这是b第一次被赋值。
b = 6; // 不合法,b不能被再次赋值。
}
}
final也可以用于声明方法或类,被声明为final的方法或类不能被继承。
注意const是java的保留字以备扩充。
Q4.3 java里面也不能用goto。
答:甚至在面向过程的语言中你也可以完全不用goto。请检查你的程序流程是否合理。
如果你需要从多层循环中迅速跳出,java增强了(和C++相比)break和continue的功能,
支持label。
例如:
outer :
while( ... )
{
inner :
for( ... )
{
... break inner; ...
... continue outer; ...
}
}
和const一样,goto也是java的保留字以备扩充。
Q4.4 java里面能不能重载操作符?
答:不能。String的+号是唯一一个内置的重载操作符。你可以通过定义接口和方法来实现
类似功能。
Q4.5 我new了一个对象,但是没法delete掉它。
答:java有自动内存回收机制,即所谓Garbarge Collection。你不需要删除对象。你再也
不用担心指针错误,内存溢出了。
Q4.6 我想知道为什么main方法必须被声明为public static?为什么在main方法中不能调
用非static成员?
答:声明为public是为了这个方法可以被外部调用,详情见Q5.4。
static是为了将某个成员变量/方法关联到类(class)而非实例(instance)。
你不需要创建一个对象就可以直接使用这个类的static成员,因而在static成员中不能调
用非static成员,因为后者是关联到对象实例(instance)的。
在A类中调用B类的static成员可以使用B.staticMember的写法。
注意一个类的static成员变量是唯一的,被所有该类对象所共享的,在多线程程序设计中尤其要谨慎小心。
类的static成员是在类第一次被JRE装载的时候初始化的。
你可以使用如下方法来使用非static成员:
public class A
{
private void someMethod() //非static成员
{}
public static void main(String args)
{
A a = new A(); //创建一个对象实例
a.someMethod(); //现在你可以使用非static方法了
}
}
Q4.7 throw和throws有什么不同?
答:throws用于方法声明中,声明一个方法会抛出哪些异常。而throw是在方法体中实际执行抛出异常的
动作。
如果你在方法中throw一个异常,却没有在方法声明中声明之,编译器会报错。
注意Error和RuntimeException的子类是例外,无需特别声明。
Q4.8 什么是异常?
答:异常最早在Ada语言中引入,用于在程序中动态处理错误并恢复。
你可以在方法中拦截底层异常并处理之,也可以抛给更高层的模块去处理。
你也可以抛出自己的异常指示发生了某些不正常情况。常见的拦截处理代码如下:
try
{
......//以下是可能发生异常的代码
...... //异常被你或低层API抛出,执行流程中断并转向拦截代码。
......
}
catch(Exception1 e) //如果Exception1是Exception2的子类并要做特别处理,应排在前
面
{
//发生Exception1时被该段拦截
}
catch(Exception2 e)
{
//发生Exception2时被该段拦截
}
finally //这是可选的
{
//无论异常是否发生,均执行此段代码
//即使在catch段中又向外抛出了新的exception,finally段也会得到执行。
}
Q4.9 final和finally有什么不同?
答:final请见Q4.2。finally用于异常机制,参见Q4.8。
五、 面向对象篇
Q5.1 extends和implements有什么不同?
答:对于class而言,extends用于(单)继承一个类(class),而implements用于实现一个接口(interf
ace)。
interface的引入是为了部分地提供多继承的功能。
在interface中只需声明方法头,而将方法体留给实现的class来做。
这些实现的class的实例完全可以当作interface的实例来对待。
在interface之间也可以声明为extends(多继承)的关系。
注意一个interface可以extends多个其他interface。
Q5.2 java怎么实现多继承?
答:java不支持显式的多继承。
因为在显式多继承的语言例如c++中,会出现子类被迫声明祖先虚基类构造函数的问题,
而这是违反面向对象的封装性原则的。
java提供了interface和implements关键字来部分地实现多继承。参见Q5.1。
Q5.3 abstract是什么?
答:被声明为abstract的方法无需给出方法体,留给子类来实现。
而如果一个类中有abstract方法,那么这个类也必须声明为abstract。
被声明为abstract的类无法实例化,尽管它可以定义构造方法供子类使用。
Q5.4 public,protected,private有什么不同?
答:这些关键字用于声明类和成员的可见性。
public成员可以被任何类访问,
protected成员限于自己和子类访问,
private成员限于自己访问。
Java还提供了第四种的默认可见性,一般称为package private,当没有任何public,protected,private修饰符时,成员
是同一包内可见。
类可以用public或默认来修饰。
Q5.5 Override和Overload有什么不同?
答:Override是指父类和子类之间方法的继承关系,这些方法有着相同的名称和参数类型
。
Overload是指同一个类中不同方法(可以在子类也可以在父类中定义)间的关系,
这些方法有着相同的名称和不同的参数类型。
Q5.6 我继承了一个方法,但现在我想调用在父类中定义的方法。
答:用super.xxx()可以在子类中调用父类方法。
Q5.7 我想在子类的构造方法中调用父类的构造方法,该怎么办?
答:在子类构造方法的第一行调用super(...)即可。
Q5.8 我在同一个类中定义了好几个构造方法并且想在一个构造方法中调用另一个。
答:在构造方法第一行调用this(...)。
Q5.9 我没有定义构造方法会怎么样?
答:自动获得一个无参数的构造方法。
Q5.10 我调用无参数的构造方法失败了。
答:如果你至少定义了一个构造方法,就不再有自动提供的无参数的构造方法了。
你需要另外显式定义一个无参数的构造方法。
另外一种可能是你的构造方法或者类不是public的,参见Q5.4了解java中的可见性。
Q5.11 我该怎么定义类似于C++中的析构方法(destructor)?
答:提供一个void finalize()方法。在Garbarge Collector回收该对象时会调用该方法。
注意实际上你很难判断一个对象会在什么时候被回收。作者从未感到需要用到该方法。
Q5.12 我想将一个父类对象转换成一个子类对象该怎么做?
答:强制类型转换。如
public void meth(A a)
{
B b = (B)a;
}
如果a实际上并不是B的实例,会抛出ClassCastException。所以请确保a确实是B的实例。
Q5.13 其实我不确定a是不是B的实例,能不能分情况处理?
答:可以使用instanceof操作符。例如
if( a instanceof B )
{
B b = (B)a;
}
else
{
...
}
Q5.14 我在方法里修改了一个对象的值,但是退出方法后我发现这个对象的值没变!
答:很可能你把传入参数重赋了一个新对象,例如下列代码就会造成这种错误:
public void fun1(A a) //a是局部参数,指向了一个外在对象。
{
a = new A(); //a指向了一个新对象,和外在对象脱钩了。如果你要让a作为传出变量,
不要写这一句。
a.setAttr(attr);//修改了新对象的值,外在对象没有被修改。
}
基本类型也会出现这种情况。例如:
public void fun2(int a)
{
a = 10;//只作用于本方法,外面的变量不会变化。
}
六、java.util篇
Q6.1 java能动态分配数组吗?
答:可以。例如int n = 3; Language[] myLanguages = new Language[n];
Q6.2 我怎么知道数组的长度?
答:用length属性。如上例中的 myLanguages.length 就为 3。
Q6.3 我还想让数组的长度能自动改变,能够增加/删除元素。
答:用顺序表--java.util.List接口。
你可以选择用ArrayList或是LinkedList,前者是数组实现,后者是链表实现。
例如: List list = new ArrayList(); 或是 List list = new LinkedList(); 。
Q 什么是链表?为什么要有ArrayList和LinkedList两种List?
答:请补习数据结构。
Q6.5 我想用队列/栈。
答:用java.util.LinkedList。
Q6.6 我希望不要有重复的元素。
答:用集合--java.util.Set接口。例如:Set set = new HashSet()。
Q6.7 我想遍历集合/Map。
答:用java.util.Iterator。参见API。
Q6.8 我还要能够排序。
答:用java.util.TreeSet。例如:Set set = new TreeSet()。放进去的元素会自动排序
。
你需要为元素实现Comparable接口,还可能需要提供equals()方法,compareTo()方法,h
ashCode()方法。
Q6.9 但是我想给数组排序。
答:java.util.Arrays类包含了sort等实用方法。
Q6.10 我想按不同方式排序。
答:为每种方式定义一个实现了接口Comparator的排序类并和Arrays或TreeSet综合运用。
Q6.11 Map有什么用?
答:存储key-value的关键字-值对,你可以通过关键字来快速存取相应的值。
Q6.12 set方法没问题,但是get方法返回的是Object。
答:强制类型转换成你需要的类型。参见Q5.12。
Q6.13 ArrayList和Vector有什么不同?HashMap和Hashtable有什么不同?
答:ArrayList和HashMap是多线程不安全的,在多个线程中访问同一个ArrayList对象可能
会引起冲突并导致错误。
而Vector和Hashtable是多线程安全的,即使在多个线程中同时访问同一个Vector对象也不
会引起差错。
看起来我们更应该使用Vector和Hashtable,但是实际上Vector和Hashtable的性能太差,
所以如果你不在多线程中使用的话,还是应该用ArrayList和HashMap。
Q6.14 我要获得一个随机数。
答:使用java.util.Random类。
Q6.15 我比较两个String总是false,但是它们明明都是"abc" !
答:比较String一定要使用equals或equalsIgnoreCase方法,不要使用 == !
==比较的是两个引用(变量)是否指向了同一个对象,而不是比较其内容。
Q6.16 我想修改一个String但是在String类中没找到编辑方法。
答:使用StringBuffer类。
String str = "......."; //待处理的字符串
StringBuffer buffer = new StringBuffer(str); //使用该字符串初始化一个StringBuf
fer
buffer.append("..."); //调用StringBuffer的相关API来编辑字符串
String str2 = buffer.toString(); //获得编辑后的字符串。
另外,如果你需要将多个字符串连接起来,请尽量避免使用+号直接连接,而是使用Strin
gBuffer.append()方法。
Q6.17 我想处理日期/时间。
答:使用java.util.Date类。你可以使用java.text.SimpleDateFormat类来在String和Da
te间互相转换。
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //规
定日期格式
Date date = formatter.parse("2003-07-26 18:30:35"); //将符合格式的String转换为
Date
String s = formatter.format(date); //将Date转换为符合格式的String
关于定义日期格式的详细信息请参见API。
J2EE FAQ
目录:
一、准备篇
Q1.1 什么是J2EE?它和普通的Java有什么不同?
Q1.2 J2EE好学吗?
Q1.3 J2EE有什么用?
Q1.4 学J2EE有前途吗?
Q1.5 据说J2EE的性能不如.NET好,是真的吗?
Q1.6 听你说了这么多,我想学着玩玩。
Q1.7 学习J2EE该怎么开始?
Q1.8 我下了一个J2EE服务器但是不会配置。
Q1.9 我发现你没有提到Tomcat。
二、 Servlet/JSP篇
Q2.1 什么是Servlet?
Q2.2 我怎么获得Http请求里的参数?
Q2.3 我怎么返回结果?
Q2.4 sendRedirect()和forward()有什么不同?
Q2.5 我写了一个Servlet程序,怎么运行它?
Q2.6 EAR和WAR有什么不同?
Q2.7 EAR格式是怎样的?
Q2.8 WAR格式是怎样的?
Q2.9 我的普通HTML文件/JSP文件应当放在哪里?
Q2.10 我访问不到servlet,甚至连HTML文件都访问不到!
Q2.11 我能访问HTML但是访问不到servlet。
Q2.12 什么是JSP?它和Servlet有什么区别?
Q2.13 我的JSP显示的汉字是乱码。
Q2.14 为什么使用gb18030而不是gb2312?
Q2.15 在JSP里面怎么引用Java Bean。
Q2.16 我想在servlet间传递数据。
Q2.17 怎么调用cookie?
Q2.18 怎么在JSP里面实现文件下载?
Q2.19 怎么实现文件上传?
Q2.20 我想让页面自动刷新,比如聊天室。
Q2.21 我想让用户登录以后才能访问页面。
Q2.22 我想要能注册用户。
Q2.23 怎么在JSP中访问数据库?
Q2.24 什么是JSTL?
一、准备篇
Q1.1 什么是J2EE?它和普通的Java有什么不同?
答:J2EE全称为Java2 Platform, Enterprise Edition。
“J2EE平台本质上是一个分布式的服务器应用程序设计环境——一个Java环境,它提供了
:
·宿主应用的一个运行基础框架环境。
·一套用来创建应用的Java扩展API。”
Q1.2 J2EE好学吗?
答:J2EE是很多技术的集合体,并且还在成长中。
你会遇到很多专有名词:比如(X)HTML,Servlet/JSP,JDBC,JMS,JNDI,EJB,XML,Web
Service……。
尤其是XML和Web Service正在快速成长。幸运的是,你不需要等到学会所有技术后再开始
编程。
大体上J2EE可以分成3个主要应用方式:Servlet/JSP,EJB,Web Service 和一些支撑技术
例如JDBC和JNDI。
你可以一个一个的学。
Q1.3 J2EE有什么用?
答:用来建设大型的分布式企业级应用程序。或者用更时髦的名词说就是“电子商务”应
用程序。
这些企业可能大到拥有中心数据库服务器,Web服务器集群和遍布全国的办公终端,也可能
小到只不过想做一个网站。但是你肯定听过“杀鸡焉用牛刀”的古训。
Q1.4 学J2EE有前途吗?
答:在这一市场目前只有一种技术可以和J2EE竞争,那就是Microsoft的.NET。
相对来说.NET要“新”一些而J2EE要“老”一些。这也意味着.NET更易用一点而J2EE更成
熟一点。
但是.NET只能用于Windows平台(Microsoft声称要开发C#在Linux上的虚拟机但是尚未兑现
该诺言)。
在过去几年,.NET的市场份额并不理想。不过Microsoft还有Longhorn这一杀手锏,鹿死谁
手还很难说。
Q1.5 据说J2EE的性能不如.NET好,是真的吗?
答:在Sun公司提供的样例程序Pet Store上,Microsoft声称不如相同的.NET程序好。
而Sun公司反驳说这一程序不能真正体现J2EE的性能,并且指责Microsoft在数据库上做了
优化。
作者没有学习过.NET因而不能妄下断言。
无论如何,大型分布式程序中的性能瓶颈通常首先来自于错误的设计。
Q1.6 听你说了这么多,我想学着玩玩。
答:除非你想靠它当饭吃或者作为技术储备,否则请不要浪费你的时间。
Flash要好玩得多。计算机游戏就更加好玩了。
Q1.7 学习J2EE该怎么开始?
答:首先,下载一个免费的J2EE服务器。其次,去java.sun.com下载J2EE的API。第三,找
一本好的参考书。最后,找一个顺手的IDE。
J2EE服务器。你可以用Sun的J2EE SDK(免费),或者Weblogic(性能最好,但是太大,而
且作者不推荐盗版行为),
或者JBoss(免费,就是文档太少),或者JRun(开发版免费,作者用这个)。
参考书作者感觉Wrox的《J2EE服务器端高级编程》不错,但是太老(作者手头的是2001年
中文版)。
(似乎很多人不喜欢这本书......所以你得自己判断它是否适合你。)
你还需要去下载一些最新的技术资料(当然肯定是英文的)。
IDE如果你的机器配置够好(内存至少512M以上,256M或以下请勿考虑),可以用IBM的WS
AD,不然就继续用Eclipse或者其他。
你也可以经常去水木清华的Java版逛逛,但是在发贴前先看看精华区里有没有你要的答案
。
Q1.8 我下了一个J2EE服务器但是不会配置。
答:请认真阅读随机指导文档,不同的服务器的配置都不一样,作者爱莫能助。
Q1.9 我发现你没有提到Tomcat。
答:Tomcat只是一个Web服务器,更准确地说主要只是一个Web Container。
如果你想要学习EJB的话,Tomcat无法满足你的需要。
二、 Servlet/JSP篇
Q2.1 什么是Servlet?
答:一个Servlet是一个Java类。它处理Http(s)请求并作出响应,包括返回一个HTML页面
或转交给其他URL处理。
Servlet必须运行在一个Web Container例如Tomcat中。
Servlet必须是javax.servlet.http.HttpServlet的子类,
你可以继承doGet()或者doPost()方法,两者分别对应于Http(s)中的Get请求和Post请求。
Q2.2 我怎么获得Http请求里的参数?
答:HttpRequest的getParameter()方法。例如:String paramValue = request.getPara
meter("paramName");
Q2.3 我怎么返回结果?
答:你可以利用相关API打开一个输出流,并向流中直接写入一个HTML页面。
但是作者完全不赞成这样做。一方面这样做会很罗嗦。
另一方面从Model-View-Controller模式(在《J2EE核心模式》中被归为Front Controlle
r模式)的观点来看,
你应当提供一些HTML或者JSP作为视图(view),而Servlet则根据请求参数决定转到哪一
个视图。
你可以利用response.sendRedirect(...)方法或request.getDispatcher(...).forward()
方法来实现。
Q2.4 sendRedirect()和forward()有什么不同?
答:sendRedirect()是向浏览器发送一个redirect通知,浏览器向新的URL发送一个新的请
求。
而forward是在服务器端直接将请求转到新的URL,对于浏览器是透明的。
换而言之,sendRedirect()应当将共享数据放在session中,forward应当将共享数据放在
request中(当然你也可以放在session中,但放在request中可以有效减小session中的数
据量,从而改善性能)。
前者浏览器的地址栏显示的是新的URL,后者浏览器的地址栏显示的是Servlet的URL。
因而当刷新目标URL时,两者会造成一些差别。
Q2.5 我写了一个Servlet程序,怎么运行它?
答:开发J2EE程序有一个部署(deploy)的概念,实际上是开发——部署——运行的三部
曲。
大多数服务器支持Hot deploy。你只需要在相应的Application目录(具体路径依赖于服务
器)下面
建立一个符合WAR或EAR格式(参见Q2.7,Q2.8)的目录,启动服务器,就可以通过浏览器
访问了。
特别的,你的Servlet的class文件应当放在/WEB-INF/classes目录中。
注意J2EE SDK不支持Hot deploy,你需要通过它的deploy tool来部署。
Tomcat只支持WAR格式。
Q2.6 EAR和WAR有什么不同?
答:EAR是一个完整的J2EE应用程序,包括Web部分和EJB部分。
WAR只是其中的Web部分。
Q2.7 EAR格式是怎样的?
答:一个EAR可以包含任意多个WAR或EJB JAR,并且包含一个META-INF的目录。
在/META-INF中包含了一个application.xml,其中描述了这个EAR包含哪些模块,以及安全
性配置。
细节请看参考书。
Q2.8 WAR格式是怎样的?
答:一个WAR包含一个WEB-INF的目录,这个目录下包含classes目录,lib目录和web.xml。
/WEB-INF/classes存放按package组织的class文件,/WEB-INF/lib目录存放jar文件,
web.xml描述了很多东西,请读参考书。
Q2.9 我的普通HTML文件/JSP文件应当放在哪里?
答:放在除了/WEB-INF以外的其他地方。
感谢antegg网友对于安全性的提醒:
如果你想直接用http://url/***.jsp的方式来访问,就要像上面说得那样放。
但是这样的做法是不安全的,安全的做法是把所有的JSP页面放在/WEB-INF目录下面,并且
通过WEB-CONTAINER来访问。
作者意见:
我更喜欢用filter来做安全性检查。
在MVC模式中,JSP只是一个视图而已,一般无需特别担忧安全性。和普通的html放在一起
也利于维护。
Q2.10 我访问不到servlet,甚至连HTML文件都访问不到!
答:
第一你没启动服务器。
第二你敲错了端口。
第三你没有正确配置context-path。
第四你的服务器不支持auto reload或者你关闭了这一选项,你得重启服务器或重新部署W
AR。
第五确认你没有把HTML放在/WEB-INF目录下,那是访问不到的。
Q2.11 我能访问HTML但是访问不到servlet。
答:请检查你的web.xml文件。确保你正确定义了<servlet>和<servlet-mapping>元素。
前者标识了一个servlet,后者将一个相对于context-path的URL映射到一个servlet。
在Tomcat中你可以通过/context-path/servlet/package/servletname的形式访问servlet
,
但是这只是Tomcat的便捷访问方式,并不是正式规范。
细节请看参考书。
Q2.12 什么是JSP?它和Servlet有什么区别?
答:你可以将JSP当做一个可扩充的HTML来对待。
虽然在本质上JSP文件会被服务器自动翻译为相应的Servlet来执行。
可以说Servlet是面向Java程序员而JSP是面向HTML程序员的,除此之外两者功能完全等价
。
Q2.13 我的JSP显示的汉字是乱码。
答:在你的JSP开头加上一行 <%@ page contentType="text/html; charset=gb18030"%>
如果你已经声明了page我想你知道该怎么修改。
Q2.14 为什么使用gb18030而不是gb2312?
答:gb18030是继gb2312之后的下一代汉字编码标准,最终将过渡到Unicode。
Q2.15 在JSP里面怎么引用Java Bean。
答:首先,确认你要引用的类在/WEB-INF/classes下或在/WEB-INF/lib的某个jar内。
其次,在JSP里加一行 <jsp:useBean id="..." scope="..." class="..."/>
具体解释请看参考书。
Q2.16 我想在servlet间传递数据。
答:利用session。在Servlet/JSP中,你可以在4个地方保存数据。
1) page,本页面。
2) session,用来存放客户相关的信息,比如购物车,对应接口为javax.servlet.http.H
ttpSession。
session机制实际上是cookie和URL Rewriting的抽象,服务器会自动使用cookie或URL Re
writing来实现。
3) request,可以在forward()时传递信息,对应接口为javax.servlet.http.HttpReques
t。
4) application,或称context,存放全局信息,对应接口为javax.servlet.ServletCont
ext。
Q2.17 怎么调用cookie?
答:作者建议使用session,你总是会遇到某些禁用cookie的用户。这时session会自动使
用URL重写来实现。
Q2.18 怎么在JSP里面实现文件下载?
答:实际上这是一个HTML的问题。答案是一个超链接<a>。
Q2.19 怎么实现文件上传?
答:客户端是HTML问题,在form中设置method为post,enctype为multi-part/form-data,
加一个<input type="file">。
而在接收的servlet中只是一个I/O问题,你可以使用jakarta的file-upload库。
Q2.20 我想让页面自动刷新,比如聊天室。
答:这是一个HTML问题,在<head>部分中加一条<meta http-equiv="refresh" content="
5" url="...">。
这是所谓的Client-pull,客户端刷新技术。
相对的还有Server-push,服务器端刷新技术,但是这一技术由于要占用服务器端资源而会
在大量访问时出现瓶颈现象,参见http://216.239.33.104/search?q=cache:autUfoakirY
J:www.kfunigraz.ac.at/edvndwww/books/books/javaenterprise/servlet/ch06_03.htm+
server-push+servlet&hl=zh-CN&ie=UTF-8
Q2.21 我想让用户登录以后才能访问页面。
答:使用声明式安全措施。
你只需要在web.xml中定义安全角色(Role),并定义受保护的URL集合只能由特定Role访
问。
大多数服务器支持基于数据库的用户映射,你只要在相应数据库中建立两张表并配置服务
器就可以了。
注意J2EE SDK不支持基于数据库的用户映射。
细节请看参考书和服务器文档。
不过在商业环境中,J2EE所提供的声明式安全措施仍然偏弱。一般商业程序会使用数据库
存储user-role-privilege模型来达到安全性要求,细节请询问你的构架设计师。
Q2.22 我想要能注册用户。
答:参看Q2.21。在接受注册请求的Servlet中执行写入数据库操作即可。
Q2.23 怎么在JSP中访问数据库?
答:标准做法是使用DAO模式,定义一个Java bean来访问数据库并在JSP中使用。
然而,当你的数据库模式很简单时,你可以使用JSTL中的<sql:query>标签来快速访问。
在一般的J2EE项目中,JSP处于表示层(展现层),需要先后通过业务层和集成层才会访问
到数据库,所以这个问题确实只会在很小的程序中才会遇到。
Q2.24 什么是JSTL?
答:JSTL是Jsp Standard Tag Library的缩写。这是一组通用标签并将成为JSP 2.0的一部
分。
其中包含赋值<c:set>,分支<c:if>,循环<c:forEach>,查询数据库<sql:query>,更新数
据库<sql:update>
等。目前你需要像添加自定义标签库一样来添加JSTL,但是可以预计JSP 2.0会将JSTL作为
组成部分。
标签库可以在http://jakarta.apache.org下载。注意JSTL需要在支持JSP 1.2或更高版本
的容器下运行。
帮助文件可以阅读sun的JSTL正式规范。
posted @
2006-12-12 11:12 保尔任 阅读(200) |
评论 (0) |
编辑 收藏
(1) 若在定义中出现了常数初始化字符,则大写static final基本类型标识符中的所有字母,单词之间用“_”连接。eg:private static final int MAX_LENGTH = 1000;Java包(Package)全都是小写字母,即便中间的单词亦是如此。
(2) 为了常规用途而创建一个类时,请采取“经典形式”,并包含对下述元素的定义: (规范要求,如果两个对象进行equals比较时如果返回true,那么它们的hashcode要求返回相等的值。但hashcode一样时两个对象不==)
equals()
hashCode()
toString()
clone()(mplement Cloneable)
mplement Serializable
(3) 对于自己创建的每一个类,都考虑置入一个main(),其中包含了用于测试那个类的代码。为使用一个项目中的类,我们没必要删除测试代码。若进行了任何形式的改动,可方便地返回测试。这些代码也可作为如何使用类的一个示例使用。main()方法在类定义的最底部。
(4)
(5) 设计一个类时,请设身处地为客户程序员考虑一下(类的使用方法应该是非常明确的)。然后,再设身处地为管理代码的人考虑一下(预计有可能进行哪些形式的修改,想想用什么方法可把它们变得更简单)。
(6) 使类尽可能短小精悍,而且只解决一个特定的问题。下面是对类设计的一些建议:
■一个复杂的开关语句:考虑采用“多形”机制
■数量众多的方法涉及到类型差别极大的操作:考虑用几个类来分别实现
■许多成员变量在特征上有很大的差别:考虑使用几个类 。
(7) 让一切东西都尽可能地“私有”——private。可使库的某一部分“公共化”(一个方法、类或者一个字段等等),就永远不能把它拿出。若强行拿出,就可能破坏其他人现有的代码,使他们不得不重新编写和设计。若只公布自己必须公布的,就可放心大胆地改变其他任何东西。在多线程环境中,隐私是特别重要的一个因素——只有private字段才能在非同步使用的情况下受到保护。
(8) 谨惕“巨大对象综合症”。对一些习惯于顺序编程思维、且初涉OOP领域的新手,往往喜欢先写一个顺序执行的程序,再把它嵌入一个或两个巨大的对象里。根据编程原理,对象表达的应该是应用程序的概念,而非应用程序本身。
(9) 若不得已进行一些不太雅观的编程,至少应该把那些代码置于一个类的内部。
(10) 任何时候只要发现类与类之间结合得非常紧密,就需要考虑是否采用内部类,从而改善编码及维护工作。
(11) 尽可能细致地加上注释,并用javadoc注释文档语法生成自己的程序文档。
(12) 避免使用“魔术数字”,这些数字很难与代码很好地配合。如以后需要修改它,无疑会成为一场噩梦,因为根本不知道“100”到底是指“数组大小”还是“其他全然不同的东西”。所以,我们应创建一个常数,并为其使用具有说服力的描述性名称,并在整个程序中都采用常数标识符。这样可使程序更易理解以及更易维护。
(13) 涉及构建器和异常的时候,通常希望重新丢弃在构建器中捕获的任何异常——如果它造成了那个对象的创建失败。这样一来,调用者就不会以为那个对象已正确地创建,从而盲目地继续。
(14) 当客户程序员用完对象以后,若你的类要求进行任何清除工作,可考虑将清除代码置于一个良好定义的方法里,采用类似于cleanup()这样的名字,明确表明自己的用途。除此以外,可在类内放置一个boolean(布尔)标记,指出对象是否已被清除。在类的finalize()方法里,请确定对象已被清除,并已丢弃了从RuntimeException继承的一个类(如果还没有的话),从而指出一个编程错误。在采取象这样的方案之前,请确定finalize()能够在自己的系统中工作(可能需要调用System.runFinalizersonExit(true),从而确保这一行为)。
(15) 在一个特定的作用域内,若一个对象必须清除(非由垃圾收集机制处理),请采用下述方法:初始化对象;若成功,则立即进入一个含有finally从句的try块,开始清除工作。
(16) 若在初始化过程中需要覆盖(取消)finalize(),请记住调用super.finalize()(若Object属于我们的直接超类,则无此必要)。在对finalize()进行覆盖的过程中,对super.finalize()的调用应属于最后一个行动,而不应是第一个行动,这样可确保在需要基础类组件的时候它们依然有效。
(17) 创建大小固定的对象集合时,请将它们传输至一个数组(若准备从一个方法里返回这个集合,更应如此操作)。这样一来,我们就可享受到数组在编译期进行类型检查的好处。此外,为使用它们,数组的接收者也许并不需要将对象“造型”到数组里。
(18) 尽量使用interfaces,不要使用abstract类。若已知某样东西准备成为一个基础类,那么第一个选择应是将其变成一个interface(接口)。只有在不得不使用方法定义或者成员变量的时候,才需要将其变成一个abstract(抽象)类。接口主要描述了客户希望做什么事情,而一个类则致力于(或允许)具体的实施细节。
(19) 在构建器内部,只进行那些将对象设为正确状态所需的工作。尽可能地避免调用其他方法,因为那些方法可能被其他人覆盖或取消,从而在构建过程中产生不可预知的结果(参见第7章的详细说明)。
(20) 对象不应只是简单地容纳一些数据;它们的行为也应得到良好的定义。
(21) 在现成类的基础上创建新类时,请首先选择“新建”或“创作”。只有自己的设计要求必须继承时,才应考虑这方面的问题。若在本来允许新建的场合使用了继承,则整个设计会变得没有必要地复杂。
(22) 用继承及方法覆盖来表示行为间的差异,而用字段表示状态间的区别。一个非常极端的例子是通过对不同类的继承来表示颜色,这是绝对应该避免的:应直接使用一个“颜色”字段。
(23) 为避免编程时遇到麻烦,请保证在自己类路径指到的任何地方,每个名字都仅对应一个类。否则,编译器可能先找到同名的另一个类,并报告出错消息。若怀疑自己碰到了类路径问题,请试试在类路径的每一个起点,搜索一下同名的.class文件。
(24) 在Java 1.1 AWT中使用事件“适配器”时,特别容易碰到一个陷阱。若覆盖了某个适配器方法,同时拼写方法没有特别讲究,最后的结果就是新添加一个方法,而不是覆盖现成方法。然而,由于这样做是完全合法的,所以不会从编译器或运行期系统获得任何出错提示——只不过代码的工作就变得不正常了。
(25) 用合理的设计方案消除“伪功能”。也就是说,假若只需要创建类的一个对象,就不要提前限制自己使用应用程序,并加上一条“只生成其中一个”注释。请考虑将其封装成一个“独生子”的形式。若在主程序里有大量散乱的代码,用于创建自己的对象,请考虑采纳一种创造性的方案,将些代码封装起来。
(26) 警惕“分析瘫痪”。请记住,无论如何都要提前了解整个项目的状况,再去考察其中的细节。由于把握了全局,可快速认识自己未知的一些因素,防止在考察细节的时候陷入“死逻辑”中。
(27) 警惕“过早优化”。首先让它运行起来,再考虑变得更快——但只有在自己必须这样做、而且经证实在某部分代码中的确存在一个性能瓶颈的时候,才应进行优化。除非用专门的工具分析瓶颈,否则很有可能是在浪费自己的时间。性能提升的隐含代价是自己的代码变得难于理解,而且难于维护。
(28) 请记住,阅读代码的时间比写代码的时间多得多。思路清晰的设计可获得易于理解的程序,但注释、细致的解释以及一些示例往往具有不可估量的价值。无论对你自己,还是对后来的人,它们都是相当重要的。如对此仍有怀疑,那么请试想自己试图从联机Java文档里找出有用信息时碰到的挫折,这样或许能将你说服。
(29) 如认为自己已进行了良好的分析、设计或者实施,那么请稍微更换一下思维角度。试试邀请一些外来人士——并不一定是专家,但可以是来自本公司其他部门的人。请他们用完全新鲜的眼光考察你的工作,看看是否能找出你一度熟视无睹的问题。采取这种方式,往往能在最适合修改的阶段找出一些关键性的问题,避免产品发行后再解决问题而造成的金钱及精力方面的损失。
(30) 良好的设计能带来最大的回报。简言之,对于一个特定的问题,通常会花较长的时间才能找到一种最恰当的解决方案。但一旦找到了正确的方法,以后的工作就轻松多了,再也不用经历数小时、数天或者数月的痛苦挣扎。我们的努力工作会带来最大的回报(甚至无可估量)。而且由于自己倾注了大量心血,最终获得一个出色的设计方案,成功的快感也是令人心动的。坚持抵制草草完工的诱惑——那样做往往得不偿失
(31) JAVA 对枚举的支持不好,但是下面的代码是一种很有用的模板:
class Colour {
public static final Colour BLACK = new Colour(0, 0, 0);
public static final Colour RED = new Colour(0xFF, 0, 0);
public static final Colour GREEN = new Colour(0, 0xFF, 0);
public static final Colour BLUE = new Colour(0, 0, 0xFF);
public static final Colour WHITE = new Colour(0xFF, 0xFF, 0xFF);
}
这种技术实现了RED, GREEN, BLUE 等可以象其他语言的枚举类型一样使用的常量。 他们可以用 == 操作符来比较。 但是这样使用有一个缺陷:如果一个用户用这样的方法来创建颜色 BLACK new Colour(0,0,0) 那么这就是另外一个对象,==操作符就会产生错误。她的 equal() 方法仍然有效。由于这个原因,这个技术的缺陷最好注明在文档中,或者只在自己的包中使用。
posted @
2006-12-12 11:11 保尔任 阅读(173) |
评论 (0) |
编辑 收藏
按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的.
静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求.
栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。
静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.
2.2 堆和栈的比较
上面的定义从编译原理的教材中总结而来,除静态存储分配之外,都显得很呆板和难以理解,下面撇开静态存储分配,集中比较堆和栈:
从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.而这种不同又主要是由于堆和栈的特点决定的:
在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行.退出函数的时候,修改栈指针就可以把栈中的内容销毁.这样的模式速度最快, 当然要用来运行程序了.需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时.
堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低.但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定.在C++中,要求创建一个对象时,只需用 new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存.当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致我们刚才所说的效率低的原因,看来列宁同志说的好,人的优点往往也是人的缺点,人的缺点往往也是人的优点(晕~).
2.3 JVM中的堆和栈
JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。
我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理中的活动纪录的概念是差不多的.
从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。
每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享.跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
2.4 GC的思考
Java为什么慢?JVM的存在当然是一个原因,但有人说,在Java中,除了简单类型(int,char等)的数据结构,其它都是在堆中分配内存(所以说Java的一切都是对象),这也是程序慢的原因之一。
我的想法是(应该说代表TIJ的观点),如果没有Garbage Collector(GC),上面的说法就是成立的.堆不象栈是连续的空间,没有办法指望堆本身的内存分配能够象堆栈一样拥有传送带般的速度,因为,谁会为你整理庞大的堆空间,让你几乎没有延迟的从堆中获取新的空间呢?
这个时候,GC站出来解决问题.我们都知道GC用来清除内存垃圾,为堆腾出空间供程序使用,但GC同时也担负了另外一个重要的任务,就是要让Java中堆的内存分配和其他语言中堆栈的内存分配一样快,因为速度的问题几乎是众口一词的对Java的诟病.要达到这样的目的,就必须使堆的分配也能够做到象传送带一样,不用自己操心去找空闲空间.这样,GC除了负责清除Garbage外,还要负责整理堆中的对象,把它们转移到一个远离Garbage的纯净空间中无间隔的排列起来,就象堆栈中一样紧凑,这样Heap Pointer就可以方便的指向传送带的起始位置,或者说一个未使用的空间,为下一个需要分配内存的对象"指引方向".因此可以这样说,垃圾收集影响了对象的创建速度,听起来很怪,对不对?
那GC怎样在堆中找到所有存活的对象呢?前面说了,在建立一个对象时,在堆中分配实际建立这个对象的内存,而在堆栈中分配一个指向这个堆对象的指针(引用),那么只要在堆栈(也有可能在静态存储区)找到这个引用,就可以跟踪到所有存活的对象.找到之后,GC将它们从一个堆的块中移到另外一个堆的块中,并将它们一个挨一个的排列起来,就象我们上面说的那样,模拟出了一个栈的结构,但又不是先进后出的分配,而是可以任意分配的,在速度可以保证的情况下, Isn't it great?
但是,列宁同志说了,人的优点往往也是人的缺点,人的缺点往往也是人的优点(再晕~~).GC()的运行要占用一个线程,这本身就是一个降低程序运行性能的缺陷,更何况这个线程还要在堆中把内存翻来覆去的折腾.不仅如此,如上面所说,堆中存活的对象被搬移了位置,那么所有对这些对象的引用都要重新赋值.这些开销都会导致性能的降低.
此消彼长,GC()的优点带来的效益是否盖过了它的缺点导致的损失,我也没有太多的体会,Bruce Eckel 是Java的支持者,王婆卖瓜,话不能全信.个人总的感觉是,Java还是很慢,它的发展还需要时间.
上面的体会是我看了TIJ.3rdEdition.Revision4.0中第四章之后得出的,内容和前面的有些不同.我没有看过侯捷的中文版本,但我觉得,在关键问题上,原版的TIJ的确更值得一读.所以和中文版配合起来学习是比较不错的选择.
我只能算一个Java的初学者,没想到起了这么个题目,却受到这么多人的关注,欣喜之余,也决心尽力写好下面的每一篇.不过这一篇完了,我就该准备赴美签证了,如果成功,那就要等到8月27号CS的研究生院开学之后,才有时间会开始研究下一章了,希望可以多从原版中获取一点经验.
posted @
2006-12-12 11:06 保尔任 阅读(743) |
评论 (0) |
编辑 收藏
数组和List的转换:
List<Integer> l = new ArrayList<Integer>();
l.add(1);
l.add(2);
l.add(3);
Integer[] ints = l.toArray(new Integer[0]);
List<Integer> l2 = Arrays.asList(ints);
assert l.equals(l2);
System.out.println("ok");
数字类型转换成字符串型:
String s = String.valueOf(value);
数字对象转换成字符串型
Long l = new Long(10);
String s = l.toString();
数字类型/字符串转换成数字对象:
byte b = 169;
String b = "169";
Byte bo = new Byte( b );
short t = 169;
String b = "169";
Short to = new Short( t );
int i = 169;
String b = "169";
Integer io = new Integer( i );
long l = 169;
String b = "169";
Long lo = new Long( l );
float f = 169f;
String b = "169";
Float fo = new Float( f );
double d = 169f;
String b = "169";
Double dObj = new Double( d );
字符串型转换成各种数字类型:
String s = "169";
byte b = Byte.parseByte( s );
short t = Short.parseShort( s );
int i = Integer.parseInt( s );
long l = Long.parseLong( s );
float f = Float.parseFloat( s );
double d = Double.parseDouble( s );
数字对象转换成数字类型:(*o为一数字对象)
b = bo.byteValue();
t = to.shortValue();
i = io.intValue();
l = lo.longValue();
f = fo.floatValue();
d = dObj.doubleValue();
posted @
2006-12-12 10:19 保尔任 阅读(539) |
评论 (0) |
编辑 收藏
第一章 一般技术
1.java只有唯一一种参数传递方式:by value(值传递)。对于primitive types(基本型别)很容易理解,对于object references(对象引用),传递的是object reference的拷贝。
2.polymorphism(多态)优于instanceof:instanceof很容易被误用,很多场合都应该以多态代替,无论何时看到instanceof,请判断是否可以改进以消除它。
3.避免创建重复对象。比如一个类A的某个方法新建了一个类B,且此类B不会改变,则每次建立该类A的一个对象就会新建B的对象,此时应把
B设为private static final。
4.清除过期的对象引用。
5.避免使用终结函数。因为终结函数可能得不到执行或很久后得到执行,所以要避免使用。显示的中止方法通常与try-finally结构结合使用,防止出现异常时终结函数得不到执行。
eg: Foo foo = new Foo(...);
try{
//do what must be done with foo
}finally{
foo.terminate();
}
6.通过私有构造函数来强化不可实例化的能力。比如一些工具类不希望被实例化,然而在缺少显示构造函数时编译器会自动提供一个默认构造函数,为防止以上情况要构造一个显示的私有的构造函数。
eg:public class UtilityClass{
private UtilityClass(){
}
}
7.通过私有构造函数强化singleton属性。singleton是指这样的类,它只能实例化一次。singleton通常被用来代表那些本质上具有唯一性的系统组件,比如视频显示或者文件系统。
eg:public class Elvis{
public static final Elvis INSTANCE = new Elvis();
private Elvis(){
}
}
8.考虑用静态工厂方法代替构造函数,但如果没有其他强烈的因素,最好还是简单的使用构造函数,毕竟它是语言规范。静态工厂方法实际上是一个简单的静态方法,他返回的是类的一个实例。
有点:a.与构造函数不同,静态工厂方法具有名字。
b.与构造函数不同,它们每次被调用的时候不要求非得创建一个对象。
c.与构造函数不同,与构造函数不同,它们可以返回一个原类型的子类型对象。
第二章 所有对象都通用的方法(equals(),hashCode(),toString(),clone(),Comparable接口)
一.按规则使用equals():
1.使用equals的规则:
a.如果一个class的两个对象占据不同的内存空间也可被视为逻辑相等的话,那么得为这个class提供一个equals()
b.检查是否等于this
c.比较关键域以判断两个对象是否相等
d.如果有java.lang.Object以外的任何base class实现了equals(),那么就应该调用super.equals()
e.如果只允许同一个class所产生的对象被视为相等,则通常使用getClass()
eg1:一般情况
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(obj != nul && getClass() == obj.getClass()){
Test test = (Test)obj;
if(***){//相等条件
return true;
}
}
return false;
}
eg2:调用super.equals()情况
public boolean equals(Object obj){
if(super.equals(obj)){//已经包含了this == obj; obj !=null && getClass() == obj.getClass()的判断
Test test = (Test)obj;
if(***){//相等条件
return true;
}
}
return false;
}
f.只有在不得不对derived class对象与base classes对象进行比较的场合中,才使用instanceof,并且你应该明白这样做带来的可能问题和复杂性,并且derived class和base classes都用instanceof实现equals()时,这种比较不会展现“对称相等性”。
Base b;Derived d;//分别表示父类、子类
1)父类实现equals,子类继承父类的equals,b.equals(d) == d.equals(d);
2)父类子类分别实现了equals,b.equals(d) != d.equals(b);
3)父类未实现equals,子类实现了equals,b.equals(d) != d.equals(b);
2.对于既不是float也不是double类型的primitive types,使用==操作符;对于对象引用域,可以递归的调用equals方法;对于float域,先使用Float.floatToIntBits转换成int类型值,然后使用==操作符比较int类型的值;对于double域,先使用Double.doubleToLongBits转换成int类型的值,然后使用==操作符比较long类型的值.(这是由于存在Float.NaN、-0.0f以及类似的double类型的常量)
二.hashCode():
1。改写equals时总是要改写hashCode方法,如果不这样作,会导致该类无法与所有基于散列值(hash)的集合类在一起正常工作,这样的集合类包括HashMap、HashSet、HashTable
2。hashCode方法的简单方法:
1。把某个非零数值(比如17),保存在int result变量里。
2。对于对象中每一个关键域f(指equals方法中考虑的每一个域),完成以下步骤:
a)为该域计算int类型的散列码c:
i.该域为boolean型,c = f ? 0 : 1
ii.byte, char, short, int型, c = (int)f
iii.long型, c = (int)(f ^ (f >>> 32))
iv.float型, c = Float.floatToIntBits(f)
v.double型, Double.doubleToLongBits(f)得到long型,然后按iii计算散列值
vi.如果是对象引用,c = (this.*** == null) ? 0 : this.***.hashCode();
vii.如果该域是个数组,则把其中每一个元素当作单独的域来处理
b)result = 37 * result + c;//把每个c都组合到result中
3。返回result
eg1:
public int hashCode() {
int result = 17;
//对于关键域是id的情况
int idValue = (this.getId() == null) ? 0 : this.getId().hashCode();
result = (result * 37) + idValue;
//如果还有第二个关键域name
//int nameValue = (this.getName() == null) ? 0 : this.getName().hashCode();
//result = (result * 37) + nameValue;
this.hashValue = result;
return this.hashValue;
}
eg2:
如果一个类是非可变的,并且计算散列码代价较大,则应把散列码存到对象内部:
private int hashValue = 17;//先定义hashValue,不需要get/set方法
........................
public int hashCode() {//对于关键域是id的情况
if (this.hashValue == 17) {
int result = 17;
int idValue = (this.getId() == null) ? 0 : this.getId().hashCode();
result = (result * 37) + idValue;
//如果还有第二个关键域name
//int nameValue = (this.getName() == null) ? 0 : this.getName().hashCode();
//result = (result * 37) + nameValue;
this.hashValue = result;
}
return this.hashValue;
}
三。toString():会使这个类用起来更加方便。
四。谨慎的改写clone()。实现拷贝的方法有两个:一是实现cloneable接口(effective java 39页,没仔细看),二是提供拷贝构造函数
public Yum(Yum yum);
或是上面的微小变形:提供一个静态工厂来代替构造函数:
public static Yum newInstance(Yum yum);
五、用到搜索、排序、计算极值的情况时,考虑实现Comparable接口。
public int compareTo(Object o)//方法不需要手工检查参数的类型,如参数类型不符合会抛出ClassCastException;如参数为null,该方法抛出NullPointerException。
第三章 类和接口
1。使类和成员(变量、方法、内部类、内部接口)的可访问能力最小化。
2。private和friendly成员都是一个类实现中的一部分,并不会影响到导出API。然而,如果这些域所在的类实现了Serializable接口,那么这些成员可能会被泄漏到导出API中。
3。如果一个方法改写了超类中的一个方法,那么子类中该方法的访问级别不能低于父类中该方法的访问级别。特别是:类实现了接口,那么接口中的方法在这个类中必须声明为公有的,因为接口中方法默认为public abstract。
六、异常处理
1.决不可忽略异常,即catch后什么也不做。
2.决不可掩盖异常
try{
e1;//异常1
e2;//异常2
}catch(Exception e){
e.printStackTrace()
}//只能捕获异常2
办法:要仔细分析,用栈来保存异常
3.覆写异常处理时:
父类不抛出异常时,自类不能抛出异常。
父类抛出异常时,自类三种情况:a)不抛出异常b)抛出父类异常c)抛出父类异常的派生异常。
4.只要有finally块就一定会进入,即使try-catch块有return/break/continue语句。
5.养成将try/catch块放在循环外的习惯,在不启动JIT时节省时间。
posted @
2006-12-05 16:16 保尔任 阅读(169) |
评论 (0) |
编辑 收藏
1。自然数是0,1,2……
2。素数是2,3,5……(不包括1的只能背1和它本身整除的自然数)
import java.util.Scanner;
public class Prime {
//最基本的做法
private int prime1(int num) {
int i = 0, s = 0;
label1: for (int n = 2; n <= num; n++) {
for (int m = 2; m * m <= n; m++) {
if (n % m == 0)
continue label1;
}
s++;
i++;
//System.out.println("第" + i + "个素数是:" + n);
}
return s;
}
//6N±1法
private int prime2(int num){
int i = 0, s = 0;
for(int n = 2; n <=3; n ++){
s++;
i++;
//System.out.println("第" + i + "个素数是:" + n);
}
label1: for(int n = 1; ; n++) {
label2: for (int m = 0; m <= 1; m++) {
int tmp = 2 * (3 * n + m) - 1;
if (tmp > num)
break label1;
for(int k = 2; k * k <= tmp; k++)
if (tmp % k == 0)
if (m == 0)
continue label2;
else
continue label1;
s++;
i++;
//System.out.println("第" + i + "个素数是:" + tmp);
}
}
return s;
}
public static void main(String args[]) {
Scanner in = new Scanner(System.in);
int num = in.nextInt();
long start = System.currentTimeMillis();
int sum = new Prime().prime1(num);
long end = System.currentTimeMillis();
System.out.println("方法一共" + sum + "个素数");
System.out.println("用时:" + (end - start));
start = System.currentTimeMillis();
sum = new Prime().prime2(num);
end = System.currentTimeMillis();
System.out.println("方法二共" + sum + "个素数");
System.out.println("用时:" + (end - start));
}
}
输入:1000000
运行结果:
方法一共78498个素数
用时:3434
方法二共78498个素数
用时:3453
(看来基本方法比6N±1法还要更快些,奇怪了,我的程序写的没什么问题阿)
【1】求10000以内的所有素数。
素数是除了1和它本身之外再不能被其他数整除的自然数。由于找不到一个通项公式来表示所有的素数,所以对于数学家来说,素数一直是一个未解之谜。像著名的 哥德巴赫猜想、孪生素数猜想,几百年来不知吸引了世界上多少优秀的数学家。尽管他们苦心钻研,呕心沥血,但至今仍然未见分晓。
自从有了计算机之后,人们借助于计算机的威力,已经找到了2216091以内的所有素数。
求素数的方法有很多种,最简单的方法是根据素数的定义来求。对于一个自然数N,用大于1小于N的各个自然数都去除一下N,如果都除不尽,则N为素数,否则N为合数。
但是,如果用素数定义的方法来编制计算机程序,它的效率一定是非常低的,其中有许多地方都值得改进。
第一,对于一个自然数N,只要能被一个非1非自身的数整除,它就肯定不是素数,所以不
必再用其他的数去除。
第二,对于N来说,只需用小于N的素数去除就可以了。例如,如果N能被15整除,实际
上就能被3和5整除,如果N不能被3和5整除,那么N也决不会被15整除。
第三,对于N来说,不必用从2到N一1的所有素数去除,只需用小于等于√N(根号N)的所有素数去除就可以了。这一点可以用反证法来证明:
如果N是合数,则一定存在大于1小于N的整数d1和d2,使得N=d1×d2。
如果d1和d2均大于√N,则有:N=d1×d2>√N×√N=N。
而这是不可能的,所以,d1和d2中必有一个小于或等于√N。
基于上述分析,设计算法如下:
(1)用2,3,5,7逐个试除N的方法求出100以内的所有素数。
(2)用100以内的所有素数逐个试除的方法求出10000以内的素数。
首先,将2,3,5,7分别存放在a[1]、a[2]、a[3]、a[4]中,以后每求出一个素数,只要不大于100,就依次存放在A数组中的一个单元 中。当我们求100—10000之间的素数时,可依次用a[1]-a[2]的素数去试除N,这个范围内的素数可以不保存,直接打印。
【2】用筛法求素数。
简单介绍一下厄拉多塞筛法。厄拉多塞是一位古希腊数学家,他在寻找素数时,采用了一种与众不同的方法:先将2-N的各数写在纸上:
在2的上面画一个圆圈,然后划去2的其他倍数;第一个既未画圈又没有被划去的数是3,将它画圈,再划去3的其他倍数;现在既未画圈又没有被划去的第一个数 是5,将它画圈,并划去5的其他倍数……依次类推,一直到所有小于或等于N的各数都画了圈或划去为止。这时,表中画了圈的以及未划去的那些数正好就是小于 N的素数。
这很像一面筛子,把满足条件的数留下来,把不满足条件的数筛掉。由于这种方法是厄拉多塞首先发明的,所以,后人就把这种方法称作厄拉多塞筛法。
在计算机中,筛法可以用给数组单元置零的方法来实现。具体来说就是:首先开一个数组:a[i],i=1,2,3,…,同时,令所有的数组元素都等于下标 值,即a[i]=i,当i不是素数时,令a[i]=0 。当输出结果时,只要判断a[i]是否等于零即可,如果a[i]=0,则令i=i+1,检查下一个a[i]。
筛法是计算机程序设计中常用的算法之一。
【3】用6N±1法求素数。
任何一个自然数,总可以表示成为如下的形式之一:
6N,6N+1,6N+2,6N+3,6N+4,6N+5 (N=0,1,2,…)
显然,当N≥1时,6N,6N+2,6N+3,6N+4都不是素数,只有形如6N+1和6N+5的自然数有可能是素数。所以,除了2和3之外,所有的素数都可以表示成6N±1的形式(N为自然数)。
根据上述分析,我们可以构造另一面筛子,只对形如6 N±1的自然数进行筛选,这样就可以大大减少筛选的次数,从而进一步提高程序的运行效率和速度。
在程序上,我们可以用一个二重循环实现这一点,外循环i按3的倍数递增,内循环j为0-1的循环,则2(i+j)-1恰好就是形如6N±1的自然数。
posted @
2006-11-20 15:28 保尔任 阅读(3163) |
评论 (6) |
编辑 收藏
插入排序:
package org.rut.util.algorithm.support;
import org.rut.util.algorithm.SortUtil;
/**
* @author treeroot
* @since 2006-2-2
* @version 1.0
*/
public class InsertSort implements SortUtil.Sort{
/* (non-Javadoc)
* @see org.rut.util.algorithm.SortUtil.Sort#sort(int[])
*/
public void sort(int[] data) {
int temp;
for(int i=1;i<data.length;i++){
for(int j=i;(j>0)&&(data[j]<data[j-1]);j--){
SortUtil.swap(data,j,j-1);
}
}
}
}
冒泡排序:
package org.rut.util.algorithm.support;
import org.rut.util.algorithm.SortUtil;
/**
* @author treeroot
* @since 2006-2-2
* @version 1.0
*/
public class BubbleSort implements SortUtil.Sort{
/* (non-Javadoc)
* @see org.rut.util.algorithm.SortUtil.Sort#sort(int[])
*/
public void sort(int[] data) {
int temp;
for(int i=0;i<data.length;i++){
for(int j=data.length-1;j>i;j--){
if(data[j]<data[j-1]){
SortUtil.swap(data,j,j-1);
}
}
}
}
}
选择排序:
package org.rut.util.algorithm.support;
import org.rut.util.algorithm.SortUtil;
/**
* @author treeroot
* @since 2006-2-2
* @version 1.0
*/
public class SelectionSort implements SortUtil.Sort {
/*
* (non-Javadoc)
*
* @see org.rut.util.algorithm.SortUtil.Sort#sort(int[])
*/
public void sort(int[] data) {
int temp;
for (int i = 0; i < data.length; i++) {
int lowIndex = i;
for (int j = data.length - 1; j > i; j--) {
if (data[j] < data[lowIndex]) {
lowIndex = j;
}
}
SortUtil.swap(data,i,lowIndex);
}
}
}
Shell排序:
package org.rut.util.algorithm.support;
import org.rut.util.algorithm.SortUtil;
/**
* @author treeroot
* @since 2006-2-2
* @version 1.0
*/
public class ShellSort implements SortUtil.Sort{
/* (non-Javadoc)
* @see org.rut.util.algorithm.SortUtil.Sort#sort(int[])
*/
public void sort(int[] data) {
for(int i=data.length/2;i>2;i/=2){
for(int j=0;j<i;j++){
insertSort(data,j,i);
}
}
insertSort(data,0,1);
}
/**
* @param data
* @param j
* @param i
*/
private void insertSort(int[] data, int start, int inc) {
int temp;
for(int i=start+inc;i<data.length;i+=inc){
for(int j=i;(j>=inc)&&(data[j]<data[j-inc]);j-=inc){
SortUtil.swap(data,j,j-inc);
}
}
}
}
快速排序:
package org.rut.util.algorithm.support;
import org.rut.util.algorithm.SortUtil;
/**
* @author treeroot
* @since 2006-2-2
* @version 1.0
*/
public class QuickSort implements SortUtil.Sort{
/* (non-Javadoc)
* @see org.rut.util.algorithm.SortUtil.Sort#sort(int[])
*/
public void sort(int[] data) {
quickSort(data,0,data.length-1);
}
private void quickSort(int[] data,int i,int j){
int pivotIndex=(i+j)/2;
//swap
SortUtil.swap(data,pivotIndex,j);
int k=partition(data,i-1,j,data[j]);
SortUtil.swap(data,k,j);
if((k-i)>1) quickSort(data,i,k-1);
if((j-k)>1) quickSort(data,k+1,j);
}
/**
* @param data
* @param i
* @param j
* @return
*/
private int partition(int[] data, int l, int r,int pivot) {
do{
while(data[++l]<pivot);
while((r!=0)&&data[--r]>pivot);
SortUtil.swap(data,l,r);
}
while(l<r);
SortUtil.swap(data,l,r);
return l;
}
}
改进后的快速排序:
package org.rut.util.algorithm.support;
import org.rut.util.algorithm.SortUtil;
/**
* @author treeroot
* @since 2006-2-2
* @version 1.0
*/
public class ImprovedQuickSort implements SortUtil.Sort {
private static int MAX_STACK_SIZE=4096;
private static int THRESHOLD=10;
/* (non-Javadoc)
* @see org.rut.util.algorithm.SortUtil.Sort#sort(int[])
*/
public void sort(int[] data) {
int[] stack=new int[MAX_STACK_SIZE];
int top=-1;
int pivot;
int pivotIndex,l,r;
stack[++top]=0;
stack[++top]=data.length-1;
while(top>0){
int j=stack[top--];
int i=stack[top--];
pivotIndex=(i+j)/2;
pivot=data[pivotIndex];
SortUtil.swap(data,pivotIndex,j);
//partition
l=i-1;
r=j;
do{
while(data[++l]<pivot);
while((r!=0)&&(data[--r]>pivot));
SortUtil.swap(data,l,r);
}
while(l<r);
SortUtil.swap(data,l,r);
SortUtil.swap(data,l,j);
if((l-i)>THRESHOLD){
stack[++top]=i;
stack[++top]=l-1;
}
if((j-l)>THRESHOLD){
stack[++top]=l+1;
stack[++top]=j;
}
}
//new InsertSort().sort(data);
insertSort(data);
}
/**
* @param data
*/
private void insertSort(int[] data) {
int temp;
for(int i=1;i<data.length;i++){
for(int j=i;(j>0)&&(data[j]<data[j-1]);j--){
SortUtil.swap(data,j,j-1);
}
}
}
}
归并排序:
package org.rut.util.algorithm.support;
import org.rut.util.algorithm.SortUtil;
/**
* @author treeroot
* @since 2006-2-2
* @version 1.0
*/
public class MergeSort implements SortUtil.Sort{
/* (non-Javadoc)
* @see org.rut.util.algorithm.SortUtil.Sort#sort(int[])
*/
public void sort(int[] data) {
int[] temp=new int[data.length];
mergeSort(data,temp,0,data.length-1);
}
private void mergeSort(int[] data,int[] temp,int l,int r){
int mid=(l+r)/2;
if(l==r) return ;
mergeSort(data,temp,l,mid);
mergeSort(data,temp,mid+1,r);
for(int i=l;i<=r;i++){
temp[i]=data[i];
}
int i1=l;
int i2=mid+1;
for(int cur=l;cur<=r;cur++){
if(i1==mid+1)
data[cur]=temp[i2++];
else if(i2>r)
data[cur]=temp[i1++];
else if(temp[i1]<temp[i2])
data[cur]=temp[i1++];
else
data[cur]=temp[i2++];
}
}
}
改进后的归并排序:
package org.rut.util.algorithm.support;
import org.rut.util.algorithm.SortUtil;
/**
* @author treeroot
* @since 2006-2-2
* @version 1.0
*/
public class ImprovedMergeSort implements SortUtil.Sort {
private static final int THRESHOLD = 10;
/*
* (non-Javadoc)
*
* @see org.rut.util.algorithm.SortUtil.Sort#sort(int[])
*/
public void sort(int[] data) {
int[] temp=new int[data.length];
mergeSort(data,temp,0,data.length-1);
}
private void mergeSort(int[] data, int[] temp, int l, int r) {
int i, j, k;
int mid = (l + r) / 2;
if (l == r)
return;
if ((mid - l) >= THRESHOLD)
mergeSort(data, temp, l, mid);
else
insertSort(data, l, mid - l + 1);
if ((r - mid) > THRESHOLD)
mergeSort(data, temp, mid + 1, r);
else
insertSort(data, mid + 1, r - mid);
for (i = l; i <= mid; i++) {
temp[i] = data[i];
}
for (j = 1; j <= r - mid; j++) {
temp[r - j + 1] = data[j + mid];
}
int a = temp[l];
int b = temp[r];
for (i = l, j = r, k = l; k <= r; k++) {
if (a < b) {
data[k] = temp[i++];
a = temp[i];
} else {
data[k] = temp[j--];
b = temp[j];
}
}
}
/**
* @param data
* @param l
* @param i
*/
private void insertSort(int[] data, int start, int len) {
for(int i=start+1;i<start+len;i++){
for(int j=i;(j>start) && data[j]<data[j-1];j--){
SortUtil.swap(data,j,j-1);
}
}
}
}
堆排序:
package org.rut.util.algorithm.support;
import org.rut.util.algorithm.SortUtil;
/**
* @author treeroot
* @since 2006-2-2
* @version 1.0
*/
public class HeapSort implements SortUtil.Sort{
/* (non-Javadoc)
* @see org.rut.util.algorithm.SortUtil.Sort#sort(int[])
*/
public void sort(int[] data) {
MaxHeap h=new MaxHeap();
h.init(data);
for(int i=0;i<data.length;i++)
h.remove();
System.arraycopy(h.queue,1,data,0,data.length);
}
private static class MaxHeap{
void init(int[] data){
this.queue=new int[data.length+1];
for(int i=0;i<data.length;i++){
queue[++size]=data[i];
fixUp(size);
}
}
private int size=0;
private int[] queue;
public int get() {
return queue[1];
}
public void remove() {
SortUtil.swap(queue,1,size--);
fixDown(1);
}
//fixdown
private void fixDown(int k) {
int j;
while ((j = k << 1) <= size) {
if (j < size && queue[j]<queue[j+1])
j++;
if (queue[k]>queue[j]) //不用交换
break;
SortUtil.swap(queue,j,k);
k = j;
}
}
private void fixUp(int k) {
while (k > 1) {
int j = k >> 1;
if (queue[j]>queue[k])
break;
SortUtil.swap(queue,j,k);
k = j;
}
}
}
}
SortUtil:
package org.rut.util.algorithm;
import org.rut.util.algorithm.support.BubbleSort;
import org.rut.util.algorithm.support.HeapSort;
import org.rut.util.algorithm.support.ImprovedMergeSort;
import org.rut.util.algorithm.support.ImprovedQuickSort;
import org.rut.util.algorithm.support.InsertSort;
import org.rut.util.algorithm.support.MergeSort;
import org.rut.util.algorithm.support.QuickSort;
import org.rut.util.algorithm.support.SelectionSort;
import org.rut.util.algorithm.support.ShellSort;
/**
* @author treeroot
* @since 2006-2-2
* @version 1.0
*/
public class SortUtil {
public final static int INSERT = 1;
public final static int BUBBLE = 2;
public final static int SELECTION = 3;
public final static int SHELL = 4;
public final static int QUICK = 5;
public final static int IMPROVED_QUICK = 6;
public final static int MERGE = 7;
public final static int IMPROVED_MERGE = 8;
public final static int HEAP = 9;
public static void sort(int[] data) {
sort(data, IMPROVED_QUICK);
}
private static String[] name={
"insert","bubble","selection","shell","quick","improved_quick","merge","improved_merge","heap"
};
private static Sort[] impl=new Sort[]{
new InsertSort(),
new BubbleSort(),
new SelectionSort(),
new ShellSort(),
new QuickSort(),
new ImprovedQuickSort(),
new MergeSort(),
new ImprovedMergeSort(),
new HeapSort()
};
public static String toString(int algorithm){
return name[algorithm-1];
}
public static void sort(int[] data, int algorithm) {
impl[algorithm-1].sort(data);
}
public static interface Sort {
public void sort(int[] data);
}
public static void swap(int[] data, int i, int j) {
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
}
posted @
2006-11-20 15:27 保尔任 阅读(189) |
评论 (0) |
编辑 收藏
1、 运行tomcat后打开网页,浏览器显示如下错误:
Unable to find a javac compiler;
com.sun.tools.javac.Main is not on the classpath.
Perhaps JAVA_HOME does not point to the JDK
首先,你必需检查一下自己的环境变量是不是正确;这个我想大家都会,只是有时候重装JDK而忘了改,不过检查一下看看就行了。
其次:在JDK的lib目录下有一个tools.jar文件,你把它拷到Tomcat安装目录下的common\lib目录下,或者在tomcat下作如下修改,效果一样
最后:如果不可以,在打开tomcat的configue tomcatg ,找到java,在java optioons里填上:-Djava.home=C:\Program Files\Java\jdk1.5.0_04;就好了。
posted @
2006-11-16 11:00 保尔任 阅读(119) |
评论 (0) |
编辑 收藏
(转自http://leaf.jdk.cn/article.asp?id=39)
虽然项目全部采用了UTF-8编码,所有的源文件*.java,*.jsc,*.html,*.ftl都采用了UTF-8编码。可是还是出现了乱码问题。很是不爽,后来找到了tomcat,和resin的配置。
- Tomcat的配置。(conf/server.xml)
<!-- Define a non-SSL HTTP/1.1 Connector on port 8080 -->
<Connector port="80" maxHttpHeaderSize="8192"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" redirectPort="8443" acceptCount="100"
connectionTimeout="20000" disableUploadTimeout="true" URIEncoding="UTF-8"/>
- Resin的配置。(conf/resin.conf)
character-encoding | Resin 1.1 |
child of: resin, server, host-default, host, web-app-default, web-app
default: The default value is ISO-8859-1.
Specifies the default character encoding for the environment.
<web-app id='/'> <character-encoding>shift_jis</character-encoding> ... </web-app>
|
这个是resin doc里面的我是在web-app-default里面加上了encoding的配置
<web-app-default>
<character-encoding>UTF-8</character-encoding>
</web-app-default> 希望对你的项目有帮助。
posted @
2006-11-16 10:59 保尔任 阅读(218) |
评论 (0) |
编辑 收藏