倚楼听风雨
淡看江湖路

Java面向对象之final修饰符

书接上回《Java面向对象之类成员浅析》,老四引出了同学提出的一个探讨问题。关于final修饰符其实涉及到的知识点还是比较多比较杂的,所以老四也是想了一些时日,目的是希望能写的更清晰一些,方便理解和复查知识点。希望能对你有一点点帮助吧,写完final的知识点之后会顺便抛出几个面试中经常被提及到的面试/笔试题供你参考,最后会详细给出关于"为什么final static"经常连用的一些理由供你参考。

首先,你经常看到的或者听到的final修饰的变量不能被赋值这种说法是错误的!严格的说法是: final修饰的变量不可被改变,一旦获得了初始值,该final变量的值就不能被重新赋值了。

final可以可用于修饰类、变量(类变量、实例变量、局部变量以及形参)和方法,表示被final修饰的类、变量和方法不可改变。

1.final修饰变量

--final修饰成员变量(类变量和实例变量)

成员变量是随类初始化或对象初始化而初始化的。如果final修饰的成员变量没有指定初始值或者没有通过静态块、构造方法指定初始值,那么final的修饰就失去了意义,所以Java的语法规定我们码农使用final修饰的成员变量必须显式的指定初始值。final修饰类变量、实例变量能指定初始值的方式如下:

  • 类变量: 必须在静态初始块中指定初始值或声明该类变量时指定初始值,而且只能二选一。
  • 实例变量: 必须在非静态初始化块、声明该实例或者构造器中指定初始值,三选一。

--final修饰局部变量

众所周知,局部变量本身就要求我们高科技代码编写人员显式初始化,所以对于final修饰局部变量,既可以在定义的时候指定默认值,也可以不指定,不指定的时候可以在后面指定且只能指定一次,不允许重复指定。

注意: final修饰的形参不能被赋值,形参的值由根据传入的参数进行初始化。

--final修饰基本类型和引用类型

  • final修饰基本类型变量,不能对及基本类型变量重新赋值,基本类型变量的值不能被改变。
  • final修饰引用类型变量,保存的仅仅是一个引用,保证这个引用类型变量所引用的地址不变,但引用的对象或者引用的值是完全可以变的。

上面也解释了文章开头所说的"final修饰的变量不能被赋值"是错误的原因。

package comglorze.finaltest;

import java.util.Arrays;

/**
 * final修饰符修饰成员变量
 * @ClassName FinalVarTest 
 * @author: glorze.com  
 * @since: 2018年4月18日 下午11:22:31
 */
public class FinalVarTest {
	
	/**
	 * 定义成员变量的时候就指定初始值
	 * 	注意,glorze的值不能够在改变了
	 * 	尽管你后面再写"glorze=18"编译器都饶不了你的
	 */
	final int glorze = 8;
	
	/**
	 * 下面四个变量将会在构造器或者初始块中指定初始值
	 */
	final String str;
	final int a;
	final static double B;
	final char ch;
	
	/**
	 * 初始化块
	 * 	注意:如果final修饰的变量还没有被赋初始值,成员变量也不允许被访问。比如,还没赋值你就"System.out.println(str);"
	 * 		这样的会报The blank final field str may not have been initialized,告诉你不初始化不能碰人家。
	 */
	{
		// System.out.println(str);
		str="高老四Java博客";
	}
	
	/**
	 * 静态初始化块
	 * 	注意,由于静态成员不能访问实例变量,所以这里面只能赋值static修饰的变量。
	 */
	static {
		B = 8.8;
	}
	
	/**
	 * 构造器赋值
	 * 	注意:普通方法中不能对final修饰的变量进行赋值。
	 * FinalVarTest.  
	 *
	 */
	public FinalVarTest() {
		a = 88;
		ch = 'g';
	}
	
	/**
	 * 不允许对形参赋值
	 * 	如下所示,如果编写gao=888;
	 * 	会报The final local variable gao cannot be assigned. It must be blank and not using a compound assignment错误
	 * @Title: testFinalLocalVar
	 * @param gao 
	 * @return void
	 * @author: 高老四博客 
	 * @since: 2018年4月18日 下午11:46:51
	 */
	public void testFinalLocalVar(final int gao) {
		// gao = 888;
	}
	
	public static void main(String[] args) {
		// final修饰局部变量
		final String glorze = "高老四博客";
		final double gaolaosi;
		gaolaosi = 888.888;
		// final修饰对象的引用,引用不变,值可以变化
		final int gls[] = {8888,8,88,888};
		Arrays.sort(gls);
		System.out.println(Arrays.toString(gls));
		gls[2] = 666;
		System.out.println(Arrays.toString(gls));
	}
}

这里在补一些关于final变量"宏替换"的相关知识点,其实"final int glorze = 8;"这句话对于程序来说变量glorze是根本不存在的,这里面final相当于定义了一个宏变量"a",用这个"a"来代替5,这种宏变量还包含基本的算术表达式以及基本的字符串连接。我们都知道字符串: "高老四博客" = "高老四" + "博客"其实是一回事,他们都直接引用String常量池中的"高老四博客",然而

String s1 = "高老四博客";
String s2 = "高老四";
String s3 = "博客";
String s4 = s2 + s3;
System.out.println(s1 == s4);

我们知道,因为无法在编译的时候确定s4的值,所以s4无法指向常量池中的"高老四博客",所以结果为false。当给s2和s3加上final修饰符来达到宏替换的目的,这样的话你就会看到输出的结果是true了。如果不太懂的话,可以好好研究一下。

2.final修饰方法

final修饰的方法不可以被重写,但是可以重载。最典型的例子就是Object中的getClass方法就是final修饰的。所以如果父类有final的方法,而子类也存在,那并不是重写,而是属于子类自己定义的,跟爷爷长得像而已。

3.final修饰类

final修饰的类不能有子类,Java.lang.Math就是活生生的例子。  我们知道Java的8个包装类是不可变类(创建该类的实例后,该实例的实例变量是不可改变的),他们底层都使用了final,同样我们自定义不可变的类需要哪些条件呢?

  • 使用private和final修饰符来修饰该类的成员变量。
  • 提供带参数的构造器,根据入参初始化成员变量。
  • 只提供getter方法
  • [可选]重写hashCode()和equals()方法。

然而,我们所说的不可变的类,根据前面所讲的,只是引用地址不变,值或者类型都是可变的,那么如何保证真正意义上的不可变呢?这里不做细讲,给一些提示: 构造方法中直接使用目标对象作为入参,使用目标对象的get方法实例化目标对象。。。(你就当没看见这句话。。。)

关于final修饰类,这里在引申一些东西,那就是缓存实例的不可变类。相信你们搞Java的都见过这段代码:

package comglorze.finaltest;

/**
 * Integer包装类缓存对象
 * @ClassName IntegerCacheTest 
 * @author: glorze.com  
 * @since: 2018年4月19日 上午12:36:40
 */
public class IntegerCacheTest {
	public static void main(String[] args) {
		
		// 新对象
		Integer glorze1 = new Integer(88);
		
		// 新对象并且缓存了起来
		Integer glorze2 = Integer.valueOf(88);
		
		// 直接读取缓存数据
		Integer glorze3 = Integer.valueOf(88);
		
		// false
		System.out.println(glorze1 == glorze2);
		
		// true
		System.out.println(glorze2 == glorze3);
		
		// Integer只缓存-128~127之间的值
		Integer glorze4 = Integer.valueOf(188);
		Integer glorze5 = Integer.valueOf(188);
		
		// false
		System.out.println(glorze4 == glorze5);
	}
}

其实这里面就涉及到了不可变类的缓存技巧,底层也是使用final定义的不可变类实现缓存池利用先进先出的规则对数据进行处理,有兴趣也可以了解一下实现原理。

好了,基本的只是就算是说完了,告一段了,接下来在说为什么"static final"经常连用之前写几个关于final修饰经常被提及到的面试/笔试题,先当做基础知识的复习和标记。

1.final, finally, finalize的区别?

就特么因为长得像就放在一起问,也不知道哪个创始人出这种问题,不过咱们得受着,毕竟可以复习到知识,也能获取到相关修饰符的知识点。回答一下这三个词基本干啥的就行了,反正他们彼此互斥,没什么Jimmy Butler关系。。。

final就不说了,简单说一下finally和finalize:

  • finally: 异常块的一部分,不管什么情况里面的代码块都会执行,包括try catch 里面用了return ,可以理解为只要执行了try或者catch,就一定会执行 finally
  • finalize: Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。JVM不保证此方法总被调用。

2.final关键字有哪些用法?

仔细看文章。

3.final与static 关键字可以用于哪里?它们的作用是什么?

仔细看这篇文章以及文章开篇的链接提到的关于static的文章。

4.能否在运行时向 static final 类型的赋值?

有的能,有的不能。至于为啥?仔细看文章!

5.使用final关键字修饰一个变量时,是引用不能变,还是引用的对象不能变?

根据传统的中国封建制度,对象不能变啊。白头到老。

6.一个类被声明为final类型,表示了什么意思?

表明了这个类是final的人了,要动她,先问问铜锣湾final答不答应!!!

所以,static final为什么经常连用呢?

首先说连用的目的:static代表静态,final不允许重新赋值,所以就是说我们需要的是静态常量。那么我们使用静态常量的意义是什么呢?显而易见了,静态常量因为一直存放在静态空间中,因为final的原因不会被释放,一直存在内存当中,当一个常数、字符串或者对象是我们需要一直在程序中为我们所用的时候,就可以考虑使用静态常量,避免内存重复的申请和释放空间。说白了,这么问没有多么高深,只不过是一种情景之下的一种应对策略而已,为优化而考虑。

更博不易,喜欢的老铁有能力烦请赞助盒烟钱,点我去赞助。抱拳。

赞(84) 给你买杜蕾斯
本站原创文章受自媒体平台原创保护,未经允许不得转载高老四博客 » Java面向对象之final修饰符
分享到: 更多 (0)

开始你的表演 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下老四,鼓励我更好的创作

支付宝扫一扫打赏

微信扫一扫打赏