Java牛角尖(经典收藏)

[color=red]Java牛角尖【001】:抽象类必须有抽象方法吗?[/color]
我们都知道,有抽象方法的类是抽象类,反过来说,抽象类都有抽象方法吗?
其实这个问题非常明白,用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);
}
}

[color=red]Java牛角尖【002】:类可以被static修饰吗? [/color]
其实这个非常确定,在一班类的定义中是不能使用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");
}
}
}

[color=red]Java牛角尖【003】:类初始化时的执行顺序 [/color]
在初始化一个类时,到底是先执行哪一部分,总体的执行顺序是什么样的呢,同样,当类被释放时,又是怎样一个顺序呢?先来看下面的代码好了。
	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方法
只是这里一个意外是:竟然父类为抽象类时也同样会调用父类的构造方法,看来抽象类在虚拟机内部还是被实例化了。
[color=red]Java牛角尖【004】:Final类可以有protected属性或方法吗? [/color]
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. }

[color=red]Java牛角尖【005】:finalize方法什么时间执行? [/color]
与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时才会被执行,所以我们在写代码用到的时候需注意,这里面的代码不知道什么时候才会去执行,所以要尽量少用。
[color=red]Java牛角尖【006】: 匿名内部类可以继承其它类吗?[/color]
在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

同样,如果我们只需要一次性地重写某类的一个方法,我人同样也可以使用这种方式,在定义一个对象时对这个类进行匿名地继承,产生一个需要的特殊的类。
[color=red] Java牛角尖【007】:Java中的Error能不能被Catch [/color]
网上看到很多朋友说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  

[color=red] Java牛角尖【008】: 可以通过调用一个线程的run方法启动一个线程吗? [/color]
我们知道,我们通过调用线程的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方法只是一个普通的单线程程式。
[color=red]Java牛角尖【009】: 多线程中synchronized的锁定方式 [/color]
同一个对象中的一个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方法将全部不能被访问。
[color=red]Java牛角尖【010】: 当对象a.equals(b)时,a.hashCode == b.hashCode吗? [/color]
当然不是了,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. }

这样的话,题目的答案当然是对的。
[color=red]Java牛角尖【011】: Java中只支持单继承吗? [/color]
又是一个牛角尖,只是语言不够严谨而已,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. }

这同已经有问题了,这时会有编译时错误,原因很简单,方法的重载只能是相同的方法名,不同的输入参数;而对于这两个方法,它们具有相同的方法名,相同的输入参数,只是不同的返回参数,是不能作为重载方法的,所以对于编译器来说,这里是一个方法的重复定义,明显是不能通过编译的。
同样,这样的问题也存在于一个类同时实现多个接口的情况,所以,在这些情况下,我们必须注意一点,就是具有相同方法名,相同输入参数的方法,是不能出现在同一个类或接口中的。
[color=red]Java牛角尖【012】: JDBC开发时为什么要用Class.forName("") [/color]
前几天看到一个帖子中提出一个问题,在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 - 如果发生数据库访问错误

[color=red]Java牛角尖【013】: finally块中的代码一定会执行吗? [/color]
在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中的代码一定会执行。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值