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


### 回答1: final 是一个修饰符,能够用于类、变量和方法,表示它们不能被修改或重写。finally是一个关键字,它在异常处理语句使用,表示肯定会被执行的代码。finalize是一个Object类的方法,在垃圾收集器准备回收对象时会调用该方法,用于清理资源。 ### 回答2: 在Javafinalfinallyfinalize是三个不同的关键字,具有不同的用途和含义。 1. finalfinal是一个修饰符,用于声明一个不能被继承的类、一个不能被重写的方法、或者一个不能被修改的变量。当一个类被声明为final时,该类不能被其他类继承。当一个方法被声明为final时,该方法不能被子类重写。当一个变量被声明为final时,该变量的值不能被修改。 2. finallyfinally是一个关键字,用于定义在try-catch语句块的一个代码块,无论是否发生异常,该代码块都会被执行。finally块通常用于释放资源,比如关闭文件、网络连接等。 3. finalizefinalize是一个方法,用于在Java对象被垃圾回收器销毁之前进行一些清理工作。finalize方法的定义在Object类,可以被子类重写。finalize方法在垃圾回收器准备销毁对象时会被调用,但并不能保证一定会执行,因为垃圾回收是由垃圾回收器自行决定的。 总结: final用于修饰类、方法或变量,表示不可继承、重写或修改。finally用于定义在try-catch语句块的一个代码块,无论是否发生异常,都会执行。finalize是一个方法,在Java对象被销毁之前执行一些清理工作,但不能保证一定会被执行。 ### 回答3: 在Javafinalfinallyfinalize是三个不同的概念。 1. finalfinal是一个关键字,可以修饰变量、方法和类。当用final修饰变量时,表示该变量的值不可被更改。当用final修饰方法时,表示该方法不可被子类重写。当用final修饰类时,表示该类不可被继承。final主要用于限定对象的状态、行为或结构的不可改变性。 2. finallyfinally也是一个关键字,用于定义在try-catch结构的一个代码块。无论try块是否发生异常,finally的代码都会被执行。通常用finally来释放资源、关闭文件、数据库连接等必要的清理操作。 3. finalizefinalize()是一个方法名,属于Object类的一个方法,用于Java的垃圾回收机制。当一个对象变成垃圾,即没有任何引用指向该对象时,垃圾回收器会在适当的时机调用该对象的finalize()方法,进行资源释放和清理操作。但是,由于finalize()方法的执行时机不确定,并且Java8已经不鼓励使用finalize()方法,因此一般不建议使用该方法。 总结:final表示不可改变性,finally表示无论是否发生异常都会执行的代码块,而finalize是垃圾回收器在释放对象时调用的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值