深入理解Java中的final关键字

2018-06-27 09:43:55来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

1     深入理解Java中的final关键字

1.1   final关键字的含义?

final在Java中是一个保留的关键字,可以声明成员变量、方法、类以及本地变量。一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编译器会报编译错误。

 

1.2   什么是final变量?

凡是对成员变量或者本地变量(在方法中的或者代码块中的变量称为本地变量)声明为final的都叫作final变量。final变量经常和static关键字一起使用,作为常量。下面是final变量的例子:

1

2

public static final String LOAN = "loan";

LOAN = new String("loan") //invalid compilation error

final变量是只读的。

1.3    什么是final方法?

final也可以声明方法。方法前面加上final关键字,代表这个方法不可以被子类的方法重写。如果你认为一个方法的功能已经足够完整了,子类中不需要改变的话,你可以声明此方法为final。final方法比非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。下面是final方法的例子:

 

 

1.4    什么是final类?

使用final来修饰的类叫作final类。final类通常功能是完整的,它们不能被继承。Java中有许多类是final的,譬如String, Interger以及其他包装类。下面是final类的实例:

 

1.5    final关键字的好处

下面总结了一些使用final关键字的好处

  1. final关键字提高了性能。JVM和Java应用都会缓存final变量。
  2. final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
  3. 使用final关键字,JVM会对方法、变量及类进行优化。

1.5.1 不可变类

创建不可变类要使用final关键字。不可变类是指它的对象一旦被创建了就不能被更改了。String是不可变类的代表。不可变类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等等。

1.5.1.1  为什么String是不可变的

String是所有语言中最常用的一个类。我们知道在Java中,String是不可变的、final的。Java在运行时也保存了一个字符串池(String pool),这使得String成为了一个特别的类。

String类不可变性的好处

  1. 只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现(译者注:String interning是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串。),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。
  2. 如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
  3. 因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
  4. 类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。
  5. 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

以上就是我总结的字符串不可变性的好处。

1.5.1.2  如何写一个不可变类?

不可变对象对于缓存是非常好的选择,因为你不需要担心它的值会被更改。不可变类的另外一个好处是它自身是线程安全的,你不需要考虑多线程环境下的线程安全问题。

 

n  下面是创建不可变类的方法,我也给出了代码,加深理解。

 

  1. 将类声明为final,所以它不能被继承
  2. 将所有的成员声明为私有的,这样就不允许直接访问这些成员
  3. 对变量不要提供setter方法
  4. 将所有可变的成员声明为final,这样只能对它们赋值一次
  5. 通过构造器初始化所有成员,进行深拷贝(deep copy)
  6. 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝
  7. 为了理解第5和第6条,我将使用FinalClassExample来阐明。

 

FinalClassExample.java

package com.journaldev.java;

 

import java.util.HashMap;

import java.util.Iterator;

 

public final class FinalClassExample {

 

    private final int id;

 

    private final String name;

 

    private final HashMap testMap;

 

    public int getId() {

        return id;

    }

 

    public String getName() {

        return name;

    }

 

    /**

     * 可变对象的访问方法

     */

    public HashMap getTestMap() {

        //return testMap;

        return (HashMap) testMap.clone();

    }

 

    /**

     * 实现深拷贝(deep copy)的构造器

     * @param i

     * @param n

     * @param hm

     */

 

    public FinalClassExample(int i, String n, HashMap hm){

        System.out.println("Performing Deep Copy for Object initialization");

        this.id=i;

        this.name=n;

        HashMap tempMap=new HashMap();

        String key;

        Iterator it = hm.keySet().iterator();

        while(it.hasNext()){

            key=it.next();

            tempMap.put(key, hm.get(key));

        }

        this.testMap=tempMap;

    }

 

    /**

     * 实现浅拷贝(shallow copy)的构造器

     * @param i

     * @param n

     * @param hm

     */

    /**

    public FinalClassExample(int i, String n, HashMap hm){

        System.out.println("Performing Shallow Copy for Object initialization");

        this.id=i;

        this.name=n;

        this.testMap=hm;

    }

    */

 

    /**

     * 测试浅拷贝的结果

     * 为了创建不可变类,要使用深拷贝

     * @param args

     */

    public static void main(String[] args) {

        HashMap h1 = new HashMap();

        h1.put("1", "first");

        h1.put("2", "second");

 

        String s = "original";

 

        int i=10;

 

        FinalClassExample ce = new FinalClassExample(i,s,h1);

 

        //Lets see whether its copy by field or reference

        System.out.println(s==ce.getName());

        System.out.println(h1 == ce.getTestMap());

        //print the ce values

        System.out.println("ce id:"+ce.getId());

        System.out.println("ce name:"+ce.getName());

        System.out.println("ce testMap:"+ce.getTestMap());

        //change the local variable values

        i=20;

        s="modified";

        h1.put("3", "third");

        //print the values again

        System.out.println("ce id after local variable change:"+ce.getId());

        System.out.println("ce name after local variable change:"+ce.getName());

        System.out.println("ce testMap after local variable change:"+ce.getTestMap());

 

        HashMap hmTest = ce.getTestMap();

        hmTest.put("4", "new");

 

        System.out.println("ce testMap after changing variable from accessor methods:"+ce.getTestMap());

 

    }

 

}

 

输出:

Performing Deep Copy for Object initialization

true

false

ce id:10

ce name:original

ce testMap:{2=second, 1=first}

ce id after local variable change:10

ce name after local variable change:original

ce testMap after local variable change:{2=second, 1=first}

ce testMap after changing variable from accessor methods:{2=second, 1=first}

现在我们注释掉深拷贝的构造器,取消对浅拷贝构造器的注释。也对getTestMap()方法中的返回语句取消注释,返回实际的对象引用。然后再一次执行代码。

Performing Shallow Copy for Object initialization

true

true

ce id:10

ce name:original

ce testMap:{2=second, 1=first}

ce id after local variable change:10

ce name after local variable change:original

ce testMap after local variable change:{3=third, 2=second, 1=first}

ce testMap after changing variable from accessor methods:{3=third, 2=second, 1=first, 4=new}

从输出可以看出,HashMap的值被更改了,因为构造器实现的是浅拷贝,而且在getter方法中返回的是原本对象的引用。

1.5.2 关于final的重要知识点

  1. final关键字可以用于成员变量、本地变量、方法以及类。
  2. final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
  3. 你不能够对final变量再次赋值。
  4. 本地变量必须在声明时赋值。
  5. 在匿名类中所有变量都必须是final变量。
  6. final方法不能被重写。
  7. final类不能被继承。
  8. final关键字不同于finally关键字,后者用于异常处理。
  9. final关键字容易与finalize()方法搞混,后者是在Object类中定义的方法,是在垃圾回收之前被JVM调用的方法。
  10. 接口中声明的所有变量本身是final的。
  11. final和abstract这两个关键字是反相关的,final类就不可能是abstract的。
  12. final方法在编译阶段绑定,称为静态绑定(static binding)。
  13. 没有在声明时初始化final变量的称为空白final变量(blank final variable),它们必须在构造器中初始化,或者调用this()初始化。不这么做的话,编译器会报错“final变量(变量名)需要进行初始化”。
  14. 将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
  15. 按照Java代码惯例,final变量就是常量,而且通常常量名要大写:

private final int COUNT = 10;

  1. 对于集合对象声明为final指的是引用不能被更改,但是你可以向其中增加,删除或者改变内容。譬如:

 

我们已经知道final变量、final方法以及final类是什么了。必要的时候使用final,能写出更快、更好的代码的。

 

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:Apache Maven(四):依赖

下一篇:opensearch空查询