1.类的初始化做了什么?
初始化阶段是执行类构造器<clinit>()方法的过程。(这里的类构造器和我们通常说的类的构造方法是不一样的,构造方法用于实例化一个对象)
举个例子:
public class SupClass {
public static int age = 18;
static{
System.out.println("Come In SuperClass!");
}
public SupClass() {//类的构造方法
}
}
小知识:编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在它后面的变量,可以赋值但不可访问。静态语句块是不可以对非静态的类变量赋值或访问的。
举个例子:
以下代码编译没有问题:
public class Test {
static{
i = 3;
//System.out.println(i);
}
static int i = 1;
}
以下代码编译出错了:
public class Test {
static{
i = 3;
System.out.println(i);
}
static int i = 1;
}
以下代码编译没有问题,运行出错了:
public class Test {
static int i = 1;
int a = 0;
static{
a = 5;
i = 3;
System.out.println(i);
}
}
2.什么是类构造器?
<clinit>()方法是由编译器自动收集类中所以变量的赋值动作和静态语句块合并组成的。可以理解为:执行<clinit>()方法就是对类所有类变量赋值和执行静态语句块的过程。
3.什么情况下才类的初始化?
JVM严格规定了有且只有以下五种情况必须立即对类进行“初始化”。
- 遇到new,getstatic,putstatic,invokestatic这四个字节指令时,如果类没有进行初始化过,必须初始化
- 使用java.lang.reflect包的方法对类进行反射调用时,如果类没有初始化过,先初始化。
- 当初始化一个类时,如果发现其父类没有初始化,先初始化父类。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机先初始化这个主类。
- 当使用JDK1.7的动态语言支持时,如果java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行过初始化时,先初始化。
针对这五种情况下面一一列举了例子:
情形1:对于new,getstatic,putstatic,invokestatic这四个字节指令时常见的java代码场景有:
- 使用new关键字实例化对象时。
- 读取,设置一个类的静态字段时。(注意:静态字段被final修饰时,在编译期间结果会被放入到常量池。这个在读取,设置是就不需要初始化了。还有如果static修饰的属性是在父类中,通过子类读取或设置时,不初始化子类,只初始化父类)
例子:
package com.example.demo.test;
public class SubClass {
static {
System.out.println("Come In SubClass!");
}
public static int realage=16;
}
package com.example.demo.moth;
import com.example.demo.test.SubClass;
import org.junit.Test;
public class TestMoth {
@Test
public void test1(){
System.out.println(SubClass.realage);
}
}
输出结果:
public class SuperClass {
public static int age = 18;
public static String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
public static int getAge(){
return age;
}
}
@Test
public void test1(){
SuperClass superClass = new SuperClass();
System.out.println(SuperClass.age);
}
结果:
为了更明显:
@Test
public void test1(){
SuperClass superClass = new SuperClass();
// System.out.println(SuperClass.age);
}
结果:
@Test
public void test1(){
// SuperClass superClass = new SuperClass();
System.out.println(SuperClass.age);
}
结果:
这里我在测试时,发现一个很有趣的问题。在idea开发工具中时,第一次测试,SuperClass类的age没有有final修饰,第二次测试的时候改成final修饰。会出现一下奇怪的现象。
第一次测试:
public class SuperClass {
public static int age = 18;
public static String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
public static int getAge(){
return age;
}
}
public class TestMoth {
@Test
public void test1(){
System.out.println(SuperClass.age);
}
}
测试结果:正常!
.class文件:
第二次:改成final修饰之后
public class SuperClass {
public static final int age = 18;
public static String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
public static int getAge(){
return age;
}
}
public class TestMoth {
@Test
public void test1(){
System.out.println(SuperClass.age);
}
}
执行结果:(不正常)
分析:
这就不正常了!理论上是不会打印Come In SuperClass!这句的。为何呢? 现在我们分析一下,由于age被static和final修饰理论上在编译期间age就会被加载到常量池,可以通过类直接调用而不用初始化了。那这就是idea工具编译的时候有问题了。(这个现象在eclipse中没有出现)
打开.class文件:
你会发现没有编译到我们修改后的代码。 这是由于idea执行的时候是不会自动编译我们的代码。第一次执行它编译成了一个.class文件,第二次的时候由于已经生产了一个它不会再自动去编译。所以导致我们的代码没有生效。
手动编译一下:
.class:
结果正常了!
这个问题很有趣吧!!!
调用一个类的静态方法时:
public class SuperClass {
public static final int age = 18;
public static final String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
public static int getAge(){
return age;
}
}
@Test
public void test2(){
System.out.println(SuperClass.getAge());
}
结果:
子类访问父类类变量时:
public class SuperClass {
public static int age = 18;
public static String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
}
public class SubClass extends SuperClass{
static {
System.out.println("Come In SubClass!");
}
}
@Test
public void test3(){
System.out.println(SubClass.age);
}
测试结果:
2.使用java.lang.reflect包的方法对类进行反射调用时,没有被初始化过,必先初始化。
例子:
public class SuperClass {
public static int age = 18;
public static String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
}
@Test
public void test2() throws Exception{
Class classType = Class.forName("com.example.demo.test.SuperClass");
Object obj = classType.newInstance();
System.out.println("使用反射反射机制创建出来的对象是否是SuperClass类的对象:" + (obj instanceof SuperClass));
}
测试结果:
3.当初始化一个类时,父类类没有比初始化,必先初始化父类。
例子:
public class SuperClass {
public static int age = 18;
public static String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
}
public class SubClass extends SuperClass{
static {
System.out.println("Come In SubClass!");
}
}
@Test
public void test4(){
SubClass subClass = new SubClass();
}
测试结果:
4.当指定一个要执行的类(主类,包含main方法的类),JVM会自动优先初始化这个类。
public class TestMain {
static{
System.out.println("static");
}
public static void main(String[] args) {
System.out.println(SuperClass.age);
}
}
public class SuperClass {
public static final int age = 18;
public static final String name = "Super";
static{
System.out.println("Come In SuperClass!");
}
public static int getAge(){
return age;
}
}
测试结果:
5.当使用JDK7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是: REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,同时方法对应对应的实体类没有初始化时先初始化类。
案例待补充!目前不知道如何操作,有大牛知道的请指导一下!
最后可能有人会用这样的疑问?spring容器中单例的bean或原型的bean。初始化是如何的呢?原型的是不是会初始化多次呢?首先singleton(单例)和prototype(原型)是指bean的作用域,描述每一个实例对象的存在的个数,singleton的bean对应的实例对象在容器中只有一个,prototype的bean对应的实例对象在容器中有多个。总之类的初始化在JVM虚拟机中只会初始化一次。