1、类的加载执行顺序
[java] view plaincopyprint?
-
public class Dervied extends Base {
-
private String name = "dervied";
-
public Dervied() {
-
tellName();
-
printName();
-
}
-
public void tellName() {
-
System.out.println("Dervied tell name: " + name);
-
}
-
public void printName() {
-
System.out.println("Dervied print name: " + name);
-
}
-
public static void main(String[] args){
-
new Dervied();
-
}
-
}
-
class Base {
-
private String name = "base";
-
public Base() {
-
tellName();
-
printName();
-
}
-
public void tellName() {
-
System.out.println("Base tell name: " + name);
-
}
-
public void printName() {
-
System.out.println("Base print name: " + name);
-
}
-
}
先初始化父类然后再初始化子类(这个初始化包括静态和非静态变量、静态和非静态的方法块、构造函数)
Dervied tell name: null
Dervied print name: null
Dervied tell name: dervied
Dervied print name: dervied
再看一下如下的例子:
[java] view plaincopyprint?
-
class ParentClass {
-
public static int a=2;
-
public int b=3;
-
{
-
System.out.println("this is anonymity b="+b);
-
}
-
static {
-
a=4;
-
System.out.println("this is static and a="+a);
-
}
-
public ParentClass() {
-
System.out.println("this is parent gozao");
-
this.s();
-
}
-
public void s() {
-
System.out.println("this is parent");
-
}
-
}
-
public class Son extends ParentClass {
-
public Son(){
-
System.out.println("this is son gozao");
-
}
-
public static void main(String[] args) {
-
ParentClass d = new Son();
-
d.s();
-
}
-
public void s() {
-
//super.s();
-
System.out.println("this is son");
-
}
-
}
运行结果如下:
[java] view plaincopyprint?
-
this is static and a=4
-
this is anonymity b=3
-
this is parent gozao
-
this is son
-
this is son gozao
-
this is son
可以看出类内的加载顺序为:
(1)
静态变量 对于静态变量肯定要首先进行初始化,因为后面的方法可能会使用这个变量,或者构造函数中也可能用到。而对于非静态变量而言,由于匿名块内、非静态方法和构造函数都可以进行操作(不仅仅是初始化),所以要提前进行加载和赋默认值。
静态代码块 多个静态代码块按顺序加载,这里需要注意:在这个顺序不难是类内书写的顺序,也是类加载的顺序,也就是说如果子类也有静态代码块,则子类的也加载。由于静态代码块可能会负责变量的初始化,或者是对象等的初始化,这样在构造函数或者方法中就变得可用了。而顺序加载多半是由于Java是按顺序执行代码的原因。
静态方法 一般静态方法中禁止引用还未初始化的非静态变量,如果引用了静态变量,则静态变量必须放到这个静态方法的前面,以保证在使用时已经被正确地初始化。
一般如上要按顺序执行加载。
如果静态代码块中用到了静态变量,则静态变量必须在前面,如果在后会出现编译错误,而静态代码块中不可以出现非静态的变量。
[java] view plaincopyprint?
-
public static int a=2; // 必须放到静态代码块前
-
// public int a=3; // 代码块中会报错
-
{
-
System.out.println("this is anonymity b="+b);
-
}
-
static {
-
System.out.println("this is static and a="+a);
-
}
静态方法与静态变量的关系和上面一样。
(2)
匿名代码块 这个要后初始化于静态代码块和静态变量,因为其依然属于实例对象,而不属于类。在这里可以对非静态成员变量进行初始化工作,同样也可以引用静态变量,因为已经被初始化过了。
非静态变量 这个要后初始化于静态代码块和静态变量,如果在匿名代码块中有对非静态变量的引用,则非静态变量必须在前面,以保证先被初始化。对静态变量的位置不做要求。举例:
[java] view plaincopyprint?
-
public int b=3; // 必须放到匿名代码块的前面,以保证先被初始化后使用
-
{
-
System.out.println("this is anonymity b="+b);
-
}
-
public static int b=2; // 可以放到匿名代码块的任意位置
如果两者没有引用,则按顺序执行加载。
(3)
构造函数 这里需要解释一下,为什么初始化子类必先初始化父类,由于子类可能会继承父类的属性或方法,所以肯定要先初始化父类了,而初始化父类则必须要调用父类的构造函数。
至于方法不用考虑,因为方法不用初始化,所以无论是静态还是不静态,和这个没有关系。
其实如上的代码还能说明一些问题,可以看到,在父类中通过this.s()调用的是子类的方法,子类的s()方法覆盖了父类的方法后,无论在哪里调用,都是调用子类的方法。
下面再来看一个比较复杂的面试题(阿里巴巴),如下:
[java] view plaincopyprint?
-
public class InitializeDemo {
-
private static int k = 1;
-
private static InitializeDemo t1 = new InitializeDemo("t1");
-
private static InitializeDemo t2 = new InitializeDemo("t2");
-
private static int i = print("i");
-
private static int n = 99;
-
static {
-
print("静态块");
-
}
-
private int j = print("j");
-
-
{
-
print("构造块");
-
}
-
-
public InitializeDemo(String str) {
-
System.out.println((k++) + ":" + str + " i=" + i + " n=" + n);
-
++i;
-
++n;
-
}
-
-
public static int print(String str) {
-
System.out.println((k++) + ":" + str + " i=" + i + " n=" + n);
-
++n;
-
return ++i;
-
}
-
-
public static void main(String args[]) {
-
new InitializeDemo("init");
-
}
-
}
最终的输出结果如下:
[java] view plaincopyprint?
-
1:j i=0 n=0
-
2:构造块 i=1 n=1
-
3:t1 i=2 n=2
-
4:j i=3 n=3
-
5:构造块 i=4 n=4
-
6:t2 i=5 n=5
-
7:i i=6 n=6
-
8:静态块 i=7 n=99
-
9:j i=8 n=100
-
10:构造块 i=9 n=101
-
11:init i=10 n=102
对其进行解释如下:
[java] view plaincopyprint?
-
1. 运行main方法的时候,JVM会调用ClassLoader来加载Test类,那么一起源于这次加载
-
2. 上面有四个静态属性,所以会按顺序逐一初始化这四个静态属性
-
3.private static int k = 1; 此时将k初始化为1
-
4.private static Test t1 = new Test("t1"); 创建Test对象,那么按照核心理念中的顺序
-
先执行 private int j = print("j"); 打印出j,然后执行构造块,最后执行构造方法
-
5.private static Test t2 = new Test("t2"); 同步骤4
-
6.private static int i = print("i"); 打印i
-
7.private static int n = 99; 直到这一步,n才被赋值为99,之前是从默认的0开始++的
-
8. 静态属性初始化完毕,代码走到静态块,打印出静态块,此时n=99
-
9. 静态属性和静态块执行完毕,然后执行main方法中的代码new Test("init");
-
10.main方法中创建对象,先初始化非静态属性,private int j = print("j");打印j,然后执行构造块,最后执行构造方法
2、不可变类
不可变类是指当创建了这个类的实例后,就不允许修改它的属性值。在JDK的基本类库中,所有基本类型的包装类,如Integer、Long等都是不可变类,除此之外还有java.lang.String也是不可变类。不可变类的实例一但创建,其内在成员变量的值就不能被修改。创建一个不可变类需要如下条件:
-
对于一般成员都是private,还可以使用public static final 来定义一个全局的常量。
-
不提供对成员的修改方法,例如:setXxx()
-
确保所有的方法不会被重载。手段有两种:使用final Class(强不可变类),或者将所有的类方法加上final关键字(弱不可变类)。
-
如果某一个类成员不是原始变量(primitive)或者不可变类,必须通过在成员初始化或者get方法时通过深度clone方法,来确保类的不可变。
对于一般成员都是private,还可以使用public static final 来定义一个全局的常量。
不提供对成员的修改方法,例如:setXxx()
确保所有的方法不会被重载。手段有两种:使用final Class(强不可变类),或者将所有的类方法加上final关键字(弱不可变类)。
如果某一个类成员不是原始变量(primitive)或者不可变类,必须通过在成员初始化或者get方法时通过深度clone方法,来确保类的不可变。
其它的都好理解,下面来着重解释一下第4条。举个例子:
[cpp] view plaincopyprint?
-
public final class MyImmutableWrong { // 使用final关键字声明为强不可变类
-
private final int[] myArray;
-
-
public MyImmutableWrong(int[] anArray) {
-
this.myArray = anArray; // wrong
-
}
-
-
public String toString() {
-
StringBuffer sb = new StringBuffer("Numbers are: ");
-
for (int i = 0; i < myArray.length; i++) {
-
sb.append(myArray[i] + " ");
-
}
-
return sb.toString();
-
}
-
}
由于int[] anArray是一个数组,属于引用类型。这样当再次去操作外部的一个引用时,其指向的共同内容,也就是数组的值。
[java] view plaincopyprint?
-
public class dd {
-
public static void main(String[] args) {
-
int array[]={1,2,3,4};
-
MyImmutableWrong service=new MyImmutableWrong(array);
-
array[2]=99;
-
System.out.println(service.toString());
-
}
-
-
}
查看控制台的输出内容:
Numbers are: 1 2 99 4
可以看到,不可变类中的数组内容发生了改变。究其原因就是 - 关键字final仅对其直接指向的对象有用,并且final引用可以指向带有非final域的对象。
为了避免这个问题,必须对数组进行深度克隆。也就是专门再为不可变类中的数组开避一份内存空间,然后将参数的值赋值过去。正确的写法如下:
[java] view plaincopyprint?
-
public final class MyImmutableCorrect {
-
private final int[] myArray;
-
-
public MyImmutableCorrect(int[] anArray) {
-
this.myArray = anArray.clone();
-
}
-
-
public String toString() {
-
StringBuffer sb = new StringBuffer("Numbers are: ");
-
for (int i = 0; i < myArray.length; i++) {
-
sb.append(myArray[i] + " ");
-
}
-
return sb.toString();
-
}
-
}
测试后发现:
Numbers are: 1 2 3 4
不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。
3、静态内部类
内部类就是在一个类的内部定义的类,内部类中不能定义静态成员(静态成员不是对象的特性,只是为了找一个容身之处,所以需要放到一个类中而已,把“全局变量”放在内部类中就是毫无意义的事情,所以被禁止),内部类可以直接访问外部类中的成员变量,即使这个成员变量是由private来修饰的。
同样拿不可变类来举一个例子。假如这个不可变类中有许多的private final属性需要初始化,这时候看起来就不太方便。可能有人会采用工厂方法来解决,但有时候也可以使用静态内部类来解决这一问题。如下:
[java] view plaincopyprint?
-
public class Author {
-
-
private final String name;
-
-
public String getName() {
-
return name;
-
}
-
-
public Author(String name_) {
-
name = name_;
-
}
-
@Override
-
public String toString() {
-
return "Author [name=" + name + "]";
-
}
-
}
[java] view plaincopyprint?
-
public final class Update { // 强不可变类
-
-
private final Author author; // 作者,是个引用变量
-
private final String updateText; // 更新内容
-
private final long createTime; // 更新时间
-
-
// 私有构造函数,防止外部实例化
-
private Update(Builder b_) {
-
author = b_.author;
-
updateText = b_.updateText;
-
createTime = b_.createTime;
-
}
-
// 构建器
-
public static class Builder {
-
private long createTime;
-
private Author author;
-
private String updateText;
-
-
public Builder author(Author author_) {
-
author = author_;
-
return this;
-
}
-
-
public Builder updateText(String updateText_) {
-
updateText = updateText_;
-
return this;
-
}
-
-
public Builder createTime(long createTime_) {
-
createTime = createTime_;
-
return this;
-
}
-
-
public Update build() {// 更新外部类的值
-
return new Update(this);
-
}
-
}
-
public Author getAuthor() {
-
return author;
-
}
-
-
public String getUpdateText() {
-
return updateText;
-
}
-
-
@Override
-
public String toString() {
-
return "Update [author=" + author + ", updateText=" + updateText
-
+ ", createTime=" + createTime + "]";
-
}
-
}
可以看到,静态内部类有与外部类同样的成员变量,但是静态内部类中的成员变量却是可以修改的。
来测试一下:
[java] view plaincopyprint?
-
public class Test {
-
-
public static void main(String[] args) {
-
final Update first = getUpdate("abc");// 获取不可变类的实例
-
System.out.println(first.toString());
-
}
-
-
private static Update getUpdate(String s) {
-
Update.Builder b = new Update.Builder();
-
b.updateText(s).author(new Author("mazhi"));
-
return b.build();// 初始化不可变类并返回实例
-
}
-
}
运行后的结果如下:
Update [author=Author [name=mazhi], updateText=abc, createTime=0]
在方法外部定义的内部类可以加上static关键字、可以定义成public、protected、默认的、private等多种类型,而普通类只能定义成public和默认的这两种类型。在外面引用静态内部类的名称为"外部类名.内部类名"。不需要创建外部类的实例对象,就可以直接创建静态内部类。例如:
[java] view plaincopyprint?
-
Update.Builder b = new Update.Builder(); // 获取静态内部类的实例对象
由于不依赖于外部类的实例对象,所以能访问外部类的非static成员变量。
想了解更多的内部类,可以查阅其它资料。
来看一道笔试题目,如下
[html] view plaincopyprint?
-
/**
-
* 不用比较运算符,判断int型的a,b两数的大小.
-
*/
-
public class Compare {
-
public static void main(String[] args) {
-
int a = 30;
-
int b = 2;
-
String[] buf = { "a>=b", "a < b" };
-
int id1 = (a - b) >> 31;
-
System.out.println(id1); // 1
-
System.out.println(buf[id1]); // a < b
-
}
-
}
要想顺利解答,需要掌握Java中一些最基础的运行符
4、移位运算符
Java中有三种移位运算符:<<(左移运算符)、>>(右移运算符)、>>>(无符号右移运算符)
在使用移位运算符时要注意:
1、只适用于byte、short、char、int、long类型运算
2、低于int类型的操作数会自动转换为int类型进行移位
3、如上的移位运算符的右边永远是移动的位数
举例:
[java] view plaincopyprint?
-
/*
-
* 12的2进制代码为1100,由于整数类型只有32位,所以如果移动的位数大于32,则与32位取余,也就是如果移动位数为35,则35%32=3,移动三位即可。而对于long类型来说,会与64位进行取余运算
-
* */
-
System.out.println(12>>33);//6
-
System.out.println(12>>32);//12
5、扩展运算符
需要好好掌握的就是赋值运算符(+=)
a=a+5与a+=5在底层的运行机制还是存在一定的差异性的。如果a为byte类型,则a=a+5会出错,而后者就不会。由于a为byte类型时,当a=a+5时,等号右边a+5会转换为int类型,在赋值byte类型(a),当然会报错。如下:
[java] view plaincopyprint?
-
byte a=3;
-
a+=5; // 会自动将int类型转换为byte
-
a=a+5; // 需要进行强制类型转换,因为a+5为int型,赋值byte会出错
6、与、或、非运算符
& 和 && 的区别
1、&和&&都可以用作逻辑与的运算符,表示逻辑与(and)。
2、&&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式。
举例:
[java] view plaincopyprint?
-
if(str != null && !str.equals("")){
-
// ...
-
}
表达式,当str为null时,后面的表达式不会执行,所以不会抛出异常NullPointerException。如果将&&改为&,则会抛出NullPointerException异常。再举例如:
[java] view plaincopyprint?
-
If(x==33 & ++y>0){ ... } // y会增长
-
If(x==33 && ++y>0){ ... } // y不会增长
3、& 还用作 位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01
4、&在C语言中也可作为一元运算符,返回操作数的地址。
归纳为逻辑操作(&,|,^)与条件操作(&&,||)之间的区别:
a.条件操作只能操作布尔型,而逻辑操作不仅可以操作布尔型,而且可以操作数值型,如取一个整的特定位数
b.逻辑操作不会产生短路
4、三元表达式(?:)
[java] view plaincopyprint?
-
char x = 'X';
-
int i = 0;
-
System.out.println(true ? x : 0);// X
-
System.out.println(false ? i : x);// 88
条件表达式结果类型的规则:
(1) 如果第二个和第三个操作数具有相同的类型,那么它就是条件表达式的类型。
(2) 如果一个操作的类型是T,T表示byte、short或char,而另一个操作数是一个int类型的“字面常量”,并且它的值可以用类型T表示,那条件表达式的类型就是T。
(3) 否则,将对操作数类型进行提升,而条件表达式的类型就是第二个和第三个操作被提升之后的类型。
现来使用以上规则解上面的迷题,第一个表达式符合第二条规则:一个操作数的类型是char,另一个的类型是字面常量为0的int型,但0可以表示成char,所以最终返回类型以char类型为准;第二个表达式符合第三条规则:因为i为int 型变量,而x又为char型变量,所以会先将x提升至int型,所以最后的结果类型为int型,但如果将i定义成final时,则返回结果类型为char,则此时符合第二条规则,因为final类型的变量在编译时就使用“字面常量0”来替换三元表
达式了:
[java] view plaincopyprint?
-
<pre code_snippet_id="136889" snippet_file_name="blog_20140818_6_8435528" name="code" class="java">final int i = 0;
-
System.out.println(false ? i : x);// X</pre>
在JDK1.4版本或之前,条件操作符 ?: 中,当第二个和延续三个操作数是引用类型时,条件操作符要求它们其中一个必须是另一个的子类型,那怕它们有同一个父类也不行:
[java] view plaincopyprint?
-
public class T {
-
public static void main(String[] args) {
-
System.out.println(f());
-
}
-
public static T f() {
-
// !!1.4不能编译,但1.5可以
-
// !!return true?new T1():new T2();
-
return true ? (T) new T1() : new T2();// T1
-
}
-
}
-
-
class T1 extends T {
-
public String toString() {
-
return "T1";
-
}
-
}
-
-
class T2 extends T {
-
public String toString() {
-
return "T2";
-
}
-
}
在5.0或以上版本中,条件操作符在延续二个和第三个操作数是引用类型时总是合法的。其结果类型是这两种类型的最小公共超类。公共超类总是存在的,因为Object是每一个对象类型的超类型,上面的最小公共超类是T,所以能编译。
7、final 关键字
1.1 final关键字的使用
(1)修饰类 如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能同时声明为abstract final或者interface final的。
(2)修饰方法 将方法声明为final,可以保证它们在使用中不被改变。被声明为final的方法也同样只能使用,不能重载。但是子类可以继承父类的final方法。
(3)修饰变量 表示属性值第一次初始化后不能被修改。final属性可以直接初始化或在构造函数中初始化
如果属性是直接初始化,则其值不能被其它函数(包括构造函数)修改。
[java] view plaincopyprint?
-
public final int a=2;
-
public BaseType(){// 构造方法
-
a=3; // 编译报错
-
}
(4)修饰方法参数 参数值不能被修改
(5)修饰方法中的局部变量 局部变量被第一次初始化后不能被修改
[java] view plaincopyprint?
-
public void hello() {
-
final String name;
-
name = "hi"; // ok
-
name = "hello"; // error
-
}
1.2 常见final的错误应用
[java] view plaincopyprint?
-
class Something {
-
final int i;
-
public void doSomething() {
-
System.out.println("i = " + i);
-
}
-
}
final int i是个final的实例变量。final的实例变量没有默认值,必须在constructor(构造器)结束之前被赋予一个明确的值。可以修改为"final int i = 0;"
再举一个例子:
[java] view plaincopyprint?
-
interface Playable {
-
void play();
-
}
-
interface Bounceable {
-
void play();
-
}
-
interface Rollable extends Playable, Bounceable {
-
Ball ball = new Ball("PingPang"); // 默认为public static final类型的
-
}
-
class Ball implements Rollable {
-
private String name;
-
public String getName() {
-
return name;
-
}
-
public Ball(String name) {
-
this.name = name;
-
}
-
public void play() {
-
ball = new Ball("Football");
-
System.out.println(ball.getName());
-
}
-
}
任何在interface里声明的成员变量,默认为public static final。
[java] view plaincopyprint?
-
Ball ball = new Ball("PingPang"); // 等价于 public static final Ball ball = new Ball("PingPang");
在Ball类的Play()方法中,改变了ball的引用,而这里的ball来自Rollable interface,因此编译器将在"ball = new Ball("Football");"这里显示有错。
从如上的两个例子可以看出,final使得被修饰的变量”不变”,但是由于对象型变量的本质是”引用”,使得”不变”也有了两种含义:引用本身的不变和引用指向的对象不变。
[java] view plaincopyprint?
-
final StringBuffer a= new StringBuffer("immutable");
-
final StringBuffer b= new StringBuffer("not immutable");
-
//a=b; // 编译期错误
-
a.append(" xx"); // 改变引用的内容
-
System.out.println(a); // 结果为:immutable xx
当a=b时会出现编译错误,不允许改变引用,但是我们却可以改变其引用的内容,如a.append(" xx");就改变了其引用指向的内容。所以经常有人会范如下的错误:
[java] view plaincopyprint?
-
public class Something {
-
public static void main(String[] args) {
-
Other o = new Other();
-
new Something().addOne(o);
-
}
-
public void addOne(final Other o) {
-
o.i++;
-
}
-
}
-
class Other {
-
public int i;
-
}
想通过添加final字段来阻止Other类中i的改变,其实这是办不到的,如上的程序依然能够通过o.i++来改变i的值。
1.3 使用final的意义
第一,为方法“上锁”,防止任何继承类改变它的本来含义和实现。设计程序时,若希望一个方法的行为在继承期间保持不变,而且不可被覆盖或改写,就可以采取这种做法。
第二,提高程序执行的效率,将一个方法设成final后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里(内嵌机制)。
1.4 Java7新特性 - final重抛
在Java7以前,经常可以看到如下的代码片段:
[java] view plaincopyprint?
-
try{
-
int a=0,x;
-
x=3/a;
-
}catch(Exception e){
-
throw e;
-
}
如上会将新抛出的异常类型java.lang.ArithmeticException声明为Exception,而真实具体的确被覆盖了。使用Java7新语法:
[java] view plaincopyprint?
-
try{
-
int a=0,x;
-
x=3/a;
-
}catch(final Exception e){
-
throw e;
-
}
只需要加个final关键字字即可,这样就不会抛出笼统的异常类型,这叫“final重抛‘。
8、finally关键字
再异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块。 所以说finally块是一定会被执行的。
finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。更加一般的说法是,finally 语句块应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 break 和 continue。
另外,throw 语句也属于控制转移语句。虽然 return、throw、break 和 continue 都是控制转移语句,但是它们之间是有区别的。其中 return 和 throw 把程序控制权转交给它们的调用者(invoker),而 break 和 continue 的控制权是在当前方法内转移。
[java] view plaincopyprint?
-
public class Test {
-
public static void main(String[] args) {
-
System.out.println("return value of getValue(): " + getValue());
-
}
-
public static int getValue() {
-
try {
-
return 0;
-
} finally {
-
return 1;
-
}
-
}
-
}
执行结果为:return value of getValue(): 1。因为finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。
[java] view plaincopyprint?
-
public class Test {
-
public static void main(String[] args) {
-
System.out.println("return value of getValue(): " + getValue());
-
}
-
public static int getValue() {
-
int i = 1;
-
try {
-
return i;
-
} finally {
-
i++;
-
}
-
}
-
}
其执行结果为:
return value of getValue(): 1
究其原因,我们来分析一下其执行顺序:分为正常执行(没有 exception)和异常执行(有 exception)两种情况。我们先来看一下正常执行的情况。打开.class文件,代码如下:
[java] view plaincopyprint?
-
public static int getValue();
-
0 iconst_1
-
1 istore_0 [i]
-
2 iload_0 [i]
-
3 istore_2
-
4 iinc 0 1 [i]
-
7 iload_2
-
8 ireturn
-
9 astore_1
-
10 iinc 0 1 [i]
-
13 aload_1
-
14 athrow
-
Exception Table:
-
[pc: 2, pc: 4] -> 9 when : any
-
Line numbers:
-
[pc: 0, line: 9]
-
[pc: 2, line: 11]
-
[pc: 4, line: 13]
-
[pc: 7, line: 11]
-
[pc: 9, line: 12]
-
[pc: 10, line: 13]
-
[pc: 13, line: 14]
-
Local variable table:
-
[pc: 2, pc: 15] local: i index: 0 type: int
-
Stack map table: number of frames 1
-
[pc: 9, full, stack: {java.lang.Throwable}, locals: {int}]
-
}
其执行图如下所示。
可以看到,如上的结果为什么是1而不是2了。
继续来看下一道题:
[java] view plaincopyprint?
-
public class Test {
-
public static void main(String[] args) {
-
System.out.println(test());
-
}
-
public static String test() {
-
try {
-
System.out.println("try block");
-
return test1();// 相当于代码String tmp=test1();return temp;
-
} finally {
-
System.out.println("finally block");
-
}
-
}
-
public static String test1() {
-
System.out.println("return statement");
-
-
return "after return";
-
}
-
}
运行结果为:
try block
return statement
finally block
after return
其实finally在项目开发中的主要作用就是关闭资源,如数据库连接对象Connection、输入流/输出流等。在JDK7中对此做了改变。把资源的作用域限定在代码块内,当程序离开这个代码块时,资源会被自动关闭,这就防止了未关闭而造成资源浪费等一些问题。
资源在处理完毕后,会自动关闭br。
9、finalize 方法名
Java 技术允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。