java編程陷阱


1.首先什么是陷阱

  簡潔的定義:

      陷阱,是指那些能夠正常編譯,但是在執行時卻產生事與願違的,有時候甚至是災難性后果的程序代碼。

  廣義的定義:

      任何可能導致程序員把大量的時間浪費在開發工具的使用上而不是最終軟件的進展上的語言特性、API或系統,都可以稱呼為陷阱。

2、陷阱的分類

  

3、分析陷阱三重奏

  a.症狀或者問題

    首先找到是哪一個代碼造成的問題,陷阱的類型是什么。

  b.問題的根源

    這個是揭示陷阱最重要的一個部分,我們要深入底層,了解可能導致程序員絆腳的詳細內部工作過程、無效的假設或者API的缺陷。

  c.解決方案

    這個是分析陷阱的最后一個步驟,最終給出一個程序實現和運行結果。

例子1:找奇數

 1   //找奇數
 2     public static boolean isOdd(int i) {
 3         return i % 2 == 1;
 4     }
 5     
 6     public static void main(String[] args) {
 7         System.out.println(isOdd(1));
 8         System.out.println(isOdd(2));
 9         System.out.println(isOdd(3));
10         System.out.println(isOdd(-1));
11         System.out.println(isOdd(-2));
12     }

你會發現所有的負數在isOdd()方法里返回的都是false

更正方法:

1   public static boolean isOdd(int i) {
2         return i % 2 != 0;
3     }

 

例子2:浮點數相減

1   public static void main(String[] args) {
2         System.out.println(2.0-1.1);
3     }

你覺得得數會是多少?0.9?還是0.9000.....?

答案是:0.8999999999999999 可是我要的是0.9啊! 那怎么怎么辦呢?

1   public static void main(String[] args) {
2         System.out.println(new BigDecimal("2.0").subtract(new BigDecimal("1.1")));
3     }

這樣就實現了變成0.9了

如果你覺得太麻煩了那你還能這樣:

 

1 System.out.printf("%.1f",2.0-1.1);

 

例子3、長整數

1 public static void main(String[] args) {
2         final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
3         final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
4         System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
5     }

你一看覺得得數是多少? 很多人回答會是 1000 因為大家都能算的出來  但是你運行發現得數是5  你會說MICROS_PER_DAY 是long啊 應該不會溢出啊,

這是因為在java中 24 默認是int型的 那么24 * 60 * 60 * 1000 * 1000都會是int型的 當然就會溢出 最后會把溢出后的值給MICROS_PER_DAY 所以最后的值會莫名其妙

改正:

1 public static void main(String[] args) {
2         final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000;
3         final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
4         System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
5     }

NO.4  互換內容

1 public static void main(String[] args) {
2         int x = 1234;
3         int y = 5678;
4         x ^= y^= x^= y;
5         System.out.println(" x = " + x + " , y =" + y);
6     }

在c或者C++中有人見過這樣的交換兩個數的方法 。但是在java中有點不相同 結果是  x = 0 , y =1234

其實它要的原理是這樣的:

1 public static void main(String[] args) {
2         int x = 1234;
3         int y = 5678;
4         y = x ^ y;
5         x = x ^ y;
6         y = x ^ y;
7         System.out.println(" x = " + x + " , y =" + y);
8     }

當由於java和c/C++存在差異所以才會出錯。

更正:

1 public static void main(String[] args) {
2         int x = 1234;
3         int y = 5678;
4         y = (x ^= (y^= x)) ^ y;
5         System.out.println(" x = " + x + " , y =" + y);
6     }

No 5 :隨機數的問題

 1 public static void main(String[] args) {
 2         Random random = new Random();
 3         StringBuffer sb = null;
 4         switch (random.nextInt(3)) {
 5         case 1:
 6             sb = new StringBuffer('P');
 7             break;
 8         case 2:
 9             sb = new StringBuffer('M');
10             break;
11         default:
12             sb = new StringBuffer('G');
13             break;
14         }
15         
16         sb.append('a');
17         sb.append('i');
18         sb.append('n');
19         System.out.println(sb.toString());
20     }

你覺得會打印出什么? 很多人會說是 "Pain" 或者 "Main" 或者 "Gain" 。

其實都錯了  答案只有一個:"ain"

其實主要的原因來自StringBuffer的構成方法 當你使用 new StringBuffer('G')是'G'會解釋成int 型  所以調用的構造方法是:

StringBuffer(int capacity) 構造一個不帶字符,但具有指定初始容量的字符串緩沖區。

而不是

StringBuffer(String str) 構造一個字符串緩沖區,並將其內容初始化為指定的字符串內容

所以改正的方法不言而喻了。

NO.6  無情的增量操作

1 public static void main(String[] args) {
2         int j = 0 ;
3         for (int i = 0; i < 100 ; i++) {
4             j = j++;
5         }
6         System.out.println(j);
7         
8     }

這個題目你一個不注意就會喊出 輸出100 ;

其實答案是 0  這里的 j = j++; 從第一次賦值開始 j 就是被賦給了0 因為j++是后綴++ 賦值的時候還沒有增值。

NO.7  整數邊界的問題

 1 public static final int MAX = Integer.MAX_VALUE;
 2     public static final int START = MAX - 100;
 3     public static void main(String[] args) {
 4         int j = 0 ;
 5         for (int i = START; i <= MAX ; i++) {
 6             j++;
 7         }
 8         System.out.println(j);
 9         
10     }

 

你可能會很快的回答100 然后細心地看了下 說 101 

其實答案都不是  程序在無限循環着 

這是因為Integer.MAX_VALUE + 1 = Integer.MIN_VALUE 所以在for循環中 i=MAX 后 i++ 這時的 i 變成Integer.MIN_VALUE 了

 依然滿足條件

要循環結束 在for中的 i <= MAX 變成 i < MAX 即可。

 

 NO.8  計數器的問題

1 public static void main(String[] args) {
2         int minutes = 0 ;
3         for (int ms = 0; ms < 60 * 60 * 1000; ms++) {
4             if(ms % 60*1000 == 0) {
5                 minutes ++ ;
6             }
7         }
8         System.out.println(minutes);
9     }

 你有可能一下子就說出了是 輸出 60 因為能整除 60 * 1000 的只能是 1 * 60 * 1000 ,2 * 60 * 1000 ....一直到 60 * 60 * 1000 一共60 個 

但是 答案讓你失望了  是60000

其實 這個的陷阱是在 ms % 60*1000 == 0 的操作符的優先級上  % 和 * 是同一級的 所以其實他是(ms % 60) *1000 == 0 這樣的 滿足ms % 60 這個就多了

 

NO.9  優柔寡斷的返回值

 1     public static void main(String[] args) {
 2         System.out.println(decisor());
 3     }
 4     
 5     public static boolean decisor(){
 6         try {
 7             return true;
 8         } finally {
 9             return false;
10         }
11     }

你覺得會返回的是什么?

答案是 false;

那你再看一個:

NO.10  你好,再見

1     public static void main(String[] args) {
2         try {
3             System.out.println("Hello world");
4             System.exit(0);
5         } finally{
6             System.out.println("GoodBye world");
7         }
8     }

你又會覺得會返回的是什么?

答案是 僅僅只有 Hello world

NO.11  打開Javac進程

 

 1 public static void main(String[] args) {
 2         try {
 3             Runtime runtime = Runtime.getRuntime();
 4             Process proc = runtime.exec("C:\\Program Files\\Internet Explorer\\iexplore.exe");
 5             int exitVal = proc.exitValue();
 6             System.out.println("process exitVal :" + exitVal);
 7         } catch (IOException e) {
 8             e.printStackTrace();
 9         }
10     }

 

運行時你會發現IE啟動了  但是拋異常了

1 Exception in thread "main" java.lang.IllegalThreadStateException: process has not exited
2     at java.lang.ProcessImpl.exitValue(Native Method)
3     at Test.main(Test.java:9)

這個異常的原因是在我們打開IE的時候就執行了proc.exitValue()即返回退出時的值。可是我們還沒有退出IE啊 所以才會報異常

解決方法:給成

int exitVal = proc.waitFor();

這樣看起來沒什么啊,是啊 很好理解 那你再看一個

 1 public static void main(String[] args) {
 2         try{            
 3             Runtime rt = Runtime.getRuntime();
 4             Process proc = rt.exec("javac");
 5             int exitVal = proc.waitFor();
 6             System.out.println("Process exitValue: " + exitVal);
 7         } catch (Throwable t){
 8             t.printStackTrace();
 9         }
10     }

你運行的時候會發現程序會死在那里: 這是因為javac會一直等待它要執行的東西 所以一直在等待:

改正:

 1 public static void main(String[] args) {
 2         try{            
 3             Runtime rt = Runtime.getRuntime();
 4             Process proc = rt.exec("javac");
 5             InputStream stderr = proc.getErrorStream();
 6             InputStreamReader isr = new InputStreamReader(stderr);
 7             BufferedReader br = new BufferedReader(isr);
 8             String line = null;
 9             System.out.println("<ERROR>");
10             while ( (line = br.readLine()) != null)
11                 System.out.println(line);
12             System.out.println("</ERROR>");
13             int exitVal = proc.waitFor();
14             System.out.println("Process exitValue: " + exitVal);
15         } catch (Throwable t){
16             t.printStackTrace();
17         }
18     }

運行結果:

<ERROR>
用法:javac <選項> <源文件>
其中,可能的選項包括:
  -g                         生成所有調試信息
  -g:none                    不生成任何調試信息
  -g:{lines,vars,source}     只生成某些調試信息
  -nowarn                    不生成任何警告
  -verbose                   輸出有關編譯器正在執行的操作的消息
  -deprecation               輸出使用已過時的 API 的源位置
  -classpath <路徑>            指定查找用戶類文件和注釋處理程序的位置
  -cp <路徑>                   指定查找用戶類文件和注釋處理程序的位置
  -sourcepath <路徑>           指定查找輸入源文件的位置
  -bootclasspath <路徑>        覆蓋引導類文件的位置
  -extdirs <目錄>              覆蓋安裝的擴展目錄的位置
  -endorseddirs <目錄>         覆蓋簽名的標准路徑的位置
  -proc:{none,only}          控制是否執行注釋處理和/或編譯。
  -processor <class1>[,<class2>,<class3>...]要運行的注釋處理程序的名稱;繞過默認的搜索進程
  -processorpath <路徑>        指定查找注釋處理程序的位置
  -d <目錄>                    指定存放生成的類文件的位置
  -s <目錄>                    指定存放生成的源文件的位置
  -implicit:{none,class}     指定是否為隱式引用文件生成類文件 
  -encoding <編碼>             指定源文件使用的字符編碼
  -source <版本>               提供與指定版本的源兼容性
  -target <版本>               生成特定 VM 版本的類文件
  -version                   版本信息
  -help                      輸出標准選項的提要
  -Akey[=value]              傳遞給注釋處理程序的選項
  -X                         輸出非標准選項的提要
  -J<標志>                     直接將 <標志> 傳遞給運行時系統

</ERROR>
Process exitValue: 2

NO.12  日志粒度的控制

 1 public class BadLogger {
 2     private Logger m_log = null; 
 3 
 4     public BadLogger(Level l){
 5         ConsoleHandler ch = new ConsoleHandler();
 6         m_log = Logger.getLogger("no2.BadLogger.logger");
 7         ch.setLevel(l);
 8     }
 9 
10     public void test(){                          
11         System.out.println("The level for the log is: "+ m_log.getLevel());  
12         m_log.finest("This is a test for finest");
13         m_log.finer("This is a test for finer");
14         m_log.fine("This is a test for fine");
15         m_log.info("This is a test for info");
16         m_log.warning("This is a warning test");
17         m_log.severe("This is a severe test");
18     }
19 
20     public static void main(String[] args){
21         Level loglevel = Level.INFO;
22         if ( args.length !=0 ){
23             if ( args[0].equals("ALL") ){
24                 loglevel = Level.ALL;
25             }
26             else if ( args[0].equals("FINE") ){
27                 loglevel = Level.FINE;
28             }
29             else if ( args[0].equals("FINEST") ){
30                 loglevel = Level.FINEST;
31             }
32             else if ( args[0].equals("WARNING") ){
33                 loglevel = Level.WARNING;
34             }
35             else if ( args[0].equals("SEVERE") ) {
36                 loglevel = Level.SEVERE;
37             }
38         }
39         BadLogger logex = new BadLogger(loglevel);
40         logex.test(); 
41     }
42 }

這是錯誤的寫法  你會發現不管你傳什么參數結果都是:

1 The level for the log is: null
2 2012-10-24 19:21:26 no2.BadLogger test
3 信息: This is a test for info
4 2012-10-24 19:21:26 no2.BadLogger test
5 警告: This is a warning test
6 2012-10-24 19:21:26 no2.BadLogger test
7 嚴重: This is a severe test

改正:

 1 public class BadLogger {
 2     private Logger m_log = null; 
 3 
 4     public BadLogger(Level l){
 5         ConsoleHandler ch = new ConsoleHandler();
 6         m_log = Logger.getLogger("no2.BadLogger.logger");
 7         m_log.addHandler(ch);
 8         m_log.setLevel(l); 
 9         m_log.setUseParentHandlers(false);
10         ch.setLevel(l);
11     }
12 
13     public void test(){                          
14         System.out.println("The level for the log is: "+ m_log.getLevel());  
15         m_log.finest("This is a test for finest");
16         m_log.finer("This is a test for finer");
17         m_log.fine("This is a test for fine");
18         m_log.info("This is a test for info");
19         m_log.warning("This is a warning test");
20         m_log.severe("This is a severe test");
21     }
22 
23     public static void main(String[] args){
24         Level loglevel = Level.INFO;
25         if ( args.length !=0 ){
26             if ( args[0].equals("ALL") ){
27                 loglevel = Level.ALL;
28             }
29             else if ( args[0].equals("FINE") ){
30                 loglevel = Level.FINE;
31             }
32             else if ( args[0].equals("FINEST") ){
33                 loglevel = Level.FINEST;
34             }
35             else if ( args[0].equals("WARNING") ){
36                 loglevel = Level.WARNING;
37             }
38             else if ( args[0].equals("SEVERE") ) {
39                 loglevel = Level.SEVERE;
40             }
41         }
42         BadLogger logex = new BadLogger(loglevel);
43         logex.test(); 
44     }
45 }

這個時候輸入ALL這個參數結果就為:

 1 The level for the log is: ALL
 2 2012-10-24 19:23:31 no2.BadLogger test
 3 最好: This is a test for finest
 4 2012-10-24 19:23:31 no2.BadLogger test
 5 較好: This is a test for finer
 6 2012-10-24 19:23:31 no2.BadLogger test
 7 良好: This is a test for fine
 8 2012-10-24 19:23:31 no2.BadLogger test
 9 信息: This is a test for info
10 2012-10-24 19:23:31 no2.BadLogger test
11 警告: This is a warning test
12 2012-10-24 19:23:31 no2.BadLogger test
13 嚴重: This is a severe test

 NO.13  遍歷容器的一些陷阱

three哪去了?

public class BadVisitor {
    public static void main(String[] args) {
        Vector v = new Vector();
        v.add("one");
        v.add("two");
        v.add("three"); 
        v.add("four");
        Enumeration enume = v.elements();
        while (enume.hasMoreElements()){
            String s = (String) enume.nextElement();
            if (s.equals("two"))
                v.remove("two");
            else{
                System.out.println(s);
            }
        }
        System.out.println("What's really there...");
        enume = v.elements();
        while (enume.hasMoreElements()){
            String s = (String) enume.nextElement();
            System.out.println(s);            
        }
    }
}

運行結果:

1 one
2 four
3 What's really there...
4 one
5 three
6 four

改正:

 1 public class GoodVisitor {
 2     public static void main(String[] args) {
 3         Vector v = new Vector();
 4         v.add("one"); v.add("two"); v.add("three"); v.add("four");
 5         Iterator iter = v.iterator();
 6         while (iter.hasNext()){
 7             String s = (String) iter.next();
 8             if (s.equals("two"))
 9                 iter.remove();
10             else{
11                 System.out.println(s);
12             }
13         }
14         System.out.println("What's really there...");
15         iter = v.iterator();
16         while (iter.hasNext()){
17             String s = (String) iter.next();
18             System.out.println(s);            
19         }
20     }
21 }

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM