拾陆——深层次的解读 String 类
一、String 类
程序需要存储的大量文字、字符等都使用字符串进行表示。Java 通过建立并使用 String 类对字符串进行处理。Java 中定义了 String 来封装对字符串的各种操作。使用户可以轻松地管理文本字符串。
什么是字符串呢?字符串就是一个或多个字符组成的连续序列,程序需要存储的大量文字、字符都使用字符串进行表示、处理。
字符串:“你好,Java!”
“Hello World”
Java 中定义了 String 和 StringBuffer 两个类来封装对字符串的各种操作,它们都被放到了 java.lang 包中,不需要用 import java.lang 这个语句导入该包就可以直接使用它们。
String 类用于比较两个字符串,查找和抽取串中的字符或子串,进行字符串与其他类型之间的相互转换等。String 类对象的内容一旦被初始化就不能再改变,对于 String 类的每次改变( 例如字符串连接等 )都会生成一个新的字符串,比较浪费内存。
StringBuffer 类用于内容可以改变的字符串,可以将其他各种类型的数据增加,插入到字符串中,也可以转置字符串中原来的内容。一旦通过 StringBuffer 生成了最终想要的字符串,就应该使用 StringBuffer.toString() 方法将其转换成 String 类,随后,就可以使用 String 类的各种方法操纵这个字符串了。StringBuffer 每次都改变自身,不生成新的对象,比较节约内存。
1.字符串的声明
字符串声明常见方式如下:
String 变量名
String s1;
声明一个字符串对象 s1,分配了一个内存空间,因为没有进行初始化,所以没有存入任何对象。
s1 作为局部变量是不会自动初始化的,必须显式地赋初始值。如果没有赋初始值,在用 System.out.println(s1) 时会报错。
2.再看 String 类
在 Java 中,用户可以通过创建 String 类来创建字符串,String 对象既可以隐式地创建,也可以显式地创建,具体创建形式取决于字符串在程序中的用法,为了隐式地创建一个字符串,用户只要将字符串字符放在程序中,Java 则会自动地创建 String 对象。
3.String 类的两种实例化方法
(1)使用字符串常量直接初始化,String 对象名称 = “ 字符串 ”;
Stirng s = "你好,Java!";
(2)使用构造方法创建并初始化( public String(String str) ),String 对象名称 = new String("字符串");
Stirng s = new Stirng("你好,Java!");
举例:
//String类实例化的三种方式
//创建String对象
public class NewString
{
public static void main(String[] args)
{
String str1 = "Hello"; //直接赋值建立对象str1
System.out.println("str1:" + str1); //输出
String str2 = new String("Hello"); //构造法创建并初始化对象str2
System.out.println("str2:" + str2);
String str3 = "Hello" + "World"; //采用串联方式生成新的字符串str3
System.out.println("str3:" + str3);
}
}
三种方式都完成了 String 对象的创建及初始化。
对于 String 对象也可以先声明再赋值。例如:
String str1; //声明字符串对象str1
str1 = "Hello"; //字符串对象str1赋值为"Hello"
构造法也可先建立对象,再赋值。
String str2 = new String(); //构造法创建一个字符串对象str2,内容为空字符串
//等同于 String str2 = new String("");
str2 = "Hello"; //字符串对象str2赋值为"Hello"
4.String 内容的比较
用户经常需要判断两个字符串的大小或相等,比如可能需要判断输入的字符串和程序中另一个编码字符串是否相等。
序号 | 方法名称 | 类型 | 描述 |
1 | public boolean equals(String anObject) | 普通 | 区分大小写比较 |
2 | public boolean equalsIgnoreCase(String anotherString) | 普通 | 不区分大小写比较 |
3 | public int compareTo(String anotherString) | 普通 | 比较字符串大小关系 |
(1)Java 中判定字符串一致的方法有两种。
a.调用 equals(object) 方法
string.equals(string2),比较当前对象( string1 )包含的值与参数对象( string2 )包含的值是否相等,若相等则 equals() 方法返回 true,否则返回 false,equals() 比较时考虑字符中字符大小写的区别。
equalsIgnoreCase() 可以忽略大小写的进行两个字符串的比较。
String str1 = "Hello Java!"; //直接赋值实例化对象str1
Boolean result = str1.equals("Hello Java!"); //result=true
Boolean result = str1.equals("Hello java!"); //result=false
Boolean result = str1.equalsIgnoreCase("Hello java!"); //result=true
b.使用比较运算符 ==。
运算符 == 比较两个对象是否引用同一个实例。
String str1 = "Hello"; //直接赋值实例化对象str1
String str2 = "Hello"; //直接赋值实例化对象str2
Boolean result1 = (str1==str2); //result=true
String str3 = new String("Hello"); //构造方法赋值
Boolean result2 = (str1==str3); //result=false
str1 和 str3 不相等,原因如下:
①如果说 String 是一个类,那么 str1 一定是这个类的对象,对象名称一定要保存在栈内存之中,那么字符串 “ Hello ” 一定保存在对内存之中。
②任何情况下使用关键字 new 都一定会开辟一个新的堆内存空间。
③String本身是一个类,所以 String 类的对象是一定可以进行引用传递的,引用传递的最终结果就是不同的栈内存将保存同一块堆内存空间的地址。
④栈内存像 int 型数据,里面保存的是数值,每一个栈内存只能够保存一块堆内存的物理地址数值。
根据上例可发现,针对于 “ == ” 在本次操作之中实际上是完成了它的相等判断功能,只是它完成的是两个对象的堆内存地址的相等判断,属于地址的数值相等比较,并不是真正意义上的字符串内容的比较。
如果现在想要进行字符串内容的比较,可以使用 equals() 方法。
举例:
//字符串对象相等判断
//创建String对象
public class StringEquals
{
public static void main(String[] args)
{
String str1 = "Hello"; //直接赋值
String str2 = new String("Hello"); //构造方法赋值
String str3 = str2; //引用传递
System.out.println(str1 == str2); //false
System.out.println(str1 == str3); //false
System.out.println(str2 == str3); //true
System.out.println(str1.equals(str2)); //true
System.out.println(str1.equals(str3)); //true
System.out.println(str2.equals(str3)); //true
}
}
字符串对象 str1,str2 是不同方法创建的不同对象,内容一致,地址不同,“ == ” 判断相等是为假,str3 和 str2 引用同一对象,内容一致,地址相同,“ == ” 判断相等为真,由于 str1,str2,str3 内容相同,用 equals() 方法判断相等时,全为 true。
(2)Java 中判断字符串大小
如果希望知道字符串大小情况,需要调用 compareTo() 方法。
字符串对象小于给定字符串:compareTo() 方法返回小于零的值;
字符串对象等于给定字符串:compareTo() 方法返回小于零的值;
字符串对象大于给定字符串:compareTo() 方法返回大于零的值;
比较是根据字母顺序,严格来讲是根据字符的 ASCII 码值进行比较的,返回结果是第一个不同字符 ASCII 码的差值。
举例:
//字符串对象大小比较
public class StringCompare
{
public static void main(String[] args)
{
String str1 = "This is a string"; //直接赋值建立对象str1
String str2 = new String("this is a string"); //构造法建立对象str2
int result = str1.compareTo("That is another string"); //result=8
int result1 = str1.compareTo("This is string"); //result1=0
int result2 = str1.compareTo(str2); //result2=-32
System.out.println(result);
System.out.println(result1);
System.out.println(result2);
}
}
compareTo() 是按字符串中逐个字符的 ASCII 码值进行比较的。
5.字符串常量是 String 类的匿名对象
任何的语言实际上都不会提供字符串类型,但是在 Java 里面为了简化用户的开发难度,专门提供了 String 类和使用 “ " ” 定义的字符串,但实际上每一个字符串严格来讲都是 String 类的匿名对象。
匿名对象特点:没有名字,而且可以调用类中的相关方法。
String str = "Hello"; //直接赋值
System.out.println(str.equals("Hello")); //true,字符串对象调用equals()
System.out.println("Hello".equals(str)); //true,字符串常量调用equals(),
//“Hello”是String类的匿名对象
判断某一个用户输入的字符串内容是否等于指定的字符串内容,若采用字符串对象 .equals("内容") 的方式,如果用户没输入字符串,会出现 NullPointerException 警告,可以采用 “ 字符串 ” .equals(字符串对象) " 的方式解决这个问题。
String str = null; //假设这个字符串由用户输入
if(str.equals("Hello")){ //若没输入字符串str的内容,出现NullPointerException
System.out.println("验证通过");
}
if("Hello".equals(str)){ //equals()可自动处理null问题,正常判断
System.out.println("验证通过");
}
6.两种字符串实例化方式的区别
实例化字符串对象可以采用两种方式完成,不同方式在内存中呈现方式不同的分配形式。
(1)直接赋值方式
如果现在采用直接赋值的形式,那么就好比将一个字符串的常量赋给了指定的字符串变量,而且每一个字符串常量都属于 String 的匿名对象。
String str = "Hello"; //直接赋值
以直接赋值的方式创建字符串对象 str,仅开辟一块栈内存和一块堆内存空间。若采用直接赋值的方式定义多个字符串对象呢?
举例:
//分析直接赋值法创建字符串时的内存分配
public class MemmoryAllocation
{
public static void main(String[] args)
{
String strA = "Hello"; //直接赋值法
String strB = "Hello"; //直接赋值法
String strC = strB; //引用传递
System.out.println(strA==strB); //true
System.out.println(strA==strC); //true
System.out.println(strB==strC); //true
}
}
在 Java 中,若字符串对象使用直接赋值方式完成,如 strA,那么首先在第一次定义字符串的时候,会自动的在堆内存之中定义一个新的字符串常量 “ Hello ”,如果后面还有其他字符串的对象( 如:strB )采用的是直接赋值的方式实例化,并且此内容已经存在,那么就不会开辟新的字符串常量,而是让其指向了已有的字符串内容,即 strA,strB指向同一块内存,所以 strA == strB 比较的结果是 true。这样的设计在开发模式上称为共享设计模式。
所谓的共享设计模式指的是在 JVM 底层( 如果是用户自己实现就是依靠动态数组 )准备出一个对象池( 多个对象 ),如果现在按照某一个特定方式进行对象实例化的操作,那么此对象的内容会保存到对象池之中,而后如果还有其他的对象也采用了固定的方式声明了与之相同的内容,则此时将不会重新保存新对象到对象池之中,而是从对象池中取出已有的对象内容继续使用,这样一来可以有效的减少垃圾空间的产生。
(2)构造方法实例化
String str = new String("Hello");
此时会开辟两块内存空间,其中有一块内存将成为垃圾,通过构造方法进行的 String 类对象,也无法进行自动入池的操作,即:数据无法共享。
在 String 类中提供了一个方法,可以帮助用户手工入池。
手工入池:public String intern();
举例:
//分析构造方法创建字符串时的内存分配
public class ConstructorMemmoryAllocation
{
public static void main(String[] args)
{
String strA = "Hello"; //直接赋值
String strB = new String("Hello").intern(); //手工入池
String strC = "Hello"; //直接赋值
String strD = new String("Hello"); //构造法
System.out.println(strA==strB); //true
System.out.println(strA==strC); //true
System.out.println(strB==strC); //true
System.out.println(strA==strD); //false
}
}
Sting 类的对象在实例化时采用不同的方式,开辟的内在空间也不同。
直接赋值:只开辟一块堆内存空间,而且保存的字符串内容可以自动入池,以供其他内容相同的字符串对象使用;
构造方法:开辟两块内存空间,有一块将成为垃圾,并且字符串的内容无法自动入池,但是可以使用 String 类中的 intern() 方法手工入池。
7.字符串一旦声明则不可改变
字符串的内容用户一旦定义,就表示开辟好了指定的空间,那么字符串的内容就不能够被改变了。
举例:
//字符串内容变化情况
public class StringChange
{
public static void main(String[] args)
{
String str = "Hello"; //直接赋值
str += "World"; //字符串连接
str = str + "!!!"; //字符串连接
System.out.println(str);
}
}
字符串 str 的内容改变了,但事实并非如此。其实,字符串的内容没有改变,改变的只是 String 类对象的引用,并且会产生大量字符串垃圾。因此,应该尽量避免出现不断修改字符串内容的现象,以免出现大量垃圾。
8. String 类的常用方法
String 是在所有的开发之中最常用的类,所有的程序都会包含字符串的操作,在 Java 之中,String 也定义了大量的操作方法,这些方法全部都在 Java Doc 文档中可以查询到。
(1)字符与字符串
Java 延续了编程语言中利用字符数组来表示字符串的特性。一个字符串可以变为一个字符数组,同样,也可以把一个字符数组,变为一个字符串,也可实现从字符串中取出指定位置的字符。
序号 | 方法名称 | 类型 | 描述 |
1 | public String(char[] value) | 构造 | 将接收到的字符数组变为字符串 |
2 | public String(char[] value,int offset,int count) | 构造 | 将部分字符数组变为字符串 |
3 | public char charAt(int index) | 普通 | 返回指定索引位置上的字符内容 |
4 | public char[] toCharArray() | 普通 | 将字符串变为字符数组 |
举例:
//求字符串中指定位置的字符
public class CharAt
{
public static void main(String[] args)
{
String str = "Hello Java!"; //直接赋值法
System.out.println(str.charAt(0)); //取出字符串中第1个字符
System.out.println(str.charAt(3)); //取出字符串中第4个字符
}
}
在程序之中,字符串中索引的下标从 0 开始的,str.charAt(0) 取出字符串中第 1 个字符,即 str.charAt(n) 取出的是字符串中第 n+1 个字符。
举例:
//字符串与字符数组互相转换
public class StringArray
{
public static void main(String[] args)
{
String str = "Hello Java!"; //直接赋值法
char data[] = str.toCharArray(); //将字符串变为字符数组
for (int x = 0; x < data.length; x++) {
System.out.println(data[x]);
data[x] -= 32; //小写变为大写
}
System.out.println();
System.out.println("将全部字符数组变为字符串:"+new String(data));
//取得部分内容的时候需要设置起始点和取得的长度
System.out.println("将部分字符数组变为字符串:"+new String(data,5,4));
}
}
new String(data,5,4) 的含义是将字符数组中下标为 5,即从第 6 个元素开始取 4 个字符。
str 字符串中的内容是小写字符,同一个字符的小写 ASCII 码比大写大 32,data[x] -= 32 完成小写转换成大写。
(2)字节与字符串
除了提供了字符串与字符的转换操作之外,在 String 类中也提供了字符串与字节数据的转换操作,可以使用的方法如下所示:
序号 | 方法名称 | 类型 | 描述 |
1 | public String(byte[] bytes) | 构造 | 将全部字节数组变为字符串 |
2 | public String(byte[] bytes,int offset,int length) | 构造 | 将部分字节数组变为字符串 |
3 | public byte[] getBytes() | 普通 | 将字符串变为字节数组 |
4 | public byte[] getBytes(String charsetName) throws UnsupportedEncodingExcepiton | 普通 | 将字节串转码 |
举例:
//字符串与字节互相转换
public class StringByte
{
public static void main(String[] args)
{
String str = "Hello Java!"; //直接赋值法建立字符串
byte data[] = str.getBytes(); //将字符串变为byte数组
for (int x = 0; x < data.length; x++) {
data[x] -= 32;
}
System.out.println(new String(data)); //将全部的byte数组变为字符串
System.out.println(new String(data,5,4)); //将部分的byte数组变为字符串
}
}
(3)字符串查找方法
由一个字符串中若干个字符按顺序形成的连续字符片段,就是字符串的子串。
从一个指定的字符串之中查找某一个子字符串是否存在的操作,称为字符串查找,查找的方法如下:
序号 | 方法名称 | 类型 | 描述 |
1 | public boolean contains(String s) | 普通 | 判断某一个字符串是否存在,JDK 1.5 之后提供 |
2 | public int indexOf(String str) | 普通 | 由前向后查找指定子字符串的位置,找不到返回 -1 |
3 | public int indexOf(String str,int fromIndex) | 普通 | 由指定位置查找子字符串位置,找不到返回 -1 |
4 | public int lastIndexOf(String str) | 普通 | 由后向前查找指定子字符串的位置,找不到返回 -1 |
5 | public int lastIndexOf(String str,int fromIndex) | 普通 | 由指定位置从后向前查找 |
6 | public boolean startsWith(String prefix) | 普通 | 判断是否以指定的字符串开头 |
7 | public boolean startsWith(String prefix,int toffset) | 普通 | 从指定位置开始判断是否以指定的字符串开头 |
8 | public boolean endsWith(String suffix) | 普通 | 判断是否以指定的字符串结尾 |
举例:
//字符串查找方法
public class StringSearch
{
public static void main(String[] args)
{
String str = "Hello Java!"; //直接赋值法建立字符串
if (str.contains("Hello")) { //查找Hello是否存在
System.out.println("内容存在,已找到");
}
if (str.indexOf("H") != -1) { //查找Hello是否存在
System.out.println("内容存在,字符串位置:" + str.indexOf("H"));
}
if (str.indexOf("H",6) != -1) { //由指定位置开始查找
System.out.println("内容存在,字符串位置:" + str.indexOf("H",6));
}
if (str.lastIndexOf("o",8) != -1) { //由指定位置从后向前开始查找
System.out.println("内容存在,字符串位置:" + str.lastIndexOf("o",8));
}
System.out.println(str.startsWith("He")); //判断字符串的起始内容是否是以"He"开始
System.out.println(str.startsWith("ll",7));
System.out.println(str.startsWith("lo")); //判断字符串是否是以"lo"结束
}
}
判断字符串中字符串是否存在时,返回的是 Boolean 值,若判断某字符串子串出现的位置,返回子串在字符串中第一次出现位置的索引值,若没找到,则返回 -1。
(4)字符串替换
Java 的 String 类有替换的操作。
序号 | 方法名称 | 类型 | 描述 |
1 | public String replaceAll(String regex,String replacement) | 普通 | 字符串全部替换为指定内容 |
2 | public String replaceFirst(String regex,String replacement) | 普通 | 替换掉首个内容 |
举例:
//字符串替换方法
public class StringReplace
{
public static void main(String[] args)
{
String str = "Hello Java!"; //直接赋值法建立字符串
System.out.println(str.replaceAll("o", "***")); //所有子串出现的位置都替换
System.out.println(str.replaceFirst("l", "~")); //替换第一次出现字符子串
}
}
(5)字符串截取
从一个指定的字符串中取出里面的部分内容,即子串。从字符串中截取子串方法如下:
序号 | 方法名称 | 类型 | 描述 |
1 | public String substring(int beginIndex) | 普通 | 由指定位置截取到结尾 |
2 | public String substring(int beginIndex,int endIndex) | 普通 | 截取指定范围的字符串 |
举例:
//字符串中取子串的方法
public class SubString
{
public static void main(String[] args)
{
String str = "Hello Java!"; //直接赋值法建立字符串
System.out.println(str.substring(5)); //截取从指定位置到末尾的子串
System.out.println(str.substring(0, 5)); //截取从指定位置到结束位置的子串
}
}
字符串中索引值从 0 开始,substring(int beginIndex,int endIndex),beginIndex 表示开始处的索引( 包括 ),endIndex 表示结束处的索引( 不包括 )。截取从指定的位置 beginIndex 处开始,一直到索引 endIndex-1 处的字符。因此该子字符串的长度为 endIndex-beginIndex。
若str.substring(0,13),会出现 IndexOutOfBoundsException 错误。取子串时,如果 beginIndex 为负,或 endIndex 大于此 String 对象的长度,或 beginIndex 大于 endIndex,则出现错误。
(6)字符串拆分
所谓的字符串拆分,指的是将一个字符串按照指定的字符串 regex 拆分为若干个字符串,返回的是一个字符型数组,regex 不作为任何数组元素的部分返回,操作方法如下:
序号 | 方法名称 | 类型 | 描述 |
1 | public String[] split(String regex) | 普通 | 按照指定的字符串全拆分 |
2 | public String[] split(String regex,int limit) | 普通 | 将字符串茶分为指定元素个数的字符数组 |
举例:
//字符串拆分的方法
public class StringSplit
{
public static void main(String[] args)
{
String str = "Hello Java Hello World!"; //直接赋值法建立字符串
String data[] = str.split(" "); //按照空格拆分
for (int i = 0; i < data.length; i++) {
System.out.println(data[i]);
}
System.out.println("——————————————————");
String data1[] = str.split(" ",3); //按照空格拆分
for (int i = 0; i < data1.length; i++) {
System.out.println(data1[i]);
}
}
}
①如果用 " . " 作为分隔的话,必须是如下写法:String.split("\\."),这样才能正确地分隔开,不能用 String.split(".");
String str = "192.168.1.1";
String data[] = str.split("\\.");
data 字符数组中的元素分别为:192,168,1,1
②如果用 “ | ” 作为分隔的话,必须是如下写法:String.split("\\|"),这样才能这缺德分隔开,不能用 String.split("|");
③如果用 " \ " 作为分隔,就得写成:String.split("\\\\"),因为在 Java 中是用 "\\" 来表示 "\" 的," . "、" | " 和 " \ " 都是转移字符,必须得加 " \\ ";
④如果在一个字符串中有多个分隔符,可以用 “ | ” 作为连接符,比如:str = “ acount=8 and uu = 45 or n = 25”,把三个都分隔出来,可以用str.split("and|or"),即用 “ and ” 或者 “ or ” 进行分隔,分隔结果为 “ acount=8 ”,“ uu=45 ”,“ n=25 ”。
(7)其他方法
除了以上常见方法外,还有一些方法如下:
序号 | 方法名称 | 类型 | 描述 |
1 | public String concat(String str) | 普通 | 字符串连接,功能与 “ + ” 操作一样 |
2 | public String intern() | 普通 | 入池 |
3 | public int length() | 普通 | 取得字符串长度 |
4 | public boolean isEmpty() | 普通 | 判断是否为空字符串( 但不是NULL ) |
5 | public String toLowerCase() | 普通 | 字符串转小写 |
6 | public String toUpperCase() | 普通 | 字符串转大写 |
7 | public String trim() | 普通 | 去掉字符串前后空格 |
举例:
//常用字符串方法
public class StringMethod
{
public static void main(String[] args)
{
String str = "Hello Java Hello World!"; //直接赋值法建立字符串
//求字符串长度
System.out.println("原始字符串内容【" + str + "】" + ",长度:" + str.length());
//去字符串前后空格,再求长度
System.out.println("原始字符串内容【" + str.trim() + "】" + ",长度:" + str.trim().length());
System.out.println("".isEmpty()); //判断字符串是否为空
System.out.println(str.isEmpty());
System.out.println("Hello World!".toUpperCase()); //字符串字符全变大写
System.out.println("Hello World!".toLowerCase()); //字符串字符全变小写
System.out.println(str.concat("java")); //将字符串str的内容和“java”连接在一起
}
}
String.isEmpty() 如果 string 长度为 0,返回 true,否则 false。
二、本文注意事项
1. String s = new String("java");
创建 String 对象的个数是两个对象,一个是 “ java ” ,一个是指向 “ java ” 的引用对象 s。
2. Java 中堆内存与栈内存的区别
(1)堆内存是栈内存的一个子集。
(2)栈内存存取速度仅次于寄存区,栈内存里面的数据可共享,但是其中数据的大小和生存期必须在运行前确定。
(3)堆内存是运行时可动态分配的数据区,从速度看比栈内存慢,堆内存里面的数据不共享,大小和生存期都可以在运行时再确定。
(4)new 关键字是运行时在堆内存里面创建对象。每 new 一次都一定会创建新对象,因为堆数据不共享。