static和final是两个我们必须掌握的关键字。不同于其他关键字,他们都有多种用法,而且在一定环境下使用,可以提高程序的运行性能,优化程序的结构。下面我们来了解一下final关键字及其用法。
final从总体上来说是“不可变的”,可用于修改类、方法、变量。
1. 前情回顾
final、finally、finalize区别
1.1 final 关键字
- final 修饰类不能被继承,比如(String、StringBuilder、StringBuffer、Math,不可变类)
- final 修饰方法不能被重写(但是可以被重载),不能同时用abstract和final修饰类(abstract修饰的类是抽象类,抽象类是用于被子类继承的,和final起相反的作用)
- final 修饰的成员变量是不可变的,如果成员变量是基本数据类型,初始化之后成员变量的值不能被改变,如果成员变量是引用类型,那么它只能指向初始化时指向的那个对象,不能再指向别的对象,但是对象当中的内容是允许改变的。
1.2 Finally
通常和try catch搭配使用,保证不管有没有发生异常,资源都能够被释放(释放连接、关闭IO流)
- 当try中有return时执行顺序:return语句并不是函数的最终出口,如果有finally语句,这在return之后还会执行finally(return的值会暂存在栈里面,等待finally执行后再返回)
(1)情况一(try中有return,finally中没有return)
/**
* try中有return,finally中没有return
* 输出结果:
* 原来的值:num = 10
* 操作后的值 num = 20
* finally num = 30
* test_1() = 20
* 先将try中return的值保存在栈中,等执行完finally中的逻辑后,再返回保存的try中的return
* @return
*/
public static int test_1(){
int num = 10;
try {
System.out.println("原来的值:num = " + num);
num = num + 10;
System.out.println("操作后的值 num = " + num);
return num;
}catch (Exception e){
num = num + 10;
System.out.println("Exception num = " + num);
return num;
}finally {
num = num + 10;
System.out.println("finally num = " + num);
}
}
(2)情况二(try和finally中均有return)
- 注意引用类型,具体看情况四
/**
* try和finally中均有return
* 输出结果:
* 原来的值:num = 10
* 操作后的值 num = 20
* finally num = 30
* test_2() = 30
* try中的return被finally 中的return ”覆盖“掉了,不再执行。
* @return
*/
public static int test_2(){
int num = 10;
try {
System.out.println("原来的值:num = " + num);
num = num + 10;
System.out.println("操作后的值 num = " + num);
return num;
}catch (Exception e){
num = num + 10;
System.out.println("Exception num = " + num);
return num;
}finally {
num = num + 10;
System.out.println("finally num = " + num);
return num;
}
}
(3)情况三(finally中改变返回值num,但是没有return)
虽然在finally中改变了返回值num,但因为finally中没有return该num的值,因此在执行完finally中的语句后,test()函数会得到try中返回的num的值,而try中的num的值依然是程序进入finally代码块前保留下来的值,因此得到的返回值为20。并且函数最后面的return语句不会执行。
(4)情况四(try和finally中均有return,但是数据类型是引用类型
/**
* 输出结果:
* 原来的值:num = 10
* 操作后的值 num = 20
* finally num = 100
* test_3() = DemoFinally.Num(num=100)
* 引用类型 的finally
* @return
*/
public static Integer test_3(){
Num num = new Num();
try {
num.num = 10;
System.out.println("原来的值:num = " + num.num);
num.num = num.num +10;
System.out.println("操作后的值 num = " + num.num);
return num.num;
}catch (Exception e){
num.num = num.num +10;
System.out.println("Exception num = " + num.num);
}finally {
num.num = 100;
System.out.println("finally num = " +num.num);
}
return num.num;
}
static class Num{
public int num = 10;
}
(5)总结
try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中的语句,而后分为以下三种情况:
- 情况一:如果finally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。
- 情况二:如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。
- 情况三:如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况:
如果return的数据是基本数据类型,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。
如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。
1.3 Finalize
Finalize是object类中的一个方法,子类可以重写finalize()方法实现对资源的回收。垃圾回收只负责回收内存,并不负责资源的回收,资源回收要由程序员完成,Java虚拟机在垃圾回收之前会先调用垃圾对象的finalize方法用于使对象释放资源(如关闭连接、关闭文件),之后才进行垃圾回收,这个方法一般不会显示的调用,在垃圾回收时垃圾回收器会主动调用。
- 使用finalize还需要注意一个事,调用super.finalize();
一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。
下面我们来深入理解一下被final修饰的类、方法、变量:
2. final 关键字
2.1 final 类
final修饰的类,该类不能被继承。当你确认一个类永远不会被继承或不想被继承,那么就可以用final修饰。
同样,对于接口(interface)和抽象类(abstract Class),其本就是为了“多态”而设计,自然无法用final关键字修饰
- final类中的成员方法默认也被隐式指定为final方法。
2.2 final 方法
final修饰的方法不可被重写。
/**
* 父类
* @author wxw
*/
public class FinalDemo1 {
public final void test() {
}
}
2.3 final 变量
final变量包括成员变量和局部变量。变量类型包括基本数据类型、对象。
- 通过final修饰局部基本类型变量(及其包装类),数值一经初始化(可以定义时初始化,也可以在使用前初始化)不可改变。如:
final int a = 0;
a = 1;//报错
final int b;
b = 1;//编译通过
- 通过final修饰局部引用类型变量时,其引用的对象(内存地址)(可以定义时初始化,也可以在使用前初始化)不可改变,但对象中存放的数据可以改变
public static void main(String[] args) {
final String str1 = "helloWorld";
str1 = "helloChina";//编译出错,String的不可变性,此处返回的是新的对象引用。
final StringBuilder sb = new StringBuilder("hello");
sb.append("world");//编译通过
sb = new StringBuilder("China");//编译出错
}
- final修饰的成员变量必须在定义的时候直接初始化,否则会编译出错
public class FinalDemo1 {
public final int age;//final修饰的基本类型,编译出错
public final int age1 = 20;//final修饰的基本类型,编译通过
public final StringBuilder address;// final修饰的引用类型,编译出错
public final StringBuilder address1 = new StringBuilder("中国");//final修饰的引用类型,编译通过
}
(1)那么final变量与普通变量之间到底有何区别,看下面的例子
public static void main(String[] args) {
String str0 = "helloWorldChina";
String str1 = "helloWorld";
String str3 = str1 + "China";
System.out.println(str0 == str3);//false
final String str2 = "helloWorld";
String str4 = str2 + "China";
System.out.println(str0 == str4);//true
final String str5;
str5 = "helloWorld";
String str6 = str5 + "China";
System.out.println(str0 == str6);//false
}
- str0 == str3运行结果为false,这是因为通过“+”生成了一个新的字符串对象,返回的引用地址和str0不再一样
- 那么str0 == str4的执行结果为什么是true?
通过final修饰的变量,如果在编译期都可以知道确切值(定义变量的时候就初始化),那么在编译器会将其当做常量使用,所有用到该变量的地方就相当于直接使用该常量,String str4 = str2 + "China" 在编译期间都已经合并处理成String str4 = "helloWorldChina",因此str0与str4引用了常量池中同一个字符串字面量的地址,故而结果为true。
- str0 == str6的执行结果为false也很好理解
str5在编译期并不知道确切值,而是在使用之前才进行初始化,因此编译器无法事先进行合并处理,str6通过“+”生成了一个新的字符串对象,返回的引用地址和str0也不再一样。
而针对基本数据类型来说定义为final变量与普通变量,比较结果来说并无差异
public static void testint(){
int int0 = 8;
final int int1;
int1 = 4;
int int2 = int1 + 4;
System.out.println(int2 == int0);//true
}
因为基本数据类型并不存在引用传递的概念,基本类型变量也是字面常量,所以对基本类型的操作都是直接对值的操作,和引用不一样,比较的并非地址。