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虚拟机》

原文地址:https://www.cnblogs.com/hapjin/p/9348404.html

时间: 2024-10-13 01:27:43

static 和 final 关键字 对实例变量赋初始值的影响的相关文章

6.static、final关键字

1.静态成员变量    1.1既可以用对象名来调用,也可以直接用类名来调用 Person.i = 10;   //Person是一个类名.i是成员变量: 类名.成员变量= : 静态变量的功用:被同类的所有实例变量共享的变量. tip:静态变量会在该类的任何静态方法执行之前就初始化. 1.2他不是对象层次的变量,是类层次的变量,它是属于某个类的. 2.静态函数 2.1函数前面加static关键字,可以用类名直接调用: 2.2因为不能使用this,所以静态函数不能引用非静态的变量. tip:静态方法

变量,final常量,类成员变量,static关键字,实例变量,静态变量,局部变量

1.常量:又称为final变量,在整个程序中仅能被赋值一次 final int num = 1215; num = 1216; // 错误,仅能赋值一次 2.类成员变量:不在方法中定义的变量即为成员变量,在方法体内定义的变量则为局部变量 成员变量含义:对象的属性 public class Book{ int id; string name; //两个成员变量 public String getName(){ int id = 0;  //局部变量,必须进行赋值或初始化 } } 3.static关

面向对象之static与final关键字

final关键字和static关键字 final关键字 final:在翻译过来的意思就是最后的,最终的,不可改变的意思.在Java中同样也是这个意思.那到底什么时候可以用到final来修饰呢?都知道,继承的应用提高了代码的复用性,通过继承,子类可以对父类的方法进行重写,那如果有些父类中的方法是固定的,不想让子类进行重写,要解决这个问题就要用到final关键字,final关键字可以用来修饰类,类的成员,以及局部变量. 1.修饰类:final修饰的类是最终类,不能有子类,不能被继承,但是可以继承其他

Java的static和final关键字的用法

static关键字的用法 static的意思是"'静态的",在java里面可用于修饰属性和方法. static关键字的应用应注意以下几种情形: 1.static作用于某个字段,一个static字段对每个类来说只有一份存储空间,而非static字段是每个对象有一份存储空间. 2.static作用于方法的重要用法是不需要创建任何对象直接调用该static方法,这对于main()方法很重要. 3.static不能应用于局部变量. 4.Java中禁止使用全局方法,所以引入static方法通过类

使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?

使用final关键字修饰一个变量时,是指引用变量不能变,引用变量所指向的对象中的内容还是可以改变的.例如,对于如下语句: final StringBuffer a=new StringBuffer("immutable"); 执行如下语句将报告编译期错误: a=new StringBuffer(""); 但是,执行如下语句则可以通过编译: a.append(" broken!"); 有人在定义方法的参数时,可能想采用如下形式来阻止方法内部修改传进

Vue中用props给data赋初始值遇到的问题解决

Vue中用props给data赋初始值遇到的问题解决 更新时间:2018年11月27日 10:09:14   作者:yuyongyu    我要评论 这篇文章主要介绍了Vue中用props给data赋初始值遇到的问题解决,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 前言 前段时间做一个运营活动的项目,上线后产品反馈页面埋点不对,在排查过程中发现,问题竟然是由于Vue中的data初始值导致,而data的初始值来自于props.为方便描述,现将问题抽象如下: 一.现象

(二)用控制器controller给模型数据赋初始值

之前博客,非常easy的就实现了模型数据和页面显示的自己主动绑定.如今我们使用控制器,给模型赋初始值. 假设使用jquery来实现变量赋初值,须要在页面载入完毕后运行$("#target").attr("value",selfValue);使用AngularJS代码例如以下: <!doctype html> <html lang="en" ng-app> <head> <meta charset=&quo

Bash 什么时候会给 HOME 赋初始值

今天无意发现下面这个表现: $  env -i bash -c cd bash: line 0: cd: HOME not set $ env -i bash -c 'echo $HOME' 这表明了,Bash 只会从环境变量中继承 HOME 变量,从来不自己初始化它?为了证实这个想法,我去翻了下源码,发现其实并不是,在一种情况下,Bash 是会主动初始化 HOME 变量的: if (login_shell == 1 && posixly_correct == 0) set_home_va

讨论:C#Calendar赋初始值

Q: 我在Page_Load的时候给Calendar赋初始值,但是Page显示之后,Calendar显示是当天(比如今天显示7月12号).我想让它默认显示出我给赋的时间(8/30/2006).我该怎么去设置呢? A: Calendar1.SelectedDate   =   new   DateTime(DateTime.Now. Year,   DateTime.Now.Month,   1);   //将1改成你要设置的天 A: 我的意思是这样:       比如我给它初始化一个10/3/2