• 理解Java中的final和static关键字


    回顾这两个关键字前,先考虑一个问题:

    Static变量存储在JVM中的位置,或者说static变量是如何被加载的?

    JVM会把类的静态方法和静态变量在类加载的过程中读入方法区(Method Area),相当于常驻内存,
    如果一个方法或者变量声明为static,可以节约内存,不必要为每个对象实例化的时候分配内存。

    1.final关键字

    根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,
    它可以修饰非抽象类、非抽象类成员方法和变量。
    final类不能被继承,没有子类,final类中的方法默认是final的;
    final方法不能被子类的方法覆盖,但可以被继承
    final成员变量表示常量,只能被赋值一次,赋值后值不再改变;
    注意,final不能用于修饰构造方法;
    父类的private方法是不能被子类方法访问和覆盖的,因此private类型的方法默认是final类型的,也就是说编译器对final方法和private方法做的优化是一样的。

    (1)final类
    final类不能被继承,因此final类的成员方法没有机会被覆盖,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。
    典型的如JDK中的String,StringBuffer和StringBuilder。

    (2)final方法
    如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。
    使用final方法的原因有二:
    把方法锁定,防止任何继承类修改它的意义和实现;
    高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。
    注意和private方法的区分,private方法不可以在子类实例中访问,final可以在子类实例中直接调用,但是不能覆盖修改。

    (3)final变量(常量)
    用final修饰的成员变量表示常量,值一旦给定就无法改变!
    final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。

    (4)final参数
    当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class FinalParam {
        public static void main(String[] args){
            FinalParam test=new FinalParam();
            test.change(10);
        }
        public void change(final int i){
    //      i++; 编译报错
            System.out.print(i);
        }
    }

      

    2.static关键字

    static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块。

    (1)JVM对static关键字的处理

    被static修饰的成员变量和成员方法独立于该类的任何对象。它不依赖类特定的实例,被类的所有实例共享。
    只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。

    (2)static变量

    按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另一种是没有被static修饰的变量,叫实例变量。两者的区别是:
    对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
    对于实例变量,没创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。

    (3)static静态方法

    静态方法可以直接通过类名调用,任何的实例也都可以调用,因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。因为实例成员与特定的对象关联。
    因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。

    (4)static代码块

    static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块。
    如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class StaticArea {
     
        private static int a;
        private int b;
     
        static {
            StaticArea.a = 1;
            System.out.println(a);
            StaticArea temp = new StaticArea();
            temp.f();
            temp.b = 1000;
            System.out.println(temp.b);
        }
     
        static {
            StaticArea.a = 2;
            System.out.println(a);
        }
     
        public static void main(String[] args) {
        }
     
        static {
            StaticArea.a = 3;
            System.out.println(a);
        }
     
        public void f() {
            System.out.println("执行实例中方法");
        }
    }

    输出:

    1       
    执行实例中方法        
    1000      
    2      
    3      

    3.同时使用static和final关键字

    static final用来修饰成员变量和成员方法,可简单理解为“全局常量”,
    对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
    对于方法,表示不可覆盖,并且可以通过类名直接访问。

    4.JVM对final常量的优化

    摘自知乎问题 JVM对于声明为final的局部变量(local var)做了哪些性能优化?

    以下代码:
    static int foo() {
    int a = someValueA();
    int b = someValueB();
    return a + b; // 这里访问局部变量
    }
    与带final的版本,
    static int foo() {
    final int a = someValueA();
    final int b = someValueB();
    return a + b; // 这里访问局部变量
    }
    效果一模一样,由javac编译得到的字节码会是这样:
    invokestatic someValueA:()I
    istore_0 // 设置a的值
    invokestatic someValueB:()I
    istore_1 // 设置b的值
    iload_0 // 读取a的值
    iload_1 // 读取b的值
    iadd
    ireturn

    字节码里没有任何东西能体现出局部变量的final与否,Class文件里除字节码(Code属性)外的辅助数据结构也没有记录任何体现final的信息。既然带不带final的局部变量在编译到Class文件后都一样了,其访问效率必然一样高,JVM不可能有办法知道什么局部变量原本是用final修饰来声明的。

    但有一个例外,那就是声明的“局部变量”并不是一个变量,而是编译时常量的情况:
    static int foo2() {
    final int a = 2; // 声明常量a
    final int b = 3; // 声明常量b
    return a + b; // 常量表达式
    }
    这样的话实际上a和b都不是变量,而是编译时常量,在Java语言规范里称为constant variable。
    Chapter 4. Types, Values, and Variables
    其访问会按照Java语言对常量表达式的规定而做常量折叠。
    Chapter 15. Expressions
    实际效果跟这样的代码一样:
    static int foo3() {
    return 5;
    }
    由javac编译得到对应的字节码会是:
    iconst_5 // 常量折叠了,没有“访问局部变量”
    ireturn

    而这种情况如果去掉final修饰,那么a和b就会被看作普通的局部变量而不是常量表达式,在字节码层面上的效果会不一样
    static int foo4() {
    int a = 2;
    int b = 3;
    return a + b;
    }
    就会编译为:
    iconst_2
    istore_0 // 设置a的值
    iconst_3
    istore_1 // 设置b的值
    iload_0 // 读取a的值
    iload_1 // 读取b的值
    iadd
    ireturn

    但其实这种层面上的差异只对比较简易的JVM影响较大,因为这样的VM对解释器的依赖较大,原本Class文件里的字节码是怎样的它就怎么执行;对高性能的JVM(例如HotSpot、J9等)则没啥影响。这种程度的差异在经过好的JIT编译器处理后又会被消除掉,上例中无论是 foo3() 还是 foo4() 经过JIT编译都一样能被折叠为常量5。

    参考:
    Final关键字对JVM类加载器的影响

  • 相关阅读:
    CXB 闯关游戏
    CXB 移动“哨兵棋子”
    GHOJ 300 Hanoi塔
    攻防世界 web 进阶区 刷题记录
    攻防世界 web 新手练习 刷题记录
    TensorFlow01:增加变量显示+tensorboard可视化
    TensorFlow01:梯度下降
    TensorFlow01:张量
    01深度学习介绍
    05Python爬虫:响应内容写入文件
  • 原文地址:https://www.cnblogs.com/binyue/p/3731304.html
Copyright © 2020-2023  润新知