目录
起因
2021年03月25日看到一篇公众号文章,感觉其文章说的有点含糊不清,或者我个人能力不够没有理解,所以,想写一个文章来记录,算是每天一个小知识。
final修饰符
那先来看看final修饰符的用法:
final修饰符在《JAVA编程思想》的第7.8章节,按照本章节的知识点,final应用场景分为下面几个场景:
场景1:final修饰基本数据类型
基本特性
①对常量进行定义时,必须对其进行赋值;
②一个static final修饰的内存域只占据一段不能改变的存储空间;
③当final修饰对象时,例如final A a = new A();时与final修饰基本类型数据时有细微差别:final修饰基本类型数据使得数值恒定不变,而final修饰对象引用时,final只是确保引用恒定不变,但对象其自身可以被修改,即a存储的堆地址不变,但堆上的new A()则可以修改;
空白final
所谓的空白final,指的是被声明为final但又未给定初值的域(举个例子:final int j;),无论什么情况,编译器都会确保空白final在使用前必须被初始化,因此这就为final使用提供了更大的灵活性,因为是在使用前必须被初始化,因此一个类中的final域就可以做到根据对象而有所不同,却又保持其恒定不变的特性,因此为了确保使用前必须被初始化,无非是下面2种途径:
①在class类中定义final变量时就立即初始化;
②在class类的每个构造方法中对final变量进行初始化;
final参数
JAVA允许在方法的参数列表中将参数声明为final,这就意味着你在方法体中无法更改参数所指向的对象;
场景2:final修饰方法
基本特性
①final方法无非想要2种效果:
效果1:方法锁定,防止任何继承类修改方法的含义,确保在继承中方法行为保持不变,并且不会被覆盖;
效果2:提高效率,只不过是以前的想法,现在已经不算是优点了;
②private修饰符都隐式包含了final功能,因为子类无法使用private方法,也就达到了final的效果,因此,对private再去添加final修饰符,意义不大,没什么额外的效果;
private与子类覆盖
假如父类中存在一个private final void f()方法,则子类中也可以出现private final void f()、final void f()、protected final void f()、public final void f()方法,这是为什么呢?
子类覆盖只有在某方法是基类的接口的一部分时才会出现,即必须能将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法为private,那该方法就不是基类的接口的一部分,该方法只是隐藏于基类中的程序代码而已,只不过是具有相同的名称而已。
如果子类中以相同名称出现了一个public、protected、包访问权限的方法时,这些方法是子类自己的方法,而不是覆盖掉父类的继承方法。
(通俗来讲就是,子类向上转型之后,还是无法调用父类的private方法,鉴于此情况,父类的private只是父类隐藏的一段代码而已,不参与整个继承树,因此,子类的重名方法只是子类新编写的方法,与父类无关,与继承无关)
场景3:final修饰类
基本特性
用final修饰class时,说明该类不允许被继承,该类永远没有子类;
由于final类禁止继承,因此final类中所有的方法都隐式指定为final,因此,虽然JAVA允许你在final类的方法体中来显示确定方法/全局变量是否为final,但这种行为没有任何意义,因为即便你全局变量/方法不是final,也没有子类去继承;
String类
final修饰符已经讲完,再来看看String类:
String类有2个需要注意的地方:
①String类是final修饰:public final class String,这表明String的设计者,并不希望有人来继承String;
②String存储内容使用的是final修饰的char数组:private final char value[];
③无论是concat、replace、substring还是trim方法的操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变;
String堆内存与String常量池
String str1 = "droid";
JVM检测这个字面量,如果JVM通过字符串常量池查找不到内容为droid的字符串对象存在,那么会在堆上创建这个字符串对象,然后JVM将刚创建的对象的引用放入到字符串常量池中,并且将引用返回给变量str1;
String str2 = "droid";
同样JVM还是要检测这个字面量,JVM通过查找字符串常量池,发现内容为”droid”字符串对象存在,于是将已经存在的字符串对象的引用返回给变量str2,这里不会重新创建新的字符串对象;
String str3 = new String("droid");
当使用new来构造字符串对象的时候,不管字符串常量池中有没有相同内容的对象的引用,新的字符串对象都会在堆上被创建,但其对象引用并不会放到字符串常量池中;
String.intern()方法
字符串常量池存放的是对象引用,而不是对象,在Java中,对象都创建在堆内存中;
String str1 = "droid";
String str3 = new String("droid");
System.out.println(str3.intern() == str1);//true
首先在执行String str1 = "droid";时,JVM发现字符串缓冲池中没有该字符串,于是JVM在堆上创建"droid"对象,并把引用放到字符串缓冲池中,然后把引用返回给str1;
然后在执行String str3 = new String("droid");时,按照new命令强制在堆上创建"droid"对象,并把引用返回给str3;
最后执行str3.intern() == str1时,JVM执行str3..intern()时想把str3的字面量放到字符串缓冲池中时,结果发现字符串缓冲池中已经有一个"droid"字面量的引用了,因此str3.intern()的引用不再需要,转而把str1所引用的引用返回给客户端,自然str1的内存地址肯定与str1的相同;(如果字符串缓冲池中没有"droid"字面量时,则返回的是str3的内存引用地址)
总结:对于任意两个字符串s和t,当且仅当s.equals(t)为true 时,s.intern()==t.intern()才为true,即s与t的字面量相同,s.intern()与t.intern(),无论是哪一个先放入字符串缓冲池,或者,无论字符串缓冲池中是否已经有了这个字面量的内存引用,intern()方法返回值肯定是一样的,因为字符串缓冲池必须确保字面量的唯一性;
本文详细探讨了Java中的final关键字在基本数据类型、方法、类中的应用,以及final修饰的String类特性,包括String常量池、intern()方法的工作原理。同时,分析了final如何影响方法的覆盖和类的继承。通过对final的深入理解,有助于提升Java编程的技能。

被折叠的 条评论
为什么被折叠?



