引言
我们都知道在C语言中是没有字符串类型的,C语言中,最常用的就是将字符串放进一个数组中,之后,对数组进行一些操作。而在 Java / C++ 中,有直接表示字符串的类型。在Java 中,String 类型就是表示字符串类型,同时它也是引用类型。比方说:
String str = "abcd";
没错,在 Java 中,字符串就是上述这样定义并初始化的,而 abcd 的末尾没有像 C语言那样有 \0,在 Java 中,里面就是 abcd.
在上面的一行代码中,被双引号引起来的 abcd 就叫做字符串,它是有a、b、c、d 这四个字符组成的,而 abcd 又属于字面值常量,其本质是常量,不可以被更改。
一、创建字符串的方式
1. 直接赋值
程序清单1:
public class Test1 { public static void main(String[] args) { String str1 = "hello"; System.out.println(str1); //输出结果:hello } }
2. 通过使用构造方法
程序清单2:
public class Test2 { public static void main(String[] args) { String str2 = new String("world"); System.out.println(str2); //输出结果:world } }
3. 通过使用字符数组转换成字符串
程序清单3:
public class Test3 { public static void main(String[] args) { char[] chars = {'x','y','z'}; String str3 = new String(chars); System.out.println(str3); //输出结果:xyz } }
二、字符串的引用变量比较相等
1. 情况一
在程序清单4中,例如(str1 == str2)的这种形式,比较的其实是两个引用的地址,而不是字符串的内容 " hello ",这点需要理解,请往下看:
程序清单4:
public class Test4 { public static void main(String[] args) { String str1 = "hello"; String str2 = "hello"; System.out.println(str1 == str2); } }
输出结果:
图解上述代码:
String 类的设计使用了共享设计模式:
如果现在采用了直接赋值的模式进行String类的对象实例化操作,那么该实例化对象(字符串内容)将自动保存到这个对象池之中。如果下次继续使用直接赋值的模式声明 String 类对象,此时对象池之中如若有指定内容,将直接进行引用。
如若没有,则开辟新的字符串对象而后将其保存在对象池之中以供下次使用。
2. 情况二
程序清单5:
public class Test5 { public static void main(String[] args) { String str1 = new String("hello"); String str2 = new String("hello"); System.out.println(str1 == str2); } }
输出结果:
图解上述代码:
3. 情况三
程序清单6:
public class Test6 { public static void main(String[] args) { String str1 = "hello"; String str2 = new String("hello"); System.out.println(str1 == str2); } }
输出结果:
图解上述代码:
总结:
- 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。
- 构造方法:会开辟两块堆内存空间,不会自动保存在对象池中,可以使用intern( ) 方法手工入池。
总结:
- 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以自动保存在对象池中以供下次使用。
- 构造方法:会开辟两块堆内存空间,不会自动保存在对象池中,可以使用intern( ) 方法手工入池。
字符串常量池:主要存放字符串常量,本质上是一个哈希表,StringTable,双引号引起来的字符串常量。
三、理解字符串不可变
String 类的内部实现是基于 char[ ] 来实现的,但是 String 类并没有提供 set 方法之类的来修改内部的字符数组。
程序清单7:
public class Test7 { public static void main(String[] args) { String str1 = "abc"; System.out.println(str1); str1 = "xyz"; System.out.println(str1); } }
输出结果:
注意:在这里,我们并不是通过把字符串的内容修改了,而是将 [ str1 引用 ] 引用了新的对象。
程序清单8:
public class Test8 { public static void main(String[] args) { String str = "hello" ; str = str + " world" ; str += " !!!" ; System.out.println(str); } }
输出结果:
同样地,在程序清单8中,我们并不是将 " hello " 中的内容修改了,而是先通过 " hello " 和 " world " 创建了一个新的对象 " hello world ",之后又通过 " hello world " 和 " !!! " 创建了另一个新的对象 “hello world !!!”。对于这样的操作,相当于每次都要通过关键字 new 实例化一个对象。所以,程序清单8是一个不好的示范,对于程序员来说,不应该这么做。
四、字符、字节、字符串、数组
1. 将字符数组转换成字符串
程序清单9:
public class Test9 { public static void main(String[] args) { char[] chars = {'a','b','c','d'}; String str = new String(chars); System.out.println(str); } } //输出结果:abcd
2. 拿到字符串中的连续字符
程序清单10:
public class Test10 { public static void main(String[] args) { char[] chars = {'a','b','c','d'}; String str = new String(chars, 1, 2); System.out.println(str); } } //输出结果:bc
String str1 = new String(char[] chars,int offset, int count);
在 String 构造方法中,其中 offset 表示偏移量,count 表示偏移个数,若 offset 为 1,count 为 2,那么,实现 str1 就从数组下标1开始,往后拿2个元素。当然,我们应该注意不能越界哦。
3. charAt( ) 方法 [ 常用 ]
程序清单11:
public class Test11 { public static void main(String[] args) { String str = "abcde"; char ch = str.charAt(2); System.out.println(ch); } } //输出结果:c
harAt(int index)
index 表示指定位置的索引,字符串第一个字符索引为 0。
同样地,我们应该注意不能越界哦。
4. 将字符串转变成字符数组 [ 常用 ]
程序清单12:
public class Test12 { public static void main(String[] args) { String str = "abcde"; char[] chars = str.toCharArray(); System.out.println(Arrays.toString(chars)); } } //输出结果:[a, b, c, d, e]
上面四个程序清单说明的是字符数组与字符串之间的一些操作,如果把字符数组换成字节数组,整型数组等等…其对应的思想是一样的。感兴趣的小伙伴可以自己试一下字节与字符串之间的关系。
5. 判断字符串是是由字符构成还是由数字构成
判断一个字符串是否是由字符构成
思路:我们创建一个 judge( ) 方法来判定每个字符是否由字母组成即可,所以我们遍历整个字符串的长度,然后通过下面两行代码来验证每一个字符:
char ch = str.charAt(i); boolean sign = Character.isLetter(ch);
程序清单13:
public class Test13 { public static boolean judge(String str){ for (int i = 0; i < str.length(); i++) { char ch = str.charAt(i); boolean sign = Character.isLetter(ch); if(sign == false){ return false; } } return true; } public static void main(String[] args) { String str = "abcde"; System.out.println(judge(str)); } } //输出结果:true
当然,我们也可以判定是否每个字符是数字,只不过逻辑需要改变一下,代码如下:
char ch = str.charAt(i); boolean sign = Character.isDigit(ch);
五、字符串比较
1. equals( ) 方法
查看底层代码,通过 equals( ) 方法比较字符串的内容,返回类型是布尔类型。
程序清单14:
public class Test14 { public static void main(String[] args) { String str1 = "hello"; String str2 = new String("hello"); System.out.println(str1.equals(str2)); String str3 = null; String str4 = "hello"; //System.out.println(str3.equals(str4)); //空指针异常 System.out.println(str4.equals(str3)); } }
输出结果:
注意,在程序清单14中,str3 和 str4 不能互换位置,因为 str3 这个引用本身不指向任何对象,如果继续使用 str3 的话,会造成空指针异常,我已经通过注释标明出来了。
在程序清单15中,我们对程序清单14做出了一些改变,当我们忽视大小写的时候,可以使用 equalsIgnoreCase( ) 方法。
程序清单15:
public class Test15 { public static void main(String[] args) { String str1 = "hello"; String str2 = "HeLLo"; System.out.println(str1.equalsIgnoreCase(str2)); } }
输出结果:
2. compareTo( ) 方法
当查看底层代码,我们发现 String 类型实现了 Comparable接口,那么String 类就会重写 compareTo( ) 方法,返回值是整型。
接下来,我们通过程序清单16来演示一下 compareTo( ) 方法是怎么使用的。
程序清单16:
public class Test16 { public static void main(String[] args) { String str1 = "abc"; String str2 = "ABC"; int ret1 = str1.compareTo(str2); System.out.println(ret1); System.out.println("--------------"); String str3 = "abc"; String str4 = "acb"; int ret2 = str3.compareTo(str4); System.out.println(ret2); System.out.println("--------------"); String str5 = "abcde"; String str6 = "ab"; int ret3 = str5.compareTo(str6); System.out.println(ret3); } }
输出结果:
在程序清单16中,compareTo( ) 方法是通过对比字符串中的字符一个一个进行比较的,字符对应的 Unicode 编码之差就是返回值( ASCII 码 )。如果两个字符串长度不等,返回的就是字符串长度之差。