我们经常会面对一个问题,String 是最基本的数据类型吗?
String 是值类型还是引用类型?
首先我们来回答第一个问题
String 是最基本的数据类型吗?
不是。Java中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本数据类型和枚举类型外都是引用类型。
String 是值类型还是引用类型?
除基本的数据类型外,都是引用类型,所以String是引用类型,但是在python中string是值类型。
但是这里还有一个概念是值传递和引用传递?
我们都知道python的所有操作是引用传递,而java的所有操作都是值传递。
引用传递指的是传的是地址,就是将实参的地址传递给形参,形参改变了,实参当然被改变了,因为他们指向相同的地址。
基本数据类型赋值都属于值传递,值传递传递的是实实在在的变量值,是传递原参数的拷贝,值传递后,实参传递给形参的值,形参发生改变而不影响实参。
java String 源码
public final class String
implements java.io.Serializable, Comparable, CharSequence
{
/** The value is used for character storage. */
private final char value[];
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
/** 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;
........
}
1)String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。
2)上面列举出了String类中所有的成员属性,从上面可以看出String类其实是通过char数组来保存字符串的。
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
char buf[] = new char[count + otherLen];
getChars(0, count, buf, 0);
str.getChars(0, otherLen, buf, count);
return new String(0, count + otherLen, buf);
}
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = count;
int i = -1;
char[] val = value; /* avoid getfield opcode */
int off = offset; /* avoid getfield opcode */
while (++i < len) {
if (val[off + i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0 ; j < i ; j++) {
buf[j] = val[off+j];
}
while (i < len) {
char c = val[off + i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(0, len, buf);
}
}
return this;
}
无论是sub操、concat还是replace操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。
在这里要永远记住一点:“String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。
字符串常量池
字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。
由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串。
运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
String a = "chenssy";
String b = "chenssy";
String c = new String("chenssy");
a、b和字面上的chenssy都是指向JVM字符串常量池中的"chenssy"对象,他们指向同一个对象。
new关键字一定会产生一个对象chenssy(注意这个chenssy和上面的chenssy不同),同时这个对象是存储在堆中。所以上面应该产生了两个对象:保存在栈中的c和保存堆中chenssy。但是在Java中根本就不存在两个完全一模一样的字符串对象。故堆中的chenssy应该是引用字符串常量池中chenssy。所以c、chenssy、池chenssy的关系应该是:c--->chenssy--->池chenssy。
public void test2(){
String str3=new String("aaa");
String str4=new String("aaa");
System.out.println("===========test2============");
System.out.println(str3==str4);//false 可以看出用new的方式是生成不同的对象
}
从这里可以看出new是先在堆中建立对象,再在jvm字符串常量池中查找是否存在,存在也是通过指针去指向它。
用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。
String使用private final char value[]来实现字符串的存储,也就是说String对象创建之后,就不能再修改此对象中存储的字符串内容,就是因为如此,才说String类型是不可变的(immutable)。程序员不能对已有的不可变对象进行修改。我们自己也可以创建不可变对象,只要在接口中不提供修改数据的方法就可以。
然而,String类对象确实有编辑字符串的功能,比如replace()。这些编辑功能是通过创建一个新的对象来实现的,而不是对原有对象进行修改。
String a = "aaa";
String b = a;
a = a.substring(0,2);
String c = "aaa";
System.out.println(b==c);
创建字符串
String类
- 使用“”或者直接赋值的办法来进行String类的创建,是直接再栈区域创建对象,去jvm字符串常量区寻找并指向。
- 构造方法
使用构造方法是在堆区域创建对象并指向jvm字符串常量区的。- String()
创建新的字符串对象 - String(char[] value)
把字符数组转化为字符串 - String(byte[] bytes, int offset, int length)
- String(StringBuffer buffer)
- String()
String的方法
String的方法都是对其拷贝后的修改
- 方法
- char charAt(int index)
返回指定位置字符 - int compareTo(String anotherString)
此 String 对象在参数字符串之前,返回一个负整数 - String concat(String str)
将指定字符串联到此字符串的结尾。 - boolean contains(CharSequence s)
判断字符串包含 char 值 - boolean endsWith(String suffix)
测试此字符串是否以指定的后缀结束 - boolean equals(Object anObject)
比较是否相等 - String format(String format, Object... args)
返回格式化字符串 - byte[] getBytes()
返回byte数组 - getChars(int srcBegin,int srcEnd,char[] dst, int dstBegin)
将指定字符串复制到字符数组 - int indexOf(int ch)
返回指定字符在此字符串中第一次出现处的索引 - int indexOf(String str)
- int lastIndexOf(int ch)
返回最后出现位置 - int length()
返回长度 - boolean matches(String regex)
通知此字符串是否匹配给定的正则表达式 - String[] split(String regex)
正则表达式的匹配来拆分此字符串 - boolean startsWith(String prefix)
判断字符前缀 - String substring(int beginIndex)
返回子字符串,到结尾 - String substring(int beginIndex, int endIndex)
返回子字符串 - char[] toCharArray()
返回字符数组 - String trim()
忽略前导空白和尾部空白 - String valueOf(char c)
把其他类型转化为字符串 - toLowerCase()
全部化为小写
- char charAt(int index)