static 表示静态,共有的含义
static可修饰的内容:
1.修饰属性,类属性,类变量
2.修饰方法,类方法。工具方法
3.static修饰代码块,静态代码块
4.static修饰内部类,静态内部类
今天主要讲前三种,static修饰内部类,后续会在讲到内部类的时候单独解释
static修饰的属性称为类属性,类变量,所有对象共享
代码示例:
class Person{
//成员变量或者说实例变量必须通过该类的对象来访问
String name;
int age;
String sex;
String country;
//成员方法,也是必须通过对象来访问
void show(){
System.out.println("name = " + name + ", age = " + age + ", country = " + country);
}
}
为什么要使用static关键字修饰属性?
country是成员变量,所有对象都在堆中保存有自己的country属性
有可能所有对象的这个值都是同一个值,比如中国人的所有对象的country = “中国”
假设有一天,中国光复了日本,那么对日本来说,所有对象的country属性值都要改成China
对country来说,有很多对象的属性值都是一样的,因此可以设置成共享,于是使用static修饰这个属性,该属性就称为静态属性
当一个成员属性/实例变量被static关键字修饰,他就表示类的属性,该类的所有对象共享这个属性,所有对象的属性值大家都一样
被static修饰的属性在JVM方法区中存储,所有该类对象共享此属性,也就是说该类的所有对象都只是使用了这个属性的值而已
被static修饰后的属性,可以直接通过类名称就可以访问,无需通过对象访问,不过通过对象也可以访问
Person.country = China;
内存图:
想调用new Person()来产生Person对象,得先要有Person类才能产生对象
因此创建对象的时候:
首先将Person类加载到内存中,Person中的所有static变量就会被加载到方法区中
static修饰的属性是全局唯一的,所有对象共享的,修改一个对象的属性值,就相当于把所有对象的属性值都该改变了
public static void main(String[] args) {
Person per1 = new Person();
Person per2 = new Person();
per1.name = "hello";
per1.age = 18;
per2.name = "hi";
per2.age = 20;
per1.show();//执行结果 name = hello, age = 18, country = Japan
per2.show();//执行结果 name = hi, age = 20, country = Japan
per2.country = "China";
per1.show();//执行结果 name = hello, age = 18, country = China
per2.show();//执行结果 name = hi, age = 20, country = China
}
class Person{
String name;
int age;
static String country = "Japan";
void show(){
System.out.println("name = " + name + ", age = " + age + ", country = " + country);
}
}
前面我们提到类属性保存在方法区中,那么方法区里还有什么呢?
方法区内存储的内容:
类中的所有方法的信息都存储在方法区,当方法被调用的时候,就被压入栈帧
所有常量、静态变量都存储在方法区中
能否在方法中定义一个static变量
public static void main(String[] args) { static int a = 10;//编译报错 }
一定不能在方法中定义static静态变量
方法中定义的变量全是局部变量,局部变量都在栈中存储,方法调用结束后,所有变量都是要销毁的,因此不可能定义出一个即在栈中又在方法区中的属性
final和static有什么区别?
class Person{
final int age = 18;//成员常量,都在堆中存储
static String country = "China";//静态变量,在方法区中存储
}
内存图:
类中定义的final修饰的是成员常量,这些成员常量存储在堆中,这和前面提到的方法区中存有常量似乎有一些矛盾。
但实际上,方法区中存储的常量指的是字面量,比如18这个数字,而final修饰的成员常量依然是存储在堆中
成员常量在定义的时候就要初始化
final int age;//error
final int age = 18;
若在类中定义了一个常量,我们通常情况下都会把static和final共同使用,称为类的常量
原因如下:
比如上述代码中的age这个属性是成员常量,在类定义的时候就被赋值为18,Person的所有对象都有age属性,且属性值均为18,而同一个值没必要重复存储多次,何不定义成静态变量,让所有对象共享,这样就只在内存中存储了1次
final关键字这就像是个给我们的天然的共享概念,因此为避免在每个对象中都存储一次,导致空间的浪费,可以直接定义为静态常量,使得所有对象都能共享这个属性,全局唯一
综上所述:在类中定义常量,一般会使用全局常量,由static final共同修饰
常量的命名规则:
所有单词全部大写,多个单词使用下划线分隔
static final String STUDENT_SCHOOL = "清华大学";
能否通过null引用访问static变量?笔试题常考!
Person per = null;
System.out.println(per.country);
//编译通过
static属性称为类属性,通过类名称即可直接访问,也就是说,没有对象也能调用,包括该类的null引用
per是Person类型的引用,哪怕是per没有存任何东西,也能通过Person类访问到类属性
这么理解:
创建引用类型变量per的时候,就已经和Person类建立了一种关系,即Person是变量per的类型,通过对象访问类属性的时候,实际上是通过per的类型Person去访问到类属性的,因此程序访问类属性的时候不会去在意引用类型变量里存的是不是null,而是直接根据per的类型去访问类属性
实际上这个案例就说明了,静态属性和对象无关
static、final总结:
static变量称为类属性,在方法区中存储,该类的所有对象共享此变量
若在类中定义了常量,一般我们使用static和final共同修饰,全局常量
要使用类属性,我们通常直接通过类名称.属性名称定义,不推荐使用对象来调用类属性,不规范
类属性可以通过类名称直接访问,包含该类的null引用
static修饰方法 - 类方法/工具方法/静态方法
static修饰的方法同样也是通过类名称直接访问,没有对象也能访问
思考几个问题
public static void main(String[] args) {}
主方法是一个程序的入口,如果主方法是个成员方法,得通过对象调用,创建对象又需要一个入口来创建,而现在连入口都没有,更何谈产生对象
程序从主方法开始执行,主方法要能调用起来,需要静态方法来直接调用,而无需产生对象
从static的语义来看,静态方法是静态的,无需产生对象就能调用的方法,或者说静态方法是没有对象的
成员域(即成员方法和成员变量)必须通过对象来调用,而静态方法没有对象或者说没有通过对象调用方法,必然无法直接访问成员变量和方法
静态变量和方法在没有对象的情况下就能访问,不在乎有没有对象
代码示例:
public class Test {
public static void main(String[] args) {
Person.fun();
}
}
class Person{
String name;
int age;
static String country = "China";
void show(){
System.out.print("调用非静态方法:");
System.out.println("name = " + name + ", age = " + age + ", country = " + country);
}
static void fun(){
System.out.println("调用静态方法");
show();//error
System.out.println(Person.country);//编译通过
}
}
为什么在静态方法中调用非静态方法会出错
其实这个问题和静态方法中访问成员属性类似,fun是静态方法,调用时没有通过对象来引用,想要在静态方法内部执行的成员方法,需要通过对象调用,所以这里不能被调用,编译报错
相反,如果是static属性,可以在static修饰的方法中调用,因为都属于静态域,不需要对象就能访问
根据上面的内容,可以总结出:
在静态方法中只能调用静态方法或者静态属性,static家族之间可以相互调用。不能直接调用成员方法和成员属性,必须通过对象来调用
在成员方法中既可以调用成员方法,也可以调用静态方法(此时都已经产生了该类对象,一定能够访问静态域)
什么时候设置static变量、方法
共享的变量如country属性,我们设计为静态变量
而工具类的方法一般设计为static方法,比如
Array.sort(int [])
普通类能否使用static关键字修饰
类定义出来是为了产生对象的,然后通过对象调用类里面定义的内容
假设static能修饰一个类,那么这个类甚至不需要创建对象就能调用了
“类本身”这种东西是不会保存在内存里的。他的作用就是告诉编译器怎么样生成代码,编译完了就没用了。就像其他简单的类型int或结构体,也是一样的
既然内存中都不存在“类”,那么用static去定义类是完全没有意义的
静态代码块是指定义在类中,使用static修饰的代码块,在类加载的时候执行一次,只会执行一次
代码示例:
public class Test {
public static void main(String[] args) {
Animal animal1 = new Animal("dog");
Animal animal2 = new Animal();
}
}
class Animal{
String name;
{
name = "test";
System.out.println("2.Animal的构造块");
}
public Animal(){
System.out.println("1.Animal的无参构造");
}
public Animal(String name){
this.name = name;
System.out.println("1.Animal的有参构造");
}
static{
System.out.println("3.Animal的静态代码块");
}
}
//执行结果 32121
3.Animal的静态代码块
2.Animal的构造块
1.Animal的有参构造
2.Animal的构造块
1.Animal的无参构造
静态代码块在类加载时执行一次,于对象无关,无论产生多少对象,都只执行一次,并且静态代码块在加载内存的时候就执行了,相比构造块、构造方法都要快
如果是定义在主类中的静态代码块,其执行顺序甚至要比main主方法更早
主类中的静态方法:
public class Test {
static {
System.out.println("主类中的静态代码块");
}
public static void main(String[] args) {
System.out.println("进入主方法");
}
}
//执行结果
主类中的静态代码块
进入主方法
结论:
主类中的静态块优先于主方法执行,JVM要执行主方法,首先得加载主类,主类一加载, 静态块就执行了
如果类中定义了静态属性和静态代码块:
public class Test {
public static void main(String[] args) {
System.out.println(Animal.age);
}
}
class Animal{
String name;
static int age = 10;
static{
age = 100;
System.out.println("3.Animal的静态代码块");
}
}
//执行结果
3.Animal的静态代码块
100
静态属性存在于方法区中,类定义的时候就会有初始值,初始值为10,这个类就被放入方法区,此时这个类只是定义了,还没有加载
当主方法中使用了Animal,也就是当程序读取到Animal的时候,就需要把Animal类从方法区加载内存,类在加载时,静态代码块就执行了,于是age就被静态代码块里的100给覆盖了
在主方法执行之前, 类就已经定义好了,其中的类属性也已经被定义,只有当类被调用的时候,类才会加载到内存中,于是类中的静态代码块在加载的过程中执行了,当加载结束后,再依次执行成员代码块和构造方法(如果有的话)