静态变量
静态变量的定义
静态变量也叫 “类的属性”(类的变量)。
- 成员变量其实是 “对象的属性”,每个对象有自己的不同于其他对象的一套成员变量值。而类属性,是属于类的,不属于对象。即对其修改,所有对象(如果使用到了这个静态变量)都会受到影响。
静态变量在整个 java 程序中只有一个,而成员变量是每个实例有一份。
由于静态变量只与类有关,和对象创建与否无关,所以可以完全在没有 new 对象的情况下,直接使用 类名.静态变量名
来使用这个静态变量。
静态变量使用 static 修饰符。
静态变量一般使用全大写字母加下划线分割。(习惯用法,非语法强制)XX_YY_ZZ
-
其实在 java 自带的 Math 类(Math.java)里,就可以看到不遵守这种“约定”命名规则的静态变量,比如
private static final class RandomNumberGeneratorHolder
类中的静态变量randomNumberGenerator
。当然因为这里是 private 类而非公共(public)类中,静态变量到底要不要遵循约定的XX_YY_ZZ
就更随意了。
public class TestClass{
static int FOO_BAR = 100; // 只对当前包(package)可见
public static int ABC_DEFG = 100; // 对所有包都可见。
}
静态变量如果不赋值,java 也会给它赋以其类型的初始值。
静态变量适用场景
一个变量,什么时候适宜定义成成员变量,什么时候适宜定义成静态变量呢?
- 成员变量:当这个变量在每个成员对象中的值都可能不同时。
- 静态变量:当这个变量在每个成员对象中的值都相同时。
导入静态变量
使用 import static
来引入一个静态变量,就可以直接用静态变量名访问了。
// 导入
import static com.abc.Testclass.ABC_DEF; // 引用 Testclass 类中的静态变量 ABC_DEF
import static com.abc.Testclass.*; // 引用 Testclass 类中的所有静态静态变量。
// 使用
System.out.println(ABC_DEF); // import static 后直接使用
//也可以导入类,然后用完整形态名来访问静态变量
// 导入类
import com.abc.Testclass;
// 使用
System.out.println(Testclass.ABC_EFG); // 用完正形态名访问
在一个类中使用别的类的静态变量时,如果没有导入,就需要使用完整形态:类名.静态变量名
。
另外,即便导入了,如果本类中有与别的类中重名的静态变量,直接使用静态变量名时引用的实际上是本类的静态变量,如果想用别的类的同名静态变量,即便导入了,也得用完整形态名:类名.静态变量名
。
静态方法
java 中的静态方法(也叫类方法)。[这点和 python 是不同的]
静态方法:只能使用参数和静态变量(自己类和别的类的静态变量都行),换言之,就是没有 this 自引用的方法。
静态方法用 static 修饰。除了没有 this ,静态方法的定义和成员方法一样,也有方法名,返回值和参数。
静态方法的方法名没有约定俗成要求全是大写字母。
静态方法没有 this 自引用,它不属于某个实例,调用的时候也无需引用,直接用类名调用,所以它也不能直接访问成员变量(但可以选择将成员变量、对象等作为参数传入再使用,也可在静态方法内直接new一个对象来使用,但这显然和 this 自引用是不同的)。
- 当然在静态方法里,也可以自己创建对象,或者通过参数,获得对象的引用,进而调用方法和访问成员变量。
导入静态方法
和导入静态变量一样,可以使用 import static
来引入一个静态方法,就可以直接用静态变量名访问了。
import static
也可以使用通配符 *
来引入一个类里的所有静态变量。
public class TestClass{
String name;
public static int count = 10; // 声明静态变量
public static String sayHello(){ // 定义静态方法
String sentence = "Hello, "+ count;
// this.name = "Tom"; // 这种写法会在静态方法里报错
/*
TestClass a = new TestClass(); // 静态方法和成员方法不同,不会有缺省的自引用,只有在你有自引用的前提下,你才能访问成员变量
a.name = "Tom"; // 这样写的话,在静态方法里就不会报错,因为这不是通过 this 自引用来访问的
*/
return sentence;
}
}
// 调用方
// import static com.test.TestClass.*; // 用通配符导入该类所有静态方法(其实也包括了所有静态变量)
import static com.test.TestClass.SayHello; // 单独导入静态方法 sayHello(),并且经过实验,并不需要另外单独 import 用到的静态变量 count(即便 TestClass 类和 TestUse 类在不同包下)
public class TestUse{
public static void main(Stringp[] args){
System.out.println(sayHello()); // Hello, 10
}
}
静态代码块
在代码块前加 static
就是静态代码块。
static{
xxx;
}
静态代码块可以给静态变量赋值(实际上可以有任意合法的代码,不仅仅是给静态变量赋值)。在静态代码块里使用当前类的静态变量,直接使用该静态变量名即可。如果使用的是别的类的静态变量,则要用静态变量的完整形态 类名.静态变量名
。
一个程序中可以有多个静态代码块,多个静态代码块也是从上到下顺序执行的。另外,静态代码块是不放在类里面定义的方法中的,而是和其他方法处在平级位置,执行时,会先执行完所有的静态代码块再执行 main 方法。
方法的定义是没有顺序的,但静态代码块定义的顺序则是会造成影响的。
疑问1: 类 B 包含静态代码块,那么导入类 B 的话,是不是会直接执行其中的静态代码块?
实验证明,
import static
(不管是导入某个单一的静态方法、变量还是使用通配符*
导入所有静态方法、变量)都等于可以加载静态代码块,但不会马上执行,而是仅仅当代码中真的调用了该类中静态方法、静态变量时,才会在调用该类这些静态方法/静态变量前执行该类的静态代码块。import static 导入类.某个静态方法/静态变量; // import static 导入类.*; // 或使用通配符导入所有静态方法、静态变量 普通语句1; 输出语句2; // <---------------------------------- 在此处执行导入类的静态代码块 语句3:调用导入类的静态方法/静态变量;
若使用
import
(不带static
)则需要import 导入类; // import 导入类.* 是没有用的,并不能导入静态变量/静态方法 普通语句1; 输出语句2; // <---------------------------------- 在此处执行导入类的静态代码块 语句3:(必须要使用完整形态名否则调用不到,如:导入类.静态方法)调用导入类的静态方法/静态变量;
静态代码块的坑
使用某个静态变量的静态代码块(这里是指除了赋值之外的别的使用方式),必须在该静态变量声明之后。
public static double SVIP_DISCOUNT;
public static double BASE_DISCOUNT;
static{
BASE_DISCOUNT = 0.99;
VIP_DISCOUNT = 0.85;
System.out.println(BASE_DISCOUNT);
}
但如果是像下面这样写就会报错Illegal forward reference
(就是顺序不对的意思),错误发生在System.out.println(BASE_DISCOUNT);
处。
public static double SVIP_DISCOUNT;
static{
BASE_DISCOUNT = 0.99;
SVIP_DISCOUNT = 0.85;
System.out.println(BASE_DISCOUNT); // 报错
}
public static double BASE_DISCOUNT;
但这里有个很奇怪的现象,就是如果静态代码块里只有静态变量的赋值,没有其他对于静态变量的使用的话,静态变量的声明其实并不强制要求出现在静态代码块之前。
当然,这种所谓的正常不报错的语法(在静态代码块中只给静态变量赋值,静态变量的声明放到静态代码块后),还是不建议用,写程序时还是尽可能先写变量声明,再给变量赋值。
注意:静态变量的赋值,在静态代码块中,或者在某个方法中才可以赋值。并不能在任意代码块(只有两个花括号括起而没有 static
修饰)
public static double SVIP_DISCOUNT;
public static double BASE_DISCOUNT;
{
BASE_DISCOUNT = 0.99;
SVIP_DISCOUNT = 0.85;
System.out.println(BASE_DISCOUNT);
}
上面这个代码块实际上是没有执行的,不但没有输出,也没有赋值,SVIP_DISCOUNT
与BASE_DISCOUNT
两个静态变量其实还是初始值 0.0
。
任何的代码块其实都是放在一个方法里的,而静态代码块也不例外,实际上静态代码块、静态变量声明(/声明并赋值)被 java 放入到了一个叫 <clinit>
(class init) 的方法中,就像成员变量(/声明并赋值)是放在<init>
方法中。
<clinit>
会在每个 class 初始化的时候被调用一次。
在第一次使用这个类时,这个类就会被初始化好,类初始化完的时候,就完成了所有静态变量的赋值。
类本身是一个磁盘中的 class 文件,被 java 加载进来 java 的进程时,第一次用到这个类之前,就会把所有的静态代码块都执行一遍。