• 定义

  概念上讲,java中的字符串就是一个Unicode字符序列,比如串“Java\u2122”就是一个由五个字符组成的字符串。java没有内置的字符串类型,而是在标准Java类库中提供了一个预定义的类String,每个用双引号括起来的字符串都是一个字符串String类的实例。

  (注:Java中内置数据类型,也就是基本数据类型,共有八种,byte,short,int,double,long,float,boolean,char.实际上,JAVA中还存在另外一种基本类型void,它也有对应的包装类 java.lang.Void,不过我们无法直接对它们进行操作。)

  其本质是个char数组. 而且用final关键字修饰。(private final char value[])

  • 特性

1.  不可被继承

  public final class String

String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。

  String类其实是通过char数组来保存字符串的。

2. 不可变性(can not be changed)

  在String类的源码中,我们可以看到这样的注释:

  

JAVA实现是否存在子串 java中有字符串类型吗_字符串

  这说明String是不可变的,一旦初始化,则不能被改变。

private final char value[];首先String类是用final关键字修饰,这说明String不可继承。再看下面,String类的主力成员字段value是个char[ ]数组,而且是用<b>final</b>修饰的。final修饰的字段创建以后就不可改变。有的人以为故事就这样完了,其实没有。因为虽然value是不可变,也只是value这个引用地址不可变。挡不住Array数组是可变的。也就是说Array变量只是stack上的一个引用,数组的本体结构在heap堆。String类里的value用final修饰,只是说stack里的这个叫value的引用地址不可变。没有说堆里array本身数据不可变。看下面这个例子,所以String是不可变,关键是因为SUN公司的工程师,在后面所有String的方法里很小心的没有去动Array里的元素,没有暴露内部成员字段。private final char value[]这一句里,private的私有访问权限的作用都比final大。而且设计师还很小心地把整个String设成final禁止继承,避免被其他人继承后破坏。所以tring是不可变的关键都在底层的实现,而不是一个final。考验的是工程师构造数据类型,封装数据的功力。

  对于Java而言,除了基本类型(即int, long, double等),其余的都是对象。对于何为不可变对象,《java concurrency in practice》一书给出了一个粗略的定义:对象一旦创建后,其状态不可修改,则该对象为不可变对象。一般一个对象满足以下三点,则可以称为是不可变对象:

  1. 其状态不能在创建后再修改;
  2. 所有域都是final类型;
  3. 其构造函数构造对象期间,this引用没有泄露。

  这里重点说明一下第2点,一个对象其所有域都是final类型,该对象也可能是可变对象。因为final关键字只是限制对象的域的引用不可变,但无法限制通过该引用去修改其对应域的内部状态。因此,严格意义上的不可变对象,其final关键字修饰的域应该也是不可变对象和primitive type值。
从技术上讲,不可变对象内部域并不一定全都声明为final类型,String类型即是如此。在String对象的内部我们可以看到有一个名为hash的域并不是final类型,这是因为String类型惰性计算hashcode并存储在hash域中(这是通过其他final类型域来保证每次的hashcode计算结果必定是相同的)。

  除此之外,String对象的不可变是由于对String类型的所有改变内部存储结构的操作都会new出一个新的String对象。

  java没有提供修改字符串的方法,因此是不可以修改的,Java文档将String类型对象称为不可变字符串,例如3永远是3一样,“hello”是个永远只包含5个字符的单元序列,而不能修改其中任意一个。只能修改字符串变量,让他引用另外一个字符串。 字符串连接操作可以使用StringBuffer的append方法。

在这里要永远记住一点:“String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”

为什么要这样做?这样做会不会降低效率?

  1.字符串常量池的需要

  只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现(String interning是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。

  2.缓存hash的需要

  因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

  HashMap在Java里太重要了,而它的key通常是String类型的。如果String是mutable,那么修改属性后,其hashcode也将改变。这样导致在HashMap中找不到原来的value。

  3.安全性考虑

  • 多线程安全

  如果String是可变的,即修改String的内容后,地址不变。那么当多个线程同时修改的时候,value的length是不确定的,造成不安全因素,无法得到正确的截取结果。而为了保证顺序正确,需要加synchronzied,但这会得到难以想象的性能问题。

  • 类加载中体现的安全

  The absolutely most important reason that String is immutable is that it is used by the class loading mechanism, and thus have profound and fundamental security aspects. Had String been mutable, a request to load "java.io.Writer" could have been changed to load "mil.vogoon.DiskErasingWriter"

  String会在加载class的时候需要,如果String可变,那么可能会修改加载中的类。

  • 字符串常量池

  字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间,但是因为字符串我们用的太多了,那如何去节省这些资源呢,java给出的方案,是创建字符串常量池,JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串(这点对理解上面至关重要)。

  Java中的常量池,实际上分为两种形态:静态常量池运行时常量池
  所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。