Java开发笔记(五十五)关键字static的用法

前面介绍嵌套类的时候讲到了关键字static,用static修饰类,该类就变成了嵌套类。从嵌套类的用法可知,其它地方访问嵌套类之时,无需动态建立外层类的实例,直接建立嵌套类的实例就行。
其实static不光修饰类,还能用来修饰方法、修饰属性等等,例如你们学习Java一开始就遇到的main方法,便为static所修饰。当一个成员方法被static修饰以后,该方法就成为静态方法;当一个成员属性被static修饰以后,该属性就成为静态属性。静态方法和静态属性,它俩同嵌套类同样不依赖于所在类的实例。外部若要访问某个类的静态方法,只需经过“类名.静态方法名”便可;同理,经过“类名.静态属性名”就能访问该类的静态属性。因为静态方法和静态属性拥有独立调用的特性,所以它们经常出如今一些通用的工具场景,例如系统的数学函数库Math,便提供了大量的静态方法和静态属性。其中常见的静态方法包括四舍五入函数Math.round、取绝对值函数math.abs、求平方根函数Math.sqrt等,常见的静态属性则有圆周率近似值Math.PI等。
那么开发者本身定义一个新类,如何得知哪些属性须要声明为静态属性,哪些方法须要声明为静态方法呢?在多数状况下,静态属性的取值通常要求是固定不变的,而静态方法只容许对输入参数进行加工,不容许操做其它的成员变量(静态属性除外)。以树木类为例,凡是会动态变化着的性状与事情,显然不适合声明为静态成员;只有与生长过程无关的概念,才适合声明为静态成员。譬如树木可分为乔木与灌木两大类,可想而知乔木与灌木的类型取值,与每棵树木的生长状况没有关联,这两种树木类型就适合做为静态属性。根据树木的类型,推断该树木的类型名称是“乔木”仍是“灌木”,这个类型名称的判断方法就适合做为静态方法。如此一来,Tree类即可添加下面的静态成员声明代码:html

	// static的字面意思是“静态的”,意味着无需动态建立便可直接使用。
	// 利用static修饰成员属性,外部便可经过“类名.属性名”直接访问静态属性。
	public static int TYPE_ARBOR = 1;
	public static int TYPE_BUSH = 2;

	// 利用static修饰成员方法,外部便可经过“类名.方法名”直接访问静态方法。
	public static String getTypeName(int type) {
		String type_name = "";
		if (type == TYPE_ARBOR) {
			type_name = "乔木";
		} else if (type == TYPE_BUSH) {
			type_name = "灌木";
		}
		return type_name;
	}

外部访问树木类的静态成员,只要按照“类名.静态成员名”的格式就好,具体的调用代码以下所示:java

	// 演示静态成员的调用方式
	private static void testStaticMember() {
		// 使用静态属性无需建立该类的实例,只要经过“类名.静态属性名”便可访问静态属性
		System.out.println("类型TYPE_ARBOR的取值为"+TreeStatic.TYPE_ARBOR);
		System.out.println("类型TYPE_BUSH的取值为"+TreeStatic.TYPE_BUSH);
		// 使用静态方法无需建立该类的实例,只要经过“类名.静态方法名”便可访问静态方法
		String arbor_name = TreeStatic.getTypeName(TreeStatic.TYPE_ARBOR);
		System.out.println("类型TYPE_ARBOR对应的名称是"+arbor_name);
		String bush_name = TreeStatic.getTypeName(TreeStatic.TYPE_BUSH);
		System.out.println("类型TYPE_BUSH对应的名称是"+bush_name);
	}

神通广大的static不只能够修饰类、属性、方法,它竟然还能修饰一段代码块!被static修饰的代码段样例以下:函数

	static {
		// 这里是被static修饰的代码段内容
	}

 

以上为static所包裹的代码段,又被称做“静态代码块”,其做用是在系统加载该类之时当即执行这部分代码。由于此处的代码被static包括,因此静态代码块内部只能操做同类的静态属性和静态方法,而不能操做普通的成员属性和成员方法。但是这里有个问题,早先提到构造方法才是建立实例之时的初始操做,那么静态代码块与构造方法比起来,它们的执行顺序孰先孰后?假若从Java的运行机制来解答该问题,不但费口舌并且伤脑筋,都说实践出真知,接下来不如作个实验,看看它们到底是怎样的先来后到。
首先在树木类中声明一个静态的整型变量leaf_count,之因此添加static修饰符,是由于要给静态代码块使用;接着在静态代码块内部对该变量作自增操做,并将变量值打印到日志;同时在树木类的构造方法里面也进行leaf_count的自增运算,以及往控制台输出它的变量值。修改后的相关代码片断示例以下:工具

	// 叶子数量,用来演示构造方法与初始静态代码块的执行顺序
	public static int leaf_count = 0;
	
	// static还能用来包裹某个代码块,一旦当前类加载进内存,静态代码块就当即执行
	static {
		leaf_count++;
		System.out.println("这里是初始的静态代码块,此时叶子数量为"+leaf_count);
	}

	public TreeStatic(String tree_name) {
		this.tree_name = tree_name;
		leaf_count++;
		System.out.println("这里是构造方法,此时叶子数量为"+leaf_count);
	}

最后回到外部建立该树木类的新实例,对应代码以下所示:学习

	// 演示静态代码块与构造方法的执行顺序
	private static void testStaticBlock() {
		System.out.println("开始建立树木类的实例");
		TreeStatic tree = new TreeStatic("月桂");
		System.out.println("结束建立树木类的实例");
	}

 

运行以上的演示代码,观察到下列的日志信息:测试

这里是初始的静态代码块,此时叶子数量为1
开始建立树木类的实例
这里是构造方法,此时叶子数量为2
结束建立树木类的实例

 

从日志结果可见,静态代码块的内部代码早早就获得执行了,而构造方法的内部代码要等到外部调用new的时候才会执行,这证实了静态代码块的执行时机确实先于该类的构造方法。this

静态修饰符一边给开发者带来了便利,一边也带来了不大不小的困惑。为了说明问题的迷惑性,接下来照例作个代码实验。仍旧在树木类中先声明一个静态的整型变量annual_ring,再补充一个成员方法grow,该方法内部对annual_ring自增的同时也打印日志。依据上述步骤给树木类新增了以下代码:设计

	// 树木年轮,用来演示静态属性的持久性
	public static int annual_ring = 0;
	
	// 注意每次读取静态属性,获得的都是该属性最近一次的数值
	public void grow() {
		annual_ring++;
		System.out.println(tree_name+"的树龄为"+annual_ring);
	}

 

而后其它地方前后建立这个树木类的两个实例,就像下面代码示范的那样:日志

	// 演示静态属性的持久性
	private static void testStaticProperty() {
		TreeStatic bigTree = new TreeStatic("大树");
		bigTree.grow();
		TreeStatic littleTree = new TreeStatic("小树");
		littleTree.grow();
	}

 

继续运行上面的测试代码,发现打印的日志以下:htm

这里是构造方法,此时叶子数量为3
大树的树龄为1
这里是构造方法,此时叶子数量为4
小树的树龄为2

 

虽然bigTree和littleTree是新建立的实例,可是从日志结果看它们的annual_ring数值居然是递增的,这可真是咄咄怪事,两个实例分明都是经过new出来的呀!产生怪异现象的罪魁祸首,原来就是static这个始做俑者,凡是被static修饰的静态变量,它在内存中占据了一块固定的区域,无论所在类被建立了多少个实例,每一个实例引用的静态变量依然是最初分配的那个。因而后面建立的树木实例littleTree,其内部的annual_ring与以前实例bigTree的annual_ring保持一致,无怪乎先后两实例的annual_ring数值是依序递增的了。
因而可知,静态属性老是保存最后一次的数值,假若它的取值每次都发生变化,即便建立新实例也得不到静态属性最初的数值。这种后果显而易见违背了静态变量的设计初衷,在多数时候,开发者定义一个静态属性,本来是想做为取值不变的常量使用,而不但愿它变来变去。对于此类用于常量定义的静态属性,能够在static前头再添加修饰符final,表示该属性只容许赋值一次,从而避免了屡次赋值致使取值更改的尴尬。下面是联合修饰final和static的属性定义代码例子:

	// 若想静态属性始终如一保持不变,就得给该属性添加final修饰符,表示终态属性只能被赋值一次
	public final static int FINAL_TYPE_ARBOR = 1;
	public final static int FINAL_TYPE_BUSH = 2;

  

更多Java技术文章参见《Java开发笔记(序)章节目录

相关文章
相关标签/搜索