一、引入
static翻译过来就是静态的意思。
首先来看一个需求:写一个JavaBean类来描述这个班级的学生。
属性:姓名、年龄、性别
行为:学习
从今天起我们涉及到的类就有很多个了,因此从现在开始每一个练习创建的不是一个类,而是一个包。而在IDEA中,包名是按照字母从小到大进行排序的,因此为了让大家在课后复习的时候更方便,就在前面加了一个
a01
,就表示是序号的意思,表示第一题,a02
表示第2题。但由于起名字不能以数字开头,因此在数字前面都加了一个a
。
Student.java
package com.itheima.a01staticdemo1;
public class Student {
//属性:姓名 年龄 性别
//新增:老师的姓名
private String name;
private int age;
private String gender;
public String teacherName;
public Student() {
}
public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
//行为
public void study() {
System.out.println(name + "正在学习");
}
public void show() {
System.out.println(name + ", " + age + ", " + gender + ", " + teacherName);
}
}
StudentTest.java
package com.itheima.a01staticdemo1;
import java.util.Random;
public class StudentTest {
public static void main(String[] args) {
//1.创建第一个学生对象
Student s1 = new Student();
s1.setName("张三");
s1.setAge(23);
s1.setGender("男");
s1.teacherName = "阿玮老师";
s1.study();
s1.show();
//2.创建第二个学生对象
Student s2 = new Student();
s2.setName("李四");
s2.setAge(24);
s2.setGender("女");
s2.teacherName = "阿玮老师";
s2.study();
s2.show();
}
}
由于一个班只有一个老师,teacherName
应该是共享的。既然是共享的,那每一次都需要用对象去调用 teacherName
去赋值,太麻烦了。能不能只赋值一次,就让这个类所有对象都共享一个值。这必定是可以的。
解决办法:在 teacherName
前面加一个修饰符 —— static
,一旦加上之后,Strudent
这个类所有的对象都共享同一个老师的姓名了。
public String teacherName;
此时当第一个学生对 teacherName
赋值的时候,所有的对象再去获取 teacherName
的时候就已经有值了。
s1.teacherName = "阿玮老师";
并且一旦用 static
修饰后,它还多了一种调用方式:直接用类名去调用。
Student.teacherName = "阿玮老师";
二、概念
static:static表示静态,是Java中的一个修饰符,可以修饰成员方法、成员变量。
因此我们需要将 static
修饰成员方法、 static
修饰成员变量分开去学习。
三、static
修饰成员变量
成员变量一旦被 static
修饰之后,这个成员变量就叫做 静态变量
。
静态变量
的特点:
-
被这个类所有对象共享
-
不属于对象,属于类
-
随着类的加载而加载,优先于对象存在
而对象一定要等
new
关键字执行了,它才在内存中出现
调用方式:
- 类名调用(推荐)
- 对象名调用
至于推荐类名调用的原因:既然所有的对象都共享这个属性,这个属性就不属于某个特定的类,因此用一个特定的对象去调用它,在语法中是可以的,但是不合理,因此我们需要用 类名调用
。
四、static
内存图
为了内存图尽可能让大家理解,所以下面代码的成员变量前面都没有加 private
。
程序刚开始启动,肯定是main方法先进栈。
然后执行main方法中的第一行代码中:Student.teacherName = "阿玮老师"
,在这行代码中,使用类名调用了 Student
类中的静态变量 teacherName
,并赋值为 "阿玮老师"
。此时就用到了 Student
类。因此在内存中,就会将 Student
类的字节码文件,加载到方法区,并在内存中创建了一个 单独存放 静态变量的空间,我们可以把这个空间叫做 静态区
。
因此当 Student
的字节码文件加载到方法区后,静态区
就出现了。
在JDK8以前,静态区是在方法区里面的。JDK8以后,就挪到了堆空间当中。
在静态区中就存着这个内所有的静态变量,例如 teacherName
。由于 teacherName
为引用数据类型,因此默认初始化值为 null
。
要注意的是,现在内存中并没有对象,因为我们代码还没有执行到 new
关键字,只有 new
关键字执行了,在内存当中才有对象!
由此可见:静态变量是随着类的加载而加载的,优先于对象出现的。
在以后我们还会用 static
去修饰其他的内容,其他的内容也会去遵守这个规则,只要使用 static
修饰的,都是随着类的加载而加载,加载的时候是优先于对象出现的。
此时第一行还没完,等号的右边还需要赋值,因此它会将 静态区
中的 teacherNamer
赋值为 "阿玮老师"
,原来的 null
就会被覆盖。
接下来再来看第二行代码:Student s1 = new Student();
,这个时候就出现 new
关键字了,此时对象在内存当中才会出现。
等号的左边相当于在栈的main方法中定义了一个变量 s1
,等号的右边有 new
关键字了,所以就在堆内存中开辟了一个空间。
假设这个空间的地址值是 0x0011
,这个空间也是我们平时所说的对象。
在这个空间里面存储的是所有的非静态的变量:name
、age
,并进行默认初始化 null
、0
。
再将这个小空间的地址赋值给 s1
,因此 s1
记录的地址就是 0x0011
。
现在如果我想通过 s1
去访问静态变量 teacherName
,此时就回去静态区中去找对应的变量。
s1.name = "张三"
,这里的 s1
记录的是 0x0011
,这行代码就相当于将 "张三"
赋值给 0x0011
的 name
。右上角的 null
就会被 "张三"
给覆盖。
再往下,s1.age = 23
,s1
记录的是 0x0011
,因此这行我们就可以这么理解:把 23
赋值给 0x0011
的 age
。右上角的 0
就会被 23
给覆盖。
再往下:s1.show()
,用 s1
去调用 show()
方法。show()
方法就会被加载进栈,因为此时 show()
方法的调用者是 s1
,就会通过 s1
去找里面的 name
、age
,然后再去找到静态区里面的 teacherName
。
因此在控制台中打印的就是 张三...23...阿玮老师
。
此时方法执行完毕,show()
方法出栈。
然后执行:Student s2 = new Student();
,创建了第二个对象 s2
,等号的右边还是有 new
关键字,因此在堆中又开辟了一个新的小空间,假设地址为 0x0022
。在这个小空间里面,它就会存储所有非静态的成员变量 name
、age
,并默认初始化为 null
、0
。
如果我想通过 s2
去获取静态变量,它也可以到下面静态区找到 teacherName
。
最后再将 0x0011
的地址赋值给左边的变量 s2
。
继续,s2.show()
,通过 s2
再去调用 show()
方法,此时 show()
方法就会被加载进栈。
又因为此时 show()
方法的调用者是 s2
,就会通过 s2
去找里面的 name
、age
,然后再去找到静态区里面的 teacherName
。
因此在控制台中打印的就是 null...0...阿玮老师
。
然后 show()
方法执行完毕,出栈。
通过上面的内存图接,我们可以看到里面的核心点。静态区里面的变量,是对象共享的,在内存中只有一份,谁要用谁去拿。
而非静态的东西,例如 name
、age
,都是每个对象独有的,每个对象都会单独存放一份,这就是它们的区别。
五、练习:请说出以下属性是否可以被定义为静态
在以后当中,以后有哪些属性可以用 static
来修饰呢?
其实很简单,我们只需要抓住两个字:共享。只要是所有对象都共享的,就必须要用 static
去修饰。
案例:在 Student
中有以下五个属性。有哪些属性可以被 static
修饰呢?
是否能被 static
修饰,就看两个字:共享。但是这个共享不是绝对的,我们要看具体的业务场景。
1、name
如果说 name
用 static
去修饰了,就表示说有的学生都共享同一个姓名,这种情况可能性非常少,因此这个 name
我们不会用 static
去修饰。
2、age
如果说 age
用 static
去修饰了,就表示说有的学生都共享同一个年龄,但每个学生的年龄也是不一样的,因此 age
也不会用 static
去修饰。
3、teacherName
如果现在这个 Student
表示一个班的学生,一个班的学生老师肯定是共享的,因此在这种情况下,teacherName
就必须用 static
去修饰。
但是还有种情况,就是你放学之后,你自己给自己请的私人家教,它就不是共享的了,因此在这种情况下,老师的姓名就不能加 static
修饰了。
因此在不同情况下是否用 static
修饰,你要自己想,抓住一个核心点:共享。