`

Java牛角尖(经典收藏)

阅读更多
Java牛角尖(经典收藏)
文章分类:Java编程
Java牛角尖【001】:抽象类必须有抽象方法吗?
  我们都知道,有抽象方法的类是抽象类,反过来说,抽象类都有抽象方法吗?
  其实这个问题非常明白,用abstract修饰的类就是抽象类,并不是说抽象类中必须有抽象方法,即使一个类中的方法全部实现过,也可以用abstract修饰为抽象类,所以抽象类不一定都有抽象方法。
  下面代码中是一个没有抽象方法的抽象类:
Java代码

    abstract class DemoClass{   
            public void printMessage(String msg){   
                System.out.println(msg);   
            }   
        }  


  看完这段代码,我不尽又想,这个类可以被实例化吗?我怎么去调用该类中的公共方法呢?通过
Java代码

    DemoClass d = new DemoClass();   


  这明显是不行的,抽象类不能被实例化,即使是一个没有抽象方法的抽象类,也同样不能被实例化。当然,还可以把printMessage方法改为static类型,那么就可以直接调用了,代码如下:
Java代码

    package net.moon.insignificant.abstractclass;   
           
        public class AbstractDemo {   
            public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException{   
                DemoClass.printMessage("Hello, World");   
            }   
        }   
           
        abstract class DemoClass{   
            public DemoClass(){}   
               
            public static void printMessage(String msg){   
                System.out.println(msg);   
            }   
        }   


Java牛角尖【002】:类可以被static修饰吗?
  其实这个非常确定,在一班类的定义中是不能使用static修饰符的,但是之所以提出来,是因为真有一种情况可以将类定义为static类型的,那就是内部类。
  如下的定义中,是一个内部类的实现:
Java代码

    package net.moon.insignificant.staticclass;   
       
    public class StaticClassDemo {   
       
        public static void main(String[] args) {   
            StaticClassDemo.InnerClass ic = new StaticClassDemo.InnerClass();   
            ic.showMessage("Hello, world");   
        }   
       
        static class InnerClass{   
            public void showMessage(String msg){   
                System.out.println(msg);   
            }   
        }   
    }   


  这里的外部类StaticClassDemo是一个普通的类,我们可以进一步修改,将StaticClassDemo改为一个抽象类,那么,我们就可以在抽象类中附带一个默认的实现,代码如下:
Java代码

    package net.moon.insignificant.staticclass;   
           
        public abstract class StaticClassDemo {   
           
            public static void main(String[] args) {   
                StaticClassDemo.InnerClass ic = new StaticClassDemo.InnerClass();   
                ic.showMessage("Hello, world");   
            }   
           
            public abstract void showMessage(String msg);   
               
            static class InnerClass extends StaticClassDemo{   
                public void showMessage(String msg){   
                    System.out.println(msg);   
                }   
            }   
        }  


  当然,更进一步,我们也可以用这种方式给接口提供默认的实现,代码如下:
Java代码

    package net.moon.insignificant.staticclass;   
       
    public interface StaticClassDemo {   
       
        public void showMessage(String msg);   
           
        static class InnerClass implements StaticClassDemo{   
            public void showMessage(String msg){   
                System.out.println(msg);   
            }   
               
            public static void main(String args[]){   
                StaticClassDemo.InnerClass ic = new StaticClassDemo.InnerClass();   
                ic.showMessage("Hello, world");   
            }   
        }   
    }   


Java牛角尖【003】:类初始化时的执行顺序
   在初始化一个类时,到底是先执行哪一部分,总体的执行顺序是什么样的呢,同样,当类被释放时,又是怎样一个顺序呢?先来看下面的代码好了。
Java代码

    package net.moon.insignificant.commonclass;   
       
    class CommonSubClass extends CommonSupperClass {   
        static {   
            System.out.println("Common sub static initial");   
        }   
       
        public CommonSubClass() {   
            System.out.println("Common sub construct");   
        }   
       
        @Override   
        protected void finalize() throws Throwable {   
            // TODO Auto-generated method stub   
            System.out.println("Common sub finalize");   
            super.finalize();   
       
        }   
    }   
       
    abstract class CommonSupperClass {   
        public CommonSupperClass() {   
            System.out.println("Common super construct");   
        }   
       
        static {   
            System.out.println("Common supper static initial");   
        }   
       
        @Override   
        protected void finalize() throws Throwable {   
            // TODO Auto-generated method stub   
            System.out.println("Common supper finalize");   
            super.finalize();   
       
        }   
    }   
       
    public class Demo {   
        public static void main(String[] args) {   
            // TODO Auto-generated method stub   
            CommonSubClass css = new CommonSubClass();   
            css = null;   
            System.gc();   
       
        }   
       
    }   


  只要运行上面的代码,结果如下:
Java代码

    1.  Common supper static initial   
    2.  Common sub static initial   
    3.  Common super construct   
    4.  Common sub construct   
    5.  Common sub finalize   
    6.  Common supper finalize   


  其实大家已经清楚,在初始化时,执行的顺序是:
1.  父类的静态代码块
2. 子类的静态代码块
3. 父类的构造方法
4. 子类的构造方法
  释放资料时,执行的顺序是:
1. 子类的finalize方法
2. 父类的finalize方法
  只是这里一个意外是:竟然父类为抽象类时也同样会调用父类的构造方法,看来抽象类在虚拟机内部还是被实例化了。
Java牛角尖【004】:Final类可以有protected属性或方法吗?
  Final类可以有protected属性或方法吗?这是一个典型的牛角尖,一个类被声明为final,说明该类不可被继承,如果类不能被继承,那么它可以有protected的属性和方法吗?
  答案是可以的,那么,这时的protected到底是什么访问权限叫呢?
  一个protected的属性或方法,它可以被同一包中的类访问,或是可以被子类所访问,但是现在它不能有子类,所以,这时protected其实就和默认的访问权限完全相同,变成了同一包中的类可以访问。
  代码如下:
Java代码

    1.  package net.moon.insignificant.finalclass;   
    2.     
    3.  final class FinalClassSuper{   
    4.      protected void sayHello(){   
    5.          System.out.println("Hello, world");   
    6.      }   
    7.  }   
    8.     
    9.  public class FinalClassDemo{   
    10.     public static void main(String[] args) {   
    11.         // TODO Auto-generated method stub   
    12.         FinalClassSuper s = new FinalClassSuper();   
    13.         s.sayHello();   
    14.     }   
    15.    
    16. }   


Java牛角尖【005】:finalize方法什么时间执行?
  与C++不同,Java有自己的垃圾回收机制,同时,Java没有了析构函数的概念,转而提供了一个finalize方法,那么finalize方法会在什么时间执行呢?
  或许有人以为是在将引用设置为null的时候,现在先看下面的例子:

1.
Java代码

    public class Test {   
    2.      public static void main(String[] args) {   
    3.          // TODO Auto-generated method stub   
    4.          Demo d = new Demo();   
    5.          System.out.println("begin to set d to null");   
    6.          d = null;   
    7.          System.out.println("d was set to null");   
    8.      }   
    9.  }   
    10.    
    11. class Demo {   
    12.     @Override   
    13.     protected void finalize() throws Throwable {   
    14.         // TODO Auto-generated method stub   
    15.         System.out.println("Demo finalized");   
    16.         super.finalize();   
    17.     }   
    18. }   


  运行一下代码,结果如下:
Java代码

    1.  begin to set d to null   
    2.  d was set to null   


  finalize方法根本没有被执行,看一下java中对finalize方法的定义:Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. 当垃圾回收确认没有指向对象的引用时,执行回收。而上面的代码新建的对象Demo的唯一引用d已经被释放,而确有执行Demo类的finalize方法,唯一的原因只能是gc并没有执行,gc只有在JVM内存不足的时候才会自动执行,为了测试,我们将代码作一下修改:
Java代码

    1.  public class Test {   
    2.      public static void main(String[] args) {   
    3.          // TODO Auto-generated method stub   
    4.          Demo d = new Demo();   
    5.          System.out.println("begin to set d to null");   
    6.          d = null;   
    7.          System.out.println("d was set to null");   
    8.          System.out.println("begin run gc");   
    9.          System.gc();   
    10.         System.out.println("gc runed");   
    11.     }   
    12. }   
    13.    
    14. class Demo {   
    15.     @Override   
    16.     protected void finalize() throws Throwable {   
    17.         // TODO Auto-generated method stub   
    18.         System.out.println("Demo finalized");   
    19.         super.finalize();   
    20.     }   
    21. }   


运行结果如下:
Java代码

    1.  begin to set d to null   
    2.  d was set to null   
    3.  begin run gc   
    4.  gc runed   
    5.  Demo finalized   


  所以finalize方法只有在JVM执行gc时才会被执行,所以我们在写代码用到的时候需注意,这里面的代码不知道什么时候才会去执行,所以要尽量少用。
Java牛角尖【006】: 匿名内部类可以继承其它类吗?
  在Swing开发时,大家应该经常用到下面的代码:
Java代码

    1.  JButton btnTest = new JButton();   
    2.  btnTest.addActionListener(new ActionListener() {   
    3.      public void actionPerformed(ActionEvent evt) {   
    4.          // do something here   
    5.      }}; 



  通过匿名内部类的使用,我们可以方便地建立一个只能在此按钮中起作用的一个ActionListener接口的实现,这个实现只在该位置可用。
  那么,能不能将这里的接口改为一个抽象类,甚至一个普通的类呢?看一下下面代码:
Java代码

    1.  public class Test {   
    2.      public static void main(String[] args) {   
    3.          Demo d = new Demo(){   
    4.              protected void showMessage(){   
    5.                      System.out.println("Printed by inner class");      
    6.              }   
    7.          };   
    8.             
    9.          d.showMessage();   
    10.     }   
    11. }   
    12.    
    13. class Demo {   
    14.     protected void showMessage(){   
    15.         System.out.println("Printed by demo");     
    16.     }   
    17. }   


代码运行结果为:
Java代码

    Printed by inner class   


  同样,如果我们只需要一次性地重写某类的一个方法,我人同样也可以使用这种方式,在定义一个对象时对这个类进行匿名地继承,产生一个需要的特殊的类。
Java牛角尖【007】:Java中的Error能不能被Catch
网上看到很多朋友说Java中Error是无法Catch到的,而Java中定义的Error类型又很难测试到,那就估且以为确是如此吧。
但是或许大家都有注意,我们时常会看到这样的代码
Java代码

    1.  try{   
    2.      ...   
    3.  }catch(Throwable ex){   
    4.      ...   
    5.  }   


其中catch中直接捕捉的是一个Throwable类,打开继承关系看一下,Exception和Error两个类同样是从Throwable类继承而来,那么,也就是说Error应该是可以被捕捉的,下面写个例子证明一下猜测:
Java代码

    1.  package net.moon.demo.errorcatch;   
    2.     
    3.  public class Demo {   
    4.     
    5.      /** 
    6.       * @param args 
    7.       */   
    8.      public static void main(String[] args) {   
    9.          // TODO Auto-generated method stub   
    10.         try {   
    11.             throw new MyError("My Error");   
    12.         } catch (MyError e) {   
    13.             System.out.println(e.getMessage());   
    14.         }   
    15.     }   
    16.    
    17. }   
    18.    
    19. class MyError extends Error {   
    20.    
    21.     /** 
    22.      *  
    23.      */   
    24.     private static final long serialVersionUID = 1L;   
    25.    
    26.     public MyError() {   
    27.         super();   
    28.         // TODO Auto-generated constructor stub   
    29.     }   
    30.    
    31.     public MyError(String message, Throwable cause) {   
    32.         super(message, cause);   
    33.         // TODO Auto-generated constructor stub   
    34.     }   
    35.    
    36.     public MyError(String message) {   
    37.         super(message);   
    38.         // TODO Auto-generated constructor stub   
    39.     }   
    40.    
    41.     public MyError(Throwable cause) {   
    42.         super(cause);   
    43.         // TODO Auto-generated constructor stub   
    44.     }   
    45.    
    46. }   


执行一下以上代码,正如前面的猜测,Error一样是可以捕捉的,运行代码结果为:
Java代码

    My Error   


Java牛角尖【008】: 可以通过调用一个线程的run方法启动一个线程吗?
  我们知道,我们通过调用线程的start方法启动一个线程,那么,我们可以直接调用run方法来启动一个线程吗?
  先看下面一段代码:
Java代码

    1.  public class Test {   
    2.      public static void main(String[] args) {   
    3.          // TODO Auto-generated method stub   
    4.          TestThread tt = new TestThread();   
    5.          tt.run();   
    6.      }   
    7.  }   
    8.     
    9.  class TestThread extends Thread {   
    10.     static int i = 0;   
    11.     final static int MAX_I = 10;   
    12.    
    13.     @Override   
    14.     public void run() {   
    15.         // TODO Auto-generated method stub   
    16.         while (i < MAX_I) {   
    17.             System.out.println(i++);   
    18.         }   
    19.     }   
    20. }   


  运行结果如下:
Java代码

    1.  0   
    2.  1   
    3.  2   
    4.  3   
    5.  4   
    6.  5   
    7.  6   
    8.  7   
    9.  8   
    10. 9  


  或许有人会得出结论,这样启动一个线程是可以的,我们再对程式稍做修改,大家就会发现一个问题:
Java代码

    1.  public class Test {   
    2.      public static void main(String[] args) {   
    3.          // TODO Auto-generated method stub   
    4.          TestThread tt = new TestThread();   
    5.          tt.run();   
    6.          System.out.println("Printed by main thread");   
    7.      }   
    8.  }   
    9.     
    10. class TestThread extends Thread {   
    11.     static int i = 0;   
    12.     final static int MAX_I = 10;   
    13.    
    14.     @Override   
    15.     public void run() {   
    16.         // TODO Auto-generated method stub   
    17.         while (i < MAX_I) {   
    18.             System.out.println(i++);   
    19.         }   
    20.     }   
    21.    
    22. }   


  这里只在主线程中加入了一行代码,打印一行"Printed by main thread",运行代码,结果如下:
Java代码

    1.  0   
    2.  1   
    3.  2   
    4.  3   
    5.  4   
    6.  5   
    7.  6   
    8.  7   
    9.  8   
    10. 9   
    11. Printed by main thread   


  熟练多线程开发的要发现问题了,为什么"Printed by main thread"会打印在最后一行呢?TestThread类中一直持有时间段吗?
  我们对上面的代码进行分析,其实非常简单,这只是一个普通的类中方法的调用,其实是一个单线程的执行,我们来修改代码进一步验证这一点:
Java代码

    1.  public class Test {   
    2.      public static void main(String[] args) {   
    3.          // TODO Auto-generated method stub   
    4.          TestThread tt = new TestThread();   
    5.          tt.run();   
    6.          System.out.println(Thread.currentThread().getName());   
    7.          System.out.println("Printed by main thread");   
    8.      }   
    9.  }   
    10.    
    11. class TestThread extends Thread {   
    12.     static int i = 0;   
    13.     final static int MAX_I = 10;   
    14.    
    15.     @Override   
    16.     public void run() {   
    17.         // TODO Auto-generated method stub   
    18.         System.out.println(Thread.currentThread().getName());   
    19.         while (i < MAX_I) {   
    20.             System.out.println(i++);   
    21.         }   
    22.     }   
    23. }   


  这段代码分别在主线程和我们的TestThread的方法中打印当前线程名字,运行结果如下:
Java代码

    1.  main   
    2.  0   
    3.  1   
    4.  2   
    5.  3   
    6.  4   
    7.  5   
    8.  6   
    9.  7   
    10. 8   
    11. 9   
    12. main   
    13. Printed by main thread   


  在TestThread类和主线程中运行的是同一个线程,说明在直接调用run时是不能使用多线程的,那么把上面的run方法调用改为start方法的调动再看一下:
Java代码

    1.  public class Test {   
    2.      public static void main(String[] args) {   
    3.          // TODO Auto-generated method stub   
    4.          TestThread tt = new TestThread();   
    5.          tt.start();   
    6.          System.out.println(Thread.currentThread().getName());   
    7.          System.out.println("Printed by main thread");   
    8.      }   
    9.  }   
    10.    
    11. class TestThread extends Thread {   
    12.     static int i = 0;   
    13.     final static int MAX_I = 10;   
    14.    
    15.     @Override   
    16.     public void run() {   
    17.         // TODO Auto-generated method stub   
    18.         System.out.println(Thread.currentThread().getName());   
    19.         while (i < MAX_I) {   
    20.             System.out.println(i++);   
    21.         }   
    22.     }   
    23. }   


  运行结果如下:
Java代码

    1.  main   
    2.  Thread-0   
    3.  0   
    4.  1   
    5.  2   
    6.  3   
    7.  4   
    8.  5   
    9.  6   
    10. 7   
    11. 8   
    12. Printed by main thread   
    13. 9   


  很明显,这才是我们想看到的结果,所以结论是只有调用Thread的start方法,将线程交由JVM控制,才能产生多线程,而直接调用run方法只是一个普通的单线程程式。
Java牛角尖【009】: 多线程中synchronized的锁定方式
  同一个对象中的一个synchronized方法如果已有一个线程进入,则其它的线程必须等该线程结束后才能进入该方法。那么,如果一个类中有多个synchronized方法,会有什么情况呢?
  看下面一段代码:
Java代码

    1.  public class Test {   
    2.      static Test t = new Test();   
    3.      static Test2 t2 = new Test2();   
    4.     
    5.      public static void main(String[] args) {   
    6.          // TODO Auto-generated method stub   
    7.     
    8.          TestThread tt = t.new TestThread();   
    9.          tt.start();   
    10.         try {   
    11.             Thread.sleep(1000);   
    12.         } catch (InterruptedException e) {   
    13.             // TODO Auto-generated catch block   
    14.             e.printStackTrace();   
    15.         }   
    16.         t2.test1();   
    17.     }   
    18.    
    19.     class TestThread extends Thread {   
    20.         @Override   
    21.         public void run() {   
    22.             // TODO Auto-generated method stub   
    23.             t2.test2();   
    24.         }   
    25.     }   
    26. }   
    27.    
    28. class Test2 {   
    29.     public synchronized void test1() {   
    30.         System.out.println("test1 called");   
    31.     }   
    32.    
    33.     public synchronized void test2() {   
    34.         System.out.println("test2 called");   
    35.         try {   
    36.             Thread.sleep(3000);   
    37.         } catch (InterruptedException e) {   
    38.             // TODO Auto-generated catch block   
    39.             e.printStackTrace();   
    40.         }   
    41.         System.out.println("test2 exit");   
    42.     }   
    43. }   


  运行结果如下:
Java代码

    1.  test2 called   
    2.  test2 exit   
    3.  test1 called  


  很明显,当对象t2的synchronized方法test2被线程tt调用时,主线程也无法进入其test1方法,直到线程tt对test2方法的调用结束,主线程才能进入test1方法。
  结论,对于synchronized方法,Java采用的是对象锁定的方式,当任何一个synchronized方法被访问的时候,该对象中的其它synchronized方法将全部不能被访问。
Java牛角尖【010】: 当对象a.equals(b)时,a.hashCode == b.hashCode吗?
  当然不是了,hashCode和equals方法都可以被重写的,如果重写了其中的一个,而没有重写另外一个, 这个结论明显是错误的。
  代码如下:
Java代码

    1.  public class Test {   
    2.      public static void main(String[] args) {   
    3.          // TODO Auto-generated method stub   
    4.          Test2 t = new Test2("zhangsan", 20);   
    5.          Test2 t2 = new Test2("zhangsan", 30);   
    6.     
    7.          System.out.println(t.equals(t2));   
    8.          System.out.println(t.hashCode() == t2.hashCode());   
    9.      }   
    10. }   
    11.    
    12. class Test2 {   
    13.     public Test2(String name, int age) {   
    14.         super();   
    15.         this.name = name;   
    16.         this.age = age;   
    17.     }   
    18.    
    19.     @Override   
    20.     public boolean equals(Object obj) {   
    21.         if (this == obj)   
    22.             return true;   
    23.         if (obj == null)   
    24.             return false;   
    25.         if (getClass() != obj.getClass())   
    26.             return false;   
    27.         Test2 other = (Test2) obj;   
    28.         if (name == null) {   
    29.             if (other.name != null)   
    30.                 return false;   
    31.         } else if (!name.equals(other.name))   
    32.             return false;   
    33.         return true;   
    34.     }   
    35.    
    36.     String name = "";   
    37.     int age;   
    38.    
    39. }   


  运行结果如下:
Java代码

    1.  true   
    2.  false   


  当然,我们在重写equals方法时最好将hashCode方法也重写了,代码如下:
Java代码

    1.  public class Test {   
    2.      public static void main(String[] args) {   
    3.          // TODO Auto-generated method stub   
    4.          Test2 t = new Test2("zhangsan", 20);   
    5.          Test2 t2 = new Test2("zhangsan", 30);   
    6.     
    7.          System.out.println(t.equals(t2));   
    8.          System.out.println(t.hashCode() == t2.hashCode());   
    9.      }   
    10. }   
    11.    
    12. class Test2 {   
    13.     public Test2(String name, int age) {   
    14.         super();   
    15.         this.name = name;   
    16.         this.age = age;   
    17.     }   
    18.    
    19.     @Override   
    20.     public int hashCode() {   
    21.         final int prime = 31;   
    22.         int result = 1;   
    23.         result = prime * result + ((name == null) ? 0 : name.hashCode());   
    24.         return result;   
    25.     }   
    26.    
    27.     @Override   
    28.     public boolean equals(Object obj) {   
    29.         if (this == obj)   
    30.             return true;   
    31.         if (obj == null)   
    32.             return false;   
    33.         if (getClass() != obj.getClass())   
    34.             return false;   
    35.         Test2 other = (Test2) obj;   
    36.         if (name == null) {   
    37.             if (other.name != null)   
    38.                 return false;   
    39.         } else if (!name.equals(other.name))   
    40.             return false;   
    41.         return true;   
    42.     }   
    43.    
    44.     String name = "";   
    45.     int age;   
    46.    
    47. }   


  这样的话,题目的答案当然是对的。
Java牛角尖【011】: Java中只支持单继承吗?
   又是一个牛角尖,只是语言不够严谨而已,Java中只支持类的单继承,接口之间的继承同样也是使用extends关键字,但是接口之间是支持多继承的,如下面的例子:
Java代码

    1.  interface IP1 {   
    2.  }   
    3.     
    4.  interface IP2 {   
    5.  }   
    6.     
    7.  public interface ISub extends IP1, IP2 {   
    8.     
    9.  }   
       

很明显,上面的代码是没有问题的。所以标题中的应该是不严谨的,严格的说应该是Java中类的继承只支持单继承。
  当然,这样我们自然会想到多继承的问题,如果两个父接口中有同样的方法,那么子接口中怎么办呢?
Java代码

    1.  interface IP1 {   
    2.      public void test();   
    3.  }   
    4.     
    5.  interface IP2 {   
    6.      public void test();   
    7.  }   
    8.     
    9.  public interface ISub extends IP1, IP2 {   
    10.    
    11. }   


  其实这个问题不用担心,因为接口只是对方法的一个声明,并没有具体的实现,所以子接口中的方法属于哪个父接口并不重要,重要的是当实现这个接口的时候只需有一个该方法的实现就可以了,这个方法的实现应该同时属于两个父接口。
  很明显,这不是真正的问题,真正的问题是如果在两个父接口中分别定义了名称和参数都相同,而返回结果却不同的方法:
Java代码

    1.  interface IP1 {   
    2.      public void test();   
    3.  }   
    4.     
    5.  interface IP2 {   
    6.      public String test();   
    7.  }   
    8.     
    9.  public interface ISub extends IP1, IP2 {   
    10.    
    11. } 


  这同已经有问题了,这时会有编译时错误,原因很简单,方法的重载只能是相同的方法名,不同的输入参数;而对于这两个方法,它们具有相同的方法名,相同的输入参数,只是不同的返回参数,是不能作为重载方法的,所以对于编译器来说,这里是一个方法的重复定义,明显是不能通过编译的。
  同样,这样的问题也存在于一个类同时实现多个接口的情况,所以,在这些情况下,我们必须注意一点,就是具有相同方法名,相同输入参数的方法,是不能出现在同一个类或接口中的。
Java牛角尖【012】: JDBC开发时为什么要用Class.forName("")
  前几天看到一个帖子中提出一个问题,在JDBC的开发中为什么要使用Class.forName,可以不用这句吗?
  我们从代码出发,来分析一下这个问题。
  下面是一段我们常用的JDBC开发中的代码(注:本文中例子使用Mysql为例子。为方便演示,代码中忽略异常处理)

Java代码

    1.  Class.forName("com.mysql.jdbc.Driver");   
    2.  conn = DriverManager.getConnection(   
    3.          "jdbc:mysql://localhost:3306/mysql", "root", "");   
    4.  stmt = conn.createStatement();   
    5.  rs = stmt.executeQuery("select count(0) from user");   
    6.  while (rs != null && rs.next()) {   
    7.      System.out.println(rs.getInt(1));   
    8.  }   


  运行代码,结果正常,打印出了Mysql数据库中的用户数。
  我们先尝试将第一句拿掉,看是不是也可以运行。

Java代码

    1.  // 拿掉Class.forName语句,看一下运行结果   
    2.  // Class.forName("com.mysql.jdbc.Driver");   
    3.  conn = DriverManager.getConnection(   
    4.          "jdbc:mysql://localhost:3306/mysql", "root", "");   
    5.  stmt = conn.createStatement();   
    6.  rs = stmt.executeQuery("select count(0) from user");   
    7.  while (rs != null && rs.next()) {   
    8.      System.out.println(rs.getInt(1));   
    9.  }  


  运行代码,报如下错误:

Java代码

    1.  java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/mysql   
    2.      at java.sql.DriverManager.getConnection(DriverManager.java:602)   
    3.      at java.sql.DriverManager.getConnection(DriverManager.java:185)   
    4.      at net.moon.jdbc.demo.Demo.main(Demo.java:18)   


  看来是不行,那让我们来分析一下,Class.forName(String clz)这样一个方法到底做了什么呢?
  看一下API,API中Class.forName方法的声明如下:

Java代码

    1.  public static Class<?> forName(String className)   
    2.                          throws ClassNotFoundException   


  该方法是根据一个字符串,得到这个字符串所表示的类,但是我们上面代码中并没有一个引用指向这个返回的结果,也就是代码并不关注返回的结果,那为什么还要执行这句话呢,继续往下看API中的说明,有这样一句:“A call to forName("X") causes the class named X to be initialized”。
  问题似乎有点眉目了,原来在执行Class.forName("com.mysql.jdbc.Driver")这个语句时,com.mysql.jdbc.Driver这个类被初始化了,那一定是在初始化中做了什么动作。为了证实这点,我们对上面的代码做一点修改:

Java代码

    1.  // Class.forName("com.mysql.jdbc.Driver");   
    2.  // 新建一个Driver对象,同样不关注返回的结果   
    3.  new com.mysql.jdbc.Driver();   
    4.  conn = DriverManager.getConnection(   
    5.          "jdbc:mysql://localhost:3306/mysql", "root", "");   
    6.  stmt = conn.createStatement();   
    7.  rs = stmt.executeQuery("select count(0) from user");   
    8.  while (rs != null && rs.next()) {   
    9.      System.out.println(rs.getInt(1));   
    10. }   


  运行代码,和预想的一样,同样可以得到运行结果。
  我们再来想一下,com.mysql.jdbc.Driver这个类在初始化的时候到底执行了什么?先来回忆一下以前的一篇文章:Java牛角尖【003】:类初始化时的执行顺序。明白了,好像有这样一个概念:静态代码块。
  一定是在com.mysql.jdbc.Driver这个类中有一段静态代码段,这段代码执行了某些动作。
  之所以用Mysql做为例子,还有另外一个优点,那就是开源,开源也就是说我们可以看到它的代码,所以下个任务就是找到com.mysql.jdbc.Driver这个类的源码来看一下了。
  代码如下:

Java代码

    1.  static {   
    2.      try {   
    3.          java.sql.DriverManager.registerDriver(new Driver());   
    4.      } catch (SQLException E) {   
    5.          throw new RuntimeException("Can't register driver!");   
    6.      }   
    7.  } 


  这段代码似乎比我们想象的要简单,是透过java.sql.DriverManager这个类的静态方法registerDriver这个方法注册这个JDBC驱动。
  最后一个问题就是为什么这里要调用registerDriver方法呢,那就是来看一下DrvierManager的API了,如下:
Java代码

      
    1.  registerDriver   
    2.  public static void registerDriver(Driver driver)   
    3.                             throws SQLException   
    4.     
    5.  向 DriverManager 注册给定驱动程序。新加载的驱动程序类应该调用 registerDriver 方法让 DriverManager 知道自己。    
    6.     
    7.  参数:   
    8.  driver - 将向 DriverManager 注册的新的 JDBC Driver    
    9.  抛出:    
    10. SQLException - 如果发生数据库访问错误   


Java牛角尖【013】: finally块中的代码一定会执行吗?
  在Sun Tutorial中有这样一句话:The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs.  看来finally块中的语句应该是总会执行的。
  先来写一个最常见的写法:
Java代码

    public class Test {   
            public static void main(String[] args) {   
                try {   
                    System.out.println(args[0]);   
                    System.out.println("I'm nomal");   
                } catch (Exception ex) {   
                    System.out.println("I'm exception");   
                } finally {   
                    System.out.println("I'm finally.");   
                }   
            }   
        }  


  运行这段代码,很明显,不论是否有参考输入,"I'm finally."这句话都会打印出来。这是最常用的写法,很显然与Tutorial中的说明是相符的。
  下面我们再进一步想一下,假如在try或是catch块中使用了return语句,那么会怎么样呢?
  我们将代码稍做修改:

Java代码

    public class Test {   
            public static void main(String[] args) {   
                try {   
                    System.out.println(args[0]);   
                    System.out.println("I'm nomal");   
                                return;   
                } catch (Exception ex) {   
                    System.out.println("I'm exception");   
                                return;   
                } finally {   
                    System.out.println("I'm finally.");   
                }   
            }   
        }   


  代码的修改很简单,只是在try和catch块的结束位置分别加了一个return语句。
  这样运行结果是什么呢?可能会有两种猜想了,或是直接退出,或是仍会打印"I'm finally."。验证真理的方法是实践,我们运行这段代码,看一下结果:
Java代码

    >java Test   
    I'm exception   
    I'm finally.   
       
    >java Test hello   
    hello   
    I'm nomal   
    I'm finally. 


  上面分别是输入和不输入参数时运行的结果,很明显,finally中的代码还是执行了。那是不是说try和catch块中的return语句并不起作用吗?我们再次简单修改代码:

Java代码

    public class Test {   
            public static void main(String[] args) {   
                try {   
                    System.out.println(args[0]);   
                    System.out.println("I'm nomal");   
                                return;   
                } catch (Exception ex) {   
                    System.out.println("I'm exception");   
                                return;   
                } finally {   
                    System.out.println("I'm finally.");   
                }   
                        System.out.println("Out of try.");   
            }   
        }   


  在try语句外面再加入一名打印代码,再次编译。
  编译错误,结果如下:

Java代码

    Exception in thread "main" java.lang.Error: Unresolved compilation problem:    
        Unreachable code   


  提示代码不可达,看来return还是有用的,只是在退出方法呼叫之前,会先去执行finally中的代码。
  现在似乎说明了另外一个问题,是不是return语句还不够厉害,“让暴风雨来的更猛烈些吧”,我们再次修改代码,将return语句修改成System.exit(),看一下执行结果。

Java代码

    public class Test {   
            public static void main(String[] args) {   
                try {   
                    System.out.println(args[0]);   
                    System.out.println("I'm nomal");   
                                System.exit(0);   
                } catch (Exception ex) {   
                    System.out.println("I'm exception");   
                                System.exit(0);   
                } finally {   
                    System.out.println("I'm finally.");   
                }   
            }   
        }   


  运行代码,终于,"I'm finally."不见了。
  为什么System.exit()有这么强大的力量呢,让我们看一下API中的说明:exit(int status): Terminates the currently running Java Virtual Machine。原来是这样,JVM都被终止掉了,当然不会再执行finally中的语句了。
  下面是我们的结论:
  在不终止VM的情况下,finally中的代码一定会执行。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics