不积圭步,无以至千里 -- Java

引言)
经常听到这样的对话:"你会Weblogic, Oracle, XML, Kylix, 等等吗?不会?你好差。这么先进的技术怎么能不会?"往往我们也能在看到很多简历,自称精通某某,某某某技术, 乍一看下来,简直是绝顶高手,精通了所有流行的先进技术。但是我常常想,就算是不吃饭不睡觉也不可能在短短时间里精通那么多范围极广,博大精深的技术啊。而我自己,却常常在实际工作中遇到一些问题,让我不得不想起基础知识的重要性。我在这里不是要打击大家学习先进技术的热情,而是为了强调一下基础知识的重要。比如,有很多的java程序员在使用JBuilder, WebLogic, WebSphere, SilverStream,写普通的java程序或者写j2ee, corba结构的程序。在但是,在掌握先进技术的同时,我们也要注重一下基础的修炼,免得不断出现本可以避免的错误。我将陆续写一系列的文章,关于 java编程常见问题。这些,都是我在日常工作中积累下来的一些笔记,不成体系(我尽量将它们按照范围不同组织一下), 严格的说,不能叫做文章吧。大家随便看看。呵呵。

引言)
经常听到这样的对话:"你会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;}
^
但是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编程的人常常遇到的一个问题。以下这段代码,编译会出现错误。
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的类型来决定调用哪一个类的方法。从而可以使我们通过这一机制来完成多样化的程序。(具体涉及到编程的方法问题,不在我们这篇文章的讨论范围,如果有需要,我会在其他文章中介绍。)
再来看另外一个类似的例子:
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);
}
}

很简单的,前面的调用:
b.print();
打印100
System.out.print(b.i);
打印100
d.print();
打印999
System.out.print(d.i);
打印999.
但是
System.out.print(b2.i);
打印什么呢?结果是100, 而不是999。这是因为,在动态联编过程中,只是函数参与了,而对于类中的变量,则还是以静态的方式编译的,就是说,编译器是凭借reference的类型来决定类变量的。b2是base类型的reference, 所以b2.i
是基类中的变量的值。

关于动态联编,还有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)

......未完,待续


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值