String使用 final 修饰的目的

一、描述

String 类被声明成final类型,不能被继承 。
通过String源码可以看到,String类型的底层是由final修饰的char数组存储,在value初始化完成后不能被其他数组引用,在String类方法中没有改变数组的方法,确保String不可变

Java语言的创建者James Gosling,曾经在一次采访中被人问到:什么时候应该使用不可变对象(immutable object),他回答:任何可以使用的时候都会使用。

在这之前,我们先来简单了解一下,什么是不可变对象?

不可变对象指的是在对象创建之后对象的内部状态以及对象的内存指针地址都不不能被改变。在Java里面final关键字就是用来辅助创建不可变对象的,但需要注意的是,对于基本类型被final修饰后,就彻底变成了不可变对象,而引用类型被final修饰后,仅仅是指针的内存地址不能改变,如果想要变成彻底的不可变类型,要把该对象里面所有的字段都得用final声明,包括嵌套的对象,否则对象的内部状态也是会变化的,这一点需要理解。

 

ok,下面我们来分析下为什么String是不可变的?为什么被final修饰?

  • 被final修饰的类,不可以被继承,所以不会别其它类改变,这样会更加的安全.
  • string是共享在常量池中的,String str="abc", char data[] = {'a', 'b', 'c'};是等价的,他们都放在了字符串常量池中.
     

 

二、原因

String 能被设计成不可变类型的一个重要前题是因为它是编程语言里面使用频率最高的一种类型。不可变类型带来的好处,体现在四个方面,分别是:缓存,安全,同步 和 性能。 

1、缓存(字符串常量池)

JVM常量池分为三种:

  1. 静态常量池 
  2. 运行时常量池 
  3. 字符串常量池

 常量池可以 阅读这篇文章:https://www.cnblogs.com/niew/p/9597379.html

 

在JVM的运行时数据区域里面,有一个专门的字符串常量池(String pool)用来存储字符串字面量,并且可以被共享使用,因此它提高了效率。由于String类是final的,它的值一经创建就不可改变,因此我们不用担心String对象共享而带来程序的混乱。字符串池由String类维护,我们可以调用intern()方法来访问字符串池。

假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象

 

如果String被创建了,从String pool中直接获取引用,只要String 不改变,才能从String pool获取 

如下面一段代码

String s1 = "Hello World";
String s2 = "Hello World";

assertThat(s1 == s2).isTrue();

s1和s2变量指针的内存地址其实是一样的,也就是说他们代表是同一个对象,这是jvm常量池做的优化,当第一个字面量声明的时候,它的值会被字符串常量池存储,当s2变量声明的时候,jvm发现常量池已经存在该对象,所以就不会再创建一次,而是直接将一样的内存指针赋值给s2变量,从避免了重复创建对象,节省了内存空间。

此外,由于字符串的不可变性,从而可以让其hashCode也被缓存,在Java里面哈希类数据结构如HashMap, HashTable, HashSet其key用的最多的基本都是String类型,如此一来key的hashCode的也可以在第一次调用之后被缓存,之后直接使用无须重新生成,从而间接的提升访问效率

 

字符串对象的创建流程

面试题:String str4 = new String(“abc”) 创建多少个对象?

  1. 在常量池中查找是否有“abc”对象

    • 有则返回对应的引用实例

    • 没有则创建对应的实例对象

  2. 在堆中 new 一个 String("abc") 对象

  3. 将对象地址赋值给str4,创建一个引用

所以,常量池中没有“abc”字面量则创建两个对象,否则创建一个对象,以及创建一个引用

根据字面量,往往会提出这样的面试题:

String str1 = new String("A"+"B") ; 会创建多少个对象? 
String str2 = new String("ABC") + "ABC" ; 会创建多少个对象?

str1:
字符串常量池:"A","B","AB" : 3个
堆:new String("AB") :1个
引用: str1 :1个
总共 : 5个

str2 :
字符串常量池:"ABC" : 1个
堆:new String("ABC") :1个
引用: str2 :1个
总共 : 3个

 

字符串常量池则存在于方法区

代码:堆栈方法区存储字符串

String str1 = “abc”;
String str2 = “abc”;
String str3 = “abc”;
String str4 = new String(“abc”);
String str5 = new String(“abc”);

 

代码:基础类型的变量和常量,变量和引用存储在栈中,常量存储在常量池中

int a1 = 1;
int a2 = 1;
int a3 = 1;

public static int INT1 =1 ;
public static int INT2 =1 ;
public static int INT3 =1 ; 

 

2、安全

不可变特性也能够减少了应用程序在运行时间的安全问题,如下面的一段代码:

void criticalMethod(String userName) {

  // check
  if (!check(userName)) {
    throw new SecurityException();
  }

  // query
  query(userName);

  }

在上面的一段代码,在调用这个方法之后,先检查用户名,如果合法才可以继续查询相关数据,如果String可变,那么攻击者就可以在通过check验证之后,再改变查询的用户名,那么就会存在安全风险,而不可变性能够避免和减少这一情况。另一方面,如果String是可变的,那么同时运行的其他线程如果修改这个值,就有可能导致混乱。
 

3、线程同步

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

由于String类型的不可变性,使得String对象可以安全的在多个线程之间传递和访问,也就是说你在多线程中是不能改变字符串本身的值,而是在堆里面新创建一个字符串然后操作。当然如果没有final修饰,你是可以改变这个变量的引用地址,也就是说你可以把新生成的内存引用覆盖原来的变量引用,但这里仅仅是引用,并不是变量的值。这一点要注意。

 

4、性能

性能方面,其实前面已经提到了,比如字符串的常量池节省内存,缓存Hash类以字符串做key数据结构的hashCode,从而提高访问性能等。由于字符串是编程语言里面最广泛使用的数据结构,所以针对字符串的不可变性带来的优势,可以放大到整个运行的应用程序,从而带来应用程序整体的性能提升。

 

总结

本文主要介绍了Java语言里面String类型为什么设计成不可变类型,以及分析了不可变类型的带来的主要优势,需要注意的是虽然不可变类型能够带来不少的好处,但并不是说其没有弊端,不可变类型的每一次修改都需要在内存中新生成一个对象,从另一个方面说针对经常变化的对象是不适合使用不可变类型的,这也是为什么Java里面还提供了可修改值的StringBuilder和StringBuffer类,这在实际开发中常常是需要根据具体情况权衡的。

 

 

String , StringBuffer ,StringBuiler比较

1、可变性

  • String是不可变的,
  • StringBuffer 和 StringBuilder是可变的

2、线程安全性

  • String是不可变的,保证了线程的安全性
  • StringBuilder是线程不安全的
  • StringBuffer由于有synchronized修饰,所以是线程安全的

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值