Java之final、finally、finalize

1. final

在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。

1.1 修饰类

当用final修饰一个类时,表明这个类不能被继承。

final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法

1.2 修饰方法

使用final方法的原因有两个:

第一个原因是把方法锁定,final方法不可被继承成员重新定义,即不能通过改写方法来实现多态性;

第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌(inline)调用。所谓内联方式,是指编译器不用像平常调用函数那样的方式来调用方法,而是直接将方法内的代码通过一定的修改后copy到原代码中(将方法主体直接插入到调用处,而不是进行方法调用)。这样可以让代码执行的更快(因为省略了调用函数的开销)

注:类的private方法会隐式地被指定为final方法。

1.3 修饰变量

对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

1.4 深入理解final关键

1.4.1 类的final变量和普通变量有什么区别?

当用final作用于类的成员变量时(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可),成员变量必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值。

public class Test {
    public static void main(String[] args)  {
        String a = "hello2"; 
        final String b = "hello";
        String d = "hello";
        String c = b + 2; 
        String e = d + 2;
        System.out.println((a == c));
        System.out.println((a == e));
    }
}
true
false

为什么第一个比较结果为true,而第二个比较结果为fasle。这就是final变量和普通变量的区别了,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。因此在上面的一段代码中,由于变量b被final修饰,因此会被当做编译期常量,所以在使用到b的地方会直接将变量b替换为它的值。而对于变量d的访问却需要在运行时通过链接来进行。

不过要注意,只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化,比如下面的这段代码就不会进行优化:

public class Test {
    public static void main(String[] args)  {
        String a = "hello2"; 
        final String b = getHello();
        String c = b + 2; 
        System.out.println((a == c));
 
    }
 
    public static String getHello() {
        return "hello";
    }
}
false

1.4.2 被final修饰的引用变量指向的对象内容可变吗?

public class Test {
    public static void main(String[] args)  {
        final MyClass myClass = new MyClass();
        System.out.println(++myClass.i);
 
    }
}
 
class MyClass {
    public int i = 0;
}
1

这段代码可以顺利编译通过并且有输出结果,输出结果为1。这说明引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。

1.4.3 关于final参数的问题

关于网上流传的“当在方法中不需要改变作为参数的对象变量时,明确使用final进行声明,会防止无意的修改而影响到调用方法外的变量”这句话,个人理解这样说是不恰当的。

因为无论参数是基本数据类型的变量还是引用类型的变量,使用final声明都不会达到上面所说的效果。


上面这段代码好像让人觉得用final修饰之后,就不能在方法中更改变量i的值了。殊不知,方法changeValue和main方法中的变量i根本就不是一个变量,因为java参数传递采用的是值传递,对于基本类型的变量,相当于直接将变量进行了拷贝。所以即使没有final修饰的情况下,在方法内部改变了变量i的值也不会影响方法外的i。

public class Test {
    public static void main(String[] args)  {
        MyClass myClass = new MyClass();
        StringBuffer buffer = new StringBuffer("hello");
        myClass.changeValue(buffer);
        System.out.println(buffer.toString());
    }
}
 
class MyClass {
    void changeValue(final StringBuffer buffer) {
        buffer.append("world");
    }
}
helloworld

运行这段代码就会发现输出结果为 helloworld。很显然,用final进行修饰并没有阻止在changeValue中改变buffer指向的对象的内容。

假如把final去掉,万一在changeValue中让buffer指向了其他对象怎么办。如果把final去掉,然后在changeValue中让buffer指向了其他对象,也不会影响到main方法中的buffer,原因在于java采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响。

2. finally

 2.1 finally语句块一定会执行吗?

Code #1:

public class Test {
	public static void main(String[] args) {
		System.out.println("return value of test(): " + test());
	}

	public static int test() {
		int i = 1;
		// if(i == 1)
		// 	return 0;
		System.out.println("the previous statement of try block");
		i = i / 0;

		try {
			System.out.println("try block");
			return i;
		} finally {
			System.out.println("finally block");
		}
	}
}

只有与 finally 相对应的 try 语句块得到执行的情况下,finally 语句块才有机会执行。以上两种情况,都是在 try 语句块之前返回(return)或者抛出异常,所以 try 对应的 finally 语句块没有执行。

那好,即使与 finally 相对应的 try 语句块得到执行的情况下,finally 语句块一定会执行吗?

Code #2:

public class Test {
	public static void main(String[] args) {
		System.out.println("return value of test(): " + test());
	}

	public static int test() {
		int i = 1;
		try {
			System.out.println("try block");
			System.exit(0);
			return i;
		} finally {
			System.out.println("finally block");
		}
	}
}

在 try 语句块中执行了 System.exit (0) 语句,终止了 Java 虚拟机的运行。在一般的 Java 应用中基本上是不会调用这个 System.exit(0) 方法的,那么即使不调用 System.exit(0) 方法,finally 语句块就一定会执行吗?

答案还是否定的。当一个线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 语句块可能不会执行。

2.2 finally 语句剖析

在排除了以上 finally 语句块不执行的情况后,finally 语句块就得保证要执行,既然 finally 语句块一定要执行,那么它和 try 语句块与 catch 语句块的执行顺序又是怎样的呢?还有,如果 try 语句块中有 return 语句,那么 finally 语句块是在 return 之前执行,还是在 return 之后执行呢?

Code #3:

public class Test {
	public static void main(String[] args) {
		System.out.println("return value of test() : " + test());
	}

	public static int test() {
		int i = 1;
		try {
			System.out.println("try block");
			i = 1 / 0;
			return 1;
		} catch (Exception e) {
			System.out.println("exception block");
			return 2;
		} finally {
			System.out.println("finally block");
		}
	}
}
输出:
try block 
exception block 
finally block 
return value of test() : 2

代码3说明了 finally 语句块在 catch 语句块中的 return 语句之前执行。

更加一般的说法是,finally 语句块应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 break 和 continue。另外,throw 语句也属于控制转移语句。虽然 return、throw、break 和 continue 都是控制转移语句,但是它们之间是有区别的。其中 return 和 throw 把程序控制权转交给它们的调用者(invoker),而 break 和 continue 的控制权是在当前方法内转移。

Code #4:

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

Code #4:

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

可以轻松的理解代码3的执行结果是 1。因为finally 语句块在 try 或者 catch 中的 return 语句之前执行,返回值为 1。

那为什么代码4的返回值不是 2,而是 1 呢?按照代码4的分析逻辑,finally 中的 i++;语句应该在 try 中的 return i 之前执行? i 的初始值为 1,那么执行 i++;之后为 2,再执行 return i;那不就应该是 2 吗?怎么变成 1 了呢?

实际上,Java 虚拟机会把 finally 语句块作为 subroutine 直接插入到 try 语句块或者 catch 语句块的控制转移语句之前。但是,还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch 语句块会保留其返回值到本地变量表中。待 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者。

Code #5:

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();
		} 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

代码5也不算很难,return test1();这条语句等同于:
String tmp = test1(); 
return tmp;

3. finalize


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值