Java学习笔记一面向对象final修饰符

final修饰符

final关键字可用于修饰类、变量和方法,用于表示它修饰的类、方法和变量不可改变。
final修饰变量时,表示该变量一旦获得初始值就不可改变。final既可以修饰成员变量(包括类变量和实例变量),也可以修饰局部变量、形参。

final修饰变量

final成员变量

Java语法规定:final修饰的成员变量必须由程序员显示指定初始值。
成员变量是随类初始化或对象初始化而初始化的。当类初始化时,系统会为该类的类变量分配内存,并分配默认值;当创建对象时,系统会为该对象的实例变量分配内存,并分配默认值。也就是说,当执行静态初始化块时可以对类变量赋初始值;当执行普通初始化块、构造器时可对实例变量赋初始值。因此,成员变量的初始值可以在定义该变量时指定默认值,也可以在初始化块、构造器中指定初始值。
对于final修饰的成员变量而言,一旦有了初始值,就不能被重新赋值,如果既没有在定义成员变量时指定初始值,也没有在初始化块、构造器中为成员变量指定初始值,那么这些成员变量的值将一直是系统默认分配的0、’\u0000’、false或null,这些成员变量也就失去存在的意义。
归纳:

  • 类变量:必须在静态初始化块中指定初始值或声明该类变量时指定初始值,而且只能在两个地方的其中之一指定。
  • 实例变量:必须在非静态初始化块、声明该实例变量或构造器中指定初始值,而且只能在三个地方的其中之一指定。

final局部变量

系统不会对局部变量进行初始化,局部变量必须由程序员显示初始化。因此使用final修饰局部变量时,既可以在定义时指定默认值,也可以不指定默认值。如果final修饰的局部变量在定义时没有指定默认值,则可以在后面代码中对该final变量赋初始值,只能一次。也可以在定义时直接指定默认值。
使用final修饰形参时,形参将由系统在调用该方法时根据传入的参数来完成初始化,因此使用final修饰的形参不能被赋值。

final修饰基本变量和引用类型变量的区别

当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象可以改变。

可执行宏替换的final变量

对于一个final变量来说,不管它是类变量、实例变量,还是局部变量,当定义final变量时就指定初始值,且该初始值在编译时就可确定下来,那么这个变量本质上就是一个宏变量,编译器会把程序中所有用到该变量的地方直接替换成该变量的值。除了赋直接量外,如果被赋表达式只是基本的算术表达式或字符串连接运算,没有访问普通变量,调用方法,Java编译器同样会将这种final变量当成宏变量处理。

// 4个final宏变量
final int a = 5 + 2;
final double b = 1.2/3;
final String str = "疯狂" + "Java";
final String book = "疯狂Java讲义:" + 99.0// book2变量的值因为调用了方法,所以无法在编译时确定
final String book2 = "疯狂Java讲义:" + String.valueOf(99.0);
System.out.println(book == "疯狂Java讲义:99.0");   // true
System.out.println(book2 == "疯狂Java讲义:99.0");  // false

final修饰方法

final方法不可被重写,比如Object类就提供的getClass方法(Java不希望任何类重写这个方法,使用final把这个方法密封)。final方法依旧可以重载。
对于一个private方法,子类无法访问该方法,所以子类无法重写该方法。如果子类定义一个与父类private方法相同方法名、相同形参列表、相同返回值类型的方法,也不是重写,只是在子类中定义了一个新方法而已。因此即使使用final修饰private访问权限的方法,子类依旧可以定义相同方法名、相同形参列表、相同返回值类型的方法。

final修饰类

final类不可有子类,可以保证某个类不可被继承。

不可变类

不可变类是指创建该类实体后,该实例的实例变量不可改变。比如包装类和java.lang.String类,当创建实例后,其实例变量不可改变。

Double d = new Double(6.5);
String str = new String("Hello");

创建不可变类可以遵循下面规则:

  • 使用private和final修饰符来修饰该类的成员
  • 提供带参数构造器,用于根据传入参数来初始化类里的成员变量
  • 仅为该类成员变量提供getter方法,不提供setter,因为普通方法无法修改final修饰的成员变量
  • 重写Object类的hashCode和equals方法。equals方法根据关键成员变量来作为两个对象是否相等的标准。除此之外,还需保证两个用equals方法判断为相等的对象的hashCode也相等。
    示例:
public class Address
{
	private final String detail;
	private final String postCode;
	public Address(){
		this.detail = "";
		this.postCode = "";
	}
	public Address(String detail, String postCode){
		this.detail = detail;
		this.postCode = postCode;
	}
	public String getDetail(){ return this.detail; }
	public String getPostCode(){ return this.postCode; }
	// 重写equals方法
	public boolean equals(Object obj)
	{
		if(this == obj) return true;
		if(obj != null && obj.getClass() == Address.class)
		{
			Address ad = (Address)obj;
			if(this.getDetail().equals(ad.getDetail()) && this.getPostCode().equals(ad.getPostCode()))
			return true;
		}
		return false;
	}
	public int hashCoe()
	{
		return detail.hashCode() + postCode.hashCode()*31;
	}
}

如果成员变量是引用类型,想让这个类变为不可变类,但是该成员变量引用的类型是可变的,如果尽量少的改动实现该需求。

class Name
{
	private String firstName;
	private String lastName;
	public Name() {}
	public Name(String firstName, String lastName){		
		this.firstName = firstName;
		this.lastName = lastName;
	}
	//这里有getter和setter方法
}
public class Person
{
	private final Name name;
	public Person(Name name){ this.name = name; }
	public Name getName(){ return name; }
	public static void main(String[] args)
	{
		Name n = new Name("悟空", "孙");
		Person p = new Person(n);
		n.setFirstName("八戒"); // 由于留有对成员变量的引用,就可以修改可变类 
	}
}
// 修改后
public class Person
{
	private final Name name;
	public Person(Name name){
		// 设置name实例变量为临时创建Name对象,不将Name变量的引用暴露在外
		this.name = new Name(name.getFirstName(), name.getLastName()); 
	}
	public Name getName(){ 
		// 返回一个匿名对象,不将Name变量的引用暴露在外
		return new Name(name.getFirstName(), name.getLastName()); 
	}
}

不可变类应用

不可变类的实例状态不可改变,可以用于共享。使用数组作为缓存池。当缓存池已满时,缓存采用先进先出的规则来决定哪个对象被移除缓存池。

class CacheImmutale{
	private static int MAX_SIZE = 10;
	// 对象共享池,由于使用动态初始化,且CacheImmutale是类,所以数组元素初始化为null不涉及到构造器
	private static CacheImmutale[] cache = new CacheImmutale[MAX_SIZE];
	private static int pos = 0;

	private final String name;
	// 隐藏构造器
	private CacheImmutable(String name) { this.name = name; }
	public String getName() ( return this.name; )

	// 类方法,操作对象池方法,没有就创建对象并共享,有就直接共享
	public static CacheImmutable valueOf(String name)
	{
		for(int i = 0; i < MAX_SIZE; i++){
			if(cache[i] != null && cache[i].getName().equals(name))
				return cache[i];
		}
		// 没有就创建
		if(pos == MAX_SZIE){  // 拐角处理
			cache[0] = new CacheImmutable(name);
			pos = 1;
		}else{
			cache[pos++] = new CacheImmutable(name);
		}
		return cache[pos-1];
	}

	public boolean equals(Object obj){
		if(this == obj) return true;
		if(obj != null && obj.getClass() == CacheImmutable.class)
		{
			CacheImmutable ci = (CacheImmutable)obj;
			return name.equals(ci.getName());
		}
		return false;
	}
	public boolean hashCode(){ return name.hashCode(); }
}
public class CacheImmutaleTest{
	public static void main(String[] args){
		CacheImmutable c1 = CacheImmutable.valueOf("hello");
		CacheImmutable c2 = CacheImmutable.valueOf("hello");
		System.out.println(c1 == c2);  // true
	}
}

例如Java所提供的java.lang.Integer类就采用了这样的策略。如果采用new构造器创建变类,则返回全新对象。如果采用valueOf方法会缓存该方法创建的对象以便于共享。由于通过new构造器创建Integer对象不会启用缓存,性能较差,Java 9已将该构造器标记为过时。

public class IntegerTest{
	Interger in1 = new Integer(6);
	Interger in2 = Integer.valueOf(6);
	Interger in3 = Integer.valueOf(6);
	System.out.println(in1 == in2); // false
	System.out.println(in2 == in3); // true
}
发布了134 篇原创文章 · 获赞 141 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/asmartkiller/article/details/104849685