Java牛角尖(经典收藏)

Java牛角尖【001】:抽象类必须有抽象方法吗?
  我们都知道,有抽象方法的类是抽象类,反过来说,抽象类都有抽象方法吗?
  其实这个问题非常明白,用abstract修饰的类就是抽象类,并不是说抽象类中必须有抽象方法,即使一个类中的方法全部实现过,也可以用abstract修饰为抽象类,所以抽象类不一定都有抽象方法。
  下面代码中是一个没有抽象方法的抽象类:
abstract class DemoClass{  
	    public void printMessage(String msg){  
	        System.out.println(msg);  
	    }  
	} 

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

  这明显是不行的,抽象类不能被实例化,即使是一个没有抽象方法的抽象类,也同样不能被实例化。当然,还可以把printMessage方法改为static类型,那么就可以直接调用了,代码如下:
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类型的,那就是内部类。
  如下的定义中,是一个内部类的实现:
	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改为一个抽象类,那么,我们就可以在抽象类中附带一个默认的实现,代码如下:
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);  
	        }  
	    }  
	} 

  当然,更进一步,我们也可以用这种方式给接口提供默认的实现,代码如下:
	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】:类初始化时的执行顺序
   在初始化一个类时,到底是先执行哪一部分,总体的执行顺序是什么样的呢,同样,当类被释放时,又是怎样一个顺序呢?先来看下面的代码好了。
	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();  
	  
	    }  
	  
	}  

  只要运行上面的代码,结果如下:
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其实就和默认的访问权限完全相同,变成了同一包中的类可以访问。
  代码如下:
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.
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.	}  

  运行一下代码,结果如下:
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内存不足的时候才会自动执行,为了测试,我们将代码作一下修改:
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.	}  

运行结果如下:
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开发时,大家应该经常用到下面的代码:
1.	JButton btnTest = new JButton();  
2.	btnTest.addActionListener(new ActionListener() {  
3.	    public void actionPerformed(ActionEvent evt) {  
4.	        // do something here  
5.	    }};
 
 
  通过匿名内部类的使用,我们可以方便地建立一个只能在此按钮中起作用的一个ActionListener接口的实现,这个实现只在该位置可用。
  那么,能不能将这里的接口改为一个抽象类,甚至一个普通的类呢?看一下下面代码:
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.	}  

代码运行结果为:
Printed by inner class  

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

其中catch中直接捕捉的是一个Throwable类,打开继承关系看一下,Exception和Error两个类同样是从Throwable类继承而来,那么,也就是说Error应该是可以被捕捉的,下面写个例子证明一下猜测:
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一样是可以捕捉的,运行代码结果为:
My Error  

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

  运行结果如下:
1.	0  
2.	1  
3.	2  
4.	3  
5.	4  
6.	5  
7.	6  
8.	7  
9.	8  
10.	9 

  或许有人会得出结论,这样启动一个线程是可以的,我们再对程式稍做修改,大家就会发现一个问题:
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",运行代码,结果如下:
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类中一直持有时间段吗?
  我们对上面的代码进行分析,其实非常简单,这只是一个普通的类中方法的调用,其实是一个单线程的执行,我们来修改代码进一步验证这一点:
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的方法中打印当前线程名字,运行结果如下:
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方法的调动再看一下:
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.	}  

  运行结果如下:
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方法,会有什么情况呢?
  看下面一段代码:
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.	}  

  运行结果如下:
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方法都可以被重写的,如果重写了其中的一个,而没有重写另外一个, 这个结论明显是错误的。
  代码如下:
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.	}  

  运行结果如下:
1.	true  
2.	false  

  当然,我们在重写equals方法时最好将hashCode方法也重写了,代码如下:
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关键字,但是接口之间是支持多继承的,如下面的例子:
1.	interface IP1 {  
2.	}  
3.	  
4.	interface IP2 {  
5.	}  
6.	  
7.	public interface ISub extends IP1, IP2 {  
8.	  
9.	}  
  
很明显,上面的代码是没有问题的。所以标题中的应该是不严谨的,严格的说应该是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.	}  

  其实这个问题不用担心,因为接口只是对方法的一个声明,并没有具体的实现,所以子接口中的方法属于哪个父接口并不重要,重要的是当实现这个接口的时候只需有一个该方法的实现就可以了,这个方法的实现应该同时属于两个父接口。
  很明显,这不是真正的问题,真正的问题是如果在两个父接口中分别定义了名称和参数都相同,而返回结果却不同的方法:
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为例子。为方便演示,代码中忽略异常处理)

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数据库中的用户数。
  我们先尝试将第一句拿掉,看是不是也可以运行。

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.	} 

  运行代码,报如下错误:

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方法的声明如下:

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这个类被初始化了,那一定是在初始化中做了什么动作。为了证实这点,我们对上面的代码做一点修改:

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这个类的源码来看一下了。
  代码如下:

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了,如下:
 
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块中的语句应该是总会执行的。
  先来写一个最常见的写法:
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语句,那么会怎么样呢?
  我们将代码稍做修改:

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 Test  
	I'm exception  
	I'm finally.  
	  
	>java Test hello  
	hello  
	I'm nomal  
	I'm finally.
 
  上面分别是输入和不输入参数时运行的结果,很明显,finally中的代码还是执行了。那是不是说try和catch块中的return语句并不起作用吗?我们再次简单修改代码:

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语句外面再加入一名打印代码,再次编译。
  编译错误,结果如下:

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

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

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中的代码一定会执行。

你可能感兴趣的:(java,thread,多线程,mysql,jdbc)