java lang包里面被问到最多的类了。
在java当中字符串属于对象。
-
String类常用创建的方法
(1) String s1 = “mpptest”
(2) String s2 = new String();
(3) String s3 = new String(“mpptest”)
-
String常用方法的实现
String的域变量:
/** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L;
-
length()
public int length() { return value.length; }
直接返回数组的长度
-
subString()
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); }
每一次subString就是利用对应范围内的数组值重新创建一个String
-
charAt()
public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; }
-
-
字符串对象的存储位置
对于String s1= “aa” 这个时候的字符串对象保存在常量池当中,s1是一个指向对象的引用。
对于String s1 = new String(“aa”) 这时,会在常量池检查是否有对应的字符串,如果有就复制一个到堆里,s1执行堆里的对象,福如果没有就现在常量池中创建然后再到堆上创建一个对象。对于这个语句实际上产生了两个String对象。
@Test public void contact () { //1连接方式 String s1 = "a"; String s2 = "a"; String s3 = "a" + s2; String s4 = "a" + "a"; String s5 = s1 + s2; //表达式只有常量时,编译期完成计算 //表达式有变量时,运行期才计算,所以地址不一样 System.out.println(s3 == s4); //f System.out.println(s3 == s5); //f System.out.println(s4 == "aa"); //t }
-
字符串的不可变性
String类中用来保存value的数组是一个final类型的,说明String是不可修改的。
String s1 = "kejia"; s1 = s1+"hello" System.out.println(s1)
这个时候s1指向的已经是一个新创建的字符串了。
-
String,StringBuffer和StringBuilder的区别
String类的不可变性导致了String类在拼接的时候会产生很多无用的中间对象。
StringBuffer就是为了解决大量拼接字符串时产生很多中间对象问题而提供的一个类,提供append和add方法,可以将字符串添加到已有序列的末尾或指定位置,它的本质是一个线程安全的可修改的字符序列,把所有修改数据的方法都加上了synchronized。但是保证了线程安全是需要性能的代价的。
在很多情况下我们的字符串拼接操作不需要线程安全,这时候StringBuilder登场了,StringBuilder是JDK1.5发布的,它和StringBuffer本质上没什么区别,就是去掉了保证线程安全的那部分,减少了开销。
使用拼接的话如果可以预先设定好数组大小就可以避免数组多次扩容带来的性能消耗。
-
String为什么不可变
首先在String类的一开始可看到一个被final和pravite修饰的char数组。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** String本质是个char数组. 而且用final关键字修饰.*/ private final char value[]; ... ... }
但是final仅仅是值value这个引用的不可变性并不是指数组内部数据的不可变性,所以String的不可变性并不是由这个final来实现的。
final int[] value={1,2,3} ; int[] another={4,5,6}; value=another; //编译器报错,final不可变 value用final修饰,编译器不允许我把value指向堆区另一个地址。 但如果我直接对数组元素动手,分分钟搞定。 final int[] value={1,2,3}; value[2]=100; //这时候数组里已经是{1,2,100} 所以String是不可变,关键是因为SUN公司的工程师。 在后面所有String的方法里很小心的没有去动Array里的元素,没有暴露内部成员字段。private final char value[]这一句里,private的私有访问权限的作用都比final大。而且设计师还很小心地把整个String设成final禁止继承,避免被其他人继承后破坏。所以String是不可变的关键都在底层的实现,而不是一个final。考验的是工程师构造数据类型,封装数据的功力。
总结一下String的不可变性:
1 首先final修饰的类只保证不能被继承,并且该类的对象在堆内存中的地址不会被改变。
2 但是持有String对象的引用本身是可以改变的,比如他可以指向其他的对象。
3 final修饰的char数组保证了char数组的引用不可变。但是可以通过char[0] = 'a’来修改值。不过String内部并不提供方法来完成这一操作,所以String的不可变也是基于代码封装和访问控制的。
-
不可变的好处
为了安全。对于String s = “a”的这种声明方式,java首先将字符串放到常量池当中。放入之后,其他创建的字符串都会先到缓存池当中看看有没有这个字符串,如果有就直接引用那个字符串。
不可变的好处就是为了多个引用同时引用到pool中的一个String时,防止一个引用者修改了String的值。
String不可变的其他好处:
1、以String作为HashMap的key,String的不可变保证了hash值的不可变。 2、String作为网络连接的参数,它的不可变性提供了安全性。 3、String不可变,所以线程安全。