java篇 类的进阶0x05:静态变量、静态方法、静态代码块

静态变量

静态变量的定义

静态变量也叫 “类的属性”(类的变量)。

  • 成员变量其实是 “对象的属性”,每个对象有自己的不同于其他对象的一套成员变量值。而类属性,是属于类的,不属于对象。即对其修改,所有对象(如果使用到了这个静态变量)都会受到影响。

静态变量在整个 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_DISCOUNTBASE_DISCOUNT两个静态变量其实还是初始值 0.0

任何的代码块其实都是放在一个方法里的,而静态代码块也不例外,实际上静态代码块、静态变量声明(/声明并赋值)被 java 放入到了一个叫 <clinit>(class init) 的方法中,就像成员变量(/声明并赋值)是放在<init> 方法中。

  • <clinit>会在每个 class 初始化的时候被调用一次。

在第一次使用这个类时,这个类就会被初始化好,类初始化完的时候,就完成了所有静态变量的赋值。

类本身是一个磁盘中的 class 文件,被 java 加载进来 java 的进程时,第一次用到这个类之前,就会把所有的静态代码块都执行一遍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值