经常听到这样的对话:"你会Weblogic, Oracle, xml, Kylix, 等等吗?不会?你好差。这么先进的技术怎么能不会?"往往我们也能在看到很多简历,自称精通某某,某某某技术, 乍一看下来,简直是绝顶高手,精通了所有流行的先进技术。但是我经常想,就算是不吃饭不睡觉也不可能在短短时间里精通那么多范围极广,博大精深的技术啊。而我自己,却经常在实际工作中碰到一些问题,让我不得不想起基础知识的重要性。我在这里不是要打击大家学习先进技术的热情,而是为了强调一下基础知识的重要。比如,有很多的java程序员在使用JBuilder, WebLogic, WebSphere, SilverStream,写普通的java程序或者写j2ee, corba结构的程序。在但是,在把握先进技术的同时,我们也要注重一下基础的修炼,免得不断出现本可以避免的错误。我将陆续写一系列的文章,关于java编程常见问题。这些,都是我在日常工作中积累下来的一些笔记,不成体系(我尽量将它们按照范围不同组织一下), 严格的说,不能叫做文章吧。大家随便看看。呵呵。 (内容) 先来看一个常见的错误。 public boolean testAns(String ans, int n){ boolean rslt; if( ans.equalsIgnoreCase("YES") && n > 5) rslt = true; return rslt; } 程序逻辑方面并没有问题,但是编译的时候会出现错误提示 : variable rslt might not have been initialized return rslt; ^ 这是因为,当if条件为false的时候,rslt可能会没有被赋予初值,而return的时候则会出错。java编译器很聪明的检查了这一错误并在编译的时候给予了提示。这需要你在声明rslt的时候或者在返回rslt之前给它赋值。 比如: public boolean testAns(String ans, int n){ boolean rslt = false; if( ans.equalsIgnoreCase("YES") && n > 5) rslt = true; return rslt; } 相关的问题还有: public boolean testAns(String ans, int n){ boolean rslt = true; while(false){rslt = false;} for(;false;){rslt = false;} if(false) {rslt = false;} return rslt; } 则Java编译器会提示 unreachable statement while(false){rslt = false;} ^ unreachable statement for(;false;){rslt = false;}[/code] ^ 但是if(false)这一段则没有错误提示,编译通过。这也是要注重的一点。 第二个例子:class Object 中有一个方法equals() public boolean Object.equals(Object) 它检查object reference是否相同,也就是说是否指向同一个对象。假如是,则返回true, 否则返回false. 而每一个继续class Object的类都会override这个方法。比如在Long, Integer等class中,equals比较该Ojbect是否相应的是Long, Integer类型。假如类型相同,值比较所包裹的值是否相同。假如相同,则返回true, 否则返回false.要注重的是,返回false并不说明所包裹的值不相同,也可能是类型不同。比如下面代码:
Long l = new Long(7); Integer j = new Integer(7); if(l.equals(j)) System.out.PRintln("Equal"); else System.out.println("Not Equal"); 编译成功,但是输出为Not Equal, 这就是因为类型不同, 不是同为Integer或者 同为Long.
再看一下使用instanceof要注重的问题. instanceof是判定一个对象的引用(reference) 是否某一类型。比如 Integer i = new Integer(0); System.out.println( i instanceof Integer); 返回为true,因为i是一个Integer的对象的引用。 Integer i = new Integer(0); System.out.println( i instanceof Long); 则返回为false, 因为i不是一个Long的对象的引用。 但是, Integer i = null; System.out.println( i instanceof Integer); 返回值为false. 这是因为i的值为null, null不是任何对象的引用。这是需要注重的。
第四个问题,是在邮件列表,news groups中提到次数比较多的一个问题,也是很多初学java编程的人经常碰到的一个问题。以下这段代码,编译会出现错误。 [code]byte x = 100; switch(x) { case 100: case 200: case 300: } 编译器提示 a1.java:6: possible loss of precision found : int required: byte case 200: ^ a1.java:7: possible loss of precision found : int required: byte case 300: ^ 2 errors
因为x为byte类型,但是300超过了byte类型的最大值127, 所以出现了错误。这段代码相当于是 if(x == 100) ... else if(x == 200) ... else if(x == 300) ... 这样子看错误原因就比较明显了。 类似的我们还有这样的代码: short x = 200; switch(x) { case 70000: case 10: case 1: } 编译也会出错,提示case 7000:这一行类型不匹配。
关于primitive类型的赋值问题,还有以下两个需要注重的问题: 这段代码 byte b = 1; short s = b; 不会出错。因为,byte为8 bits, 而short为16 bits,将byte类型的数值赋予给short类型的变量不会引起数值精度问题。但是 short s = 1; byte b = s; 则不能正确编译。因为这样赋值可能导致s所含数值的高8 bit被舍弃,因而数值不正确。这样需要我们在写程序的时候指定 byte b = (byte)s;
以便通知编译器,"嘿, 假如有精度的损失,那是我自愿的,你不必担心!"这样编译就不会出错了。同样的, short s = 1; byte b = 1; b = b + s; 不能正确编译。需要我们在写程序的时候指定 b = (byte)(b + s); 这些错误很多人现在都会避免了。但是这样子的代码是否会编译错误呢? short s = 1; byte b = 1; b += s; 照以上的解释,你一定认为这段代码不能正确编译。但是假如你实际编译一下则会发现,它编译通过了!为什么呢?这是因为, +=, -=, *=, /=, %=这类操作符号比较非凡,对于编译器来说,他们相当于 b += s; -------> b = (byte)(b + s); 呵呵,有意思吧?
inner class的问题,更多的人感到迷惑。 比如,inner class是否能够有static的变量? 一般的说法是,static inner class能够有static的变量,而non-static inner class则不能有static的变量。但是,假如这样: class outer { class inner { static final int i = 999; } } 编译通过。而你不能这么写: static final int i; 就是说,一定要在声明它的时候赋予初值, 因为i是final的,它的值不能再被改变。 关于inner class, 还有一个代码也很能说明一些问题。
public class TestClass { public TestClass(int i) { }
public void m1() { TestClass al = new TestClass(10) { public void actionPerformed(ActionEvent e) { } }; } } 这说明了一下四个问题: 1. 类中的方法也可以有inner classes.(但是他们不能有static inner classes). 2. Inner class 能够继续包含它的外部class. 3. 匿名 inner class 可以继续一个 class 或者 implements 一个 interface。 4. 匿名 inner class 可以有初始化参数。(假如这个class继续的基类有相应的 constrUCtor 的话。)
再来看看动态联编 ( dyanamic link )的问题。 考虑这段代码:
class base{ public void whoami() { System.out.println("Base"); } } class derived extends base{ public void whoami() { System.out.println("Derived"); } }
class test{ public static void main(String[] args) { base b = new base(); derived d = new derived(); b.whoami(); d.whoami(); base b2 = d; b2.whoami(); } } 当然大家很清楚,b.whoami()打印Base, 而d.whoami()打印Derived.但是,b2.whoami()打印什么呢?也就是说,b2.whoami()将调用那一个方法呢?是基类的还是派生类的?运行以后看到,调用的是派生类的方法。这是因为java是在运行过程中采用了动态联编的方法,在运行时刻来决定该reference指向的什么类的对象,从而决定调用哪一个类的方法,而不是根据reference的类型来决定调用哪一个类的方法。从而可以使我们通过这一机制来完成多样化的程序。打印的结果将是Derived。 再来看另外一个类似的例子: class base{ int i = 100; public void print() { System.out.println(i); } } class derived extends base{ int i = 999; public void print() { System.out.println(i); } }
class test{ public static void main(String[] args) { base b = new base();
关于动态联编,还有2个要注重的问题: class base{ } class derived extends base{ public void print() { System.out.println("Derived"); } }
class test{ public static void main(String[] args) { base b = new base(); derived d = new derived(); d.print(); base b2 = d; b2.print();//出错 } } 为什么呢?因为在编译过程中,编译器会检查基类中是否有print()方法,假如没有,则会报错。注重:动态联编出现在运行过程中,而不是编译过程中。
class base{ int i = 100; public void print() { System.out.println(i); } } class derived extends base{ int i = 999; public void print() { System.out.println(i); } }
class test{ public static void main(String[] args) { base b = new base(); derived d = new derived(); b.print(); System.out.print(b.i); d.print(); System.out.print(d.i); base b2 = d; b2.print(); System.out.print(b2.i); derived d2 = b;//出错 d2.print();//出错 System.out.print(d2.i);//出错 } } 这是因为,在编译过程中,derived类型的reference可以赋给base类型的reference. 而base类型的reference则不可以赋给derived类型的reference.假如要这么做, 则需要在赋值的过程中指定 derived d2 = (derived)b; 编译才能通过。这和primitive types的赋值是一样的道理。而编译完成后在运行时刻还需要做进一步的检验,假如类型不能匹配,则会抛出 ClassCastException. Exception in thread "main" java.lang.ClassCastException: base at test.main(a2.java:12)