gpt4 book ai didi

Java中的String类真的不可变吗?java面试常见问题

转载 作者:我是一只小鸟 更新时间:2023-03-21 14:31:09 25 4
gpt4 key购买 nike

 

其实在Java中,String类被final修饰,主要是为了保证字符串的不可变性,进而保证了它的安全性。那么final 到底是怎么 保证 字符串 安全性 的 呢?接下来就让我们一起来看看吧.

一. final的作用

1.    final关键词修饰的类不可以被其他类继承,但是该类本身可以继承其他类,通俗 地 说就是这个类可以有父类,但不能有子类 .

                                  final class MyTestClass1 {
    // ...
}
                                

2.    final关键词修饰的方法不可以被覆盖重写,但可以被继承使用 .

                                  class MyTestClass2 {
    final void myMethod() {
        // ...
    }
}

                                
 

3.    final关键词修饰的基本数据类型 被 称为常量,只能被赋值一次.

                                  class MyTestClass3 {
    final int number = 100;
}

                                
 

4.    final关键词修饰的引用数据类型变量 ,其 值为地址值, 该 地址值不能改变,但 该 地址 对应 的数据对象可以被改变 ( 其实这一点就 和 我们今天要说的 内容有关 了,在后面 我 会结合案例跟大家 重点 解释,大家一定要打起精神仔细学习 哦 ).

  。

5.    final关键词修饰的成员变量,需要在创建对象前就赋值,否则会报错( 即需要在定义时直接赋值 ).

  。

综上所述, 我们可以知道, final在Java中是一个非常有用的关键字,主要 可以提 高我们代码的稳定性和可读性。 当然 , 我们今天 要讲解的 重点是被final修饰的String类,所以接下来我们 还是把目光转回到String身上来, 看看 String 都有哪些 特性 吧! 。

 

二. 被final修饰的String类

为了 让大家 更好 地 理解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<String>, CharSequence {
    
    ......

                                            
 

我 先 把 上面的源码及其注释 , 给大家 作 一个简单的解释:

●  final:请参考第1小节对final特点的介绍; 。

●  Serializable:用于序列化; 。

●  Comparable<String>:默认的比较器; 。

●  CharSequence: 提供对字符序列进行统1、只读的操作.

从这段源码及其注释中,我们可以得到 下面 这 些 结论:

●  String类用final关键字修饰,说明String不可被继承; 。

●  String字符串是常量,字符串的值一旦被创建,就不能被改变; 。

●  String字符串缓冲区支持可变字符串; 。

●  String对象是不可变的,它们是可以被共享的.

 

三. String的不可变性

在学习了上面的这些核心源码之后,接下来,我们 可以通过一个案例 来实践验证一番,看看String 字符串的内容 到底能不能 改变 。 这里有个 代码案例 , 如 下 图所示:

  。

在上述的案例结果中,大家可以看出, s的内容竟然发生了改变 ?!但我们不是一直说String是不可变的吗?这是咋回事? 大家先别急,我们继续往下看.

要想 弄 明白这个问题,我们首先得 知道 一个 知识 点: 引用和值的区别 ! 。

在上面的代码中 , 我们先是创建了一个 "yiyige" 为内容的字符串引用s ,如下图:

  。

  s其实先是指向了value对象,而value对象 又 指向 了 存储 "y,i,y,i,g,e" 字符的字符数组 。但 因为value被final修饰,所以value的值不可被更改 。 因此,上面代码中改变的其实是 s的 引用指向 , 而不是改变了String对象的值 ! 。

换句话说,上面实例中s的值, 其实只是value的引用地址,并不是String 的 内容本身 。 当我们执行 s = "yyg" 语句时 , Java会创建一个新的字面量对象 "yyg" , 而原来的 "yiyige" 字面量对象 其实 依然存在于内存的intern缓存池中.

在这里, String对象的改变 , 实际上是通过内存地址的 “断开-连接” 变化来完成的 。在 这个过程中 , 原字符串中的内容并没有 发生 任何的改变。String s = "yiyige" 和 s = "yyg" 这两行代码, 实质上是开辟了2个内存空间,s只是由原来指向 "yiyige" 变为指向 "yyg" 而已,而其原来的字符串内容,是没有 发生 改变的,如下图所示.

因此,我们在以后的开发中,如果要经常修改字符串的内容,请尽量少用String ! 因为 如果 字符串的指向 经常的 “断开-连接”, 就 会大大降低性能, 我 建议 大家 使用StringBuilder  或   StringBuffer  进行替换 .

我们继续把上面的代码深入地分析一下。 在Java中,因为数组也是对象, 所以value中存储的也只是一个引用,它指向一个真正的数组对象。在执行了String s = “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字符串的内容吧。代码案例如下所示:

                                              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类出现“可变的”的情况 ; 。

●  然后,重要的字符数组value属性 ,要 被private 和 final修饰 。 它是String的底层数组,用于存贮字符串内容。又因为数组是引用类型,所以只能限制引用不被改变,也就是说数组元素的值是可以改变的, 这 在上面的案例中已经证明过了 ; 。

●  接着,所有修改的方法都返回新的字符串对象,保证修改时不会改变原始对象的引用; 。

●  最后,不同的字符串对象都可以指向缓存池中的同一个字符串字面量.

当然 , “ Java中的String类使用final修饰 ”这个概念非常重要,因为它确保了字符串的安全性和可靠性。 但是我们也要清楚 不可改变的 只 是它的地址,而不是它的内容, 它的内容是可以利用反射来改变的! 只不过在一般的描述中,大家都会说String内容不可改变,毕竟很多时候是不允许利用反射这种特殊的功能去进行这样的操作的.

  。

最后此篇关于Java中的String类真的不可变吗?java面试常见问题的文章就讲到这里了,如果你想了解更多关于Java中的String类真的不可变吗?java面试常见问题的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com