【Java基础知识】final 关键字解析

staticfinal是两个我们必须掌握的关键字。不同于其他关键字,他们都有多种用法,而且在一定环境下使用,可以提高程序的运行性能,优化程序的结构。下面我们来了解一下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
}

因为基本数据类型并不存在引用传递的概念,基本类型变量也是字面常量,所以对基本类型的操作都是直接对值的操作,和引用不一样,比较的并非地址。

猜你喜欢

转载自blog.csdn.net/qq_41893274/article/details/113770260