★ 类常量
Java类常量(final修饰)
的值在编译阶段就已经写入了class文件的常量池中(可见《Class文件内容及常量池
》)。该类在JVM中运行的任何阶段都不可能改变这个常量值。这也就形成了Java常量定义的两个语法规定:(1) Java类常量必须在类中声明的时候就赋给特定的值。(2) Java类常量绝对不允许赋第二次值。
下面是Java类常量的初始化代码:
//代码1: 类常量的初始化
public class Init{
//定义类常量ITEM,声明的同时必须赋值
public static final int ITEM=100;
public static void main(String[] args){
//Init.ITEM=200; //编译错误, 常量值不允许在程序运行中改变
}
}
如果你了解JVM。就知道上面的Init类被JVM加载之后,会在方法区(内存)中开辟一块存放Init类信息的空间。其中的常量池数据结构中就存放了ITEM常量值100。因此,被final修饰的常量并不是存放于某个对象在堆中的内存空间中。这就是为什么我们将final和static一并修饰常量已经成为了某种习惯了。
记住, final类常量在编译阶段就已经有确定的值,并不需要在JVM虚拟机加载之后才初始化。
这也是唯一一种不需要运行就已经初始化的Java数据值。
★ 类静态变量/
类静态初始化块
类静态变量是用static修饰的类变量(不用final)。在编译源代码阶段,任何对类静态变量定义的值都不会作为常量值存在于常量池中。看看下面两句定义。
public static final int ITEM=100;
public static int itemI=200;
常量池中只会存储整型值100,而不会记录200。因此在编译阶段,鬼才知道itemI的值是多少。只有当JVM装载class文件的完成之后,会为所有的类静态变量开辟内存空间(在方法区中),然后在这些内存空间中赋予默认值(比如int默认值是0,boolean默认值是false,引用类型默认值是null等)。
注意,此时类静态变量的内存空间里是默认值,而并非我们在源代码中的赋值。这也就是为什么下面的代码是正确的:
// 类静态变量的默认值
public class Init{
//类整型静态变量
public static int itemI;
public static void main(String[] args){
System.out.println(Init.itemI); //打印结果: 0
}
}
赋予默认值之后,接下来就是对类静态变量的初始化我们想要的值了(如下面代码2)。
//代码2:类静态变量的初始化
public class Init{
//将类静态变量itemI初始化为200
public static int itemI=200;
public static void main(String[] args){}
}
对于虚拟机而言,对类静态变量的初始化实质上是执行一段名字叫做“<clinit>
”的方法。该方法字节码指令如下:
0 bipush 200 //将整型值200压入操作数栈
2 putstatic itemI : int [10] //解析常量池中符号引用"itemI"的入口地址为直接地址(JVM为itemI类静态变量开辟的在方法区的内存空间的地址)。然后弹出操作数栈的栈顶数据100,存储在这个直接地址上。
5 return
另外,类静态变量只初始化一次,换句话说就是只在方法区中开辟一次空间。且只在JVM首次主动使用类型
的时候才初始化。我们认为只有6种活动属于主动使用类型:
(1) 指定的类作为JVM启动时的初始化类。也就是JVM开始运行程序时,最先加载的包含main()方法的类。
(2) 创建类的实例,比如最常见的new方法等。
(3) 调用类中声明的静态方法。
(4) 操作类或接口中声明的非常量静态字段。
(5) 调用Java API中特定的反射方法。
(6) 初始化指定类的子类。实际上,在初始化当前类之前,JVM会将这个类的所有父类全部加载、链接和初始化。然后再初始化当前类。
记住: 类静态变量只初始化一次,且在首次主动使用类的时候才被初始化。JVM初始化类静态变量实际上是调用了<clinit>方法(在编译阶段偷偷生成,并不是源代码中的某个方法)。类静态变量在内存中的存储空间属于方法区,并不属于某个对象的堆空间内。变量值的改变会影响所有类的对象。
事实上类静态初始化块的初始化过程和类静态变量基本一样。对于类静态初始化块中的语句,编译器也会将其编译成<clinit>方法中的指令。
★ 类对象的初始化(类实例化)
类静态变量的初始化完成也标志着该类已经被JVM成功的装载、连接和类初始化了。接下来这个类可以随时被调用,程序可以访问它的静态字段和方法,也可以创建它的对象 —— 类实例化。
在Java程序中,类实例化有四种途径:(1) new 指令;(2) 调用Class对象的newInstance()方法;(3) 调用任何现有对象的clone()方法;(4) 通过ObjectInputStream类的getObject()方法反序列化。除此四种初始化方法,JVM在装载类的同时还会隐含的创建两种对象:
(1) 实例化一个Class对象代表类型信息;(2) 装载常量池中的CONSTANT_String_info入口的类的时候,创建拘留字符串对象。
当JVM创建一个类对象时,首先在堆中分配一个内存空间用来存放对象所属类和超类的实例变量(类中非常量非静态变量),然后对这些变量赋默认值(与类静态变量类似),最后初始化我们需要的值。我们有三种方法对类对象的实例变量进行初始化:(1) 在声明中赋值,(2) 在构造器中设置值,(3) 在初始化块中赋值。
JVM初始化这些变量都是通过调用一个名叫<init>的方法。实际上针对每一个类的构造器,都会产生一个对应的<init>()方法。
分享到:
相关推荐
在继承关系中,当创建子类对象时,Java虚拟机(JVM)首先会去查找并加载父类的字节码,进行父类的初始化。父类的初始化顺序还是按照静态变量、静态初始化块、变量、初始化块、构造器的顺序执行。一旦父类初始化完成...
Java语言中的类初始化顺序是面试中常见的问题,尤其对于Java程序员和工程师来说,理解这一概念至关重要。本篇文章将深入解析类初始化的顺序以及在继承情况下的表现。 首先,我们需要明确类初始化顺序的基本规则: ...
JAVA面试题解惑系列(一)——类的初始化顺序 JAVA 是一门面向对象的编程语言,类的初始化顺序是 JAVA 程序员和 JAVA 工程师面试中一个非常重要的知识点。本文将详细讲解类的初始化顺序,并提供了相关的测试代码,...
这是因为静态成员属于类,不依赖于类的实例,所以在创建任何对象之前就已经完成初始化。 2. **实例变量和初始化块**:当创建子类对象时,先执行父类的实例变量初始化和初始化块,接着执行子类的实例变量初始化和...
### JAVA面试题解惑系列:类的初始化顺序详解 在JAVA面试中,考察候选人对类初始化顺序的理解是一项常见且重要的环节。本篇文章旨在深入解析这一主题,帮助读者掌握类初始化的详细过程,尤其是在继承关系中的类如何...
### Java面试题解惑系列:类的初始化顺序详解 在Java面试中,关于类的初始化顺序问题是非常常见的考察点之一。此类题目旨在检测面试者对于Java语言基础的理解程度,特别是对象创建过程中的细节掌握情况。下面我们将...
《JAVA面试题解惑系列——类的初始化顺序》 在Java编程中,理解类的初始化顺序是面试中常见的考察点,因为它直接关系到程序的执行逻辑。本文将深入探讨类的初始化过程,以及在继承场景下如何理解这个过程。 首先,...
这意味着在子类对象初始化之前,父类的初始化过程会先执行完毕。我们可以通过一个简单的示例来进一步探讨这个问题: ```java class Parent { // 静态变量 public static String p_StaticField = "父类--静态变量...
它包括了从类的初始化顺序、String对象的创建数量、变量的覆盖、final关键字、传值和传引用的区别、字符串的处理、日期和时间的处理、基本类型的细节、继承和多态、多线程以及运算符相关的面试题目。下面将详细解析...
JAVA面试题解惑系列(一)——类的初始化顺序 在Java编程语言中,理解类的初始化顺序是非常重要的,尤其是在涉及继承的情况下。本节将详细介绍类初始化的规则,并通过实例来帮助理解。 ##### 类的初始化顺序规则 ...
除了类的初始化顺序之外,Java面试中还经常涉及到面向对象的其他基本问题,如多线程、同步、死锁等问题。在多线程编程中,同步是用来控制多线程访问共享资源的一种机制,目的是防止数据竞争和确保数据的一致性。而...
### JAVA面试题解惑系列——类的初始化顺序 #### 一、基础知识回顾 在Java编程中,类的初始化顺序是一个非常重要的概念,特别是在面试时,它经常被用来考察面试者对于Java类加载机制的理解程度。类的初始化顺序...