static 作为一个可选 修饰符,本身含义是“静态”,不仅仅指修饰方法 public static void main(String[] args) {},还可以修饰属性、代码块、内部类。
目前我们所学习过的有:类的属性、方法、构造函数, static 可以修饰属性、方法,唯独 不能修饰构造函数
那么对于属性和方法的修饰,用与不用 static 修饰,语法、运行效果、内存区别以及设计理念有何区别?

static 和 非 static 修饰的属性

在一个类里,当属性使用 static 进行修饰,称为静态属性,没有 static 修饰称为成员属性
首先回忆一下对于类的属性书写语法:

  1. public class Car {
  2. private String name;
  3. private int price;
  4. }

语法对比

  • 有 static 修饰的静态属性,可以使用 类名. 的方式进行访问,也允许通过 实例.方式访问(不推荐)
  • 非 static 修饰的成员属性只能使用 实例. 的方式访问

11.png
用 static 修饰的属性是全类的所有对象共享同一个值,无论该类的那个实例修改了这个值,大家都跟着变。
12.png
而非 static 修饰的属性是每个实例对象身上,各有该属性对应的属性值。
从语法上对比可以看出 java 设计意图:

  • 让 static 修饰的静态属性使用 类名. 方式去访问,是为了体现这个属性是 全类共享
  • 而非 static 修饰的成员属性通过 实例. 方式去访问是为了清晰划分到底是操作具体哪个对象的哪个属性

想想看,在类里,有什么属性是所有实例共享的吗?或者说,Student 类里有没有什么属性是所有学生共享的相同的?

在一个类中能够用 static 修饰的属性其实是不多见的,通常需要在某个特定问题域限制中才找得到。在实际开发当中通常是非 static 的属性居多,根据经验 通常只有常量能够设置为 static。
原因在于:

  1. 常量属性的值,是在类的定义中给定的,每个对象去访问都是统一值,所以没必要在每个实例上都存放一份,只需要全类共享一个
  2. 常量属性的值在类中定义好后,外部也无法修改,所以可以直接 public 公布给外部看
  3. 所以通常对于常量属性修饰符都是 public static final 的,其中
    • public 声明了访问权限为公开
    • static 声明该属性是是静态的,是类共享的
    • final 声明为常量

像在 ATM 中,假设我们设计存取款机内部都有一个存放钞票上限的 MAX_CASH ,难么所有 new 出来的的实例 atm 对象都是固定不变的,就可以将该“最大容量”属性设置为 static。或者是像对于学生总人数属性 numberOfStudents 也可以作为 static 的,每新增一个学生人数就加一,每减少一个学生人数就减一,但无论通过哪个学生实例去访问总人数,数量都是相同的。
所以在设计时切记要注意不要为了“方便”就去把属性设计为 static,而是一定要找到全类共享一个值,才设置为静态属性。

内存上的表现

属性存放位置

在内存上,静态属性和成员属性,存放位置会有不同:

  • 非 static 修饰的成员属性是存放在每个实例对象本身,即堆里
  • static 修饰的静态属性单独作为类的共享信息,被存放在静态区的内存空间,一个类的静态属性只划分一个存放空间,所以没有存放在实例对象上

    属性产生时机

    静态属性和成员属性,在内存中产生的时机不同:

  • 成员属性在 new 对象时产生

  • 静态属性在加载期产生
    • 加载期就是在 java 工作流程时,书写了 .java 文件 - 经过编译器得到 .class 文件 - 类加载

凡是用 static 修饰的都有具有共性:

  1. 与对象无关,属于类相关的
  2. 都是在加载期就开始处理

    static 和 非 static 修饰的方法

    有 static 修饰的方法称为 静态方法类方法,没有 static 修饰的方法称为 成员方法

    语法对比

    使用 static 和 非 static 修饰的方法在调用时:
  • 静态方法可以通过 类.() 方法来访问,也允许通过 实例.() 方法来访问(不推荐)
  • 成员方法只能通过 实例.() 方式来访问

13.png
这样的设计同样表明了使用 static 修饰的方式是跟对象无关的,非 static 的方法时一定要确定到底是哪个对象去做这个事情。

实现部分

在函数内部实现中,静态方法只能操作本类的静态属性和静态方法。
14.png
成员方法在方法的实现内部,本类的静态属性、静态方法、非静态属性、非静态方法都可以操作。

之所以静态方法不能操作非静态属性的原因在于:

  1. 从面向对象的设计思想来看,静态方法是通过 类.() 的方式调用,此时方法内部是没有当前对象这一目标的,也就是说没有 “this” 所以无法确认这里访问的成员属性、成员方法到底是哪个对象
  2. 从 java 虚拟机的家在机制来看,在进行类加载、编译时,在进行加载的顺序划分时,JVM 都是先加载静态的,再加载非静态的。如果在加载静态内容时发现有非静态的调用,此时 JVM 是不认识这些非静态内容的,导致报错。反之,在加载非静态方法时由于静态内容已经被加载过了,JVM 能够正常识别,自然能够识别,编译通过

    设计上的区别

    在程序设计时,我们要把工具类的工具方法设计为 static 的,其他的设计为非 static。
    这里又提到两个个新词儿:“工具类”、“工具方法”。那什么是工具类的工具方法?
    设想一个场景:课堂上回答问题。
    在该场景中,老师提问,学生回答,将答案 return 给老师。整个过程当中不会改变到学生实例的任何数据或状态,那么对于学生类 Student 中任一实例来回答问题,对提问的老师来说是没有任何差别的。这种情况下就可以将 Student 的 answer 方法设计为 static 的了。
    但假如在该场景中,谁回答对了问题就能加分。那么此时就必须明确,行为的执行者到底是哪个学生实例,这个实例的数据或状态就会发生改变。就必须设计为非 static 的了。
    所以 static 一定不能用来修饰构造函数,原因在于构造方法的用途就是创建对象,而 static 天生就是用于表示与对象无关。

推荐继续阅读:初始化块