1、字符串的构造
1.1 简单初始化字符串
在我们前面也对字符串进行了简单的使用,在Java当中,String是字符串类型,本质上也是一个类,这个类中提供了很多的方法,我们后续会学习到,现在先来简单看一下String类常见的构造方法:
public static void main(String[] args) { String str1 = "hello"; String str2 = new String("hello"); char[] array = { 'h','e','l','l','o' }; String str3 = new String(array); }
还有其他的构造方法,但是常用的就上面这三种,分别是使用常量字符串构造,也就是直接用一个常量字符串赋值给String类型的变量(引用),第二种就是new一个String的对象,第三种是将字符数通过String构造方法转换成字符串。
至于第一种和第二种写法有什么区别,等后面我们学了字符串常量池放在那个时候讲解。
1.2 String是引用类型
因为String是引用类型,所以内部并不存储字符串本身,在String内部原码中,String类实例变量如下:
通过查看String类的原码可以发现,其实一个String引用的对象里面是有两个实例变量的,分别是value 字符数组 和 hash 也就是哈希, 所以在JDK1.8中,字符串实际保存在char类型的数组里头,这里我们先来简单的看一段代码:
public static void main(String[] args) { // s1和s2引用的是不同对象 s1和s3引用的是同一对象 String s1 = new String("hello"); String s2 = new String("hello"); String s3 = s1; System.out.println(s1 == s2); // false System.out.println(s1 == s3); // true }
为了方便大家更好的了解,我们给上面的代码画一个草图,并不是真正的内存布局图!但不影响我们的理解,等到字符串常量池会给大家画真实的内存布局:
所以上面的代码打印的结果也就是 fasle 和 ture,因为引用使用 == 比较比较的是存储的地址是否相同,而通过上图就能发现 s1 和 s3 存储的地址是相同的,所以打印结果正如我们猜想的一样:
这里我们还需要注意一点,使用 "" 引起来的也是String类型对象,也可也调用对象中可以调用的属性:
// 打印"hello"字符串(String对象)的长度 System.out.println("hello".length());
2、String类常用方法
2.1 String对象的比较方法
在Java中我们有四种的比较方法,当然对于初学者见过最多的就是 == 的比较,但是这个在引用类型比较的是引用的地址是否相同,而基本类型则是比较里面存储的值是否相同,简单来说就是比较变量存储的值,所以 == 号我们不做过多的阐述。
2.1.1 boolean equals(Object anObject) 方法
这个方法其实也不陌生,之前也见过,他是按照字典序也就是字符大小的顺序进行比较,而Sting类中重写了 equals 方法,具体我们可以看一下方法的实现:
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
这个就是String类中重写 equals 方法的实现代码了,我们简单分析一下:
第一步:首先判断当前引用和 anObject也就是传过来的引用所引用的对象是否相同,因为Object是所有类的父类,这里用到了向上转型,如果引用的是同一个对象则返回 true
第二步:判断 anObject 里面的对象是不是 String 类型对象,如果是就继续比较,不是则直接返回false。
第三步:如果 anObject里面的对象是String类型对象,我们就先把anObject向下转型成String对象,这样就能访问本身对象自己的属性了,接着把当前对象里面字符数组的长度赋值给了n,在判断当前两个对象的里面存放的字符数组长度是否相同,相同则继续比较不是还是返回false。
第四步:如果if里面的字符长度相同,我们就按照字典序逐个字符往后比较,当这个if里面循环成功走完了,也就返回了true,一旦发现有一个字符不相同就会返回false
原码已经简单的分析过了,至于这个方法的使用前面也见到过,所以剩下使用就交给各位感兴趣的下来摸索了。
2.1.2 int compareTo(String s)方法
这个方法是实现的 Comparable 接口里面的 compareTo 方法,与上面不同的是,他返回的是int类型,并不是boolean类型,具体比较方式也是按照字典序进行比较,如果出现不同的字符,就直接返回这两个字符的差值,如果前 k 个字符(k为两个字符串最小的长度值),就返回两个字符串的差值。
public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } return len1 - len2; }
这个原码实现的也很简单我们上面也简单说明了一下,就不多说,感兴趣的还是要自己下来用一用这个方法。
2.1.3 int compareToIgnoreCase(String s)方法
这个方法跟上面的 compareTo 方法的区别就是忽略了大小写比较,这里如果要看原码已目前的知识储备是不好理解的,这个知道如何使用就行:
public class Main { public static void main(String[] args) { String s1 = "HeLLO"; String s2 = "hEllo"; System.out.println(s1.compareToIgnoreCase(s2)); } }
2.2 字符串查找方法
字符串查找是很常见的操作,在日常刷题的过程中也会碰到,而Java中用的比较多的就是 char charAte(int index) ,int indexOf(...),int lastIndexOf(...),里面打三个点是因为这些方法都被重载了,形参列表是不同的,具体看下面代码和注释:
public static void strFind() { String str = "hello"; //返回字符串某个位置上的字符,不能越界也不能是负数 System.out.println(str.charAt(1)); //返回指定字符第一次出现的位置,没有返回-1 System.out.println(str.indexOf('l')); //从fromIndex位置开始找指定字符第一次出现的位置,没有返回-1 System.out.println(str.indexOf('l', 3)); //返回一个字符串第一次出现的起始位置,没有返回-1 System.out.println(str.indexOf("el")); //从fromIndex位置开始找指定字符串第一次出现的位置,没有返回-1 System.out.println(str.indexOf("el", 2)); //从后往前找,返回指定字符第一次出现的位置,没有返回-1 System.out.println(str.lastIndexOf('e')); //从fromIndex位置从后往前找到指定字符第一次出现的位置,没有返回-1 System.out.println(str.lastIndexOf('e', 3)); //从后往前找指定字符串第一次出现的位置,没有返回-1 System.out.println(str.lastIndexOf("el")); //从fromIndex位置从后往前找指定字符串第一次出现的位置,没有返回-1 System.out.println(str.lastIndexOf("el", 4)); }
这个比较简单,下来多练习下就好了,至于原码的实现不必太过关心,如果每个都去解读原码那得讲到后年马月, 感兴趣的可以下来自己看一看,按Ctrl+鼠标左键,点一下你要进入的方法名就好了。
注意:上面的方法都是要依赖于对象的,也就是实例方法。
2.3 转换相关的方法
2.3.1 数值和字符串之间的转换
public static void strTransform() { //数字转字符串 String s1 = String.valueOf(123); String s2 = String.valueOf('b'); String s3 = String.valueOf(12345f); String s4 = String.valueOf(true); String s5 = String.valueOf(new Student("张三", 24)); //字符串转数字 int a = Integer.parseInt("1234"); double d = Double.parseDouble("123.4"); }
2.3.2 大小写转换
public static void strTransform() { //大小写转换 String str1 = "hELlo"; //转换会产生一个新的对象,不会修改原有的字符串 System.out.println(str1.toUpperCase()); //小写转大写 System.out.println(str1.toLowerCase()); //大写转小写 }
2.3.3 字符串转数组
public static void strTransform() { //字符串转数组 String str2 = "hello"; char[] array1 = str2.toCharArray(); //使用toCharArray方法 for (int i = 0; i < array1.length; i++) { System.out.print(array1[i] + " "); } System.out.println(); //数组转字符串 char [] array2 = new char [] { 'a','b','c' }; String str3 = new String(array2); System.out.println(str3); }
2.3.4 格式化
public static void strTransform() { //格式化 String str4 = String.format("%d-%d-%d", 2022, 8, 19); System.out.println(str4);
最终输出这个字符串也就是会打印:2022-8-19,学过C语言的小伙伴可能会比较的熟悉,这里照葫芦画瓢即可,得注意一个点,如果是特殊字符格式化比如 %d\%d\%d,这里我们要转义一下即可,如果不知道 %d 是什么意思,可以去参考下C语言的文章。