Java中静态子句的使用

1 简介

随着计算机的发展,“不安全”编程方式已逐渐成为编程代价高昂的主因之一。而初始化与清理正是涉及安全的两个因素。C++中引入了构造器的概念,这是一个在创建对象时会被自动调用的特殊方法,Java也采用了构造器,并额外提供了垃圾回收器。
由于Java中使用构造器来完成对象的创建过程,(清理工作由垃圾回收机制进行保证),因此熟悉构造过程中类对象的创建顺序对于程序员开发良好的程序是必不可少的。

在Java中默认构造器(即无参构造器)没有形式参数,它的作用是创建一个“默认对象”,如果你写的类中没有构造器,则编译期会自动帮你创建一个默认构造器。如果已经定义了一个构造器(无论是否有参数,编译期就不会自动创建默认构造器。)

2 初始化与清理

在编程思想中,该部分的章节梗概如下图所示:
Java中静态子句的使用_第1张图片

使用XMind对本章内容进行总结,可以看到大体如下的内容:
Java中静态子句的使用_第2张图片

3 static

由static修饰的变量、常量和方法被称作静态变量、常量和方法。
在处理问题时会需要两个类在同一个内存区域共享一个数据。例如球类中PI这个常量,在不同的圆类对象中均要使用,这时没有必要在两个类对象中同时创建两个PI常量,另外,在同级某种类型对象的个数时也需要独立于对象之外的变量来同级,不能属于某个对象,而是属于某种类型的变量。调用方法:
类名.静态类常量
静态数据与静态方法的作用通常是为了提供共享数据或方法。
注意:在静态方法中不可以使用this关键字
在静态方法中不可以直接调用非静态方法。
在每个类中都可以有一个主方法,主方法是类的入口点,它定义了程序从何处开始,主方法提供对程序流向的控制,Java编译期通过主方法来执行程序。

public static void main(String[] args) {
		//方法体
}

由于static的修饰,main方法有如下的特性:

由于main方法是static,因此如果要在其中调用类中其他方法,则该方法必须也是static
主方法没有返回值
主方法的形参为数组,可以使用args.length获取参数的个数。

在普通的方法中,为了能够简便、面向对象的语法来编写代码----即发“消息给对象”,编译期会做一些幕后工作,它暗自把所操作的对象的引用作为参数传递给方法。因此

a.peel(1)   ----> Banana.peel(a, 1);

而static方法中就是一种不需要对象传入对象引用的方法。在static方法中不能调用普通方法,而反过来却是可以得。

4 静态初始化块

4.1 初始化顺序

在Java程序中,可以使用构造器进行初始化,在运行时刻,可以调用方法或执行某些动作来确定初值。但Java尽力保证,所有变量会在使用前都得到恰当的初始化。但是需要注意的是:无法阻止自动初始化的进行,它将在构造器被调用之前进行。

//: initialization/Counter.java
public class Counter {
  int i;
  Counter() { i = 7; }
  // ...
} ///:~

i首先会被置0,然后被置为7。
在类的内部,变量定义的先后顺序决定了初始化的顺序,即使变量定义散布于方法定义之间,它们仍旧会在任何方法(包括构造器)被调用之前得到初始化

4.2 静态数据的初始化

无论创建多少个对象,静态数据都只占用一份存储区域。static关键字不能用于局部变量,而只能作用于域。

//: initialization/StaticInitialization.java
// Specifying initial values in a class definition.
import static net.mindview.util.Print.*;

class Bowl {
  Bowl(int marker) {
    print("Bowl(" + marker + ")");
  }
  void f1(int marker) {
    print("f1(" + marker + ")");
  }
}

class Table {
  static Bowl bowl1 = new Bowl(1);
  Table() {
    print("Table()");
    bowl2.f1(1);
  }
  void f2(int marker) {
    print("f2(" + marker + ")");
  }
  static Bowl bowl2 = new Bowl(2);
}

class Cupboard {
  Bowl bowl3 = new Bowl(3);
  static Bowl bowl4 = new Bowl(4);
  Cupboard() {
    print("Cupboard()");
    bowl4.f1(2);
  }
  void f3(int marker) {
    print("f3(" + marker + ")");
  }
  static Bowl bowl5 = new Bowl(5);
}

public class StaticInitialization {
  public static void main(String[] args) {
    print("Creating new Cupboard() in main");
    new Cupboard();
    print("Creating new Cupboard() in main");
    new Cupboard();
    table.f2(1);
    cupboard.f3(1);
  }
  static Table table = new Table();
  static Cupboard cupboard = new Cupboard();
} /* Output:
Bowl(1) //Table类的加载
Bowl(2)
Table()
f1(1)
Bowl(4) //4、5、3的顺序说明先依次执行静态字段初始化,然后普通字段初始化
Bowl(5)
Bowl(3) //该类中普通字段的初始化在构造器之前执行
Cupboard()
f1(2)
Creating new Cupboard() in main  //进入构造器了。
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)
*///:~

从上面的例子可以看出,初始化的顺序是先静态对象,而后是非静态对象
要执行main()静态方法,必须加载StaticInitialization类,然后其静态域table和cupboard被初始化,这将导致它们对应的类也被加载,并且由于它们也都包含静态的Bowl对象,因此Bowl随后也被加载。这个特殊的过程在main()开始之前就都被加载了。
因此对象的的创建过程,假如有个名为Dog的类:

  1. 即使没有显示的使用static关键字,构造器实际上也是静态方法。因此,当首先创建类为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次被访问时Java解释器必须查找类路径,以定位Dog.class文件
  2. 然后载入Dog.class,这将创建一个Class对象,有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载时进行一次。
  3. 当用new Dog()创建对象时,首次将在堆上为Dog对象分配足够的存储空间。
  4. 这块存储控件将会被清零,这就自动的将Dog对象中的所有基本类型数据都设置成了默认值(对数字来说是0,对布尔值和字符型也相同),而引用则被设置成null。
  5. 执行所有出现于字段定义处的初始化动作
  6. 执行构造器,这会牵涉到许多动作,尤其是涉及继承的时候

4.3 显示的静态初始化

Java允许将多个静态初始化动作组织成一个特殊的“静态子句”,有时候也叫做静态块

//: initialization/Spoon.java
public class Spoon {
  static int i;
  static {
    i = 47;
  }
} ///:~

与其他静态初始化动作一样,这段代码仅执行一次:当首次生成这个类的一个对象时,或者首次访问属于哪个类的静态数据成员时(即便从未生成过那个类的对象)。
注意:静态初始化块动作只进行一次。

4.4 非静态实例初始化

Java也有被称为实例初始化的类似语法,用来初始化每一个对象的非静态变量。

//: initialization/Mugs.java
// Java "Instance Initialization."
import static net.mindview.util.Print.*;

class Mug {
  Mug(int marker) {
    print("Mug(" + marker + ")");
  }
  void f(int marker) {
    print("f(" + marker + ")");
  }
}

public class Mugs {
  Mug mug1;
  Mug mug2;
  {
    mug1 = new Mug(1);
    mug2 = new Mug(2);
    print("mug1 & mug2 initialized");
  }
  Mugs() {
    print("Mugs()");
  }
  Mugs(int i) {
    print("Mugs(int)");
  }
  public static void main(String[] args) {
    print("Inside main()");
    new Mugs();
    print("new Mugs() completed");
    new Mugs(1);
    print("new Mugs(1) completed");
  }
} /* Output:
Inside main()
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs()
new Mugs() completed
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs(int)
new Mugs(1) completed
*///:~

可以看到实例初始化子句

{
    mug1 = new Mug(1);
    mug2 = new Mug(2);
    print("mug1 & mug2 initialized");
  }

看起来与静态初始化子句一模一样,只不过少了static子句。这种语法对于支持匿名内部类的初始化时必须的,但是它使得你可以保证无论你调用了那个显示构造,某些动作都会发生。从输出可以看出实例初始化子句在两个构造器之前执行的。

4.5 实践

在进行iSecure平台资源获取时,使用sdk调用平台所管理的资源时,可以看到如下的代码

package com.hikvision.ga;

import java.util.HashMap;
import java.util.Map;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.hikvision.artemis.sdk.ArtemisHttpUtil;
import com.hikvision.artemis.sdk.config.ArtemisConfig;

public class ArtemisPostTest {
	static {
		ArtemisConfig.host = "10.33.47.50:443"; // artemis网关服务器ip端口
		ArtemisConfig.appKey = "28601151"; // 秘钥appkey
		ArtemisConfig.appSecret = "wqaVdUU88PHjxuGn71yD";// 秘钥appSecret
	}
	/**
	 * 能力开放平台的网站路径
	 * TODO 路径不用修改,就是/artemis
	 */
	private static final String ARTEMIS_PATH = "/artemis";

	/**
	 * 调用POST请求类型接口,这里以获取组织列表为例
	 * https://ip:port/artemis/api/resource/v1/org/orgList
	 *
	 * @return
	 */
	public static String callPostApiGetOrgList() {
		/**
		 * https://ip:port/artemis/api/resource/v1/org/orgList
		 * 根据API文档可以看出来,这是一个POST请求的Rest接口, 而且传入的参数为JSON字符串.
		 * ArtemisHttpUtil工具类提供了doPostFormArtemis这个函数, 一共五个参数在文档里写明其中的意思. 因为接口是https,
		 * 所以第一个参数path是个hashmap类型,请put一个key-value, querys为传入的参数. 
		 * body 为JSON字符串.
		 * query不存在,所以传入null,accept和contentType不指定按照默认传null.
		 */
		String  getCamsApi = ARTEMIS_PATH + "/api/resource/v1/org/orgList";
		Map paramMap = new HashMap();// post请求Form表单参数
		paramMap.put("pageNo", "1");
		paramMap.put("pageSize", "2");
		String body = JSON.toJSON(paramMap).toString();
		Map path = new HashMap(2) {
			{
				put("https://", getCamsApi);
			}
		};
		String result = ArtemisHttpUtil.doPostStringArtemis(path, body, null, null, "application/json");
		return result;
	}
}

在编码时同样适用了如下的静态初始化块
static {
ArtemisConfig.host = “10.33.47.50:443”; // artemis网关服务器ip端口
ArtemisConfig.appKey = “28601151”; // 秘钥appkey
ArtemisConfig.appSecret = “wqaVdUU88PHjxuGn71yD”;// 秘钥appSecret
}
这是因为接口在每次调用时都需要这三个参数,通过把这些配置信息在类加载时自动进行静态子句的执行,可以确保在Controller层调用之前就正确的配置了正确的AppKey和AppSecret等参数。

另外,由于要为Controller层进行单元测试,因此需要在相应的测试类中添加该静态子句,以保证调用接口之前调用必须的参数已经得到恰当的初始化。

5 总结

本来只是想要总结一下静态子句的时候,结果啰啰嗦嗦的又写了一些内容,希望能够对初始化和清理理解的更加透彻吧。

参考

Java类中普通成员的初始化顺序
Java中静态子句的使用.doc
初始化与清理.xmind

你可能感兴趣的:(Java,编程思想)