java类锁和对象锁(1)---先搞清楚静态的含义

1,128 阅读5分钟

首先我们来搞清楚java中的静态属性和静态方法。

有关静态常见的问题

这里我问几个问题,供大家思考一下,看看自己是否可以秒答。

  • 1.静态属性可以被继承吗?
  • 2.静态方法可以访问非静态属性吗?
  • 3.非静态方法可以访问静态属性吗?
  • 4.静态方法中可以使用this/super关键字吗?
  • 5.为什么静态方法不能调用非静态方法或者变量呢?
  • 6.静态属性、非静态属性、静态代码块、非静态代码块、构造函数在初始化时的执行顺序是怎样的?

先说答案:

  • 1.可以,继承跟是否静态无关,只跟作用域有关(private public protected)
  • 2.不可以,同4
  • 3.可以,因为静态变量是存储在静态内存单元内,可以直接用类进行调用,也可以用实例化的对象对其引用。所以非静态方法是可以引用静态变量的
  • 4.不可以,this指代当前对象的引用,super指代父类对象的引用,静态方法属于类,不属于某一个对象,当静态方法加载的时候,对象还不一定存在。如果静态方法中有this和super那么当静态方法被加载到内存中,其中的this和super也被加载到内存中,但是对象还没创建this和super还没被初始化,所以加载时会报错。
  • 5.静态方法是属于类的,不需要实例化,不是属于对象的,而非静态变量是属于对象的,需要先实例化。在一个类的静态方法中访问非静态成员,之所以出错是因为,在类的非静态成员不存在的时候,类的静态属性已经存在了,访问一个内存中不存在的东西当然会报错。
  • 6.不考虑继承关系的话,静态属性和代码块(按照代码出现的顺序)>非静态属性和非静态代码块>构造函数 马上来验证一下
public class StaticDemo {

    public String b = "非静态属性";
    public static String a = "静态属性";
	
    static {
    	 System.out.println(a);  
         System.out.println("静态代码块"); 
	}

    {
    	 System.out.println(b);  
         System.out.println("非静态代码块"); 
    }
    
    public StaticDemo() {
    	System.out.println("构造函数");
    }
    
    public static void main(String[] args) {
	StaticDemo demo = new StaticDemo();
    }
}

打印结果:

静态属性
静态代码块
非静态属性
非静态代码块
构造函数

在有继承关系的情况下

父类的静态属性和静态代码块>子类的静态属性和静态代码块>父类的非静态属性>父类的构造函数>子类的非静态函数>子类的构造函数

再来验证一下

class ParentClass{
	public static String pA = "父类-静态属性";
	public String pB = "父类-非静态属性";

    static {
    	 System.out.println(pA);  
         System.out.println("父类-静态代码块"); 
    }
    {
   	System.out.println(pB);  
        System.out.println("父类-非静态代码块"); 
    }
    
    public ParentClass() {
    	System.out.println("父类-构造函数");
    }
}

public class StaticDemo extends ParentClass{

	public String b = "非静态属性";
	public static String a = "静态属性";
	
    static {
    	 System.out.println(a);  
         System.out.println("静态代码块"); 
    }

    {
    	 System.out.println(b);  
         System.out.println("非静态代码块"); 
    }
    
    public StaticDemo() {
    	System.out.println("构造函数");
    }
    
    public static void main(String[] args) {
        StaticDemo demo = new StaticDemo();
    }
}

执行结果:

父类-静态属性
父类-静态代码块
静态属性
静态代码块
父类-非静态属性
父类-非静态代码块
父类-构造函数
非静态属性
非静态代码块
构造函数

这几个问题我们知道答案了,但是为什么会产生这样的结果呢?

类是如何加载的

我们看一下类的加载过程:

graph TD
加载 --> 验证--> 准备 --> 解析--> 初始化

流程图中的过程就是一个类加载的生命周期,每个阶段会做相应的工作

  1. 加载:主要做了三件事:
  • 获取二进制字节流,也就是通过全限定名找到这个类在哪里,是jar还是class文件
  • 将静态存储结构转化为方法区运行时数据结构
  • 在java堆中生成一个class类对象(相当于一个句柄,方法区的访问入口),去访问方法区
  1. 验证:主要就是验证类的正确性,包括魔数、版本号、常量池验证、元数据验证、字节码验证、符号引用验证等等
  2. 准备:为类变量分配内存并设置类变量的初始化。这些内存都在方法区分配。注意此时就会为我们的类变量也就是静态变量分配内存,但是普通成员变量还没。 还有一点需要注意的是对final修饰的类变量,在准备阶段就赋值了
static int n = 2;//初始化的值是0 而不是2 因为此时还没执行任何java方法
static final int n = 2;//初始化的值是2,ConstantValue对应到常量池,在准备阶段n被赋值成2
  1. 解析:对符号引用进行解析。(直接引用:指向目标的指针或者偏移量)把符号引用变为直接引用
  2. 初始化: 在编译生成class文件时,会自动产生两个方法,一个是类的初始化方法< clinit>, 另一个是实例的初始化方法< init>
  • < init >方法 实例的初始化阶段
  • < clinit >方法 静态变量、静态块的初始化(如果没有静态块,静态变量,则没有这个方法)
Class A{

  //clinit方法中初始化
  static int i = 2;

  //clinit
  static {
     System.out.print("");
  }

  int n;
}

静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接去访问;非静态成员(变量和方法)属于类的对象,所以只有该对象初始化之后才存在,然后通过类的对象去访问。

也就是说如果我们在静态方法中调用非静态成员变量会超前,可能会调用了一个还未初始化的变量。因此编译器会报错。