初始化块
Java使用构造器来对单个对象进行初始化操作,使用构造器先完成整个Java对象的状态初始化,然后将Java对象返回给程序,从而让该Java对象的信息更加完整。与构造器作用非常类似的是初始化块,它也可以对Java对象进行初始化操作。
使用初始化块
初始化块是Java类里可出现的第4种成员,一个类里面可以有多个初始化块,相同类型的初始化块之间有顺序:前面定义的初始化块先执行,后面定义的初始化块执行。初始化块的语法格式如下:
[修饰符] {
/ 始化块的可执行性代码
...
}
初始化块的修饰符只能是static,使用static修饰的初始化块被称为静态初始化块。初始化块里的代码可以包含任何可执行性语句,包括定义局部变量、调用其他对象的方法,以及使用分支、循环语句等。
下面程序定义了一个Person类,它既包含了构造器,也包含了初始化块。下面看看在程序中创建Person对象时发生了什么:
public class Person
{
//下面定义一个初始化块
{int a=6;
//在初始化块中
if (a > 4){System.out.println("Person初始化块:局部变量a的值大于4"); }
System.out.println("Person的初始化块");
}
// 定义第二个初始化块
{System.out.println("Person的第二个初始化块"); }
//定义无参数的构造器
public Person()
{
System.out.println("Person类的无参数构造器");
}
public static void main(String[] args)
{
new Person();
}
}
从运行结果可以看出,当创建Java对象时,系统总是先调用该类里定义的初始化块,结果一个类里定义了2个普通初始化块,则前面定义的初始化块执行,后面定义的初始化块后执行。
初始化块虽然也是Java类的一种成员,但它没有名字,也就没有标识,因此无法通过类、对象来调用初始化块。初始化块只在创建Java对象时隐式执行,而且在执行构造器之前执行。
初始化块和构造器之间差异,普通初始化块、声明实例Field指定的默认值都可认为是对象的初始化代码,它们的执行顺序与源程序中的排列顺序相同。看如下代码:
public class InstanceInitTest
{
//先执行初始化块将a Field赋值为6
{a=6;}
//再执行将a Field赋值为9
int a=9;
public static void main(String[] args)
{
//下面代码将输出9
System.out.println(new InstanceInitTest().a);
}
}
上面程序中定义了两次对a实例变量赋值,执行结果是a实例变量的值为9,这表明int a=9这行代码比初始化块后执行。
初始化块和构造器
如果两个构造器中有相同的初始化代码,这些初始化代码无须接收参数,就可以把它们放在初始化块中定义。通过把多个构造器中的相同代码提取到初始化块中定义,能更好地提高初始化代码的复用,提高整个应用的可维护性。
除此之外,如果希望类加载后对整个类进行某些初始化操作,例如当Person类加载后,则需要把Person类的eyeNumber类Field初始化为2,此时需要使用static关键字来修饰初始化块,使用static修饰的初始化块被称为静态初始化块。
静态初始化块
静态初始化块总是比普通初始化块先执行。静态初始化块是类相关的,用于对整个类进行初始化处理,通常用于对类Field执行初始化处理。静态初始化块不能对实例Field进行初始化处理。
下面程序创建了三个类:Root、Mid和Leaf,这三个类都提供了静态初始化块和普通初始化块,而且Mid类里还使用this调用重载的构造器,而Leaf使用super显式调用其父类指定的构造器:
class Root {
static{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root()
{
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root {
static{
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid()
{
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg)
{
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:"
+ msg);
}
}
class Leaf extends Mid
{
static{
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf()
{
//通过super调用父类中有一个字符串参数的构造器
super("疯狂Java讲义");
System.out.println("执行Leaf的构造器");
}
}
public class Test
{
public static void main(String[] args)
{
new Leaf();
new Leaf();
}
}
结果如下:
从图来看,第一次创建一个Leaf对象时,因为系统中还不存在Leaf类,因此需要先加载并初始化Leaf类,初始化Leaf类时会先执行其顶层父类的静态初始化块,再执行其直接父类的静态初始化块,最后才执行Leaf本身的静态初始化块。
一旦Leaf类初始化成功后,Leaf类在该虚拟机里将一直存在,因此当第二次创建Leaf实例时无须再次对Leaf类进行初始化。
普通初始化块和构造器的执行顺序与前面介绍的一致,每次创建一个Leaf对象时,都需要先执行最顶层父类的初始化块、构造器,然后执行其父类的初始化块、构造器……最后才执行Leaf类的初始化块和构造器。
当JVM第一次主动使用某个类时,系统会在类准备阶段为该类的所有静态Field分配内存;在初始化阶段则负责初始化这些静态Field,初始化静态Field就是执行类初始化代码或者声明类Field时指定的初始值,它们的执行顺序与源代码中的排列顺序相同。
Java实例联系
查看类的声明
通常类的声明包括常见修饰符(public、protect、private、abstract、static、final等)、类的名称、类的参数、类的继承类(实现的接口)和类的注释等。本实例将为大家演示如何利用反射获得这些信息。
1.
新建项目ClassDeclaration,并在其中创建一个ClassDeclaration.java文件。在该类的主方法中输出与类的声明相关的各个项:
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
public class ClassDeclaration {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("java.util.ArrayList");// 获得ArrayList类对象
System.out.println("类的标准名称:" + clazz.getCanonicalName());
System.out.println("类的修饰符:" + Modifier.toString(clazz.getModifiers()));
// 输出类的泛型参数
TypeVariable<?>[] typeVariables = clazz.getTypeParameters();
System.out.print("类的泛型参数:");
if (typeVariables.length != 0) {
for (TypeVariable<?> typeVariable : typeVariables) {
System.out.println(typeVariable + "\t");
}
} else {
System.out.println("空");
}
// 输出类所实现的所有接口
Type[] interfaces = clazz.getGenericInterfaces();
System.out.println("类所实现的接口:");
if (interfaces.length != 0) {
for (Type type : interfaces) {
System.out.println("\t" + type);
}
} else {
System.out.println("\t" + "空");
}
// 输出类的直接继承类,如果是继承自Object则返回空
Type superClass = clazz.getGenericSuperclass();
System.out.print("类的直接继承类:");
if (superClass != null) {
System.out.println(superClass);
} else {
System.out.println("空");
}
// 输出类的所有注释信息,有些注释信息是不能用反射获得的
Annotation[] annotations = clazz.getAnnotations();
System.out.print("类的注解:");
if (annotations.length != 0) {
for (Annotation annotation : annotations) {
System.out.println("\t" + annotation);
}
} else {
System.out.println("空");
}
}
}
运行程序,结果如下:
类的标准名称:java.util.ArrayList
类的修饰符:public
类的泛型参数:E
类所实现的接口:
java.util.List<E>
interface java.util.RandomAccess
interface java.lang.Cloneable
interface java.io.Serializable
类的直接继承类:java.util.AbstractList<E>
类的注解:空
基本的Java类型(boolean、byte、char、short、int、long、float和double)和关键字void也都对应一个Class对象。Class类的常用方法如表所示:
通常只能通过API来查看类的定义,不过Java反射还提供了另一种方式来获取类的声明信息。读者也可以在程序中使用这些信息。另外,使用getInterfaces()方法也可以获得对象类的所有接口,但是不包含泛型信息,即是说getSuperclass()方法也不能获得所有泛型信息的父类。
数组中的最大值和最小值
一维数组常用于保存线性数据,如数据库中的单行数据就可以使用一维数组来保存。数组的索引就好像酒店房间的编号一样,要想找到某个房间,就要先找到房间编号,所以我们要想获取一维数组中的最大最小值也就必须找到其对应的数组编号。
1.
新建项目ArrayMaxMin,并在其中创建一个ArrayMaxMin.java文件。在该类的主方法中先声明一个整数数组A,并为其赋初值,然后通过for循环来判断该数组元素的最大最小值,并分别实现输出:
public class ArrayMaxMin {
public static void main(String arg[]){
int i,min,max;
int A[]={25,65,98,36,45}; // 声明整数数组A,并赋初值
min=max=A[0];
System.out.print("数组A的元素包括:");
for(i=0;i<A.length;i++){
System.out.print(A[i]+" ");
if(A[i]>max) // 判断最大值
max=A[i];
if(A[i]>min) // 判断最小值
min=A[i];
}
System.out.println("\n数组的最大值是:"+max); // 输出最大值
System.out.println("数组的最小值是:"+min); // 输出最小值
}
}
想要使用数组里的元素,可以利用索引来完成。Java的数组索引编号从0开始,以一个score[8]的整型数组为例,score[0]代表第1个元素,score[1]代表第2个元素,score[7]代表第8个元素,也就是最后一个元素。
需要特别注意的是,在Java中取得数组的长度(也就是数组元素的个数)可以利用“.length”属性来完成。