一,static使用场景:
static修饰符被用来修饰一个类中的域变量、方法、初始化代码块以及嵌套的接口、嵌套的类;
或者被用来修饰一个接口中的域变量、嵌套的接口与嵌套的类。
二,static域变量的存储
static修饰的域变量存储在类的方法区内,试图使用以下方法耗费内存是徒劳的:
class Go{
static Go a =new Go();
public void t(){}
public static void main(String args[]){
Go.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.t();
}
}
因为a被存储在类Go的方法区,它指向了一个对象Go,当试图通过Go访问a时,每次拿到的都是方法区内的a。
如果是接口中的域变量将被存放在接口的方法区内。
三,static域变量初始化的时机
static修饰的域是在类的某个方法被调用时初始化的,比如类中的main方法被虚拟机调用,或者是类的构造方法被调用时,或是类的任何static方法被调用
因为这些方法被调用时,虚拟机就会去加载相应的类,将这些方法放入方法区,顺便也把static域一起初始化后一块儿放入方法区。
如果是接口中的被static修饰的域,因为接口中不存在静态方法,所以,这些域只能在被访问时被初始化。
四,static域变量+static初始化块+static方法
域变量按照代码的先后顺序被赋值,静态代码块中只能调用当前类的static方法。类加载完成时,方法区就有了static变量和static方法。
五,static与非static的桥梁
static方法要调用到非static方法必须通过构造方法这座桥梁,在static方法中唯一可以调用的非static方法就是构造方法(Thinking in JAVA中说构造方法也是static的,只是省略了static关键字而已),不管是本类的构造方法还是其他类的构造方法。
1,通过构造方法,我们可以构造出实例来访问非static方法 2,通过构造方法,可以在内部继续调用非static方法 3,通过构造方法,利用他的副作用可以调用到非static初始化
代码块
另外,还需要注意一点,static与非static方法不能有相同的方法签名,但有一个例外。
public class Go {
static Object Go(){return "";}
}
省略的默认构造方法与同名的static空参数方法不会有冲突。
只有显式声明的2个方法才会产生冲突。
java为什么要有这样的设定呢?static方法是使用类访问的,非static方法是使用类实例访问的,不会出现混乱的情况啊?
原因就在于java允许通过对象来访问static方法,这会造成调用的混乱。我始终觉得java的这个设计是个缺陷,gosling大叔应该在下个版本修改掉。(java总是想让新手用的更简单,但深入之后问题多多,垃圾回收也是例子)
你会发现在下面的例子中通过对象访问static方法真的让人烦躁,因为它会让人感觉方法是动态绑定的。
六,static+继承/实现
static的变量和static方法与类之间是静态绑定的,即在运行之前他们就被捆绑在了一起。
所以static变量和static方法在继承中不存在覆盖的情况,比如
public class Go extends Father{
static int i=1;
static void test(){
System.out.println("son");
}
public static void main(String args[]){
System.out.println(i);
Father f = new Go();
f.test();
System.out.println(f.i);
Go g = new Go();
g.test();
System.out.println(g.i);
}
}
class Father{
static int i=2;
static void test(){
System.out.println("father");
}
}
1,JAVA不赞成使用对象去调用static方法,当然如果要强行调用,JAVA会寻找对象的声明类型,根据声明类型调用static方法或访问相应的变量。
如上例中的f.i被转换成了Father.i,g.i被转换成了Go.i.
2,而上述main方法的第一行说明了在static方法中直接访问static变量时JAVA的处理方式,首先该static方法找到自己所属的类,然后在该变量前
添加类名,如上例种的i被转换为Go.i。
下面的这个增强版的例子说明了这一点
public class Go extends Father{
static int i=1;
static void test(){
System.out.println("son"+i);
}
public static void main(String args[]){
System.out.println(Go.i);
Father f = new Go();
test();
System.out.println(f.i);
Go g = new Go();
f.test();
System.out.println(g.i);
}
}
class Father{
static int i=2;
static void test(){
System.out.println("father"+i);
}
}
main方法调用Go类中的test方法,它首先找到与自己绑定的类Go,然后将方法中的i转换成了Go.i
3,上面说明了继承时父类与子类变量的分离性(即他们互不相干),但是,在继承或接口实现中又存在着父类的变量被子类复用的情况,我们可以去掉子类中
的i变量,不会报任何错误。
public class Go extends Father{
static void test(){
System.out.println("son"+i);
}
public static void main(String args[]){
System.out.println(Go.i);
Father f = new Go();
test();
System.out.println(f.i);
Go g = new Go();
f.test();
System.out.println(g.i);
}
}
class Father{
static int i=2;
static void test(){
System.out.println("father"+i);
}
}
这个时候Go的test方法找到了自己所绑定的类Go,将i装换成了Go.i,在Go的方法区内找不到变量i,由于继承的关系,它继续在Father类的方法区内寻找i。
将Father改成接口类型,也是一样