一.初始化块
一个类可以有多个初始化块,相同类型的初始化块之间有顺序:
前面定义的初始化块先执行,后面定义的初始化块后执行。
从某种程度上来看,初始化块是构造器的补充,初始化块总是在构造器执行之前执行,与构造器不同的是,初始化块是一段固定执行的代码,它不能接收任何参数。如果有一段初始化处理代码对所有对象完全相同,且无须接收任何参数,就可以把这段初始化处理代码提取到初始化块中。
二.静态初始化块
用static修饰的初始化块称为静态初始化块,也称为类初始化块(普通初始化负责对对象执行初始化,类初始化则负责对类进行初始化)。静态初始化块是类相关的,系统将在类初始化阶段执行静态初始化块,而不是在创建对象时才执行。因此静态初始化块总是比普通初始化块先执行。
静态初始化块不能访问非静态成员,包括不能访问实例变量和实例方法。
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();
}
}
二.final关键字
java提供了final关键字来修饰变量,方法和类,系统不允许为final变量重新赋值,子类不允许覆盖父类的final方法,final类不能派生子类。通过使用final关键字,允许java实现不可变类,不可变类会让系统更加安全。
1.final修饰的成员变量必须由程序员显式地指定初始值。
final修饰的实例变量,要么在定义该实例变量时指定初始值,要么在普通初始化块或构造器中为该实例变量指定初始值。
public class FinalVariableTest
{
// 定义成员变量时指定默认值,合法。
final int a = 6;
// 下面变量将在构造器或初始化块中分配初始值
final String str;
final int c;
final static double d;
// 既没有指定默认值,又没有在初始化块、构造器中指定初始值,
// 下面定义的ch实例变量是不合法的。
// final char ch;
// 初始化块,可对没有指定默认值的实例变量指定初始值
{
//在初始化块中为实例变量指定初始值,合法
str = "Hello";
// 定义a实例变量时已经指定了默认值,
// 不能为a重新赋值,因此下面赋值语句非法
// a = 9;
}
// 静态初始化块,可对没有指定默认值的类变量指定初始值
static
{
// 在静态初始化块中为类变量指定初始值,合法
d = 5.6;
}
// 构造器,可对既没有指定默认值、有没有在初始化块中
// 指定初始值的实例变量指定初始值
public FinalVariableTest()
{
// 如果在初始化块中已经对str指定了初始化值,
// 构造器中不能对final变量重新赋值,下面赋值语句非法
// str = "java";
c = 5;
}
public void changeFinal()
{
// 普通方法不能为final修饰的成员变量赋值
// d = 1.2;
// 不能在普通方法中为final成员变量指定初始值
// ch = 'a';
}
public static void main(String[] args)
{
FinalVariableTest ft = new FinalVariableTest();
System.out.println(ft.a);
System.out.println(ft.c);
System.out.println(ft.d);
}
}
结果
6
5
5.6
2.final局部变量
系统不会对局部变量进行初始化,局部变量必须由程序员显式初始化,
public class FinalLocalVariableTest
{
public void test(final int a)
{
// 不能对final修饰的形参赋值,下面语句非法
// a = 5;
}
public static void main(String[] args)
{
// 定义final局部变量时指定默认值,则str变量无法重新赋值
final String str = "hello";
// 下面赋值语句非法
// str = "Java";
// 定义final局部变量时没有指定默认值,则d变量可被赋值一次
final double d;
// 第一次赋初始值,成功
d = 5.6;
// 对final变量重复赋值,下面语句非法
// d = 3.4;
}
}
上面的例子示范了final修饰形参的情形,因为形参在调用该方法时,由系统根据传入的参数来完成初始化,因此使用final修饰的形参不能被赋值。
3.final修饰基本类型变量和引用类型变量的区别
当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。
import java.util.*;
class Person
{
private int age;
public Person(){}
// 有参数的构造器
public Person(int age)
{
this.age = age;
}
// 省略age的setter和getter方法
// age的setter和getter方法
public void setAge(int age)
{
this.age = age;
}
public int getAge()
{
return this.age;
}
}
public class FinalReferenceTest
{
public static void main(String[] args)
{
// final修饰数组变量,iArr是一个引用变量
final int[] iArr = {5, 6, 12, 9};
System.out.println(Arrays.toString(iArr));
// 对数组元素进行排序,合法
Arrays.sort(iArr);
System.out.println(Arrays.toString(iArr));
// 对数组元素赋值,合法
iArr[2] = -8;
System.out.println(Arrays.toString(iArr));
// 下面语句对iArr重新赋值,非法
// iArr = null;
// final修饰Person变量,p是一个引用变量
final Person p = new Person(45);
// 改变Person对象的age实例变量,合法
p.setAge(23);
System.out.println(p.getAge());
// 下面语句对p重新赋值,非法
// p = null;
}
}
使用final修饰的引用类型变量不能被重新赋值,但可以改变引用类型变量所引用对象的内容。
4.可执行宏替换的final变量
不管是类变量,实例变量,还是局部变量,只要该变量满足三个条件,这个final变量就不再是一个变量,而是相当于一个直接量。
1.1 使用final修饰符修饰。
1.2 在定义该final变量时指定了初始值。
1.3 该初始值可以在编译时就被确定下来。
public class FinalLocalTest
{
public static void main(String[] args)
{
// 定义一个普通局部变量
final int a = 5;
System.out.println(a);
}
}
执行 System.out.println(a),代码实际转换为执行System.out.println(5).
5.final方法
final修饰的方法不可被重写,如果出于某些原因,不希望子类重写父类的某个方法,则可以使用final修饰该方法。
public class FinalMethodTest
{
//final修饰的方法只是不能被重写,完全可以被重载
public final void test(){}
public final void test(String arg){}
}
class Sub extends FinalMethodTest
{
// 下面方法定义将出现编译错误,不能重写final方法
//public void test(){}
}
如果子类中定义一个与父类private方法有相同方法名,相同形参列表,相同返回值类型的方法,也不是方法重写,只是重新定义了一个新方法。
public class PrivateFinalMethodTest
{
private final void test(){}
}
class Sub extends PrivateFinalMethodTest
{
// 下面方法定义将不会出现问题
public void test(){}
}
final修饰的方法仅仅是不能被重写,并不是不能被重载,因此下面程序完全没有问题。
public class FinalOverload {
//final修饰的方法只是不能被重写,完全可以被重载
public final void test(){}
public final void test(String arg) {}
}
6.final类
final修饰的类不可以有子类,如java.lang.Math类就是一个final类,他不可以有子类。
public final class FinalClass {}
//下面的类定义将出现编译错误
class Sub extends FinalClass {}
三.类成员
1.static关键字不能修饰构造器。static修饰的类成员属于整个类,不属于单个实例。
当使用实例来访问类成员时,实际上依然是委托给该类来访问类成员,因此即使某个实例为null,它也可以访问它所属类的类成员。
public class NullAccessStatic
{
private static void test()
{
System.out.println("static修饰的类方法");
}
public static void main(String[] args)
{
// 定义一个NullAccessStatic变量,其值为null
NullAccessStatic nas = null;
// 使用null对象调用所属类的静态方法
nas.test();
NullAccessStatic nas2 = new NullAccessStatic();
nas2.test();
}
}
如果一个null对象访问实例成员(包括实例变量和实例方法),将会引发NullPointerException异常,因为null表明该实例根本不存在,既然实例不存在,那么它的实例变量和实例方法自然也不存在。
2.单例类
如果一个类始终只能创建一个实例,则这个类被称为单例类。
一旦把该类的构造器隐藏起来,就需要提供一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static修饰(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)。
除此之外,该类还必须缓存已经创建的对象,否则该类无法知道是否曾经创建对象,也就无法保证只创建一个对象。为此该类需要使用一个成员变量来保存曾经创建的对象,因为该成员变量需要被上面的静态方法访问,故该成员变量必须用static修饰。
class Singleton
{
// 使用一个类变量来缓存曾经创建的实例
private static Singleton instance;
// 将构造器使用private修饰,隐藏该构造器
private Singleton(){}
// 提供一个静态方法,用于返回Singleton实例
// 该方法可以加入自定义的控制,保证只产生一个Singleton对象
public static Singleton getInstance()
{
// 如果instance为null,表明还不曾创建Singleton对象
// 如果instance不为null,则表明已经创建了Singleton对象,
// 将不会重新创建新的实例
if (instance == null)
{
// 创建一个Singleton对象,并将其缓存起来
instance = new Singleton();
}
return instance;
}
}
public class SingletonTest
{
public static void main(String[] args)
{
// 创建Singleton对象不能通过构造器,
// 只能通过getInstance方法来得到实例
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // 将输出true
}
}