关于java:深入理解-Java-中的-final-关键字

7次阅读

共计 5698 个字符,预计需要花费 15 分钟才能阅读完成。

前言

对于 Java 中的 final 关键字,咱们首先能够从字面意思下来了解,百度翻译显示如下:

final 英文意思示意是最初的,不可更改的。那么对应在 Java 中也是表白这样的意思,能够用 final 关键字润饰变量、办法和类。不论是用来润饰什么,其本意都是指“它是无奈更改的”,这是咱们须要牢记的,为什么要无奈更改?无非就是设计所需或者能提高效率,牢记 final 的不可变的设计理念后再来理解 final 关键字的用法,便会顺其自然了。

注释

润饰变量

首先咱们看一个例子

    public static void main(String[] args) {
        String a = "hello1";
        final String b = "hello";
        String d = "hello";
        String c = b + 1;
        String e = d + 1;
        System.out.println(a == c);
        System.out.println(a == e);
    }
}

输入后果:

true
false

Process finished with exit code 0

为什么会失去这种后果呢?咱们来剖析一下:

  1. 变量 a 指的是字符串常量池中的hello1
  2. 变量 b 是 final 润饰的,变量 b 的值在编译时候就曾经确定了它的确定值,换句话说就是提前晓得了变量 b 的内容到底是个啥,相当于一个编译期常量;
  3. 变量 c 是 b + 1 失去的,因为 b 是一个常量,所以在应用 b 的时候间接相当于应用 b 的原始值 hello 来进行计算,所以 c 生成的也是一个常量,a 是常量,c 也是常量,都是 hello1,而 Java 中常量池中只生成惟一的一个 hello1 字符串,所以 a 和 c 是相等的;
  4. d 是指向常量池中 hello,但因为 d 不是 final 润饰,也就是说在应用 d 的时候不会提前晓得 d 的值是什么,所以在计算 e 的时候就不一样了,e 的话因为应用的是 d 的援用计算,变量 d 的拜访却须要在运行时通过链接来进行,所以这种计算会在堆上生成 hello1 , 所以最终 e 指向的是堆上的 hello1,所以 a 和 e 不相等。

论断:a、c 是常量池的hello1,e 是堆上的hello1

final 关键字润饰的变量称为常量,常量的意思是不可更改。变量为根本数据类型,不可更改很容易了解

能够看到 根本变量应用 final 润饰了就不可变了

那么对于援用类型呢?不可能改的是其援用地址,还是对象的内容?

咱们首先结构一个实体类:Student

public class Student {

    private String name;

    public Student(String name){this.name = name;}

    public String getName() {return name;}

    public void setName(String name) {this.name = name;}
}

接着依据创立一个 Person 对象:

能够看到,首先通过 final 关键字润饰一个对象 p,而后接着将 p 对象指向另一个新的对象,发现报错,也就是说 final 润饰的援用类型是不能扭转其援用地址的。

接着咱们改变 p 对象的 name 属性:

发现程序没有报错,输入的后果也是 小军

论断:被 final 润饰的变量不可更改其援用地址,然而能够更改其外部属性。

润饰办法

final 关键字润饰的办法不可被笼罩。

应用 final 办法起因有两个:

  1. 第一个起因是把办法锁定,以避免任何继承类批改它的含意,这是出于设计的思考:想要确保在继承中使办法的行为放弃不变,并且不会被笼罩。
  2. 第二个起因是效率,在 Java 的晚期实现中,如果将一个办法申明为 final,就是批准编译器将针对该办法的所有调用都转为内嵌调用,内嵌调用可能进步办法调用效率,然而如果办法很大,内嵌调用不会进步性能。而在目前的 Java 版本中(JDK1.5 当前),虚拟机能够主动进行优化了,而不须要应用 final 办法。

所以 final 关键字只有明确禁止笼罩办法时,才应用其润饰办法。

PS:《Java 编程思维》中指出类中所有的 private 办法都隐式指定为 final 的,所以对于 private 办法,咱们显式的申明 final 并没有什么成果。然而咱们创立一个父类,并在父类中申明一个 private 办法,其子类中是可能重写其父类的 private 办法的,这是为什么呢?

父类:Teacher.class

public class Teacher {private void study(){System.out.println("teacher");
    }
}

子类:Student.class

public class Student extends Teacher{private void study(){System.out.println("student");
    }
}

其实认真看看,这种写法是办法的笼罩吗?咱们通过多态的模式并不能调用到父类的 say() 办法:

并且,如果咱们在子类的 say() 办法中,增加 @Override 注解也是会报错的。

所以这种模式并不算办法的笼罩。

final 润饰的办法不能被子类笼罩,然而能够被子类应用和重载。

父类:A.class

public class A {

    public int a = 0;

    public int getA() {return a;}

    public final void setA(int a) {System.out.println("before set:A =" + this.a);// 必须加 this, 不加就会应用传入的 a
        this.a = a;
        System.out.println("after set:A =" + a);
    }

}

子类:B.class

public class B extends A {public B() {super.setA(2);// 正确,能够应用父类的 final 办法
        setA();// 调用本类本人办法}

    public final void setA() {System.out.println("before set:super a =" + a);
        super.a++;
        System.out.println("after set:super a =" + a);
    }
}

测试一下:

   public static void main(String[] args) {B b = new B();
    }

输入后果:

before set:A = 0
after set:A = 2
before set:super a = 2
after set:super a = 3

Process finished with exit code 0

论断:final 关键字润饰的办法不可被笼罩,然而能够被子类应用和重载。

润饰类

final 润饰类示意该类不可被继承。

  1. 也就是说不心愿某个类有子类的时候,用 final 关键字来润饰。并且因为是用 final 润饰的类,其类中所有的办法也被隐式的指为 final 办法。
  2. 在 JDK 中有个最显著的类 String,就是用 final 润饰的,将 String 类用 final 润饰很重要的一个起因是常量池。

彩蛋

面试题:说说 final、finally、finalize 三者的区别?

finally

finally 关键字个别在异样中应用,配合 try catch 一起应用,示意不论是否产生异样,finally 中的内容肯定会被执行。

1、try 中有 return 时执行程序

return 语句并不是函数的最终进口,如果有 finally 语句,这在 return 之后还会执行 finally(return 的值会暂存在栈外面,期待 finally 执行后再返回)

2、return 和异样获取语句的地位

  1. 状况一(try 中有 return,finally 中没有 return)
public class TryTest {public static void main(String[] args) {System.out.println(test());
    }

    private static int test() {
        int num = 10;
        try {System.out.println("try");
            return num += 80;
        } catch (Exception e) {System.out.println("error");
        } finally {if (num > 20) {System.out.println("num>20:" + num);
            }
            System.out.println("finally");
        }
        return num;
    }
}

输入后果:

try
num>20:90
finally
90

Process finished with exit code 0

剖析:“return num += 80”被拆分成了“num = num+80”和“return num”两个语句,线执行 try 中的“num =num+80”语句,将其保存起来,在 try 中的”return num“执行前,先将 finally 中的语句执行完,而后再将 90 返回。

  1. 状况二(try 和 finally 中均有 return)
public class TryTest {public static void main(String[] args) {System.out.println(test());
    }

    private static int test() {
        int num = 10;
        try {System.out.println("try");
            return num += 80;
        } catch (Exception e) {System.out.println("error");
        } finally {if (num > 20) {System.out.println("num>20:" + num);
            }
            System.out.println("finally");
            return 100;
        }
    }
}

输入后果:

try
num>20:90
finally
100

Process finished with exit code 0

剖析:try 中的 return 被”笼罩“掉了,不再执行。

  1. 状况三(finally 中没 return,然而 finally 中扭转返回值 num)
public class TryTest {public static void main(String[] args) {System.out.println(test());
    }

    private static int test() {
        int num = 10;
        try {System.out.println("try");
            return num;
        } catch (Exception e) {System.out.println("error");
        } finally {if (num > 20) {System.out.println("num>20:" + num);
            }
            System.out.println("finally");
            num = 100;
        }
        return num;
    }
}

输入后果:

try
finally
10

Process finished with exit code 0

剖析:尽管在 finally 中扭转了返回值 num,但因为 finally 中没有 return 该 num 的值,因而在执行完 finally 中的语句后,test()函数会失去 try 中返回的 num 的值,而 try 中的 num 的值仍然是程序进入 finally 代码块前保留下来的值,因而失去的返回值为 10。并且函数最初面的 return 语句不会执行。

  1. 状况四:(将 num 的值包装在 Num 类中)
public class TryTest {public static void main(String[] args) {System.out.println(test().num);
    }

    private static Num test() {Num num = new Num();
        try {System.out.println("try");
            return num;
        } catch (Exception e) {System.out.println("error");
        } finally {if (num.num > 20) {System.out.println("num.num>20:" + num.num);
            }
            System.out.println("finally");
            num.num = 100;
        }
        return num;
    }
}

class Num {public int num = 10;}

输入后果:

try
finally
100

Process finished with exit code 0

剖析:如果 return 的数据是援用数据类型,而在 finally 中对该援用数据类型的属性值的扭转起作用,try 中的 return 语句返回的就是在 finally 中扭转后的该属性的值。

finalize

finalize()是 Object 类的办法,java 技术运行应用 finalize()办法在垃圾收集器将对象从内存中革除进来之前做必要的清理工作。这个办法是在垃圾收集器在确定这个对象没有援用指向它时调用。finalize()办法是在垃圾收集器删除对象之前对这个对象调用的子类笼罩 finalize()办法以整顿系统资源或执行其余清理操作。

public class FinalizeTest {public static void main(String[] args) {Person p =  new Person("小马",55);
        p = null;// 此时堆当中的 Person 对象就没有变量指向了,就变成了垃圾,等到垃圾回收机制调用的 finalize()的时候会输入
        System.gc();}
}

class Person {

    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected void finalize() throws Throwable {System.out.println("执行 finalize()回收对象");
    }
}

总结

应用 final 关键字的益处:

  1. final 办法比非 final 快一些。
  2. final 关键字进步了性能。JVM 和 Java 利用都会缓存 final 变量。
  3. final 变量能够平安的在多线程环境下进行共享,而不须要额定的同步开销。
  4. 应用 final 关键字,JVM 会对办法、变量及类进行优化。

结尾

我是一个正在被打击还在致力后退的码农。如果文章对你有帮忙,记得点赞、关注哟,谢谢!

正文完
 0