Java:static关键字小记

对 Java 中的 static 关键字,做一个微不足道的小小小小记

static 修饰变量

静态变量:是被 static 修饰的变量,也称为类变量,它属于类,因此不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;静态变量可以实现让多个对象共享内存;可以直接通过 类名.静态变量名 来访问它。

静态的注意事项

  1. 静态方法只能访问静态成员(包括静态成员变量和静态成员方法),不能访问非静态成员或方法;非静态方法可以访问静态也可以访问非静态方法或成员。
  2. 静态方法中不能出现 this,super 关键字。因为静态是优先于对象存在的,所以不能出现 this,super 关键字。
  3. 主函数是静态的。

实例变量:属于某一实例,需要先创建对象,然后通过对象才能访问到它。

总之:静态变量属于类的级别,而实例变量属于对象的级别

static 修饰方法

静态方法:被 static 修饰的方法,静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字;

再次注意:

  1. 被 static 修饰的静态变量与静态方法为静态资源,静态资源是类初始化的时候加载的,而非静态资源是类new的时候加载的
  2. 静态方法不能引用非静态资源:非静态资源为new的时候才会产生的东西,对于初始化后就存在的静态资源来说,根本不认识它。
  3. 静态方法里面可以引用静态资源;
  4. 非静态方法里面可以引用静态资源:非静态方法就是实例方法,那是new之后才产生的,那么属于类的内容它都认识。

static 修饰代码块

静态代码块:静态代码块在类初始化时运行一次;

  • 初始化顺序:静态变量和静态代码块优先于实例变量和普通语句块,静态变量和静态代码块的初始化顺序取决于它们在代码中的顺序。

    public class InitialOrderTest {
    
        // 1:被调用
    public static String staticField = "静态变量";
    // 2:被调用
    public static String staticMethod = staticMethod(); // 4:只有new的时候才会输出
    {
    System.out.println("普通语句块");
    } // 3:被调用
    static {
    System.out.println(staticField);
    System.out.println("静态语句块");
    } public String field = "实例变量"; public static String staticMethod(){
    System.out.println("静态变量调用静态方法");
    return "staticMethod";
    } // 最后才是构造函数的初始化
    public InitialOrderTest() {
    // 5:只有new的时候才会输出
    System.out.println("构造函数");
    } public static void main(String[] args) {
    new InitialOrderTest();
    }
    }
    // 结果:
    // 静态变量调用静态方法
    // 静态变量
    // 静态语句块
    // 普通语句块
    // 构造函数

    从字节码的角度对上述调用顺序进行解释:

    1. <cinit>()V

      编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码,合并为一个特殊的方法 <cinit>()V

      // java源码:
      public class Demo3_8_1 {
      static int i = 10;
      static {
      i = 20;
      }
      static {
      i = 30;
      }
      } // 对应的字节码:
      0: bipush 10 // 把10放入栈中
      2: putstatic #2 // Field i:I,常量池中找到i变量
      5: bipush 20 // 把20放入栈中
      7: putstatic #2 // Field i:I
      10: bipush 30 // 把20放入栈中
      12: putstatic #2 // Field i:I
      15: return

      <cinit>()V 方法会在类加载的初始化阶段被调用

    2. <init>()V

      编译器会按从上至下的顺序,收集所有 {} 代码块成员变量赋值 的代码,形成新的构造方法,但原始构造方法内的代码总是在最后

      // java源码:
      public class Demo3_8_2 {
      private String a = "s1"; {
      b = 20;
      } private int b = 10; {
      a = "s2";
      } public Demo3_8_2(String a, int b) {
      this.a = a;
      this.b = b;
      } public static void main(String[] args){
      Demo3_8_2 d = new Demo3_8_2("s3", 30);
      System.out.println(d.a); // "s3"
      System.out.println(d.b); // 30
      }
      } // 对应的字节码:
      Code:
      stack=2, locals=3, args_size=3
      0: aload_0 // this
      1: invokespecial #1 // super.<init>()V
      4: aload_0
      5: ldc #2 // <- "s1"
      7: putfield #3 // -> this.a
      10: aload_0
      11: bipush 20 // <- 20
      13: putfield #4 // -> this.b
      16: aload_0
      17: bipush 10 // <- 10
      19: putfield #4 // -> this.b
      22: aload_0
      23: ldc #5 // <- "s2"
      25: putfield #3 // -> this.a
      28: aload_0 // ------------------------------
      29: aload_1 // <- slot_1(a) "s3" |
      30: putfield #3 // -> this.a |
      33: aload_0 // |
      34: iload_2 // <- slot 2(b) 30 |
      35: putfield #4 // -> this.b --------------------
      38: return

    参考:https://www.bilibili.com/video/BV1yE411Z7AP?p=116 ~ https://www.bilibili.com/video/BV1yE411Z7AP?p=117

  • 静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问

    public class InitialOrderTest {
    
        static {
    // 不能访问,报错↓↓↓
    // System.out.println(staticField);
    // 可以赋值
    staticField = "?????";
    System.out.println("静态语句块");
    } public static String staticField; static {
    System.out.println(staticField); // 结果:?????
    } public static void main(String[] args) {
    new InitialOrderTest();
    }
    }

    对上述现象做进一步解释:

    对上述代码查看静态代码块的字节码如下:

    {
    // 类加载的链接中的 **准备阶段**,为static变量分配空间,即staticField已经分配好了空间
    public static java.lang.String staticField;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC public cn.xyc.other.InitialOrderTest();
    // ... 略
    public static void main(java.lang.String[]);
    // ... 略 // 在类的初始化阶段调用<cinit>()V完成对静态变量的赋值
    static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
    stack=2, locals=0, args_size=0
    0: ldc #4 // String ?????
    2: putstatic #5 // Field staticField:Ljava/lang/String;
    5: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
    8: ldc #7 // String 静态语句块
    10: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    13: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
    16: getstatic #5 // Field staticField:Ljava/lang/String;
    19: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    22: return
    LineNumberTable:
    line 9: 0
    line 10: 5
    line 16: 13
    line 17: 22
    }
    SourceFile: "InitialOrderTest.java"
  • 静态代码块是严格按照:父类静态代码块->子类静态代码块的顺序加载的,且只加载一次。

    对初始化顺序的补充:存在继承的情况下,初始化顺序为:

    1. 父类(静态变量、静态代码块)
    2. 子类(静态变量、静态代码块)
    3. 父类(实例变量、普通代码块)
    4. 父类(构造函数)
    5. 子类(实例变量、普通代码块)
    6. 子类(构造函数)

static 修饰内部类

静态内部类:非静态内部类依赖于外部类的实例,而静态内部类不需要。但静态内部类不能访问外部类的非静态的变量和方法;

静态内部类是不需要依赖于外部类的,通常称为嵌套类(nested class);

对于普通内部类对象,其隐含的保存了一个指向创建它的外围类对象的引用;然而当内部类用static修饰后变为静态内部类,就没有这个引用了,这就意味着:

  1. 要创建嵌套类的对象,并不需要其外围类的对象;
  2. 不能从嵌套类的对象中访问非静态的外围类对象。
// 静态内部类
public class Outer{
private static int num1 = 10;
private int num2 = 20;
private String name = "Java"; // 外部类普通方法
public void outer_func1(){
System.out.println("外部类Outer的普通方法:outer_func1");
} // 外部类静态方法
public static void outer_func2(){
System.out.println("外部类Outer的普通方法:outer_func2");
} // 静态内部类可以用public、protected、private修饰
// 静态内部类可以定义静态类或非静态内部类
public static class Inner{
static int num3 = 30;
String name = "Gava"; // 静态内部类里的静态方法
static void inner_func1(){
System.out.println("静态内部类的静态方法");
// 静态内部类只能访问外部类的静态成员(静态变量、静态方法)
System.out.println(num1);
// System.out.println(num2); //error
// outer_func1(); //error
outer_func2();
} //静态内部类非静态方法
void inner_func2(){
System.out.println("静态内部类的非静态方法");
System.out.println(num1);
System.out.println(num3);
System.out.println(name); // 可以访问静态内部类中的非静态成员
}
} public static void main(String[] args) {
// 静态内部类的静态方法
Outer.Inner.inner_func1(); // 静态内部类的非静态方法
Inner inner = new Inner();
inner.inner_func2();
}
}

通过静态内部类还可以实现安全的单例模式,后续再写

参考

https://www.cnblogs.com/jasonboren/p/11052500.html

https://www.cnblogs.com/xrq730/p/4820992.html

https://www.cnblogs.com/GrimMjx/p/10105626.html

Java:static关键字小记的更多相关文章

  1. Java Static关键字详解

    提起static关键字,相信大家绝对不会陌生,但是,想要完全说明白,猛的一想,发现自己好像又说不太明白... ...比方说,昨天被一个同学问起的时候... ... 当然,不是所有人都像我一样学艺不精的 ...

  2. java static关键字

    方便在没有创建对象的情况下来进行调用(方法/变量). 很显然,被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问. static可以用来修饰类的 ...

  3. Java static 关键字详解

    引言 在<Java编程思想>中有这样一段话:static方法就是没有this的方法.在static方法内部不能调用非静态方法,反过来是可以的.而且可以在没有创建任何对象的前提下,仅仅通过类 ...

  4. 【转载】java static 关键字的四种用法

    原文链接点这里,感谢博主分享 在java的关键字中,static和final是两个我们必须掌握的关键字.不同于其他关键字,他们都有多种用法,而且在一定环境下使用,可以提高程序的运行性能,优化程序的结构 ...

  5. [java]static关键字的四种用法

    在java的关键字中,static和final是两个我们必须掌握的关键字.不同于其他关键字,他们都有多种用法,而且在一定环境下使用,可以提高程序的运行性能,优化程序的结构.下面我们先来了解一下stat ...

  6. Java——static关键字

    前言 static关键字算是Java中比较复杂的关键字之一,它可以修饰变量.方法.类以及代码块.下面将介绍static的具体使用. static引入的目的 static的作用 static修饰变量 s ...

  7. Java——static关键字---18.09.27

    static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但在Java语言中没有全局变量的概念. static关键字主要有两种作用: 一.为某特定数据类 ...

  8. Java static关键字的重新思考

    上完Java课,虽然也写了不少的Java代码,但是一直有不少的疑惑,而static关键字一直困惑着我很久,今天无意探究竟,上知乎再仔细查了一下,发现了这个话题的优秀答案https://www.zhih ...

  9. java static关键字和代码块

    static关键字 代码块 方法重写 1. 方法重写的特点: 2. 注意事项: static关键字 为什么需要学习static关键字? 针对某一个变量属于类而不属于某一个具体的对象的时候,我们可以考虑 ...

随机推荐

  1. Linux下查看哪个网口插了网线

    场景: 一台服务器有多个网卡,一个网卡有多个网口,当插了一根网线的时候,不知道网线是插在哪一个网口. 1.查看网口信息 2.查看网口是否插了网线(命令ethtool) 命令:ethtool + 网口名 ...

  2. Appium问题解决方案(3)- java.lang.IllegalStateException: UiAutomation not connected!

    背景 连着手机,运行脚本,一段时间之后就报错了,看了Appium-server,发现报了这样一个错误 如何解决呢? 步骤一 通过 adb devices ,确定设备是否已连接上 步骤二(最终解决方案) ...

  3. 快速模式第一包: quick_outI1()

    文章目录 1. 序言 2. quick_outI1()流程图 3. quick_outI1()源码分析 4. quick_outI1_continue()源码分析 5. quick_outI1_tai ...

  4. SparkPi的编程计算

    Pi的计算方式有很多,本文主要是通过Spark在概论统计的方法对Pi进行求解: 算法说明: 在边长为R的正方形中,其面积为R^2,而其内接圆的面积为pi * R^2 /4 ,圆的面积与正方形的面积比为 ...

  5. 谈谈raft fig8 —— 迷惑的提交条件和选举条件

    谈谈raft fig8 -- 迷惑的提交条件和选举条件 前言 这篇文章的思路其实在两个月前就已经成型了,但由于实习太累了,一直没来得及写出来.大概一个月前在群里和群友争论fig8的一些问题时,发现很多 ...

  6. 洛谷P1056——排座椅(模拟,贪心,排序)

    https://www.luogu.org/problem/show?pid=1056 题目描述 上课的时候总会有一些同学和前后左右的人交头接耳,这是令小学班主任十分头疼的一件事情.不过,班主任小雪发 ...

  7. 如何在word中美观地插入编程代码

    零.缘起 在整理Java笔记时,想把代码直接贴到word文档中,原来一直截图很麻烦,所以找到以下方法. 思想:问题比答案更重要!你能想到问题,才知道去百度搜索. 一.打开网站 http://www.p ...

  8. 鸿蒙内核源码分析(进程概念篇) | 进程在管理哪些资源 | 百篇博客分析OpenHarmony源码 | v24.01

    百篇博客系列篇.本篇为: v24.xx 鸿蒙内核源码分析(进程概念篇) | 进程在管理哪些资源 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内 ...

  9. xLua中Lua调用C#

    xLua中Lua调用C# 1.前提 这里使用的是XLua框架,需要提前配置xlua,设置加载器路径: 可以参考之前的Blog:<xlua入门基础>: //调用段,所有的lua代码都写在Lu ...

  10. java 从零开始手写 RPC (07)-timeout 超时处理

    <过时不候> 最漫长的莫过于等待 我们不可能永远等一个人 就像请求 永远等待响应 超时处理 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RP ...