static 和 final 关键字 对实例变量赋初始值的影响
static 和 final 关键字 对实例变量赋初始值的影响
最近一直在看《深入理解Java虚拟机》,在看完了对象内存分配、Class文件格式之后,想深扒一下实例变量是如何被赋上初始值的这个问题的细节。
在2.3.1小节中讲对象创建的时候,讲到内存分配有两种方式:一种是指针碰撞;另一种是空闲列表。
而选择哪种分配方式是由JAVA堆是否规整决定,而JAVA堆是否规整则由虚拟机所采用的垃圾收集器是否带压缩整理功能决定。
我们不管内存分配采用何种方式,当内存分配完成后,虚拟机将分配到的内存空间都初始化为零值,这里的零值代表各个类型的初始值。比如 int类型的实例变量的初始值为0,boolean类型的默认初始值为false。因此,这里的初始值,是从虚拟视角看到的初始值。但是从JAVA程序视角来看,我们给变量赋上的值,是在 <init>
方法中进行的。下面讨论从JAVA程序视角看,实例变量如何被赋上初始值的。
如果一个实例变量在被final修饰后,该变量赋初始值的时候,要么在声明实例变量的时候赋值,如下所示:
public class VariableTest {
private final int a;//编译期报错:Variable a may not be initialized
}
public class VariableTest {
private final int a = 127;//在声明实例变量的时候赋值
}
或者在构造方法中为变量赋初始值:
public class VariableTest {
private final int a ;
public VariableTest() {
a = 127;
}
}
这与 final 关键字的“不可变性”有关,至于加了final修饰后,在编译或者运行的时候,对这个变量的影响是什么,我就不知道怎么解释了。
接下来,再来看,在实例变量上使用 static 修饰,该实例变量就变成了类变量。
在第6章中讲到:
对于非 static 类型的变量的赋值是在实例构造器
<init>
方法中进行的;----这与我们前面的描述相符。对于类变量有两种方式赋初始值:
- 在类构造器
<clinit>
方法中赋值- 使用ConstantValue属性赋值
而对于类变量的这两种赋值方式,选用哪一种是则编译器来决定的:对于Sun javac编译器,当实例变量使用static 和 final 修饰的时候,就会生成 ConstantValue属性为之赋值。
但是,这里需要注意的是:只有基本类型(int、long...)和String类型才能生成 ConstantValue属性。如下所示:
public class VariableTest {
private static final Integer b = 1;
private static final int a = 1;
private static final String s = "test";
}
(我猜想基本类型的包装类型,比如int类型的包装类Integer,应该也是采用ConstantValue属性方式来赋初始值的吧)
那这里就有个问题:为什么只有基本类型(int、long..)和String类型才能生成 ConstantValue属性呢?
下面来说下ConstantValue属性是个啥?
从头说起:class文件结构中有一个常量池,常量池存放 字面量 和 符号引用。字面量就是下面的一些“字符串”
public class VariableTest {
private static final String s = "test";
}
比如说,字符串"test" 就是一个字面量。
那符号引用是啥呢?有这三类:
- 类和接口的全限定名称
- 字段的名称和描述符
- 方法的名称和描述符
那描述符又是什么呢?
描述符 的作用是 用来描述字段的数据类型、方法的参数列表(包括数量、类型、参数顺序)和返回值
描述符 就是按某种规则 来描述字段或者方法。字段就是我们写代码时定义的某个实例变量。
举例来说:
public class VariableTest {
public void inc() {
}
}
上面那个inc方法的描述符就是:()V
,为什么是这样呢?那就是书上177页解释嘛。
感觉有点跑偏了。
class文件格式里面,有一种存储结构叫做 方法表,方法表里面有一个 attribute_info 类型的 属性(暂且叫属性吧,参考书上表6-11)。
这个attribute_info 类型的 属性 是一个属性集合,里面又定义了若干属性(参考表6-13),其中就有一个名为ConstantValue的属性,而ConstantValue属性的 值是一个常量池的索引号,而根据:表6-3常量池的项目类型来看,只有一些:CONSTANT_Utf8_info、CONSTANT_Integer_info、CONSTANT_Long_info…类型的字面量。(CONSTANT_Utf8_info 对应String类型 字面量)
因此,只有基本类型(int、long...)和String类型才能生成 ConstantValue属性了。
既然常量池是存储字面量和符号引用的,而ConstantValue属性值 就指向了常量池中的索引号,这就是一个被static final 修饰的实例变量的 初始值 的来源了(或者说:一个被static final修饰的 基本类型 或者 String类型 的变量的初始值,其实是存储在Class文件的常量池中的)。
介绍到这里,那如果一个实例变量没有使用final修饰,而只使用了static修饰,那它就应该在类构造器clinit
方法中赋初始值了。
最后总结一下:
根据实例变量的类型来分:一类是 基本类型和String类型;另一类是 引用类型(比如自定义的类)
如果一个实例变量被static修饰,那它就是一个类变量。
对于 基本类型和String类型的实例变量:
- 该实例变量被 static 修饰了,则在
clinit
方法中赋初始值(类变量嘛) - 该实例变量被 final 修饰了,在
init
方法中赋初始值(实例变量嘛) - 该实例变量同时被 static 和 final 修饰了,通过ConstantValue属性方式赋初始值(有相应的字面量类型支持嘛)
对于 其他引用类型的实例变量:
- 该实例变量被 static 修饰了,则在
clinit
方法中赋初始值(类变量嘛) - 该实例变量被 final 修饰了,在
init
方法中赋初始值(实例变量嘛) - 该实例变量同时被 static 和 final 修饰了,在
clinit
方法中赋初始值
说了这么多,final关键字如何影响 类变量的初始化呢?
在7.2节中说的:类加载的时机,给出了一张类的生命周期的示意图:
上面有一个准备阶段,还有一个初始化的阶段。
如果一个类变量没有被final修饰,如下:
public class VariableTest {
private static int a = 123;
}
类变量a 在准备阶段,被赋值为0,在初始化阶段被赋值为123
如果该类变量被final修饰了,如下:
public class VariableTest {
private static final int a = 123;
}
类变量a 在准备阶段就被赋值为123。这是因为 a 是一个基本类型的变量,被static 和 final 修饰后,能够生成ConstantValue属性。
那么我想对于下面这个 mylist 这个变量:在准备阶段应该被赋值为null,在初始化阶段被赋值为 指向一个ArrayList对象吧。
public class VariableTest {
private static final List<String> mylist = new ArrayList<>();
}
参考:《深入理解Java虚拟机》
static 和 final 关键字 对实例变量赋初始值的影响的更多相关文章
- static和final关键字
static关键字 静态变量 静态变量:又称做类变量,也就是这个变量属于整个类,而不属于单个实例.类所有的实例共享静态变量,可以直接通过类名来访问它.静态变量在内存中只存在一份,当系统第一次加载类时, ...
- 10.使用final关键字修饰一个变量时...
10.使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变? 答:引用不能变,不能将引用再次指向另一个新的对象,但引用所指向的对象中的内容是可以改变的. 补充: 1.对于基本类型,f ...
- Java的static和final关键字的用法
static关键字的用法 static的意思是"'静态的",在java里面可用于修饰属性和方法. static关键字的应用应注意以下几种情形: 1.static作用于某个字段,一个 ...
- 【Java面试题】2 Java中使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?超详细解析
/* * 问题:使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变 * 答: * 使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的 ...
- 使用 final 关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
使用 final 关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的.例如,对于如下语句:final StringBuffer a=new StringBuffer( ...
- java中使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
java中使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变? 是引用对象的地址值不能变,引用变量所指向的对象的内容是可以改变. final变量永远指向这个对象,是一个常量指针,而 ...
- 使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?
使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的.例如,对于如下语句: final StringBuilder a=new StringBuilder ...
- Java语言基础(六)char成员变量默认初始值 最简单的Java源文件 Java的main()方法
①char成员变量的初始值是:'\u0000' ②package用来指定该文件所处的包的名称,必须位于源文件的顶端. import java.util.*; package com.hyy.test; ...
- Vue中用props给data赋初始值遇到的问题解决
Vue中用props给data赋初始值遇到的问题解决 更新时间:2018年11月27日 10:09:14 作者:yuyongyu 我要评论 这篇文章主要介绍了Vue中用props给dat ...
随机推荐
- Python的快排应有的样子
快排算法 简单来说就是定一个位置然后,然后把比它小的数放左边,比他大的数放右边,这显然是一个递归的定义,根据这个思路很容易可以写出快排的代码 快排是我学ACM路上第一个让我记住的代码,印象很深 ...
- 【BZOJ2940】条纹(博弈论)
[BZOJ2940]条纹(博弈论) 题面 BZOJ 神TM权限题. 题解 我们把题目看成取石子的话,题目就变成了这样: 有一堆\(m\)个石头,每次可以取走\(c,z,n\)个,每次取完之后可以把当前 ...
- CSS实现背景透明,文字不透明(兼容所有浏览器)
我们平时所说的调整透明度,其实在样式中是调整不透明度,如下图所示例: 打开ps,在图层面板上,可以看到设置图层整理不透明度的菜单,从 0% (完全透明)到 100%(完全不透明). 实现透明的css方 ...
- 一个GD初二蒟蒻的自我介绍
emmm……今天博客第一天使用呢,好激动啊…… 这里是一个来自GD的初二蒟蒻+无脑OIER,什么都不会 NOIP2017普及组:260压线1=还是看RP过的…… GDKOI2018:120暴力大法吼啊 ...
- rdesktop ERROR: CredSSP: Initialize failed, do you have correct kerberos tgt initialized ? Failed to connect, CredSSP required by server
错误信息: ERROR: CredSSP: Initialize failed, do you have correct kerberos tgt initialized ? Failed to co ...
- python学习day7 深浅拷贝&文件操作
4-4 day07 深浅拷贝&文件操作 .get()用法 返回指定键的值,如果值不在字典中返回默认值. info={'k1':'v1,'K2':'v2'}mes = info.get('k1' ...
- LOJ#2245 魔法森林
这道题以前zbtrs大佬给我讲过.但是我只知道思想,不知道要lct维护... 这个套路很常见. 题意:给你一个无向图,每条边有a,b两个权值.求1到n号点的一条路径,路径的权值是每条边的最大a与最大b ...
- openvpn部署账号密码登录
1.开启服务器端路由转发功能: 修改配置文件/etc/sysctl.conf中 net.ipv4.ip_forward = 0 改为 net.ipv4.ip_forward = 1 [root@nod ...
- render_template 网页模板
模板简单介绍: 视图函数:视图函数就是装饰器所装饰的方法,视图函数的主要作用是生成请求的响应,这是最简单的请求.实际上,视图函数有两个作用:处理业务逻辑和返回响应内容.在大型应用中,把业务逻辑和表现内 ...
- 认识Jmeter工具
1.Apache jmeter 是一个100%的纯java桌面应用,是Apache组织开发的基于java的压力测试工具.它最初被设计用于Web应用测试但后来扩展到其他测试领域,可以用于对静态的和动态的 ...