Java教程

深入分析Java中的关键字static

本文主要是介绍深入分析Java中的关键字static,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

深入分析Java中的关键字static

首先,描述了static关键字去修饰java类、方法、变量、代码块的方法然后,从底层分析static关键字,接下来,给出static的一些使用场景和案例最后,对static进行一个总结,包括和普通变量的区分。

static最基本用法

1、static关键字基本概念

我们可以一句话来概括:方便在没有创建对象的情况下来进行调用。

也就是说:被static关键字修饰的不需要创建对象去调用,直接根据类名就可以去访问。对于这个概念,下面根据static关键字的四个基本使用来描述。然后在下一部分再来去分析static的原理,希望你能认真读完。

2、static关键字修饰类

java里面static一般用来修饰成员变量或函数。但有一种特殊用法是用static修饰内部类,普通类是不允许声明为静态的,只有内部类才可以。下面看看如何使用。

public class StaticTest {
    //static关键字修饰内部类
    public static class InnerClass{
        InnerClass(){
            System.out.println("===静态内部类===");
        }

        public void InnerMethod(){
            System.out.println("===静态内部方法===");
        }
    }

    public static void main(String[] args) {
        //直接通过StaticTest类名访问静态内部类InnerClass
        InnerClass inner = new StaticTest.InnerClass();

        //静态内部类可以和普通类一样使用
        inner.InnerMethod();
    }
    
}
Snipaste_2021-07-25_13-09-43.png
  • 如果没有用static修饰InterClass,则只能new 一个外部类实例。再通过外部实例创建内部类。

3、static关键字修饰方法

修饰方法的时候,其实跟类一样,可以直接通过类名来进行调用:

4、static关键字修饰变量

被static修饰的成员变量叫做静态变量,也叫做类变量,说明这个变量是属于这个类的,而不是属于是对象,没有被static修饰的成员变量叫做实例变量,说明这个变量是属于某个具体的对象的。

Snipaste_2021-07-25_13-13-47.png

我们同样可以使用上面的方式进行调用变量:

static的常规用法

  1. static变量是所属类的变量,它的访问权限通过访问控制符(public,private,protected,默认访问权限)进行控制。它在内存中只有一份,相同类的不同实例都指向同一份内存。也就是一个实例中变更该变量,其他实例获取的该变量的值也会发生变化。

  2. 静态方法:通过static修饰的方法。该方法也是独立于任何实例的,该方法中不能直接调用实例方法和实例变量

  3. 静态代码块:如果希望在类实例化之前对类进行一些初始化操作,可以将这些操作定义到静态代码块中。当父子类都存在静态代码块,它们的调用关系如示例所示

    父类静态代码块》子类静态代码块》父类构造代码块》父类构造方法》子类构造代码块》子类构造方法

    public class Father {
        static{
            System.out.println("我是父类的static代码块");
        }
        {
            System.out.println("我是父类的构造代码块");
        }
        Father(){
            System.out.println("我是父类的构造方法");
        }
    }
    
    public class Son extends Father {
        static{
            System.out.println("我是子类的static代码块");
    
        }
    
        {
            System.out.println("我是子类的构造代码块");
        }
        public Son(){
            System.out.println("我是子类的构造方法");
        }
    
        public static void main(String[] args){
            Son son=new Son();
        }
    }
    
    ​//结果
    我是父类的static代码块
    我是子类的static代码块
    我是父类的构造代码块
    我是父类的构造方法
    我是子类的构造代码块
    我是子类的构造方法
    
  4. 导入静态资源:可以在import static **(类的全路径名)。在你编写java代码时,使用这个import static 这个语法可以引入你需要的所有类的所有静态资源。之后可以直接通过方法名调用静态方法。不需要使用类名.方法名进行使用。减少了代码量。但同时削减了代码的可读性。

  5. 静态内部类:static可以修饰类,但只能修饰内部类。一旦内部类标记了static关键字。它就变成了顶级类,可以直接创建实例。而不依赖外部类的实例进行创建。Java中4种内部类区别再此不做赘述。1,静态内部类可以访问外部类的静态属性和方法。但不能访问直接访问外部类实例方法,需要生成外部类实例后才能访问外部类的实例方法。2,静态内部类可以定义静态方法和静态属性。

    //实例内部类创建方式
    OutObject outObject=new OutObject();
    OutObject.Inner inner=outObject.new Inner();
    
    //静态内部类创建方式
    OutObject.Inner inner=new OutObject.Inner();
    

static高级用法

  1. 类加载:因为static定义的属性,代码块,方法,类都是属于类的。所以static的初始化就绕不开类的加载。当一个类编译生成字节码文件。通过以下步骤进行加载

    20201129211638175 (1).png

    1. 加载:主要完成3件事情
      • 通过一个类的全限定名来获取定义此类的二进制字节流
      • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
      • 在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口
    2. 连接:
      • 验证(以下你可以理解为检查加载的字节码是否合法即可)
        文件格式验证,第一个阶段验证字节流是否符合class文件格式的规范
        元数据验证,是对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求,从定义的角度来说
        这个类是否继承了不允许被继承的类
        这个类是否有父类
        字节码验证,这个过程复杂的,是通过数据流和控制流分析语义是否合法是否符合逻辑,从运行的阶段
        例如运行一个方法是否会超出方法体
        符号引用验证:它是对自身类以外的信息进行匹配性校验,确保解析动作可以正常执行
      • 准备:正式为类变量分配内存并设置类变量初始值的阶段(int赋值为0等),仅包括类的静态变量设置内存空间并赋予默认值
      • 解析:将虚拟机常量池中符号引用替换为直接引用的过程
    3. 初始化:是执行类构造器方法的过程。类构造器方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并而成。编译器收集的顺序是由语句在源文件中出现的顺序所决定。
  2. 何时进行初始化。jvm对每类只会进行一次初始化但并不是程序中的每个类都会进行初始化。所有的Java程序虚拟机实现必须在每个类或者接口被Java程序“首次主动使用”时才会初始化他们

    • 7大主动使用
      1. 创建类的实例
      2. 访问某个类或接口的静态变量,或者对该静态变量赋值
      3. 调用类的静态方法
      4. 反射(如Class.forName("com.test.Test"))
      5. 初始化一个类的子类
      6. Java虚拟机启动时被表明为启动类的类
      7. JDK1.7开始提供的动态语言支持:Java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_pitStatic,REF__invokeStatic句柄所对应的类没有进行初始化则进行初始化
  3. static初始化示例

    public class TestStatic {
     
        public static  int a;
     
     
        static{
         b=2;
        //System.out.println(b); 报错非法向前引用
         System.out.println(TestStatic.b);
        }
        public static  int b = 1;
        public static void main(String[] args){
     
            System.out.println("static b==="+b);
        }
    }
    //结果
    2
    static b===1
    

    两个问题:为什么报非法向前引用,为什么会出现这个结果

    1. 非法向前引用:在类的变量初始化中满足一下四点会报非法向前引用

      1.设定C为直接包含该成员变量的类或者接口

      1. 如果a所在为C的静态成员/非静态成员初始化 或C的静态或非静态代码块中
      2. 如果a不是 一个赋值不等式的左值
      3. 通过简单名称来访问

      答案引用自:https://segmentfault.com/q/1010000002569214

    2. 类根据代码编写顺序进行初始化,当执行静态代码块时,静态变量b已经在类的准备阶段进行所占内存的划分并赋予默认值0,执行静态代码块时,b=2,然后在执行b的初始化所以b等于1.

这篇关于深入分析Java中的关键字static的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!