java语言学习之初始化块,final关键字,类成员

一.初始化块
一个类可以有多个初始化块,相同类型的初始化块之间有顺序:
前面定义的初始化块先执行,后面定义的初始化块后执行。
从某种程度上来看,初始化块是构造器的补充,初始化块总是在构造器执行之前执行,与构造器不同的是,初始化块是一段固定执行的代码,它不能接收任何参数。如果有一段初始化处理代码对所有对象完全相同,且无须接收任何参数,就可以把这段初始化处理代码提取到初始化块中。

二.静态初始化块
用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
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值