JAVA并发(五):关键词final

JAVA并发(五):关键词final

整理自https://www.pdai.tech/md/java/thread/java-thread-x-key-final.html

1 本文介绍以下问题

  • 所有的final修饰的字段都是编译期常量吗?
  • 如何理解private所修饰的方法是隐式的final?
  • 说说final类型的类如何拓展? 比如String是final类型,我们想写个MyString复用所有String中方法,同时增加一个新的toMyString()的方法,应该如何做?
  • final方法可以被重载吗? 可以
  • 父类的final方法能不能够被子类重写? 不可以
  • 说说final域重排序规则?
  • 说说final的原理?
  • 使用 final 的限制条件和局限性?
  • 看本文最后的一个思考题

2 final基础使用

2.1 修饰类

当某个类的整体定义为final时,就表明了你不能打算继承该类,而且也不允许别人这么做。即这个类是不能有子类的。

注意:final类中的所有方法都隐式为final,因为无法覆盖他们,所以在final类中给任何方法添加final关键字是没有任何意义的

这里顺道说说final类型的类如何拓展? 比如String是final类型,我们想写个MyString复用所有String中方法,同时增加一个新的toMyString()的方法,应该如何做?

设计模式中最重要的两种关系,一种是继承/实现;另外一种是组合关系。所以当遇到不能用继承的(final修饰的类),应该考虑用组合, 如下代码大概写个组合实现的意思:

/**
* @pdai
*/
class MyString{

    private String innerString;

    // ...init & other methods

    // 支持老的方法
    public int length(){
        return innerString.length(); // 通过innerString调用老的方法
    }

    // 添加新方法
    public String toMyString(){
        //...
    }
}

2.2 修饰方法

主要介绍

  • private 方法是隐式的final
  • final方法是可以被重载的

2.2.1 private与final

类中所有private方法都隐式地指定为final的,由于无法取用private方法,所以也就不能覆盖它。可以对private方法增添final关键字,但这样做并没有什么好处。看下下面的例子:

public class Base {
    private void test() {
    }
}

public class Son extends Base{
    public void test() {
    }
    public static void main(String[] args) {
        Son son = new Son();
        Base father = son;
        //father.test();
    }
}

Base和Son都有方法test(),但是这并不是一种覆盖,因为private所修饰的方法是隐式的final,也就是无法被继承,所以更不用说是覆盖了,在Son中的test()方法不过是属于Son的新成员罢了,Son进行向上转型得到father,但是father.test()是不可执行的,因为Base中的test方法是private的,无法被访问到

2.2.2 final方法可以重载

我们知道父类的final方法是不能够被子类重写的,那么final方法可以被重载吗? 答案是可以的,下面代码是正确的。

public class FinalExampleParent {
    public final void test() {
    }

    public final void test(String str) {
    }
}

2.3 修饰参数

Java允许在参数列表中以声明的方式将参数指明为final,这意味这你无法在方法中更改参数引用所指向的对象。这个特性主要用来向匿名内部类传递数据

2.4 修饰变量

  1. 不是所有的final修饰字段都是编译期常量

    现在来看编译期常量和非编译期常量, 如:

    public class Test {
        //编译期常量
        final int i = 1;
        final static int J = 1;
        final int[] a = {1,2,3,4};
        //非编译期常量
        Random r = new Random();
        final int k = r.nextInt();
    
        public static void main(String[] args) {
    
        }
    }
    

    k的值由随机数对象决定,所以不是所有的final修饰的字段都是编译期常量,只是k的值在被初始化后无法被更改

  2. static final

    一个既是static又是final 的字段只占据一段不能改变的存储空间,它必须在定义的时候进行赋值,否则编译器将不予通过。

    import java.util.Random;
    public class Test {
        static Random r = new Random();
        final int k = r.nextInt(10);
        static final int k2 = r.nextInt(10); 
        public static void main(String[] args) {
            Test t1 = new Test();
            System.out.println("k="+t1.k+" k2="+t1.k2);
            Test t2 = new Test();
            System.out.println("k="+t2.k+" k2="+t2.k2);
        }
    }
    //k=2 k2=7
    //k=8 k2=7
    

    我们可以发现对于不同的对象k的值是不同的,但是k2的值却是相同的,这是为什么呢? 因为static关键字所修饰的字段并不属于一个对象,而是属于这个类的。也可简单的理解为static final所修饰的字段仅占据内存的一个一份空间,一旦被初始化之后便不会被更改

  3. blank final

    java允许生成空白final,也就是说被声明为final但又没有给出定值的字段,但是必须在该字段被使用之前被赋值,这给予我们两种选择:

    • 在定义处进行赋值(这不叫空白final)
    • 在构造器中进行赋值,保证了该值在被使用前赋值。

    这增强了final的灵活性。

    看下面代码:

    public class Test {
        final int i1 = 1;
        final int i2;//空白final
        public Test() {
            i2 = 1;
        }
        public Test(int x) {
            this.i2 = x;
        }
    

    注意,如果字段由static和final修饰,仅能在定义处赋值,因为该字段不属于对象,属于这个类

3 final域重排序规则

这部分太绕了,等以后理解了再整理吧

https://www.pdai.tech/md/java/thread/java-thread-x-key-final.html

原文地址:https://www.cnblogs.com/cpaulyz/p/14305898.html