static 作为一个可选 修饰符,本身含义是“静态”,不仅仅指修饰方法 public static void main(String[] args) {}
,还可以修饰属性、代码块、内部类。
目前我们所学习过的有:类的属性、方法、构造函数, static 可以修饰属性、方法,唯独 不能修饰构造函数。
那么对于属性和方法的修饰,用与不用 static 修饰,语法、运行效果、内存区别以及设计理念有何区别?
static 和 非 static 修饰的属性
在一个类里,当属性使用 static 进行修饰,称为静态属性,没有 static 修饰称为成员属性。
首先回忆一下对于类的属性书写语法:
public class Car {
private String name;
private int price;
}
语法对比
- 有 static 修饰的静态属性,可以使用
类名.
的方式进行访问,也允许通过实例.
方式访问(不推荐) - 非 static 修饰的成员属性只能使用
实例.
的方式访问
用 static 修饰的属性是全类的所有对象共享同一个值,无论该类的那个实例修改了这个值,大家都跟着变。
而非 static 修饰的属性是每个实例对象身上,各有该属性对应的属性值。
从语法上对比可以看出 java 设计意图:
- 让 static 修饰的静态属性使用
类名.
方式去访问,是为了体现这个属性是 全类共享 - 而非 static 修饰的成员属性通过
实例.
方式去访问是为了清晰划分到底是操作具体哪个对象的哪个属性
想想看,在类里,有什么属性是所有实例共享的吗?或者说,Student 类里有没有什么属性是所有学生共享的相同的?
在一个类中能够用 static 修饰的属性其实是不多见的,通常需要在某个特定问题域限制中才找得到。在实际开发当中通常是非 static 的属性居多,根据经验 通常只有常量能够设置为 static。
原因在于:
- 常量属性的值,是在类的定义中给定的,每个对象去访问都是统一值,所以没必要在每个实例上都存放一份,只需要全类共享一个
- 常量属性的值在类中定义好后,外部也无法修改,所以可以直接 public 公布给外部看
- 所以通常对于常量属性修饰符都是
public static final
的,其中- public 声明了访问权限为公开
- static 声明该属性是是静态的,是类共享的
- final 声明为常量
像在 ATM 中,假设我们设计存取款机内部都有一个存放钞票上限的 MAX_CASH
,难么所有 new 出来的的实例 atm 对象都是固定不变的,就可以将该“最大容量”属性设置为 static。或者是像对于学生总人数属性 numberOfStudents
也可以作为 static 的,每新增一个学生人数就加一,每减少一个学生人数就减一,但无论通过哪个学生实例去访问总人数,数量都是相同的。
所以在设计时切记要注意不要为了“方便”就去把属性设计为 static,而是一定要找到全类共享一个值,才设置为静态属性。
内存上的表现
属性存放位置
在内存上,静态属性和成员属性,存放位置会有不同:
- 非 static 修饰的成员属性是存放在每个实例对象本身,即堆里
static 修饰的静态属性单独作为类的共享信息,被存放在静态区的内存空间,一个类的静态属性只划分一个存放空间,所以没有存放在实例对象上
属性产生时机
静态属性和成员属性,在内存中产生的时机不同:
成员属性在 new 对象时产生
- 静态属性在加载期产生
- 加载期就是在 java 工作流程时,书写了 .java 文件 - 经过编译器得到 .class 文件 - 类加载
凡是用 static 修饰的都有具有共性:
- 与对象无关,属于类相关的
- 都是在加载期就开始处理
static 和 非 static 修饰的方法
有 static 修饰的方法称为 静态方法、类方法,没有 static 修饰的方法称为 成员方法。语法对比
使用 static 和 非 static 修饰的方法在调用时:
- 静态方法可以通过
类.()
方法来访问,也允许通过实例.()
方法来访问(不推荐) - 成员方法只能通过
实例.()
方式来访问
这样的设计同样表明了使用 static 修饰的方式是跟对象无关的,非 static 的方法时一定要确定到底是哪个对象去做这个事情。
实现部分
在函数内部实现中,静态方法只能操作本类的静态属性和静态方法。
成员方法在方法的实现内部,本类的静态属性、静态方法、非静态属性、非静态方法都可以操作。
之所以静态方法不能操作非静态属性的原因在于:
- 从面向对象的设计思想来看,静态方法是通过 类.() 的方式调用,此时方法内部是没有当前对象这一目标的,也就是说没有 “this” 所以无法确认这里访问的成员属性、成员方法到底是哪个对象
- 从 java 虚拟机的家在机制来看,在进行类加载、编译时,在进行加载的顺序划分时,JVM 都是先加载静态的,再加载非静态的。如果在加载静态内容时发现有非静态的调用,此时 JVM 是不认识这些非静态内容的,导致报错。反之,在加载非静态方法时由于静态内容已经被加载过了,JVM 能够正常识别,自然能够识别,编译通过
设计上的区别
在程序设计时,我们要把工具类的工具方法设计为 static 的,其他的设计为非 static。
这里又提到两个个新词儿:“工具类”、“工具方法”。那什么是工具类的工具方法?
设想一个场景:课堂上回答问题。
在该场景中,老师提问,学生回答,将答案 return 给老师。整个过程当中不会改变到学生实例的任何数据或状态,那么对于学生类 Student 中任一实例来回答问题,对提问的老师来说是没有任何差别的。这种情况下就可以将 Student 的 answer 方法设计为 static 的了。
但假如在该场景中,谁回答对了问题就能加分。那么此时就必须明确,行为的执行者到底是哪个学生实例,这个实例的数据或状态就会发生改变。就必须设计为非 static 的了。
所以 static 一定不能用来修饰构造函数,原因在于构造方法的用途就是创建对象,而 static 天生就是用于表示与对象无关。
推荐继续阅读:初始化块