当前位置: 首页 > 图灵资讯 > 技术篇> 在java中String类为什么要设计成final?Java面试常见问题

在java中String类为什么要设计成final?Java面试常见问题

来源:图灵教育
时间:2023-05-25 09:13:54

事实上,在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字符串的内容是否可以更改。这里有一个代码案例,如下图所示:

在java中String类为什么要设计成final?Java面试常见问题_System

在这里,String对象的变化实际上是通过内存地址进行的“断开-连接”变化来完成。

在这个过程中,原字符串中的内容没有改变。Strings="yiyige" 和s="yyg“这两行代码本质上开辟了两个内存空间,s只是从原来的方向指向”yiyige“变为指向”yyg只是,它原来的字符串内容没有改变,如下图所示。

在java中String类为什么要设计成final?Java面试常见问题_数组_03

因此,在未来的开发中,如果我们想经常修改字符串的内容,请尽量少使用string!因为如果字符串经常指向“断开连接”,就它将大大降低性能,我建议您使用Stringbuilder或Stringbufer进行替换。

让我们继续深入分析上述代码。在Java中,因为数组也是对象,所以存储在Value中的只是一个参考,它指向一个真正的数组对象。Strings=执行yiyige”;在这个代码之后,真正的内存布局应该如下图所示:

在java中String类为什么要设计成final?Java面试常见问题_System_04

由于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();}

执行结果如下图所示:

在java中String类为什么要设计成final?Java面试常见问题_字符串_05

从以上结果可以看出,通过反射修改String字符串的字符数组后,字符串的“内容”真的发生了变化!

我们使用底层,我们使用底层java.lang.System#identityHashCode()方法(无论是否重写hashCode)获得字符串对象的唯一哈希值,该方法获得的hash值与hashCode()相同。

从结果中,我们可以看到两个字符串中唯一的hash值是相同的引用字符串的地址没有改变。

所以这说明我们并没有像以前那样创建一个新的String字符串,而是真正改变了原来的String内容。

这个代码案例进一步证明了我们的上述结论:String字符串的不可变性实际上是指Value对象在栈中的引用地址不可变,而不是常量池中Value中的数据元素不可变!简言之,String字符串的内容实际上是可以改变的。不能更改表格的是其对象地址。

在java中String类为什么要设计成final?Java面试常见问题_System_06

所以这就是我们上面提到的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的内容是不可改变的。毕竟,这种操作不允许使用反射的特殊功能。