Java常见问题总结

发表于:2007-07-01来源:作者:点击数: 标签:
1. Java编译运行问题 2. Object操作容易出现NullPointException错误 3. 多余处理语句 4. 参数传递问题 5. 例外处理 6. 数据库操作的问题 7. index越界 8. 其它 9. 有待讨论的问题 1.Java编译运行问题 Java程序文件首先得编绎成字节码的class文件, 然后通过JV
1. Java编译运行问题

2. Object操作容易出现NullPointException错误

3. 多余处理语句

4. 参数传递问题

5. 例外处理

6. 数据库操作的问题

7. index越界

8. 其它

9. 有待讨论的问题















































1.Java编译运行问题
Java程序文件首先得编绎成字节码的class文件, 然后通过JVM来运行。 Java之所以具有平台无关性, 是因为Sun几乎为大部分的操作系统提供了JVM(Java虚拟机), 这样我们只要用统一的API而不用关心底层系统。

在使用Java的初期, 遇到最多的问题恐怕就是java.lang.ClassNotFoundException和java.lang.NoClassDefFoundError,这主要是classpath设置不对的问题, 类似于C/C++里面的动态链接库, 如果你的source里面使用了其它package的API, 这样你在编绎和运行的时候都得将它设置到classpath里面去, 设置的时候可以指向一个目录, 一般为含有所需要的classes的目录, 或者指向一个jar或zip文件, 它们则是classes的打包的文件。如:

set classpath=C:\jdk1.3.1\lib\tools.jar;D:\Develop\CSC\class

在windows command下面运行

set classpath

可以查看当前已经设置的classpath, 如果想追加设置运行:

set classpath=%classpath%;c:\bea\wlserver6.1\lib\weblogic.jar;

如果用命令行来进行编绎运行的话, 得要将jdk的path设置一下。 如:

set path=C:\jdk1.3.1\bin;%path%

当然如果你不嫌麻烦可以指定全路径:

C:\jdk1.3.1\bin\javac yourOwn.java

C:\jdk1.3.1\bin\java yourOwn

Classpath也可以在编绎运行的时候进行指定。 如:

javac –classpath %MY_CLASSPATH% yourOwn.java

java –classpath %MY_CLASSPATH% yourOwn

一般来说, JVM运行的时候有缺省load的classes, 可以运行java –verbose进行查看, 一般是%JDK_HOME%\jre\lib下面的i18n.jar与rt.jar等, 如果将你的jar文件放到这个目录下面的ext目录下面去, 则不用指定它, JVM会自动load这些jar的。

另外需要注意的是, 运行Class的时候是用这个类的Class全名,即包含它的Package名, 如有一个类声明如下:

package cn.com.sunjapan.util



public class StringUtil {

public static void main(String[] args) {

System.out.println(“Hello World”);

}

}

你运行的时候得要用

java cn.com.sunjapan.util.StringUtil

而不能用java StringUtil否则会出java.lang.NoClassDefFoundError。





2.Object操作容易出现NullPointException错误
这种错误恐怕是编程初期最容易犯的错误。 Java是面向对象的语言, 操作几乎都是在对象之间进行的, 一个类的实例如果是空(null)的话则不能调用这个实例的方法, 否则就会出java.lang.NullPointException错误。 最常见的String的操作, 如:

String str = null;

if (str.equals(“Hello”)) {

System.out.println(“str is Hello”);

}

常用的避免方法就是在使用一个Object之前要判断一下是否为null, 除非你确定它肯定是不为null的。 接上例修改如下:

String str = null;

if (str !=null && str.equals(“Hello”)) {

System.out.println(“str is Hello”);

}

对于String的这种equals或equalsIgnoreCase的操作常常还可以用下面的方法进行安全操作:

String str = null;

if (“Hello”.equals(str)) {

System.out.println(“str is Hello”);

}

用一个确定的不为null的String去与未知的String进行比较。





3.多余处理语句
这种问题当然不只是java才有的, 任何程序都有可能出现多余的垃圾, 尽管它的最终结果是正确的, 我们在写程序的时候要尽可能避免这种不必要的处理。

常见的情况有以下几种:



3.1多余的实例构造
声明了一个对象的实例, 有的人喜欢同时new一下,即给它分配了空间, 而在后面并没用到分配的空间, 而是进行了其它的操作,例:

ArrayList resultList = new ArrayList();



try {

resultList = SomeModule.getResultList();

} catch (Exception e) {

return null;

}



resultList在声明的时候同时给它分配了空间, 但在下面却用它指向了另外返回的地址。 虽然在写Java程序的时候我们不用考虑内存的分配等令人头疼的问题, Java有自己的一套内存管理机制, 但Java在对象的构造的时候开销是很大的, 所以诸如此类的浪费效率的处理还是得要注意避免。



3.2循环多余
我们经常会从一个数组或Collection中通过循环来找出一个符合条件的元素进行操作, 而在执行完之后往往会忘记跳出循环体。 例:

String[] week = new String[]{“Sun”, “Mon”, “Tue”, “Wen”, “Thu”, “Fri”, “Sat”};

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

if (week[i].equals(“Tue”)) {

System.out.println(“Tue is found”);

}

}

如果上面这段程序只是找出week中是否有Tue的话,则在找到之后应该跳出循环体, 正确的写法如下:

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

if (week[i].equals(“Tue”)) {

System.out.println(“Tue is found”);

break; (或有可能 return)

}

}



循环处理的原则就是在处理完毕的地方跳出。

3.3 重复语句
建议如果有两个或两个以上的地方需要用到相同的程序块代码, 就要考虑到使用函数, 如果一个功能块比较独立, 有可能在其它被调用, 这个时候也尽可能的使用方法独立开来。

还有种情况就是在条件分支语句里面, 各个分支都需要执行某个相同的语句, 这个时候就需要提到分支的外面去执行,下面给出几个例子:

&Oslash; 使用方法

public void someMethod1() {



String str = “This is a sample”;

// 对str进行一定的处理, 返回一个新的str

if (str != null) {

str = …;

}



}



public void someMethod2() {



String str = “This is a sample”;

// 对str进行一定的处理, 返回一个新的str

if (str != null) {

str = …;

}



}

蓝色字体部分的功能就可以使用一个独立的方法完成, 这样在两个地方中调用同一个方法就可以了。 在后期的维护等方面都有帮助, 不用到处去找。改写如下:

public void someMethod1() {



String str = “This is a sample”;

str = getSomeStr(str);



}



public void someMethod2() {



String str = “This is a sample”;

str = getSomeStr(str);



}



/**

* 对str进行一定的处理, 返回一个新的str

*/

private String getSomeStr(String str) {

if (str != null) {

str = …;

}



return str;

}



&Oslash; 语句合并



if (expression1) {



someStatement;

} else if (expression2) {



someStatement;

} else {



someStatement;

}









switch (flag) {

case result1:



someStatement;

break;

case result2:



someStatement;

break;

default:



someStatement;

break;

蓝色字体部分为在每个条件分支都会执行到的部分, 这样就大不必写在每个分支里面, 而是调到条件语句外面统一调用, 改写如下:



if (expression1) {



} else if (expression2) {



} else {



}

someStatement;









switch (flag) {

case result1:



break;

case result2:



break;

default:



break;

}

someStatement;







4. 参数传递问题
4.1 一般对象传递
调用一个方法需要传递参数的时候, 如果参数为一个对象, 则缺省的是按照地址进行传递的, 类似于C/C++里面的指针, Java称之为”句柄”。 例:

public class TestPassParam {

public SomeClass param = new SomeClass();



public void processParam(SomeClass newParam) {

System.out.println(newParam == param);

}



public static void main(String[] args) {

TestClassParam testClassParam = new TestClassParam();

TestClassParam.processParam(testClassParam.param);

}

}

结果应该返回true; 因为它们指向同一个句柄, 即地址相同。 如果参数是基本数据类型, 则是按照值传递, Java没有象C那样的引用传递。

看下例:

private SomeActionForm processActionForm(SomeActionForm myForm) {

SomeActionForm otherForm = myForm;



otherForm…..

…;



return otherForm;

}

调用如下:

myForm = processActionForm(myForm);

其中myForm是一个对象, 它在传递到processActionForm中去做处理的时候, 先用另外一个otherForm去接了一下, 这个时候otherForm也就指向myForm的句柄, 而在外面再用myForm去接返回值, myForm这个时候其实还是指向原来的句柄。 虽然结果没错, 但这个时候有个问题就是同时有两个别名指向同一个句柄。 执行其中任何一个别名的方法, 另一个别名的对象也随着改变了。 所以上面的这个例子要尽量改写为:

private void processActionForm(SomeActionForm myForm) {



myForm…..

…;



return otherForm;

}

调用如下:

processActionForm(myForm);

特别在画面之间传递参数的时候, 除非你能保证你的数据不被别人篡改, 否则你就得小心, 或者你需要重新clone一个对象传过去, 然后取返回值。

注: 如果在EJB的client与server之间传递参数, 则不存在句柄的传递, 数据在server端修改之后client端并不改变。 因为它们是通过网络进行Object字节流传输的, 不存在句柄地址相同的条件。 你尽可放心的使用, 最后如果client端需要再使用这个新的Object, 则需要再回传过去。 (EJB2.0 支持local interface, 这种情况下有可能就跟前面所说的句柄传递一样了, 没试过)。



4.2 String的特殊性
String在Java中被设计成安全的String, 对于String的任一个操作都是先重新生成一个String的拷贝, 然后对这个拷贝进行操作。 所以String在参数传递的时候可以看作是值传递。 即如果你需要修改一个String并返回修改后的String, 你得要再去接一下返回值。 如:

String str = “This is a sample”;

str = editStr(str);

System.out.println(str); // “Here is a sample”



private String editStr(String str) {

String newStr = str. substring(4);

newStr = “Here” + newStr;



return newStr;

}

如果想用句柄传递, 可以使用String的内部操作使用的一个类StringBuffer, 对它的操作都是同一个对象上进行的, 所以效率也自然高一些。 上面的例子用StringBuffer改写如下:

StringBuffer str = new StringBuffer(“This is a sample”);

editStr(str);

System.out.println(str); // “Here is a sample”



private void editStr(StringBuffer str) {

str.replace(0, 4, “Here”);

}



5. 例外处理
有些新手总是习惯于使用返回值来进行错误处理, 如果使用异常处理这种方法将会使程序结构更合理, 效率更高。 比如在Client端需要通过EJB来进行DB操作, Client端需要知道DB处理有没有出错就可以通过层层的向上抛Exception的方法, 一直到Client端需要处理的地方截住, 然后进行例外处理。 如:

后台DB处理:

public static java.sql.Timestamp getDBSysdate() throws CSCWebException {

Connection conn = null;

Timestamp sysTime = null;



try {

conn = PJEJBSvrUtil.getWLPoolConnection();

sysTime = CommonDAO.getDBSysdate(conn);

} catch (SQLException ce) {

throw new CSCWebException(ce.getMessage());

} finally {

try {

if (conn != null) {

conn.close();

}

} catch (Exception e) {

throw new CSCWebException(e.getMessage());

}

}



return sysTime;

}

在前台:

try {

commonIntf.getDBSysdate();

} catch (CSCWebException cex) {

cat.debug(“”, cex);

return getExceptionForward(cex);

}

要防止违例被漏处理, 除非是你肯定不需要处理的, 提倡在遇到Exception的时候就要往上抛, 由最终调用处来进行处理, 当然也不能一概而论, 视情况而定。 比如我想例外在方法体内就要解决掉, 给出一个CSC中出现的bug。

public boolean checkTelFormat(String telNo) {

boolean error = false;



if ( telNo == null || telNo.equals("") ) {

error = true;

} else {

if ( ejb.util.StringUtil.chkPhone(telNo) ) {

error = false;

} else {

error = true;

}

}



if (telNo.startsWith("184") || telNo.startsWith("186")) {

if (telNo.length() == 3) {

error = true;

}

}



if ( error ) {

ObjMngr.showError("MCSTC001E");

cmbTelNo.requestFocus();

return false;

} else {

return true;

}

}

用了一个Flag来记住每次Check的结果, 然后在最后再出Error Dialog, 这是比较典型的C的写法, 改写如下:

public boolean checkTelFormat(String telNo) {

try {

if (telNo == null || telNo.equals("")) {

throw new Exception();

} else {

if (!ejb.util.StringUtil.chkPhone(telNo)) {

throw new Exception();

}

}

if (telNo.startsWith("184") || telNo.startsWith("186")) {

if (telNo.length() == 3) {

throw new Exception();

}

}

} catch (Exception e) {

ObjMngr.showError("MCSTC001E");

cmbTelNo.requestFocus();

return false;

}



return true;

}

使用了抛Exception的方法, 在方法的最后截住, 这样一遇到Error就能马上处理掉, 从效率上讲也是最高的。

6.数据库操作的问题
根据现有的开发经验, 一般我们在SessionBean(或不用EJB的时候的Module Bean)中取到Connection然后调用专门操作数据库(DAO)中的方法, 也就是在Bean中进行Connection的打开与关闭操作, 而在DAO中进行Statement和ResultSet操作, 一般在最后都需要进行关闭它们。 接上节的例子, 在CommonDAO里面有这样的一个方法:

public static java.sql.Timestamp getDBSysdate(Connection conn)

throws CSCWebException {

Statement stmt = null;

ResultSet rs = null;

Timestamp sysTime = null;



try {

stmt = conn.createStatement();

rs = stmt.executeQuery("SELECT SYSDATE FROM DUAL");

if (rs.next()) {

sysTime = rs.getTimestamp("SYSDATE");

}

} catch (SQLException ce) {

throw new CSCWebException(ce.getMessage());

} catch (Exception e) {

throw new CSCWebException(e.getMessage());

} finally {

try {

if (rs != null) {

rs.close();

}

if (stmt != null) {

stmt.close();

}

} catch (Exception e) {

throw new CSCWebException(e.getMessage());

}

}



return sysTime;

}

在finally里面的语句总是会被执行到的, 所以即使上面抛出了SQLException也会去执行stmt与rs的close操作的。



7. index越界
index越界包括很多:

&Oslash; String的index

String的index从0开始, 最大为它的字符长度。常用的为substring这个方法:

String str = “This is a sample”;

str.substring(5, 7); 返回 is;



str.substring(15, 16) 或 str.substring(15); 都返回最后一个字符 e;



str.substring(16, 17); 这个会抛java.lang.StringIndexOutOfBoundsException错误;



&Oslash; 数组下标

数组下标也由0开始, 最大为长度-1, 例:

int[] columnLen = new int[]{20, 30, 40, 30, 25, 50, 65, 100};

columnLen[0]为20;

columnLen[7]为100;

columnLen[8]会抛java.lang.ArrayIndexOutOfBoundsException错误。



&Oslash; Vector, ArrayList等Collection的size

Vector与ArrayList同属于List, 它们都是有序的集合体, 下标也都是从0开始, 最大也是长度-1, 跟数组不同的是, 它们的元素必须都是Object, 但可以为不同类型的对象, 不过在取出之后得要进行类型转换。 而数组得要所有元素的类型相同。 ArrayList在构造之后, 并不存在元素的情况下, 如果调用set(index, Object)就会出错。得要先进行add(Object)才行。 例:

ArrayList list = new ArrayList(10); // 这个地方的10只是list的初始容量, 并不代表它具有了10个元素, 这个跟数组不同, 数组在这种情况下具有了10个初始值, 初始值跟具体的元素类型有关, 一般的对象为null;

list.set(0, “First”); // 会抛java.lang.IndexOutOfBoundsException

list.add(“First”);

list.set(0, “new First”); // 正确, 因为位置0已经存在元素



8.其它
具体到每个项目, 不同的API使用都还有可能遇到一些共通的问题。 需要在项目开始的时候进行必要的培训, 往往给出一个好的sample会事半功倍。

还用一些问题在此不再详细描述:

ü 大小写问题(String.equals()与String.equalsIgnoreCase(), 变量名等)

ü 括号匹配问题

ü 。。。



9.有待讨论的问题
9.1 方法返回地点
private int getStatus( String sFlag ){

if (CodeBook.STATUS_OK_NAME.equals( sFlag )) {
return CodeBook.STATUS_OK_VALUE;
} else if (CodeBook.STATUS_OK1_NAME.equals( sFlag )) {
return CodeBook.STATUS_OK1_VALUE;
} else if (CodeBook.STATUS_NG_NAME.equals( sFlag )) {
return CodeBook.STATUS_NG_VALUE;
}


return CodeBook.STATUS_NG_VALUE;
}



private int getStatus(String sFlag) {

int iStatus = CodeBook.STATUS_NG_VALUE;



if (CodeBook.STATUS_OK_NAME.equals(sFlag)) {

iStatus = CodeBook.STATUS_OK_VALUE;

} else if (CodeBook.STATUS_OK1_NAME.equals(sFlag)) {

iStatus = CodeBook.STATUS_OK1_VALUE;

} else if (CodeBook.STATUS_NG_NAME.equals(sFlag)) {

iStatus = CodeBook.STATUS_NG_VALUE;

}



return iStatus;

}



9.2 变量声明点
例:

for (int i = 0; i < 10; i++) {

String sTmp = String.valueOf(i);

...

}



String sTmp = null;

for (int i = 0; i < 10; i++) {

sTmp = String.valueOf(i);

...

}

原文转自:http://www.ltesting.net