静态与静态内部类详解

java的生命周期

java的生命周期为:装载、连接、初始化、使用和卸载

1. 加 载

一个java类的代码,经过编译之后生成一个后缀为.class的文件,java虚拟机能识别这种文件。
java的生命周期就是class文件从加载到消亡的过程。
关于加载,就是将源文件的class文件找到类的信息将其加载到方法区中,然后再堆中实例化一个java.lang.Class对象,作为方法区这个类的信息的入口。但是这一功能是在JVM之外实现的,主要的原因是方便让应用程序自己决定如何获取这个类,在不同的虚拟机加实现的方式不一定相同,hotspot虚拟机是采用需要时再加载的方式,也有其他是先预先加载的。

2. 连接

连接一般是加载阶段和初始化阶段交叉进行,过程由以下三部分组成

  1. 验证:确定该类是否符合java语言的规范,有没有属性和行为的重复,继承是否合理,总之,就是保证jvm能够执行

  2. 准备:主要做的就是为由static修饰的成员变量分配内存,并设置默认的初始值
    默认初始值如下:
    1. 八种基本数据类型的初始值是0
    2. 引用类型默认的初始值为null
    3. 有static final 修饰的会直接赋值,例如:static final int x = 10; 则默认就是10

  3. 解析:这一阶段的任务就是把常量池中的符号引用转换成直接引用,说白了就是jvm会将所有的类或者接口名、字段名、方法名转换为具体的内存地址

3. 初始化

初始化这个阶段是将静态变量(类变量)赋值的过程,即只有static修饰的才能被初始化,执行的顺序是:父类静态域或者静态代码块,然后是子类静态域或者子类静态代码块(静态代码块先被加载,然后再是静态属性)

4. 使用

在类的使用过程中依然存在以下三步

  1. 对象实例化:就是执行类中构造函数的内容,如果该类存在父类,JVM会通过显示或者隐式的方式先执行父类的构造函数,在堆内存中为父类的实例变量开辟空间,并赋予默认的初始值,然后在根据构造函数的代码内容将真正的值赋予实例变量本身,然后,引用变量获取对象的首地址,通过操作对象来调用实例变量和方法
  2. 垃圾收集:当对象不再被引用的时候,就会被虚拟机标上特别的垃圾记号,在堆中等待GC回收
  3. 对象的终结:对象被GC回收后,对象就不再存在,对象的生命也就走到了尽头

5. 类卸载

类卸载即类的生命周期走到了最后一步,程序中不再有该类的引用,该类也就会被JVM执行垃圾回收,从此生命结束

静态代码、变量、方法

静态代码在类的初始化阶段被初始化
非静态代码则在类的使用阶段(也就是实例化一个类的时候)才会被初始化

静态变量

静态变量理解为类变量(与对象无关)
实例变量则属于一个特定的对象。
静态变量只在内存中申请一次空间。如果这块改了,其他地方就会收到影响
静态变量有两种情况

  1. 静态变量是基本数据类型。这种情况下在类的外部不必创建该类的实例就可以直接使用
  2. 静态变量是一个引用。这种情况比较特殊,主要问题是由于静态变量是一个对象的引用,那么必须初始化这个对象之后才能将引用指向它。
    因此如果一个引用定义为static的时候,就必须在定义的时候就对其对象进行初始化
public class TestForStaticObject{  
    static testObject o = new testObject (); //定义一个静态变量并实例化  
    public static void main(String args[]){  
    //在main中直接以“类名.静态变量名.方法名”的形式使用testObject的方法  
    }  
}  

静态方法

与类变量不同,方法(静态方法与实例方法)在内存中只有一份,无论该类有多少个实例,都共用一个方法

静态方法与实例方法的不同:

  1. 静态方法可以直接使用,而实例方法必须类实例化之后通过对象来调用
  2. 在外部调用静态方法时,可以使用** 类名.方法名** 或者 对象名.方法名 的形式,而实例方法只能用 对象名.方法名
  3. 静态方法只能访问静态成员。而实例方法中可以访问静态成员和实例成员
  4. 静态方法中不能使用this(因为this是与实例相关的)

静态代码块

静态代码块主要用于类的初始化。只执行一次,并且在同属于一个类的main函数之前执行。

静态代码块的特点:

  1. 静态代码块会在类被加载时自动执行
  2. 静态代码块只能定义在类中,不能定义在方法中
  3. 静态代码块中的变量都是局部变量,只在块内有效
  4. 一个类中可以定义多个静态代码块,按顺序执行
  5. 静态代码块只能访问类的静态成员,而不允许访问实例成员

静态代码块和静态函数的区别
静态代码块:一般情况下,如果有些代码必须在项目启动前就执行的时候,需要使用静态代码块,这种代码是主动执行的,它只执行一次,并且在同属于一个类的main函数之前执行
静态函数:需要在项目启动的时候就初始化,在不创建对象的情况下,其他程序来调用的时候,需要使用静态方法,这种代码是被动

静态内部类

  1. 静态内部类是由static修饰的内部类(普通的类无法用static关键字修饰
  2. 静态内部类也是类,在继承和实现接口方面,和普通的类都是一样的
  3. 外部类可以访问静态内部类的private属性。静态内部类,不能访问外部类的非静态的方法和属性,(如果在静态类的方法里,实例化了外部类,通过引用,静态内部类可以访问外部类的private属性)
  4. 静态内部类可以被实例化,外部类每次实例化都会创建一个新的静态内部类对象,不管外部类的状态(是否创建),可以直接使用它的内部类
    5. 静态内部类不会常驻内存,静态变量和方法才会常驻内存的方法区
内部类相关问题
静态内部类,为什么每次实例化是不同的对象
public  class Test01 {

    static final class InnerTest{
        static final InnerTest in = new InnerTest();
    }

    public static void main(String[] args) {
        InnerTest iin1 = new Test01.InnerTest();
        System.out.println(iin1 );
        InnerTest iin2 = new Test01.InnerTest();
        System.out.println(iin2 );

        System.out.println(iin1.in);
        System.out.println(iin2.in);
    }
}
输出结果为
com.lvbu.Test01$InnerTest@45ee12a7
com.lvbu.Test01$InnerTest@330bedb4
com.lvbu.Test01$InnerTest@2503dbd3
com.lvbu.Test01$InnerTest@2503dbd3

因为对于每次每个new Test01来说,都要创建一个新的innerTest
它是静态的,但是因为Test01有很多,所以不是全局唯一的,而是每个Test01实例中唯一的。

参考博客: https://www.cnblogs.com/lubocsu/p/5099558.html

java静态内部类的延迟加载,以及内部类里面的静态元素的加载时机
public class Outer {
    private Outer() {
        System.out.println("outer instance create");
    }

    static {
        System.out.println("outer init");
    }

    public static void main(String[] args) {
        System.out.println("outer main execute");
       //        Outer outer = Inner.outer;
    }

    private static class Inner {
        static {
            System.out.println("inner init");
        }

        private static final Outer outer = new Outer();
    }
}

运行以上代码,输出结果为下,证明当项目启动,静态内部类以及内部类里面的静态元素不会被初始化

     outer init
     outer main execute

当把 Outer outer = Inner.outer; 这一行取消注释后,输出结果如下,证明只有当访问静态内部类里面的静态元素的时候,静态内部类以及里面的静态元素才会被初始化

    outer init
    outer main execute
    inner init
    outer instance create

当把main方法换成这样的时候

    public static void main(String[] args) {
        System.out.println("outer main execute");
        Outer outer = new Outer();
    }

运行结果如下,证明,外部类初始化,静态内部类并没有初始化

    outer init
    outer main execute
    outer instance create

当把静态内部类里面的属性变成Integer,不让它实例化外部类,并且main方法访问这个属性的时候,具体代码如下

public class Outer {
    private Outer() {
        System.out.println("outer instance create");
    }

    static {
        System.out.println("outer init");
    }
    private static class Inner {
        static {
            System.out.println("inner init");
        }

        private static final Integer outer = 3;
    }
    public static void main(String[] args) {
        System.out.println("outer main execute");
        Integer outer = Inner.outer;
    }
}

输出结果如下,证明当访问静态内部类以及静态内部类的静态元素的时候,静态内部类会初始化,而外部类并没有初始化,证明静态内部类的初始化与外部类的初始化无关

outer init
outer main execute
inner init

结论:

  1. 静态内部类以及静态内部类里面的静态元素,不会随项目启动而被加载。
  2. 静态内部类不会依赖于外部类的初始化而初始化(静态内部类初始化与外部类无关,外部类初始化,静态内部类不会初始化。 静态内部类初始化了,外部类也不会初始化,两者没联系)
  3. 当访问静态内部类里面的静态元素的时候,静态内部类以及里面的静态元素才会被初始化

参考博客:https://www.jianshu.com/p/ef263cf7f7f9

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值