理解Java中的final和static关键字

简介:

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

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

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

>>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);
     }
}

  

>>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      

>>同时使用static和final关键字

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

>>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。

 


目录
相关文章
|
8天前
|
Java
Java基础—笔记—static篇
`static`关键字用于声明静态变量和方法,在类加载时初始化,只有一份共享内存。静态变量可通过类名或对象访问,但推荐使用类名。静态方法无`this`,不能访问实例成员,常用于工具类。静态代码块在类加载时执行一次,用于初始化静态成员。
10 0
|
2月前
|
存储 缓存 Java
保护隐私数据:使用Java `transient`关键字
保护隐私数据:使用Java `transient`关键字
25 0
|
13天前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
【4月更文挑战第6天】Java中的`synchronized`关键字用于处理多线程并发,确保共享资源的线程安全。它可以修饰方法或代码块,实现互斥访问。当用于方法时,锁定对象实例或类对象;用于代码块时,锁定指定对象。过度使用可能导致性能问题,应注意避免锁持有时间过长、死锁,并考虑使用`java.util.concurrent`包中的高级工具。正确理解和使用`synchronized`是编写线程安全程序的关键。
|
4天前
|
Java
Java关键字(1)
Java关键字(1)
|
1月前
|
安全 Java 编译器
Java 中的关键字
Java 中的关键字
74 0
|
1月前
|
存储 Java
【Java】深入理解Java中的static关键字
【Java】深入理解Java中的static关键字
22 0
|
1月前
|
算法 Java C++
【Java】深入理解Java中的Native关键字
【Java】深入理解Java中的Native关键字
36 0
|
1月前
|
Java
Java关键字大全
Java关键字大全
20 1
|
1月前
|
Java API PHP
多多关键字API php java Python
多多关键字API接口广泛应用于商家进行市场分析、竞品分析、关键词优化等场景。商家可以通过分析关键词数据,了解用户需求,制定针对性的营销策略,提高产品的曝光率和转化率。