事实上,在Java中,final修改了String类别,主要是为了确保字符串的不可变性,然后确保其安全性。那么,final如何确保字符串的安全呢?让我们一起看看吧。
一.final的作用1. final关键词修改的类别不能被其他类别继承,但它们本身可以继承其他类别。一般来说,这个类别可以有父亲,但不能有子女。
final class Mytestclass1 { // ...}
2. final关键词修改方法不能覆盖重写,但可以继承使用。
class Mytestclass2 { final void myMethod() { // ... }}
3. Final关键词修改的基本数据类型称为常量,只能赋值一次。
class Mytestclasss3 { final int number = 100;}
4. Final关键字修改引用的数据类型变量,其值为地址值,地址值不能改变,但地址对应的数据对象可以改变(事实上,这与我们今天要谈论的内容有关。稍后,我将结合案例向您解释。我们必须振作起来,仔细学习)。
5. Final关键词修改的成员变量需要在创建对象之前赋值,否则会出错(即定义时需要直接赋值)。
综上所述,我们可以知道final是Java中一个非常有用的关键字,主要可以提高我们代码的稳定性和可读性。当然,我们今天要解释的重点是由final修改的string,所以让我们把注意力转向string,看看string的特点!
2.String类别被final修改为了让您更好地理解String的不可变性,首先,我想简要介绍String的源代码设计。从下面的源代码中,我们可以找出许多底层的设计理念。接下来,请跟随我来看看String的核心源代码。
/** * ...其他略... * * Strings are constant; their values cannot be changed after they * are created. String buffers support mutable strings. * Because String objects are immutable they can be shared. For example: * * ...其他略... * */public final class String implements java.io.Serializable, Comparable, CharSequence { ...
先给大家简单解释一下上面的源码及其注释:
●final:请参考第一节对final特征的介绍;
●Serializable:序列化;
●Comparable:默认比较器;
●CharSequence:提供统一、只读字符序列的操作。
从这段源码及其注释中,我们可以得出以下结论:
●用final关键词修改String类,说明String不能继承;
●String字符串是常量,字符串的值一旦创建,就不能改变;
●String字符串缓冲区支持可变字符串;
●String对象是不可变的,可以共享。
三.String的不可变性在学习了上述核心源代码后,我们可以通过一个案例来验证String字符串的内容是否可以更改。这里有一个代码案例,如下图所示:
在这里,String对象的变化实际上是通过内存地址进行的“断开-连接”变化来完成。
在这个过程中,原字符串中的内容没有改变。Strings="yiyige" 和s="yyg“这两行代码本质上开辟了两个内存空间,s只是从原来的方向指向”yiyige“变为指向”yyg只是,它原来的字符串内容没有改变,如下图所示。
因此,在未来的开发中,如果我们想经常修改字符串的内容,请尽量少使用string!因为如果字符串经常指向“断开连接”,就它将大大降低性能,我建议您使用Stringbuilder或Stringbufer进行替换。
让我们继续深入分析上述代码。在Java中,因为数组也是对象,所以存储在Value中的只是一个参考,它指向一个真正的数组对象。Strings=执行yiyige”;在这个代码之后,真正的内存布局应该如下图所示:
由于value是String封装的字符数组,value中的所有字符都属于String。而因为value是private,没有提供setvalue等公共方法来修改value值,所以我们不能在string外部修改value值,也就是说,一旦字符串初始化,就不能再修改。
此外,value变量由final修改,即在string类别中,一旦该值初始化,value变量引用的地址将不会改变,即总是引用相同的对象。正是基于这一层,我们才说String对象是不可变的对象。
因此,String的不可变性实际上是指value在栈中的引用地址不可变,而不是常量池中value字符数组中的数据元素不可变。也就是说,value引用的数组对象中的内容实际上是可以改变的。
那我们又如何改变它呢?这需要通过反射来消除String对象的不可变性!
反射消除String对象的不可变特性
四.String真的不可变吗?在以上内容中,我们将重点向您解释String字符串的可变性。现在我们应该知道,String字符串的内容实际上是可变的。不可改变的是String字符串的对象地址。那么,我们应该如何改变String字符串的内容呢?我们在上面提到了反射。接下来,让我们来看看如何通过反射改变String字符串的内容。代码案例如下:
try { String str = "yyg"; System.out.println("str=" + str + ", 唯一性hash值=” + System.identityHashCode(str)); Class stringClass = str.getClass(); ///在String类中获取value属性 Field field = stringClass.getDeclaredField("value"); //设置私有成员的可访问性,暴力反射 field.setAccessible(true); ///在value数组中获取内容 char[] value = (char[]) field.get(str); System.out.println("value=" + Arrays.toString(value)); value[1] = 'z'; System.out.println("str=" + str + ", 唯一性hash值=” + System.identityHashCode(str));} catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace();}
执行结果如下图所示:
从以上结果可以看出,通过反射修改String字符串的字符数组后,字符串的“内容”真的发生了变化!
我们使用底层,我们使用底层java.lang.System#identityHashCode()方法(无论是否重写hashCode)获得字符串对象的唯一哈希值,该方法获得的hash值与hashCode()相同。
从结果中,我们可以看到两个字符串中唯一的hash值是相同的引用字符串的地址没有改变。
所以这说明我们并没有像以前那样创建一个新的String字符串,而是真正改变了原来的String内容。
这个代码案例进一步证明了我们的上述结论:String字符串的不可变性实际上是指Value对象在栈中的引用地址不可变,而不是常量池中Value中的数据元素不可变!简言之,String字符串的内容实际上是可以改变的。不能更改表格的是其对象地址。
所以这就是我们上面提到的final的作用之一:final关键词修改引用数据类型的变量,其值为地址值,地址值不能改变,但地址中的数据对象可以改变!
五.总结到目前为止,我们已经分析了今天的面试问题。你现在明白了吗?最后,让我总结一下今天的关键内容:
1. 为什么要用final来修饰java中的String类?
核心:因为它保证了字符串的安全性和可靠性。
2. java中的String真的不可变吗?
核心:String字符串的内容实际上是可变的,但String字符串对象的地址应通过特殊手段实现。
3. 如何消除String对象的不可变性?
核心:利用反射消除String对象的不可变性。
4. 如果要保证String的不可变性,应该注意什么?
●首先,将String类声明为final类型。这意味着String类别是不可继承的,以防止程序员通过继承和重写String类别的某些方法来“可变”String类别;
●然后,private和final修改重要字符数组value属性。它是用于存储字符串内容的String底层数组。由于数组是一种引用类型,因此只能限制引用不被改变,即数组元素的值可以改变,这在上述情况下得到了证明;
●然后,所有修改方法都返回到新的字符串对象,以确保原始对象的引用在修改过程中不会改变;
●最后,不同的字符串对象可以指向缓存池中同一字符串的字面量。
当然,“使用final修饰Java中的String类“这个概念非常重要,因为它确保了字符串的安全性和可靠性。但我们也应该清楚不能改变的只是它的地址,而不是它的内容,它的内容可以通过反射来改变!然而,在一般描述中,每个人都会说String的内容是不可改变的。毕竟,这种操作不允许使用反射的特殊功能。