1 String是不可变类
这句话其实大家都很熟悉了,那么具体什么是不可变类呢?一般认为:当对象一旦创建完成后,在正常情况下,对象的状态不会因外界的改变而改变(对象的状态是指对象的属性,包括属性的类型及属性值)。
例如:
String s = "Google";
System.out.println("s = " + s);
s = "Runoob";
System.out.println("s = " + s);
输出结果为:
s = Google
s = Runoob
从结果上看是改变了,但为什么门说String对象是不可变的呢?
原因在于实例中的 s只是指向堆内存中的引用,存储的是对象在堆中的地址,而非对象本身,s本身存储在栈内存中
当执行 s = "Runoob"; 创建了一个新的对象 "Runoob",而原来的 "Google" 还存在于内存中。
那么为什么String类具有不可变性呢,显然,既然不可变说明String类中肯定没有提供对外可setters方法。接下来来具体看一下String类的定义。
下面是String类中主要属性的定义(Java 1.7源码):
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
}
与之前版本的Java String源码相比,String类减少了int offset 和 int count的定义。这样变化的结果主要体现在:
- 避免之前版本的String对象subString时可能引起的内存泄露问题;
- 新版本的subString时间复杂度将有O(1)变为O(n);
通过上面String类的定义,类名前面用了final class修饰,因此,String类不能被继承。对于其属性定义,可以看出,属性value[]和hash都是被定义成private类型,且由于没有提供对外的public setters方法,String类属性不可被改变。
其中,需要重点关注属性value[],其被final char修饰,因此字符型数组value只会被赋值一次就不可修改。其存储内容正好是String中的单个字符内容。
2 常量池
JVM为了提高性能和减少内存开销,内部维护了一个字符串常量池,每当创建字符串常量时,JVM首先检查字符串常量池,如果常量池中已经存在,则返回池中的字符串对象引用,否则创建该字符串对象并放入池中。
因此下述结果返回true。
String a = "abc";
String b = "abc";
System.out.print(a == b); //true
但与创建字符串常量方式不同的是,当使用new String(String str)方式等创建字符串对象时,不管字符串常量池中是否有与此相同内容的字符串,都会在堆内存中创建新的字符串对象。
因此,下面代码片段有如下结果。
String a = "Hello";
String b = new String("Hello");
System.out.println(a == b); //false
System.out.println(a.equals(b)); //true
即使字符串内容相同,字符串常量池中的字符串与通过new String(..)等方式创建的字符串对象之间没有直接的关系,但是,可以通过字符串的intern()方法找到此种关联。
intern()方法返回字符串对象在字符串常量池中的对象引用,若字符串常量池中尚未有此字符串,则创建一新的字符串常量放置于池中。
于是,很据如上理解,很自然的,可以得到如下结果。
String a = "Hello";
System.out.println(a == a.intern()); //true
String b = new String("corn");
String c = b.intern();
System.out.println(b == c); //false
String d = "corn";
System.out.println(c == d); //true
3 String相关 +
String中使用 + 字符串连接符进行字符串连接时,连接操作最开始时如果都是字符串常量,编译后将尽可能多的直接将字符串常量连接起来,形成新的字符串常量参与后续连接(通过反编译工具jd-gui也可以方便的直接看出);
接下来的字符串连接是从左向右依次进行,对于不同的字符串,首先以最左边的字符串为参数创建StringBuilder对象,然后依次对右边进行append操作,最后将StringBuilder对象通过toString()方法转换成String对象(注意:中间的多个字符串常量不会自动拼接)。
也就是说
String c = "xx" + "yy " + a + "zz" + "mm" + b;
实质上的实现过程是:
String c = new StringBuilder("xxyy").append(a).append("zz")
.append("mm").append(b).toString();
由于得出结论:当使用+进行多个字符串连接时,实际上是产生了一个StringBuilder对象和一个String对象。
4 String/StringBuilder/StringBuffer区别
String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变),StringBuffer线程安全,StringBuilder非线程安全。
5 既然String是不可变字符串对象,如何才能改变让其可变?
既然String对象中没有对外提供可用的public setters等方法,因此只能通过Java中的反射机制实现。因此,前文中说到的String是不可变字符串对象只是针对"正常情况下"。而非必然。
public static void stringReflection() throws Exception {
String s = "Hello World";
System.out.println("s = " + s); //Hello World
//获取String类中的value字段
Field valueField = String.class.getDeclaredField("value");
//改变value属性的访问权限
valueField.setAccessible(true);
char[] value = (char[]) valueField.get(s);
//改变value所引用的数组中的第5个字符
value[5] = '_';
System.out.println("s = " + s); //Hello_World
}
6 String类常用方法
- 求字符串长度
public int length()//返回该字符串的长度
String str = new String("asdfzxc");
int strlength = str.length();//strlength = 7
- 求字符串某一位置字符
public char charAt(int index)//返回字符串中指定位置的字符;注意字符串中第一个字符索引是0,最后一个是length()-1。
String str = new String("asdfzxc");
char ch = str.charAt(4);//ch = z
- 提取子串
用String类的substring方法可以提取字符串中的子串,该方法有两种常用参数:
1)public String substring(int beginIndex)//该方法从beginIndex位置起,从当前字符串中取出剩余的字符作为一个新的字符串返回。
2)public String substring(int beginIndex, int endIndex)//该方法从beginIndex位置起,从当前字符串中取出到endIndex-1位置的字符作为一个新的字符串返回。
String str1 = new String("asdfzxc");
String str2 = str1.substring(2);//str2 = "dfzxc"
String str3 = str1.substring(2,5);//str3 = "dfz"
- 字符串比较
1)public int compareTo(String anotherString)//该方法是对字符串内容按字典顺序进行大小比较,通过返回的整数值指明当前字符串与参数字符串的大小关系。若当前对象比参数大则返回正整数,反之返回负整数,相等返回0。
2)public int compareToIgnore(String anotherString)//与compareTo方法相似,但忽略大小写。
3)public boolean equals(Object anotherObject)//比较当前字符串和参数字符串,在两个字符串相等的时候返回true,否则返回false。
4)public boolean equalsIgnoreCase(String anotherString)//与equals方法相似,但忽略大小写。
String str1 = new String("abc");
String str2 = new String("ABC");
int a = str1.compareTo(str2);//a>0
int b = str1.compareTo(str2);//b=0
boolean c = str1.equals(str2);//c=false
boolean d = str1.equalsIgnoreCase(str2);//d=true
- 字符串连接
public String concat(String str)//将参数中的字符串str连接到当前字符串的后面,效果等价于"+"。
String str = "aa".concat("bb").concat("cc");
相当于String str = "aa"+"bb"+"cc";
- 字符串中单个字符查找
1)public int indexOf(int ch/String str)//用于查找当前字符串中字符或子串,返回字符或子串在当前字符串中从左边起首次出现的位置,若没有出现则返回-1。
2)public int indexOf(int ch/String str, int fromIndex)//改方法与第一种类似,区别在于该方法从fromIndex位置向后查找。
3)public int lastIndexOf(int ch/String str)//该方法与第一种类似,区别在于该方法从字符串的末尾位置向前查找。
4)public int lastIndexOf(int ch/String str, int fromIndex)//该方法与第二种方法类似,区别于该方法从fromIndex位置向前查找。
String str = "I am a good student";
int a = str.indexOf('a');//a = 2
int b = str.indexOf("good");//b = 7
int c = str.indexOf("w",2);//c = -1
int d = str.lastIndexOf("a");//d = 5
int e = str.lastIndexOf("a",3);//e = 2
- 字符串中字符的大小写转换
1)public String toLowerCase()//返回将当前字符串中所有字符转换成小写后的新串
2)public String toUpperCase()//返回将当前字符串中所有字符转换成大写后的新串
String str = new String("asDF");
String str1 = str.toLowerCase();//str1 = "asdf"
String str2 = str.toUpperCase();//str2 = "ASDF"
- 字符串中字符的替换
1)public String replace(char oldChar, char newChar)//用字符newChar替换当前字符串中所有的oldChar字符,并返回一个新的字符串。
2)public String replaceFirst(String regex, String replacement)//该方法用字符replacement的内容替换当前字符串中遇到的第一个和字符串regex相匹配的子串,应将新的字符串返回。
3)public String replaceAll(String regex, String replacement)//该方法用字符replacement的内容替换当前字符串中遇到的所有和字符串regex相匹配的子串,应将新的字符串返回。
String str = "asdzxcasd";
String str1 = str.replace('a','g');//str1 = "gsdzxcgsd"
String str2 = str.replace("asd","fgh");//str2 = "fghzxcfgh"
String str3 = str.replaceFirst("asd","fgh");//str3 = "fghzxcasd"
String str4 = str.replaceAll("asd","fgh");//str4 = "fghzxcfgh"
9、其他类方法
1)String trim()//截去字符串两端的空格,但对于中间的空格不处理。
String str = " a sd ";
String str1 = str.trim();
int a = str.length();//a = 6
int b = str1.length();//b = 4
2)boolean statWith(String prefix)或boolean endWith(String suffix)//用来比较当前字符串的起始字符或子字符串prefix和终止字符或子字符串suffix是否和当前字符串相同,重载方法中同时还可以指定比较的开始位置offset。
String str = "asdfgh";
boolean a = str.statWith("as");//a = true
boolean b = str.endWith("gh");//b = true
3)regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)
- ignoreCase -- 如果为 true,则比较字符时忽略大小写。
- toffset -- 此字符串中子区域的起始偏移量。
- other -- 字符串参数。
- ooffset -- 字符串参数中子区域的起始偏移量。
- len -- 要比较的字符数。
String Str1 = new String("www.runoob.com");
String Str2 = new String("runoob");
String Str3 = new String("RUNOOB");
String Str4 = new String("runaab");
String Str5 = new String("aaaoob");
System.out.println(Str1.regionMatches(4, Str2, 0, 5)); //true
System.out.println(Str1.regionMatches(4, Str3, 0, 5)); //false
System.out.println(Str1.regionMatches(true, 4, Str3, 0, 5)); //true
System.out.println(Str1.regionMatches(4, Str4, 0, 4)); //false
System.out.println(Str1.regionMatches(4, Str5, 2, 4)); //false
4)contains(String str)//判断参数s是否被包含在字符串中,并返回一个布尔类型的值。
String str = "student";
str.contains("stu");//true
str.contains("ok");//false
5)String[] split(String str)//将str作为分隔符进行字符串分解,分解后的字字符串在字符串数组中返回。
String str = "asd!qwe|zxc#";
String[] str1 = str.split("!|#");
//str1[0] = "asd";str1[1] = "qwe";str1[2] = "zxc";
7 字符串与基本类型的转换
- 字符串转换为基本类型
java.lang包中有Byte、Short、Integer、Float、Double类的调用方法:
1)public static byte parseByte(String s)
2)public static short parseShort(String s)
3)public static short parseInt(String s)
4)public static long parseLong(String s)
5)public static float parseFloat(String s)
6)public static double parseDouble(String s)
例如:
int n = Integer.parseInt("12");
float f = Float.parseFloat("12.34");
double d = Double.parseDouble("1.124");
- 基本类型转换为字符串类型
String类中提供了String valueOf()放法,用作基本类型转换为字符串类型。
1)static String valueOf(char data[])
2)static String valueOf(char data[], int offset, int count)
3)static String valueOf(boolean b)
4)static String valueOf(char c)
5)static String valueOf(int i)
6)static String valueOf(long l)
7)static String valueOf(float f)
8)static String valueOf(double d)
例如:
String s1 = String.valueOf(12);
String s1 = String.valueOf(12.34);
- 进制转换
使用Long类中的方法得到整数之间的各种进制转换的方法:
Long.toBinaryString(long l)
Long.toOctalString(long l)
Long.toHexString(long l)
Long.toString(long l, int p)//p作为任意进制