Java程序员常犯的十大错误

来源:互联网 发布:java字符串分割 编辑:程序博客网 时间:2024/04/28 22:23

初次翻译,如有不妥之处还望大家批评指正!!!

Java程序员常犯的十大错误

无论你是一名熟练的java程序员,熟悉java的程度就像熟悉自己的手背一样;或者你是一名java新手,你都会犯错误。这是很自然的,更是人之常情。你所想象不到的确实,你犯的错误很可能是其他人也在犯的错误,这些错误犯了一次又一次。在这里我给出来了经常犯的十大错误列表,通过它我们可以发现它们并解决它们。

10.在静态方法中访问非静态的成员变量(例如在main方法中)。

许多程序员,特别是那些刚刚接触JAVA的,都有一个问题,就是在main方法中访问成员变量。Main方法一般都被标示为“静态的”,意思就是我们不需要实例化这个类来调用main方法。例如,java虚拟机能够以这样的形式来调用MyApplication类:

MyApplication.main ( 命令行参数 );

这里并没有实例化MyApplication类,或者这里没有访问任何的成员变量。例如下面的程序就会产生一个编译器的错误。

public class StaticDemo
{
        public String my_member_variable = "somedata";
        public static void main (String args[])
        {
                 // Access a non-static member from static method
                System.out.println ("This generates a compiler error" +
                          my_member_variable );
        }
}

如果你要访问一个静态方法中的成员变量(比如main方法),你就需要实例化一个对象。下面这段代码示例了如何正确的访问一个非静态的成员变量,其方法就是首先实例化一个对象。

public class NonStaticDemo
{
        public String my_member_variable = "somedata";
 
        public static void main (String args[])
        {
                NonStaticDemo demo = new NonStaticDemo();
 
                 // Access member variable of demo
                System.out.println ("This WON'T generate an error" +
                        demo.my_member_variable );
        }
}

9.在重载的时候错误的键入方法名

重载允许程序员用新的代码去覆盖方法的实现。重载是一个便利的特性,很多面对对象的程序员都在大量的使用它。如果你使用AWT1.1的事件处理模型,你通常会覆盖listener方法去实现定制的功能。一个在重载方法的时候很容易犯的错误就是错误的键入要重载的方法名。如果你错误的输入了方法名,你就不是在重载这个方法了。相反的,你是在重新定义一个方法,只不过这个方法的参数和返回类型和你要重载的方法相同罢了。public class MyWindowListener extends WindowAdapter {
         // This should be WindowClosed
         public void WindowClose(WindowEvent e) {
                 // Exit when user closes window
                 System.exit(0);
         }
};

这个方法不会通过编译,很容易就能捕捉找它。在过去我曾经注意过一个方法,并且相信它是被调用的,花了很多时间找这个错误。这个错误的表现就是你的方法不会被调用,你会以为你的方法已经被跳过了。一种可行的解决方法就是增加一条打印输出语句。在日志文件中记录下信息。或者是使用跟踪调试程序(例如VJ++或者是Borland JBuilder)来一行一行的调试。如果你的方法还不能被调用,那很可能就是你的方法名键入错误了。

8.比较和分配(“=”强于“==)

这是一个很容易犯的错误。如果你以前使用过别的语言,比如Pascal,你就会知道计算机语言的设计们选择这样的方式是何等的乏味。举个例子,在Pascal中,我们使用:=运算符来表示分配,而使用=来表示比较。这样好像是退回了C/C++,也就是java的起源。

幸运的是,即使你没有发现在屏幕上发现这个错误,你的编译器会帮助你发现它。通常情况下,编译器会报出这样一个错误信息:“不能转换xxx到布尔类型”,这里的XXX是你用来代替比较的java类型。

7.比较两个对象(==来代替instead of)

当我们使用==运算符的时候,我们实际上是在比较两个对象的引用,来看看他们是不是指向的同一个对象。举个例子,我们不能使用==运算符来比较两个字符串是否相等。我们应该使用.equals方法来比较两个对象,这个方法是所有类共有的,它继承自java.lang.Object

下面是比较两个字符串相等的正确的方法。

// Bad way
if ( (abc + def) == "abcdef" )
{
    ......
}
// Good way
if ( (abc + def).equals("abcdef") )
{
   .....
}
 

6.混淆值传递和引用传递。

这是一个不太容易发现的错误。因为,当你看代码的时候,你会十分确定这是一个引用传递,而它实际上却是一个值传递。Java这两者都会使用,所以你要理解你什么时候需要值传递,什么时候需要引用传递。当你要传递一个简单的数据类型到一个函数中,比如,charintfloat或者double,你是在传递一个值。这个意味着这种数据类型的被复制了一个拷贝,是这个拷贝被传递到了函数中。如果这个函数去修改这个值,仅仅是这个值的拷贝被修改了。这个函数结束以后,将会返回到控制调用函数去,这时候那个“真正的”值没有受到影响,没有任何改变被存储。

如果你想修改一个简单的数据类型,可以将这个数据类型定位一个返回值或者将它封装到一个对象中。

当你要传递一个java对象到一个函数中,比如,数组、向量或者是一个字符串,此时你传递的就是一个对象的引用。这里的字符串也是一个对象,而不是一个简单数据类型。这就意味这你要传递一个对象到一个函数,你就要传递这个对象的引用,而不能去复制它。任何对这个对象的成员变量的改变都会持久化,这种改变的好坏要取决于你是否是刻意而为之。

有一点要注意,如果字符串没有包含任何方法改变它的值的时候,你最好将它作为值来传递。

5.写一个空的异常处理。

我知道一个空的异常处理就像忽略错误一样很诱人。但是如果真的发生了错误,你不会得到一个错误信息的输出,它使得不太可能发现错误的原因。甚至是最简单的异常处理都是很有用处的。举个例子,在你的代码加上try{}catch{},去试着捕捉任何的异常抛出,并打印出错误信息。你不用为每个异常都写出定制的处理(虽然这是一个很好的编程习惯)。但是不要将这个异常处理空着,否则你就不会知道有什么错误发生了。

举例:

public static void main(String args[])
{
    try {
         // Your code goes here..
    }
    catch (Exception e)
    {
         System.out.println ("Err - " + e );
    }
}

4.忘记java中索引是从0开始的。

如果你有C/C++的编程背景的话,你在使用其他编程语言的时候就不会发现同样的问题了。

java中数组的索引是从0开始的,这就是说第一个元素的索引必须是0.困惑了?让我们看看例子吧。

// Create an array of three strings
String[] strArray = new String[3];
 
// First element's index is actually 0
strArray[0] = "First string";
 
// Second element's index is actually 1
strArray[1] = "Second string";
 
// Final element's index is actually 2
strArray[2] = "Third and final string";

在这个例子中,我们定义了一个有着三个字符串的数组,当我们访问它的元素时候减去了一个。现在,当我们试着去访问strArray[3],也就是第四个元素的时候,就会有一个ArrayOutOfBoundsException异常被抛出。这个就是最明显的例子-忘记了0索引规则。

在其他地方0索引规则也能使你陷入麻烦。例如字符串中。假设你要从一个字符串确定的偏移位置处得到一个字符,使用String.charAtint)函数,你就能看到这个信息。但是在java中,字符串类的索引也是从0开始的,这就是说第一个字符的偏移位置为0,第二个为1.你可能会陷入一些麻烦,如果你不注意这个问题的话,特别是你的应用程序中使用了大量的字符串处理程序,那样的话你就很可能使用错误的字符,同时在运行是抛出一个StringIndexOutOfBoundsException异常,就像ArrayOutOfBoundsException异常一样。下面的例子证明了这些:

public class StrDemo
{
 public static void main (String args[])
 {
        String abc = "abc";
 
        System.out.println ("Char at offset 0 : " + abc.charAt(0) );
        System.out.println ("Char at offset 1 : " + abc.charAt(1) );
        System.out.println ("Char at offset 2 : " + abc.charAt(2) );
 
         // This line should throw a StringIndexOutOfBoundsException
        System.out.println ("Char at offset 3 : " + abc.charAt(3) );
 }
}
同时应该注意的是,0索引规则不应该只应用在数组或是字符串中,java的其他部分也会用到。但是并不是全部都会用到。Java.util.Datejava.util.Calendar,这两个类的月份都是从0开始的,但是日期却通常是从1开始的,下面的程序证明了这一点。
import java.util.Date;
import java.util.Calendar;
 
public class ZeroIndexedDate
{
        public static void main (String args[])
        {
                // Get today's date
                Date today = new Date();
         
                 // Print return value of getMonth
                 System.out.println ("Date.getMonth() returns : " +
                           today.getMonth());
 
                 // Get today's date using a Calendar
                 Calendar rightNow = Calendar.getInstance();
 
                 // Print return value of get ( Calendar.MONTH )
                 System.out.println ("Calendar.get (month) returns : " +
                          rightNow.get ( Calendar.MONTH ));
        }
}
0索引规则在你不注意它的时候就会发生,如果你不想在运行时遇到这个错误的话,请注意查阅你的API文档。
3.防止线程在共享变量中并行存取。
在写一个多线程的应用程序的时候,许多程序员都喜欢抄近路。而这样会是他们的应用程序或者是小应用程序发生线程冲突。当两个或者两个以上的线程访问同一个数据的时候,就存在一定的概率(概率大小取决与墨菲法则)使得两个线程同时的访问或者修改同一个数据。不要愚蠢的认为这样的情况不会发生在单线程的应用程序中。当访问同一个数据的时候,你的线程就很可能被挂起,而第二个线程进入是就会覆盖第一个线程修改的地方。
这样的问题不是仅仅出现在多线程应用程序或者是小应用程序中的。如果你写了java api或者是java bean,你的代码就很可能不是线程安全的。即使你从来没有写过一个使用线程的单独的应用程序,人们也有可能使用你的程序。为了其他人,不仅仅是你,你就应该采取措施防止线程在共享变量中并行存取。
怎样来解决这个问题呢,最简单的就是让你的变量私有化。同时使用同步存取方法。存取方法允许访问似有的成员变量,但是仅仅是在一种控制方式中。下面的存取方法就能够以安全的方式修改计数器的值。
public class MyCounter
{
         private int count = 0; // count starts at zero
 
         public synchronized void setCount(int amount)
         { 
                 count = amount;
         }
         
         public synchronized int getCount()
         {
                 return count;
         }
}

2.大写错误。

这是一个我们最经常犯的错误。它是很简单的,但是有时我们看着一个没有大写的变量或者方法却并不能发现这个错误。我自己也常常感到困惑,因为我认为这些方法和变量都是存在的,但是却发现不了他们没有大写。

这里你不能用银子弹来检查它,你只能自己训练着来减少这种错误。这里有一个窍门:

Java api中所用的方法和变量名都应该以小写字母来开头。

所有的变量名和方法名的新词的开头都要用大写字母。

如果你以这样的形式来定义你的变量名和类名,你就是在有意识的使它们走向正确,你就能逐渐的减少这样错误的数量。这可能需要一段时间,但是在以后有可能会避免更为严重的错误。

 

下来就是java程序员最常犯的错误了!!!

1.空指针!

空指针是java程序员最经常犯的错误了。编译器不会为你检查出这个错误它仅仅在运行时在表现出来,如果你发现不了它,你的用户将很可能发现它。

当试着访问一个对象的时候,这个对象的引用就是空的,一个NullPointerException异常就会被抛出。空指针错误的原因是多种多样的,但是一般情况下发生这种错误意味着你没有对一个对象初始化,或者是你没有检查一个函数的返回值。

许多函数返回一个空是用来指示一个错误的条件被执行。如果你不检查返回值的话,你由于不可能知道发生了什么。既然原因是一个错误的条件,一般的测试就不会发现它,这就意味着你的用户可能在最后的时候替你发现它。如果API函数指明一个空的对象很可能被返回,那在使用对象的引用之前一定要进行检查。

另外的原因可能是你在初始化对象的时候不规范,或者是它的初始化是有条件的。举例,检查下面的代码,看看你是否能发现这个错误。

public static void main(String args[])
{
         // Accept up to 3 parameters
         String[] list = new String[3];
 
         int index = 0;
 
         while ( (index < args.length) && ( index < 3 ) )
         {
                 list[index++] = args[index];
         }
 
         // Check all the parameters 
         for (int i = 0; i < list.length; i++)
         {
                 if (list[i].equals "-help")
                 {
                          // .........
                 }
                 else
                 if (list[i].equals "-cp")
                 {
                          // .........
                 }
                 // else .....
         }        
}

上面的代码(作为人为的例子),显示了通常的错误。在某些情况下,用户输入了三个或者更多的参数,上述代码将会正常运行。但是如果没有参数被输入,那么在运行的时候就会得到一个空指针异常。某些时候你的变量将会被初始化,但是其他时候它们却不会。一个简单的解决办法就是在你访问数组元素的时候先检查它十分为空。

总结:

这些错误是我们常犯的错误的一些代表。虽然在编码的时候不可能完全的避免错误,但是你应该去避免犯一些重复的错误。很明显的是,所有的java程序员都会犯这样的错误。唯一能让人感到安慰的就是,当你在夜深人静的时候去跟踪一个错误,在某时某地某个人也在犯着同样的错误。

 

 

原创粉丝点击