在Java中,我们在定义类的时候,类中都有相应的属性和行为。而这些属性和行为都是通过创建本类对象调用的。当在调用对象的某个行为时,这个行为没有访问到对象特有的数据时,方法创建这个对象就显得有点多余了。可是不创建对象,我们就无法调用在定义在类中的行为。举个简单的例子:
/* 定义一个类 person */ class Person { int age; String name; String addr; //说话行为 void speak(){ System.out.println(age+"+"+name+"+"+addr); } /吃饭行为 void eat(){ System.out.println("吃饭...."); } } /*创建Person的对象,并且调用定义在类中的行为
*/ class Demo { public static void main ( String[] agrs){ Person p = new Person();new一个对象 p. eat(); 调用吃饭行为 } }
我们都知道,在以上的代码中,只要调用eat的行为,我们都需要创建一个新的对象,如果我们调用多次eat的行为,我们就需要创建多个对象,这显得太多余了。
如果创建对象调用方法,发现这个方法中没有使用到对象中特有的数据,仅仅是调用类中的方法,这个时候我们可以用static关键字来休息我们的方法。 这个时候,被static修饰的方法是静态的。所以,eat行为可以定义为:
static void eat(){ System.out.println("吃饭...."); }
这个时候eat是静态方法,属于类的方法,可以使用类名直接调用。这个时候我们就不需要创建一个对象来调用eat了,就直接地写成:Person.sleep(); 就可以了。那么我们来尝试一下,用static来修饰speak的行为如下:
static speak(){
System.out.println(age+"+"+name+"+"+addr);
}
............
............
//在主函数中直接调用speak行为
Person.speak();
来编译一下,结构如下:
按照错误提示:非静态的变量不能被静态的方法引用。这是为什么呢?原因有一下几点:
- 静态时随着类的加载就加载了,也是随着类的消失而消失了。
- 静态时优先于对象而存在内存里面的,被对象共享
- 类中的变量是随着对象的加载而加载的,而静态时优先出现在内存里面的,所以静态无法后来出现在内存中的数据也就理所当然了。进一步讲,在静态方法里面无法使用this关键字,因为在静态方法里面,对象还没出现,所以this就没有任何指向了。
在上诉的例子中,我们在类中定义的成员变量age,name, addr是非静态的的,而且被static修饰的方法无法访问非静态的成员属性和行为。如果我们要在静态方法中访问成员属性呢?我们可以就直接用static修饰成员变量,那么被修饰的那个成员变量就变为静态的了,同样地,随着类的加载而加载了。优先于对象出现了。我们都知道,定义的类中,非静态的成员变量随着对象的出现而加载,如果成员属性有一个是所有的对象共同的呢?比如输入很多个对象的属性,国籍。大家的国籍都是Chinese。那么每次new一个对象,都会加载一个国籍的属性,但是这么多国籍属性都是一样的,确实浪费了内存空间,通过以上的的例子我们知道,使用静态修饰一个方法,我们可以让众多对象共享一个方法。同样的道理,使用static修饰一个成员变量,我们让对个对象共享这个数据。继续上面的例子,我们把这个代码改为:
/* 定义一个类 person */ class Person { int age; String name; String addr; Static String nation = Chinese; //说话行为 void speak(){ System.out.println(age+"+"+name+"+"+addr+"+"+nation); } /吃饭行为 static void eat(){ System.out.println("吃饭...."); } } /*创建Person的对象,并且调用定义在类中的行为 */ class Demo { public static void main ( String[] agrs){ Person p = new Person();new一个对象 p. eat(); 调用吃饭行为 } }
nation这个成员变量是大家共同的,所以呢,我们可以使用static修饰这个变量来让所有的对象共享这个数据。static修饰的变量是类共有的,比如chinese国籍是所有中国人这一类人共有的,所以是属于类的,也成为静态变量也称为类变量。
我们通过以上的总结,我们了解了static的引入以及作用,那么静态在内存中使怎么样加载的呢?
当输入java Demo之后,JVM加载Demo.class文件到方法区,分配内存空间,然后执行main方法,但是main方法是静态的,所以main方法被加载到方法区内的静态代码区域里面。之后执行main,main在栈内压栈,遇到Person的类,同样JVM加载person.class的文件,首先Person类文件的静态变量(nation)和静态方法(eat)也被加载到方法区的静态代码区里面,静态成员初始化nation = chinese。非静态的变量的模板被加载到方法区内。开始加载Person这个类,首先静态成员变量初始化,nation = null,加载这个到栈内存里面去,这样完成类的加载。之后,new 一个对象Person被加载到堆内存里面开辟一个物理地址(我们假设), 成员变量默认初始化 :age = 0 name = null addr = null,加载speak到栈内存。之后调用默认的构造函数,所以将对象的内存空间赋值给p,p指向0x45。回到主函数,执行p.eat,所以p到方法区里面找eat函数,执行eat的行为,打印之后,main函数执行完毕,弹栈。这个程序执行完毕,内存加载过程如上图所示。
关于static,有时候还用来修饰一段代码块,我们称为静态代码块。如下代码所示:
public class Person {
private String name;
private int age;
static{
System.out.println("静态代码块执行了");
}
}
在类中,不管创建多少个对象,静态代码块都只执行一次。可以用来给变量进行初始化和给类进行初始化。