目录
Static用法详解
1、静态属性
在类中,使用static修饰的属性,就是静态属性。例如,
public class Demo{
static int num;
}
静态属性是属于类的,可以直接使用类名来访问,也可以使用对象访问,但推荐使用类名访问,例如:
public class Demo{
static int num;
public static void main(String[] args){
Demo.num = 10; // 可以使用类名来访问
Demo demo = new Demo();
demo.num = 20; // 也可以对象来访问,但不推荐
}
}
注意,非静态属性,是属于对象的,一定要使用对象来访问,没有其他方式!
静态属性,是属于类的,并且是这个类所有对象共享的,例如
public class Demo{
static int num;
}
public static void main(String[] args){
Demo.num = 10;
Demo demo1 = new Demo();
Demo demo2 = new Demo();
System.out.println(demo1.num);//输出结果为 10
System.out.println(demo2.num);//输出结果为 10
Demo.num = 20;
System.out.println(demo1.num);//输出结果为 20
System.out.println(demo2.num);//输出结果为 20
demo1.num = 30;
System.out.println(demo1.num);//输出结果为 30
System.out.println(demo2.num);//输出结果为 30
}
可以看出,无论是使用类访问静态属性,还是使用这个类的某个对象访问静态属性,效果是一样 的,这个属性对这个类的所有对象都是可见的、共享的。
1.1、静态属性的存储位置
类中的静态属性,跟随着类,一起保存在内存中的方法区。当创建对象的时候,对象中只会保存类中定义的非静态属性的信息,而静态属性是不会进入到对象中的。
1.2、静态属性的初始化
无论是静态属性还是非静态属性,都必须进行初始化后才能使用,要么是系统给属性初始化赋默认值, 要么是我们自己手动给属性赋值。
属性的初始化时间:
- 非静态属性:创建对象后,系统会自动给对象中的非静态属性做初始化赋默认值,也正是因为这个 原因,非静态属性只有在创建对象后,使用对象才能访问。
- 静态属性:类加载到内存中(方法区)的时候,系统就会给类中的静态属性做初始化赋默认值,所以,即使还没有创建对象,只要这个类加载到了内存,就可以直接使用类名来访问静态属性,因为这个时候静态属性已经完成了初始化赋默认值的操作。
静态属性是属于类的,只要类加载到内存了,就可以使用类名来访问。非静态属性是属于对 象的,只有创建出对象了,使用对象才可以访问。
内存图
注意1,Demo类,被加载到内存的时候,静态属性num已经完成了默认的初始化赋值操作
注意2,可以通过类名(Demo.num)来访问,它可以直接找到方法区中存储的静态变量num
注意3,可以通过对象(demo.num)来访问,引用demo先找到堆区中的对象,再根据对象中存储的 Demo.class信息,找到方法区中存储的静态变量num
注意4,通过上述可知,无论使用类名还是使用对象来访问静态变量num,都是访问的同一个num,但是 官方推荐的是使用类名来访问更加合适。
注意,只有实例变量才会保存在对象中,并做初始化操作。静态变量保存在类中,并做初始化操作。
生活中的例子: 非静态属性(实例变量),就像教室中,同学们的水杯,每个同学都有一个自己的水杯,和其他人相互不影响
静态属性,就像教室中,角落里的饮水机,它是这个教室中所有同学共享的,张三接一杯水,李四就会 看到饮水机中的水少了一些,同样的李四接一杯水,张三也会看到饮水机中的水少了一些。
2、静态方法
在类中,使用static修饰的方法,就是静态方法。例如,
public class Demo{
public static void test(){
}
}
2.1、静态方法的调用
可以使用类名来调用,也可以使用对象来调用,但推荐使用类名:
public class Demo{
public static void test(){
}
public static void main(String[] args){
Demo.test();//推荐的方式
Demo demo = new Demo();
demo.test();//可以调用,但是不推荐
}
}
静态方法中不能调用类中的非静态方法或非静态属性:
public class Demo{
public String num;
public static void test(){
this.num = 10;//编译报错
this.sayHello();//编译报错
}
public void sayHello(){}
}
静态方法中,不能访问this,所有也就不能直接访问类中的非静态属性和非静态方法
在类加载的时候,JVM会优先给类中的静态属性做初始化,给类中的静态方法分配内存空间。 而类中非静态属性的初始化,非静态方法的分配空间,是要等到创建对象之后才会进行的。 所以类加载完成之后,就可以直接使用类名访问静态属性和静态方法。 所以创建对象之后,才可以使用对象访问非静态属性和调用非静态方法。
但是在类加载完成的时候,往往在内存中,还没有创建这个类的对象,没有对象(也就没有this)就不能访问类中的非静态属性和非静态方法。
正是因为这个原因,在静态方法中,才不能直接调用类中非静态属性和非静态方法。
3、静态代码块
3.1、什么是静态代码块
静态代码块,也叫做静态初始化代码块,它的作用就是给类中的静态属性做初始化的。
例如,
public class Demo {
public static int num;
static{
num = 10;
}
public static void main(String[] args){
System.out.println(Demo.num);//输出结果为 10
}
}
3.2、静态代码块的执行时刻
由于静态代码块没有名字,我们并不能主动调用,它会在类加载的时候,自动执行。所以静态代码块,可以更早的给类中的静态属性,进行初始化赋值操作。并且,静态代码块只会自动被执行一次,因为JVM在一次运行中,对一个类只会加载一次!
3.3、匿名代码块
和静态代码块类似的,还有一种非静态代码块,也叫做匿名代码块,它的作用是给非静态属性做初始化操作。
public class Demo {
public int num;
{
num = 10;
}
public static void main(String[] args){
Demo demo = new Demo();
System.out.println(demo.num);//输出结果为 10
}
}
注意,类中的构造器,既能给非静态属性进行初始化,又能配合new关键字进行对象的创建,所以匿名 代码块使用的场景较少,它能完成的工作,使用构造器也一样可以完成。
3.3、匿名代码块执行的时刻
由于匿名代码块没有名字,我们并不能主动调用,它会在创建对象的时候,构造器执行之前,自动执行。并且每次创建对象之前,匿名代码块都会被自动执行。
例如,
public class Demo {
static {
System.out.println("静态代码块执行");
}
{
System.out.println("匿名代码块执行");
}
public Demo(){
System.out.println("构造器执行");
}
public static void main(String[] args){
new Demo();
new Demo();
}
}
//输出结果为:
静态代码块执行
匿名代码块执行
构造器执行
匿名代码块执行
构造器执行
可以看出,静态代码执行了一次,因为JVM只会加载Demo类一次,而匿名代码块会在每次创建对 象的时候,先执行,然后再执行构造器。
创建和初始化对象的过程
Student s = new Student();
以这句代码为例进行说明
- 对Student类进行类加载,同时初始化类中静态的属性赋默认值,给静态方法分配内存空间
- 执行类中的静态代码块
- 堆区中分配对象的内存空间,同时初始化对象中的非静态的属性赋默认值
- 调用Student的父类构造器
- 对Student中的属性进行显示赋值,例如 public int age = 20;
- 执行匿名代码块
- 执行构造器代码
- =号赋值操作,把对象的内存地址赋给变量s
例如:
public class Person{
private String name = "zs";
public Person() {
System.out.println("Person构造器");
print();
}
public void print(){
System.out.println("Person print方法: name = "+name);
}
}
class Student extends Person{
private String name = "tom";
{
System.out.println("Student匿名代码块");
}
static{
System.out.println("Student静态代码块");
}
public Student(){
System.out.println("Student构造器");
}
public void print(){
System.out.println("student print方法: name = "+name);
}
public static void main(String[] args) {
new Student();
}
}
该代码的运行输出结果为:
Student静态代码块
Person构造器
student print方法: name = null
Student匿名代码块
Student构造器
注意1,子类重写父类的方法,在创建子类对象的过程中,默认调用的一定是子类中重写后的方法
注意2,非静态属性的显示赋值,是在父类构造器执行结束之后和子类中的匿名代码块执行之前的时候
注意3,以上代码中,因为方法的重写,会调用子类中重写后的print方法,同时该方法恰好是在父类构造 器执行中调用的,而这个时候子类中的name属性还没有进行显示赋值,所以是输出结果为null
注意4,如果此时在子类的匿名代码块中也输出name的值,那么就会显示tom,因为已经完成了属性的显 示赋值
静态导入
在自己的类中,要使用另一个类中的静态属性和静态方法,那么可以进行静态导入,导入完成后,可以直接使用这个类中的静态属性和静态方法,而不用在前面加上类名。
注意,只有JDK1.5及以上版本,才可以使用静态导入。
例如,没有使用静态导入的情况:
public class Demo {
public void test(){
System.out.println(Math.PI);//访问Math类中的静态属性PI,表示圆周率π
System.out.println(Math.random());//访问Math类中的静态方法random(),生成随机数
}
}
例如,使用静态导入的情况:
import static java.lang.Math.PI;
import static java.lang.Math.random;
public class Demo {
public void test(){
System.out.println(PI);
System.out.println(random());
}
}