java中的final变量

08月19日 收藏 0 评论 5 java开发

java中的final变量

转载声明:文章来源https://blog.csdn.net/xufei512/article/details/50773710

【java中为什么会有final变量】

      final这个关键字的含义是“这是无法改变的”或者“终态的”;那么为什么要阻止改变呢?

      java语言的发明者可能由于两个目的而阻止改变:
1).效率问题:
      jdk中的某些类的某些方法,是不允许被用户覆盖的,设计者可能认为,所用方法已经是最好的方法,
      用户私自覆盖,或是由于疏忽而覆盖,就会影响JVM或是系统的系能;
2). 设计所需:
      众所周知,有些情况必须使用final关键字,比如方法中的匿名内部类的参数传递。

【final关键字的使用方法】

【修饰变量】:final成员变量表示常量,只能被赋值一次,赋值后值不再改变。

【修饰方法】:final方法不能被子类方法覆盖,但可以被继承。

【修饰类】:final类不能被继承,没有子类,final类中所有方法都是final的。

【final的内存分配方式】

【修饰变量】:通常情况下,final变量有3个地方可以赋值:直接赋值,构造函数中,或是初始化块中。

【初始化】:
      由于在java的语法中,声明和初始化是联系在一起的,也就是说:如果你不显示的初始化一个变量,系统会自动用一个默认值来对其进行初始化。(如int就是0)

      对于final变量,在声明时,如果你没有赋值,系统默认这是一个空白域,在构造函数进行初始化,如果是静态的,则可以在初始化块。

【内存】:
      常量(final变量)和非final变量的处理方式是不一样的;每一个类型在用到一个常量时,都会复制一份到自己的常量池中;常量也像类变量(static)一样保存在方法区,只不过他保存在常量池。(可能是,类变量被所有实例共享,而常量池是每个实例独有的。)

【修饰方法】:保存在方法区,并且可以被函数代码直接替换,而不用等到执行时再决定具体是那个函数。

【修饰类】:保存在方法区。

【java中变量的初始化顺序】
      变量的初始化次序优于任何方法,甚至在构造方法的前面。对于static变量也是一样,如果变量是原始类型,那么它得到一个标准的原始类型的初始值,如果是一个对象的引用,除非你创建了一个新的对象给这个引用,否则就是null。

      static变量在需要的时候才会初始化,并且在这个类的构造函数和所有其他普通变量之前调用,static之后就不再进行初始化了,static变量在类初始化时(注意不是实例),就必须分配内存空间,static变量单独划分一块存储空间。

      java类首次装入时,会对静态成员变量或方法进行一次初始化,先初始化父类的静态代码-->初始化子类的静态代码-->(创建使历史,如果不创建实例,则后面的不执行)初始化父类的非静态代码-->初始化父类的构造-->初始化子类的非静态代码-->初始化子类的构造

      类只有在使用new调用创建的时候才会被java类装载器装入。

【final方法为何会高效】
      final方法会在编译的过程中利用内嵌机制进行inline优化

      inline优化是指:在编译的时候直接调用函数代码替换,而不是在运行时调用函数。

      inline需要在编译的时候就知道最后要用哪个函数,显然,非final是不行的。非final方法可能在子类中被重写,由于可能出现多态的情况,编译器在编译阶段。并不能确定将来调用方法的对象的真正类型,也就无法确定到底调用哪个方法。

【什么是多态】
      按字面的意思是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

【非final方法为什么会出现多态】
      显然,如果派生出一个子类,覆盖非final方法,就会出现2个这样的方法可供调用,这就是多态。

【final变量的变与不变】

      有人说final变量在赋值后就不可变,那么这个不变到底指的是什么呢?这个不变指的是引用,是地址,而所引用的对象的内容仍然是可变的。就是说,这个final变量永远指向某个对象,是一个常量指针,而不是指向常量的指针。

【final变量的效率】
      在能够通过编译的前提下,无论局部变量声明时带不带final关键字修饰,对其访问的效率都一样。

      例如说,以下代码:

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。其访问会按照Java语言对常量表达式的规定而做常量折叠。
实际效果跟这样的代码一样:

static int foo3() {
return 5;
}

      由javac编译得到对应的字节码会是:

iconst_5 // 常量折叠了,没有“访问局部变量”
ireturn

      (用Eclipse里的Java编译器ECJ来编译 foo3() 可能会在iconst_5之前看到一些冗余的对局部变量的代码。那个其实没有任何作用,真正有用的还是后面的iconst_5,所以仍然符合Java语言规范的要求。可以在Preferences->Java->Compiler->Code Generation->Preserve unused (never read) local variables把钩去掉来改变Eclipse这一行为,然后得到的代码就会跟javac更接近。)

       而这种情况如果去掉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。

      还有,先把成员或静态变量读到局部变量里保持一定程度的一致性,例如:在同一个方法里连续两次访问静态变量A.x可能会得到不一样的值,因为可能会有并发读写;但如果先有final int x = A.x然后连续两次访问局部变量x的话,那读到的值肯定会是一样的。这种做法的好处通常在有数据竞态但略微不同步没什么问题的场景下,例如说有损计数器之类的。

      最后,其实很多人用这种写法的时候根本就没想那么多吧。多半就是为了把代码写短一点,为了把一串很长的名字弄成一个短一点的而把成员或静态变量读到局部变量里,顺便为了避免自己手滑在后面改写了局部变量里最初读到的值而加上final来让编译器(javac之类)检查。例如:

final int threshold = MySuperLongClass.someImportantThreshold;

歪打正着 ^_^
===========================================================================================
      【final关键字的具体应用】【final+变量】:在实际应用中,这种形式是非常少见的。 比如logger是可以的,但是貌似并不是非常实用,或许用户仍然希望通过setter来改变logger变量。

      【static+final+变量】:常量。经常使用。

      【final+方法】:JDK中常用,但是自己并未常用。

      【final+类】:helper类经常使用。

      【final用于匿名内部类的参数传递】:在多线程测试时,经常使用。

      【final用于方法的参数】:并不常用。

C 5条回复 评论
灵魂火符

我想咨询一下产品经理对技术的要求有多高呢?请问数据科学专业投递平台型产品经理是否合适呢?我是海外留学生,并没有相关的产品实习经验,本科时期的实习经历也很少,都是会计师事务所的事情,感觉对这个岗位应聘没有任何帮助。由于今年疫情原因现在还在国外也没办法回去进行实习,现在秋招就快开始了真的很焦虑了

发表于 2024-04-08 23:00:00
0 0
一只小鹿哈

懂了懂了

发表于 2022-05-10 23:00:00
0 0
Alone

这是我一直没记住的一个重点

发表于 2021-11-27 21:00:00
0 0
卡卡卡乐星

真棒!茅塞顿开的感觉。

发表于 2021-11-20 22:00:00
0 0
壁虎极点多

技多不压身

发表于 2021-09-10 18:20:00
0 0