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

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。

好文书签——
Final关键字对JVM类加载器的影响

理解Java中的final和static关键字的更多相关文章

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

    Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使 ...

  2. (转)深入理解Java中的final关键字

    转自:http://www.importnew.com/7553.html Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方 ...

  3. Java基础——深入理解Java中的final关键字(转载)

    Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使 ...

  4. 深入理解Java中的final关键字(转)

    文章转自http://www.importnew.com/7553.html Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量, ...

  5. 【转】深入理解Java中的final关键字

    Java 中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了 什么?使用final的好处是什么?最后也有一 ...

  6. 深入理解 Java 中的 final 关键字

    final 是Java 中重要关键字之一,可以应用于类.方法以及变量上.这篇文章中将讲解什么是 final 关键字?将变量.方法和类声明为 final 代表了什么?使用 final 的好处是什么? f ...

  7. [转] Java中的final、static、this、super

    final 关键字 final关键字主要用在三个地方:变量.方法.类. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改:如果是引用类型的变量,则在对其初始化之后便 ...

  8. Java基础之final和static关键字

    一.final        根据程序上下文环境,它可以修饰非抽象类.非抽象类成员方法和变量.         final类不能被继承,没有子类,final类中的方法默认是final的.        ...

  9. Java基础之final、static关键字

    一.前言 关于这两个关键字,应该是在开发工作中比较常见的,使用频率上来说也比较高.接口中.常量.静态方法等等.但是,使用频繁却不代表一定是能够清晰明白的了解,能说出个子丑演卯来.下面,对这两个关键字的 ...

随机推荐

  1. suspend

    两个线程的run方法同时调用一个加了同步锁的方法,如果一个线程使用了suspend方法,那么会独占并且锁死这个同步方法,别的线程就永远没有办法进入这个线程了. 特别的是,在main方法中,调用一个线程 ...

  2. [Maven]Eclipse插件之Maven配置及问题解析.

    前言:今天在自己环境装了Maven环境, 并且安装了Eclipse插件, 在查找插件过程中确实遇到一些问题, 好不容易找到一个  却又有问题.装好了插件之后, 用Eclipse创建Maven项目却出现 ...

  3. Ios cordova安装

    安装NODEJS:http://nodejs.org/ (如果你安装了旧版本的需要手动安装npm,不过最新版的自带了) 进入终端命令行使用npm安装cordova如下: $ sudo npm inst ...

  4. BI案例:某通信企业综合报表和决策分析系统实施案例

    业务需求 某家位列国内五大电信运营商之列的通信公司(以下简称“通信公司”)在多年的运营过程中,积累了大量的业务数据,分散在各个运营系统中(计费系统.九七工程.网管系统等).这些系统主要是面向公司的日常 ...

  5. 通过Driver获取数据库连接

    先看一下文件,在当前包下有一个properties配置文件,在根目录下有一个lib文件夹,里面放的是mySql的驱动jar包 Driver :是一个接口,数据库厂商必须提供实现的接口,能从其中获取数据 ...

  6. 让PHP程序永远在后台运行

    PHP里有个函数很有用.这是在最近的开发中才逐渐用到的. int ignore_user_abort ( [bool setting] ) 这个函数的作用是指示服务器端在远程客户端关闭连接后是否继续执 ...

  7. javascript 数组方法解析

    测试数组:testArrayA = ['a','b','c','d','e'] , testArrayB = [2,3,6,1] 1.删除数组最后一项(pop()): 返回删除那一项的值:var po ...

  8. 压力测试工具Ab简介

    Apache安装包中自带的压力测试工具 Apache Benchmark(简称ab) 简单易用,这里就采用 ab作为压力测试工具了. 1.独立安装 通过 yum-utils中的yumdownload  ...

  9. docker 安装 MySQL 8,并减少内存占用 记录

    目前vps 1cpu 512m内存 MySQL内存占用77% ,约350m ,经过修改配置文件优化后如下 $ ps aux 进入docker bash $ docker exec -it pwc-my ...

  10. 1 vmware 如何联网,以及行命令令初步

    VMware安装Linux(我安装的是ubuntu),没法上网,上网教程见:https://blog.csdn.net/qq_28090573/article/details/78730552 安装完 ...