final与static相关知识

306 阅读5分钟

final与static

final

  1. 修饰类,类无法被继承
  2. 修饰方法,方法无法被修改或者覆盖*(注意:private修饰方法也是隐式的将其方法注明为无法被修改)*
  3. 修饰引用变量,不能改变其引用
  4. 修饰常量,不能改变其值

final关键字的好处

static

  • 静态变量,变量在类初始化的时候被赋值(注意!类初始化≠类实例化)
  • 静态代码块,代码块在类初始化的时候被赋值
  • 静态方法,静态方法是无法被覆盖的,在类加载前就有了,不需要依赖类的实例来实现,所以不能是抽象方法
类初始化赋值顺序
class father{
    int j;
    static int i=1;
    static { System.out.println("父类静态代码块"); }
    father(){ System.out.println("父类构造器"); }
}
public class Demo extends father{
    static int i=1;
    static int j;
    int a;
    //子类普通语句块
    {System.out.println("子类语句块");}
    //静态代码块01
    static {
        k=4;
        System.out.println("静态代码块01,无法在这里输出k值,被提示不能前向引用!");
    }
    static int k=3;
    //静态代码块02
    static {
        System.out.println("*******************静态代码块02**********************");
        j=4;
        Demo demo = new Demo();
        System.out.println("demo中的a为"+demo.a);
        System.out.println("被第一个代码块修改过的k值为"+k);
        System.out.println("*******************静态代码块02**********************");
    }
    //静态方法
    static void fun(){
        System.out.println("*******************静态方法************");
        System.out.println(j);
        System.out.println("*******************静态方法************");
    }
    //子类构造方法
    Demo(){
        j=5;
        System.out.println("子类构造方法");
    }

    public static void main(String[] args) {
        Demo.fun();
    }
}

结果:
父类静态代码块
静态代码块01,无法在这里输出k值,被提示不能前向引用!
*******************静态代码块02**********************
父类构造器
子类语句块
子类构造方法
demo中的a为0
被第一个代码块修改过的k值为3
*******************静态代码块02**********************
*******************静态方法************
5
*******************静态方法************


  • 可以得出结论,在调用Demo.fun()之前,需将Demo类进行初始化,而在将Demo类初始化之前,需要先将他的父类初始化,所以会发现在出先静态代码块01之前出现了父类静态代码块,所以父类静态代码块会先于子类静态代码块
  • 静态代码块01在**"static int k=3;"之前,但可以修改k值,但不能引用它,会被提示不能前向引用!并且在静态代码块02中输出的是静态代码块01修改过后的值,说明静态变量是在静态代码块之前初始化的**。
  • 在静态代码块02中我们调用了子类的构造器进行类实例化,在子类实例化之前进行了父类实例化,而调用构造函数时会先初始化实例变量,然后是普通语句块,在然后才会执行构造函数。
  1. 父类(静态变量,静态代码块)
  2. 子类(静态变量,静态代码块)
  3. 父类(实例变量,普通代码块)
  4. 父类构造函数
  5. 子类(实例变量,普通代码块)
  6. 子类构造函数`

注意!如果在代码中没有对类进行实例化,那么3,4,5,6并不会发生!!

站在JVM角度思考初始化赋值顺序

jvm中类构造器的**()**方法与我们一开始认识的构造函数并不一样,在类加载过程中,分为五个阶段,加载-验证-准备-解析-初始化,在准备阶段就已经为变量赋过一次值,而在初始化阶段,便是执行()方法。

  • ()方法是有编译器自动收集所有静态类变量和静态语句块合并产生的,静态语句块中只能访问到定义到静态语句块之前的变量,定义在他之后的变量,他只能赋值,不能访问——《深入理解JAVA虚拟机》,这也是之前出现"不能前向引用的原因"。
  • 虚拟机会保证子类的()方法执行之前,父类的()会被执行,所以父类的静态变量和静态代码块会被先执行。
  • ()方法对于类或接口并不是必须的,如果类并没有对静态变量赋值和静态代码块,这个方法不会生成。
  • 接口也存在对变量进行赋值操作,所以他也可能存在()方法,但与类不同的是,执行接口的()方法不会去先执行父接口的()方法,除非父类接口的变量被使用时父接口才会被初始化。
  • 在多线程中,当多个线程去初始化同一个类,虚拟机会保证()方法只被执行一次,对这个方法进行加锁同步。(这一点多线程笔记中记得继续讨论)
关于类初始化的时机
public class Demo{
    public static void main(String[] args) {
        Demo02[] demo02s =new Demo02[10];//1.数组定义引用类不会被初始化
        System.out.println(Demo02.l);//2.引用常量不会被初始化
        System.out.println(Demo02.j);//3.子类引用父类变量只有父类会被初始化
        System.out.println(Demo02.i);//4.子类父类都会被初始化
        }}
class Demo02 extends Demo03{
    static final int l=10;  static int i=10;
    static { System.out.println("Demo02静态代码块"); }}
    
class Demo03{
    static int j=20;
    static { System.out.println("Demo03静态代码块"); }}
  1. 通过数组定义来引用类不会触发类org.xxx.xxx.Demo02的初始化,但他会触发一个Lorg.xxx.xxx.Demo02的类的初始化,这个类是一个由虚拟机自动生成的并继承于java.lang.Object的子类,创建动作由指令newarray触发。
  2. 引用常量可能不会被初始化,因为常量在编译阶段会存入调用这个类的类的常量池中(很绕。),本质上并没有引用定义这个常量的类,因此不会触发类的初始化。途中常量值 "10"存储到了Demo的常量池中而不是Demo02中,如果是在Demo02中运行代码则会触发定义该常量的类的初始化。
  3. 通过子类引用父类的静态字段,不会导致子类的初始化。(深入java虚拟机里就这一句话。)

(本文仅用于记录平时学习心得,如有误导,恳请指教)