一、static修饰的范围
static:静态的、全局的,表明被修饰的变量、方法、方法块在一定范围内是共享的;由此应注意并发读写的问题。
1.1修饰变量
static修饰变量时,表示该变量任何类可直接访问;如:
int MAXVALUE = Integer.MAX_VALUE;//获取int类型的最大值
查看Integer类中MAX_VALUE的源码可以看到,它是通过public static final 定义,公有、直接访问、不可修改;
/**
* A constant holding the maximum value an {@code int} can
* have, 2<sup>31</sup>-1.
*/
@Native public static final int MAX_VALUE = 0x7fffffff;
其次使用static定义变量时,要注意线程安全的问题。使用方式:1.使用线程安全的类型;2.每次更改访问变量时,手动加锁
/*使用线程安全变量*
*/
static ArrayList<Integer> arr = new ArrayList<Integer>();//非线程安全的
static CopyOnWriteArrayList<Integer> arr_safe = new CopyOnWriteArrayList<Integer>();//线程安全
1.2 修饰方法
static修饰方法,不需要新建类,直接初始化即可,但是被static修饰的方法中,不能包含非静态方法,工具类中的大多数方法为静态方法(如:Arrays,Collections中方法大多为私有静态方法)方便使用;
在static修饰的方法中是不用考虑线程安全问题的,当方法执行时在虚拟机栈中创建栈帧,存储方法信息,每个方法的都是线程私有的,每个方法被隔离开,所以没有线程安全的问题;
1.2.1 静态方法 vs 非静态方法
早期的结构化编程,几乎所有的方法都是“静态方法”,引入实例化方法概念是面向对象概念出现以后,区分静态方法和实例化方法不能单单从性能上去理解,静态方法与非静态方法不仅仅是关乎内存,性能的问题,而是为了让开发更加模式化、面向对象化。也即静态方法和实例化方式的区分是为了解决模式的问题。
方法定义 | 内存分配 | 调用过程 | 应用场景 | |
---|---|---|---|---|
静态方法 | 使用static方法定义静态方法 | 在类定义时候被装载、分配内存 | 静态方法只能调用静态成员或方法,不能调用非静态方法或成员;静态方法依赖于类,在创建类对象前就可以通过Class.staticMethod()调用。 |
方法与类的对象无关,如工具类等 |
非静态方法 | 无须使用关键字 | 在类定义时没有占用内存,在类实例化为对象时被分配内存 | 非静态方法既可以调用静态成员或方法,又可以调用非静态成员或方法;实例化方法依赖于类的对象,需要创建类对象通过Object.staticMethod()调用。 | 方法依赖于类的对象,多线程中,如静态方法中含有静态比哪里,对于静态变量的更改操作容易引发线程安全问题。 |
1.2.2 静态方法与非静态方法应用策略
如果一个方法与对象的实例无关,用静态方法,反之采用实例方法,但如果方法和对象的实例有关,又想维护一份实例时,可以考虑使用单例模式。如:系统运行时,需要加载一些的配置和属性,是公共的,需要在整个生命周期存在,只需要一份就可以,但此时这些配置和属性又是通过面向对象的编码方式得到的,虽然用静态方法也能解决,但最好的方式是采用单例模式。在整个软件生命周期中均长期存在的方法应当在设计之初就定义为静态方法(如常用的工具类,Arrays,Collections中方法大多为私有静态方法),因为各模块调用的多,为了提高软件效率,内存空间不能省;而对于生命周期较短的方法,例如查询、修改用户所需信息等方法,应当坚决使用实例化方法,并注意在周期结束后的内存注销,这样可以节省更多的内存空间。
1.3 修饰方法块
static修饰方法块,也即静态块,是在类启动前初始化一些值;如:
执行的结果应该为:
static block1
static block2
public class TestStatic3 {
static{
System.out.println("static block1");
}
public static void main(String[] args) {
}
static{
System.out.println("static block2");
}
}
表面静态块在类加载前就已经进行了初始化;
二、static的加载顺序
2.1 静态变量的访问
静态成员变量虽然独立于对象,但是并不代表不可以通过对象去访问,所有静态方法和静态变量都可以通过对象访问。
/**
*this代表当前对象,通过new TestStatic4()调用printValue()方法,当前对象就是new TestStatic4()
* 所产生的对象,而static变量是被对象所共有,故在printValue()方法中this.value的值为10。
* printValue()方法中的int value = 1 是局部变量,不与this关联。
* 静态成员变量虽然独立于对象,但是并不代表不可以通过对象去访问,所有静态方法和静态变量都可以通过对象访问。
*/
public class TestStatic4 {
static int value = 10;
public static void main(String[] args) {
new TestStatic4().printValue();
}
private void printValue(){
int value = 1;
System.out.println(this.value);
}
}
2.2 静态变量的加载顺序
创建父类和子类,分别包含静态变量、静态方法块、构造函数,观察加载顺序。
import java.util.ArrayList;
import java.util.List;
//父类
public class A03_staticOrder_ParentStatic {
public static List<String> PARENT_LIST = new ArrayList() {{
System.out.println("父类静态变量初始化");
}};
static {
System.out.println("父类静态块初始化");
}
public A03_staticOrder_ParentStatic(){
System.out.println("父类初始化");
}
public static void call() {
System.out.println("父类静态方法初始化");
}
}
import java.util.ArrayList;
import java.util.List;
public class A03_staticOrder_SunStatic extends A03_staticOrder_ParentStatic{
public static List<String> LIST = new ArrayList() {{
System.out.println("子类静态变量初始化");
}};
static {
System.out.println("子类静态块初始化");
}
public A03_staticOrder_SunStatic(){
System.out.println("子类初始化");
}
public static void call() {
System.out.println("子类静态方法初始化");
}
public static void main(String[] args) {
new A03_staticOrder_SunStatic();
System.out.println("Main");
}
}
执行结果为:
父类静态变量初始化
父类静态块初始化
子类静态变量初始化
子类静态块初始化
父类初始化
子类初始化
Main
结论:
-
父类的优先级>子类的优先级;
-
静态变量与静态块的优先级与代码顺序有关;
-
static修饰的对象在代码执行前加载;