深入理解String类
String类的不变性
java.lang.String类使用了final修饰,不能被继承。Java程序中的所有字面值,即双引号括起的字符串,如"abc",都是作为String类的实例实现的。String是常量,其对象一旦构造就不能再被改变。换句话说,String对象是不可变的,每一个看起来会修改String值的方法,实际上都是创造了一个全新的String对象,以包含修改后的字符串内容。而最初的String对象则丝毫未动。String对象具有只读特性,指向它的任何引用都不可能改变它的值,因此,也不会对其他的引用有什么影响。但是字符串引用可以重新赋值。java字符串在内存中采用unicode编码方式,任何一个字符对应两个字节的定长编码,即任何一个字符(无论中文还是英文)都算一个字符长度,占用两个字节。
public class Immutable {
public static String upcase(String s) {
return s.toUpperCase();
}
public static void main(String[ ] args) {
String str1= "Hello World";
System.out.println(str1); //Hello World
String str2 = upcase(str1);
System.out.println(str2); //HELLO WORLD
System.out.println(str1); //Hello World
}
}
当把str1传递给upcase()方法时,实际传递的是引用的一个拷贝。其实,每当把String对象作为方法的参数时,都会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置上,从未动过。
String常量池(上一章仔细讲了)
常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。Java为了提高性能,静态字符串(字面量/常量/常量连接的结果)在常量池中创建,并尽量使用同一个对象,重用静态字符串。对于重复出现的字符串直接量,JVM会首先在常量池中查找,如果常量池中存在即返回该对象。
public class test1 {
public static void main(String[] args){
String str1 = "Hello";
//不会创建新的String对象,而是使用常量池中已有的"Hello",
String str2 = "Hello";
System.out.println(str1 == str2); //true
//使用new关键字会创建新的String对象
String str3 = new String("Hello");
System.out.println(str1 == str3); //false
}
}
String、StringBuffer和StringBuilder的区别
1.对象的可变与不可变(不可变的底层原因)
翻开JDK源码,java.lang.String类起手前三行,是这样写的:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** String本质是个char数组. 而且用final关键字修饰.*/
private final char value[]; ... ...
}
---------------------
作者:How 2 Play Life
来源:CSDN
原文:https://blog.csdn.net/a724888/article/details/80048782
版权声明:本文为博主原创文章,转载请附上博文链接!
首先String类是用final关键字修饰,这说明String不可继承。再看下面,String类的主力成员字段value是个char[]数组,而且是用final修饰的。final修饰的字段创建以后就不可改变。 有的人以为故事就这样完了,其实没有。因为虽然value是不可变,也只是value这个引用地址不可变。挡不住Array数组是可变的事实。
Array的数据结构看下图:
也就是说Array变量只是stack上的一个引用,数组的本体结构在heap堆。String类里的value用final修饰,只是说stack里的这个叫value的引用地址不可变。没有说堆里array本身数据不可变。
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。考验的是工程师构造数据类型,封装数据的功力。
---------------------
作者:How 2 Play Life
来源:CSDN
原文:https://blog.csdn.net/a724888/article/details/80048782
版权声明:本文为博主原创文章,转载请附上博文链接!
重点理解:
private的私有访问权限的作用都比final大。而且设计师还很小心地把整个String设成final禁止继承,避免被其他人继承后破坏。所以String是不可变的关键都在底层的实现,而不是一个final。(String的不可变也是基于代码封装和访问控制的)
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存数据,这两种对象都是可变的。如下:char[ ] value;
下面是jdk1.8中AbstractStringBuilder的部分源码
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
jdk1.8 StringBuilder部分源码
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
/** use serialVersionUID for interoperability */
static final long serialVersionUID = 4383685877147921099L;
/**
* Constructs a string builder with no characters in it and an
* initial capacity of 16 characters.
*/
public StringBuilder() {
super(16);
}
jdk1.8 StringBuffer部分源码
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
/** use serialVersionUID from JDK 1.0.2 for interoperability */
static final long serialVersionUID = 3388685877147921107L;
/**
* Constructs a string buffer with no characters in it and an
* initial capacity of 16 characters.
*/
public StringBuffer() {
super(16);
}
作者:lucus_guo
链接:https://www.jianshu.com/p/f87fd4dbdf23
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。在这里插入代码片
2.线程是否安全
String中的对象是不可变的,也就可以理解为常量,所以是线程安全。
AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。
StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
如果程序不是多线程的,那么使用StringBuilder效率高于StringBuffer。
创建了几个对象的问题
1.String str1 = "abc";
2.String str2 = new String("abc");
对于1中的 String str1 = “abc”,首先会检查字符串常量池中是否含有字符串abc,如果有则直接指向,如果没有则在字符串常量池中添加abc字符串并指向它.所以这种方法最多创建一个对象,有可能不创建对象。
对于2中的String str2 = new String(“abc”),首先会在堆内存中申请一块内存存储字符串abc,str2指向其内存块对象。同时还会检查字符串常量池中是否含有abc字符串,若没有则添加abc到字符串常量池中。所以 new String()可能会创建两个对象。
1 String temp="apple";
2 for(int i=0;i<1000;i++) {
3 temp=temp+i;
4 }
答案: 1001个对象
1 String temp=new String("apple")
2 for(int i=0;i<1000;i++) {
3 temp=temp+i;
4 }
答案:1002个对象