第十讲 static关键字
static: 意为静态的。
类:属性 + 方法
属性: 成员属性,成员是什么?写在方法外,类体中的属性。类是成员吗?类显然不是成员,成员是对象,是实例。
成员属性的时候:指的是对象的属性,这个属性属于对象的,每个对象有不一样的值。
比如: Person类,有身高这样的属性,对于不同的实例,不同的个体来说,有不同的身高。这就叫做成员属性。
方法:
成员方法:也是成员的方法,成员就是对象,具体的对象,不同的对象有不同的方法。比如Person类中,有 getHeight() 的方法,不同的对象因为身高不一样,所以调用该方法得到的结果就不一样。
成员属性和成员方法:都是对象级别的。是对象拥有的。
属性:
静态属性:在类体中,方法外被static关键字修饰的。
成员属性:在类体中,方法外没有带static关键字的
(变量:成员变量、静态变量、局部变量(方法的参数列表、方法内定义的变量))
方法:
静态方法:被static关键字修饰的方法
成员方法:不带static关键字的方法
为什么要有static?什么是static?
public class JSUStudent
{
private int id;// 每个人都不一样
private String name; // 基本上不一样
private String universName; // "JSU"一样的值
public JSUStudent() {
}
public JSUStudent(int sid, String sName, String uName) {
id = sid;
name = sName;
universName = uName;
}
public void setId(int sid) {
id = sid;
}
public int getId() {
return id;
}
public void setName(String sName) {
name = sName;
}
public String getName() {
return name;
}
public void setUniversName(String uName) {
universName = uName;
}
public String getUniversName() {
return universName;
}
public String toString() {
return "{ " + id + ", " + name +", " + universName + " }";
}
}
// --------------test code------------
public class StaticTest01
{
public static void main(String[] args) {
JSUStudent liAng = new JSUStudent(1000, "li Ang", "JSU");
JSUStudent zz = new JSUStudent(1001, "zou zan", "JSU");
System.out.println(liAng);
System.out.println(zz);
}
}
- 内存图
我们可以看到在堆内存中,每个对象都有一个String类型的universName这个属性,他们的"值"是一样的,如果这样存储是很浪费内存空间的。
作为程序员,我们不希望这样,现实应用中也是,不希望这样浪费内存开销。
对于这样每个对象都有的且值是一样的属性,我们不需要给每个对象一份,所有的对象都共用一份就够了。这样的话就不会这么浪费了。
那么:
我们知道在JVM中,栈内存空间中存局部变量,不是成员变量。堆内存中存的是成员变量。如果这个String universName属性存在于栈内存空间中是不可能的,如果存在于堆内存空间中,就会冗余。那么,这样的变量只能存储在方法区内存中。
如何让它存储在方法区内存中呢?---- static修饰
只要被static修饰的变量,都会存储在方法区内存中。
被static关键字修饰的变量,只有一份,因为方法区内存是最先有东西的,它是用来进行类加载的。类一旦加载完,方法区内存中的内容就固定了。不可能一直在加载类。
static关键字修饰的变量,会在类加载的时候被赋值。而且只会执行一次。因为类加载只做一次。
因为static关键字修饰的变量,不是对象级别的,是类级别的。是这个类中所有的对象都共有的,值是一样的。我们称其类级别的变量。它的访问不依赖对象,用"类名."就可以访问。
public class JSUStudent
{
private int id;// 每个人都不一样
private String name; // 基本上不一样
private static String universName = "JSU"; // "JSU"一样的值
public JSUStudent() {
}
public JSUStudent(int sid, String sName) {
id = sid;
name = sName;
}
public void setId(int sid) {
id = sid;
}
public int getId() {
return id;
}
public void setName(String sName) {
name = sName;
}
public String getName() {
return name;
}
public String toString() {
return "{ " + id + ", " + name +", " + JSUStudent.universName + " }";
}
}
// ---------------test code------------------
public class StaticTest01
{
public static void main(String[] args) {
JSUStudent liAng = new JSUStudent(1000, "li Ang");
JSUStudent zz = new JSUStudent(1001, "zou zan");
System.out.println(liAng);
System.out.println(zz);
}
}
什么时候使用static关键字修饰变量:
当类级别的变量存在的时候,也就是说这个类中所产生的对象的属性都拥有相同的值,这样的变量我们需要用static修饰。
因为它是类级别的,所以,我们使用"类名."的方法访问。我们称这样的变量为静态变量
静态变量存在于方法区内存中,在类加载的时候就存在了。
问大家:上述代码中,是静态变量先赋值,还是main()先执行?
一定是静态变量先赋值,main()方法后执行。因为它是在类加载的时候被执行的。
静态代码块:
语法是这样的
static {
java 语句;
...
}
静态代码块是代码块,存放在方法区内存中,但是它是static修饰的,是静态的,所以它会在类加载的时候执行代码块中的java语句。
静态代码块的执行顺序:也是自上而下,依次执行。
构造方法也可以在静态代码块中执行。
main方法在类加载完成以后才会被执行。
静态代码块可以执行java语句,同时也可以执行方法,只不过
public class StaticCodeBlock
{
private int i = 100;
static {
System.out.println("Static code block 执行---1!");
System.out.println("i----->" + i);
//如果我能在这里拿到这个i的值,说明静态变量在main方法执行之前就已经被赋值了。
// StaticCodeBlock.java:6: 错误: 无法从静态上下文中引用非静态 变量 i
// System.out.println("i----->" + i);
// ^
}
}
// 编译报错:为什么?
// private int i = 100;这是成员属性。成员属性在什么时候被赋值?
// 成员属性只有当构造方法执行的时候才会第一次被赋值。构造方法只有在main方法执行之后,才会被执行。main方法压栈是在类加载完成之后。
类完整的内容: 成员属性/静态属性 + 成员方法/静态方法 + 构造方法
补充:
静态代码块:它是SUN公司为程序员准备的一个时刻,类加载时刻。在加载的时候完成一些事情。比如打印日志。比如:我们有一个大型的项目,网站,如果我们都是类加载完成之后才开始执行所有的代码,就会很卡,效率很低,有些代码就可以在类加载的时候完成,这样就能提高效率。
public class StaticCodeBlock
{
private int i = 100;
StaticCodeBlock scb; //非静态的引用要在栈中,栈是在方法执行之后才进行的。
static {
StaticCodeBlock scb1 = new StaticCodeBlock();// 从这里得出一个结论,构造方法是成员方法吗?
// 构造方法也要压栈,我们就把静态代码块当成是一个方法,它在类加载的时候压栈,当构造方法执行完毕之后,创建了一个对象,它的引用是scb1.当静态代码块执行完毕,弹栈,scb1消失,scb1指向的堆内存的引用就断掉了。堆内存中的对象就被回收了。出了静态代码块之后,在其他地方就没有办法拿到这个scb1.因为它被销毁了。
// 但是如果把它赋值给一个静态的引用,这个引用存在于方法区内存中。它会一直存在
test();
System.out.println("Static code block 执行---1!");
// System.out.println("i----->" + i);
//如果我能在这里拿到这个i的值,说明静态变量在main方法执行之前就已经被赋值了。
// StaticCodeBlock.java:6: 错误: 无法从静态上下文中引用非静态 变量 i
// System.out.println("i----->" + i);
// ^
}
public static void main(String[] args)
{
System.out.println("Hello World!");
// System.out.println("i----->" + i);
// 这样可以吗?不可以,成员都没有,无法访问成员变量。
System.out.println("i----->" + scb.i);
/*
StaticCodeBlock.java:20: 错误: 找不到符号
System.out.println("i----->" + scb.i);
^
符号: 变量 scb
位置: 类 StaticCodeBlock
1 个错误
*/
}
public static void test() {
System.out.println("test excution");
}
static {
System.out.println("Static code block 执行---2!");
}
}
静态方法:
有别于成员方法。它是被static修饰的方法。该方法是类级别的。因为静态变量不需要使用对象来调用,那么操作的时候我们可以给静态的变量提供静态的方法进行静态数据的操作(读、改)。
静态方法的调用是"类名."的方式。
比如:public static void main(String[] args) {}
它是属于哪个对象的吗?每个对象都有可能调用一次吗?显然不是。
它是类级别的,一个类中的方法要执行,只要调一次main就够了。
请问:静态方法被调用的时候会产生空指针异常吗?
我们说过,静态的都是类级别的,与对象无关,空指针只会发生在对象身上。
public class Person
{
public static void main(String[] args)
{
Person zhangSan = new Person();
zhangSan.bornCry();
Person lisi = new Person();
lisi.bornCry();
Person.bornCry();
zhangSan = null;
zhangSan.bornCry();
}
public static void bornCry() {
System.out.println("人出生的时候都会哇哇哇的哭!!");
}
}
/*
E:\java基础\day05-2>java Person
人出生的时候都会哇哇哇的哭!!
人出生的时候都会哇哇哇的哭!!
人出生的时候都会哇哇哇的哭!!
人出生的时候都会哇哇哇的哭!!
*/
- 总结
static:
该关键字是修饰类级别变量和方法的,被static修饰的变量和方法与对象无关,它是静态的变量和静态方法,它在类加载的时候在方法区中存在,静态变量会在方法区内存中在类加载的时候进行赋初始值。静态代码块也会在类加载的时候执行。
静态的方法执行有两种情况会被执行,第一种情况在main方法中调用,第二种情况,在静态代码块中执行。
静态变量和静态方法都是通过"类名."的方式进行访问,也可以使用"引用."的方式访问,但不推荐后者。
静态代码块只会被执行一次。为什么?因为它是在类加载的时候执行的。
记住:静态的东西与对象无关。它是类级别的概念。
补充:
除了静态代码块之外,还有实例代码块。
语法:
{
java语句;
java语句;
}
实例代码块在构造方法执行之前执行,构造方法每执行一次,实例代码块就会被提前执行一次。
public class Person
{
{
System.out.println("hello world!");
}
public Person() {
System.out.println("构造方法执行!");
}
public static void main(String[] args)
{
Person zhangSan = new Person();
Person lisi = new Person();
}
public static void bornCry() {
System.out.println("人出生的时候都会哇哇哇的哭!!");
}
{
System.out.println("hello everyone!");
}
}
/*
hello world!
hello everyone!
构造方法执行!
hello world!
hello everyone!
构造方法执行!
*/
in(String[] args)
{
Person zhangSan = new Person();
Person lisi = new Person();
}
public static void bornCry() {
System.out.println(“人出生的时候都会哇哇哇的哭!!”);
}
{
System.out.println("hello everyone!");
}
}
/*
hello world!
hello everyone!
构造方法执行!
hello world!
hello everyone!
构造方法执行!
*/