【3】java之string类
String 是一个字符串类型的类,使用双引号定义的内容都是字符串,但是 String 本身是一个类,使用上会有一些特殊。
一、 String类对象的两种实例化方式
1.1 直接赋值
public class StringDemo{ public static void main(String args[]){ String str = "Hello World!"; System.out.println(str); }
String 对象的直接赋值,代码并没有使用关键字 new 进行。
直接赋值相当于将一个匿名对象设置了一个名字。但是唯一的区别是: String 类的匿名对象是由系统自动生成的,不再由用户自己创建。
举例:观察字符串是匿名对象的验证
public class StringDemo{ public static void main(String args[]){ String str = "Hello"; System.out.println("Hello".equals(str)); }
1.2 构造方法赋值
构造方法:public String(String str),在构造方法里面依然要接收一个本类对象;
举例:利用构造方法实例化
public class StringDemo{ public static void main(String args[]){ String str = new String("Hello World"); System.out.println(str); }
String 类有两种形式,主观上会认为第二种构造方法的形式更加适合于我们,因为只要是类就要用关键字 new 的做法似乎很符合道理的。
1.3 两种实例化方式的区别(重点)
1.3.1 分析直接赋值
直接赋值就是将一个字符串的匿名对象设置了一个名字。
String str = "Hello";
此时在内存之中会开辟一块堆内存,并且由一块栈内存指向此堆内存。
public class StringDemo{ public static void main(String args[]){ String strA = "Hello"; String strB = "Hello"; String strC = "Hello"; System.out.println(strA == strB); // true System.out.println(strA == strC); // true System.out.println(strB == strC); // true }
所有采用直接赋值的 String 类对象的内存地址完全相同,即 strA、strB、strC指向了同一块堆内存空间。
共享设计模式:在 JVM 底层实际上会存在一个对象池(不一定只保存 String 对象),当代码之中使用了直接赋值的方式定义了一个 String 类对象时,会将此字符串对象所使用的匿名对象入池保存,如果后续还有其它 String 类对象也采用直接赋值方式,那么将不会开辟新的堆内存空间,而是使用已有的对象引用的分配,继续使用。
1.3.2 采用构造方法实例化
构造方法如果要使用一定要用关键字 new ,一旦使用了关键字 new 就表示要开辟新的堆内存空间。
public static void main(String args[]){ String strA = new String("Hello"); String strB = new String("Hello"); String strC = new String("Hello"); System.out.println(strA == strB); // false System.out.println(strA == strC); // false System.out.println(strB == strC); // false }
使用构造方法方式进行 String 类对象实例化的时候,开辟了两块堆内存空间,并且其中有一块堆内存空间将成为垃圾空间。
除了内存的浪费之外,如果使用了构造方法定义的 String 类对象,其内容不会保存到对象池之中,因为是使用了关键字 new 开辟了新内存。如果希望开辟的新内存数据也可以进行对象池的保存,那么可以采用 String 类定义的手工入池的方法: public String intern();
举例:手工入池
public static void main(String args[]){ String strA = new String("Hello").intern(); String strB = "Hello"; System.out.println(strA == strB); // true }
面试题:请解释 String 类对象实例化的两种方式的区别?
- 直接赋值,只会开辟一块堆内存空间,并且匿名对象会自动保存到对象池中,以供下次重复使用。
- 构造方法赋值,会开辟两块堆内存空间,其中有一块空间将成为垃圾,并且不会自动入池,可以使用intern()方法手工入池。
工作中,使用直接赋值方式。
二、字符串内容不可改变
2.1 字符串变更
字符串一旦定义则不可改变。观察一段代码:
public static void main(String args[]){ String str = "Hello"; str += " World"; str += "!!!"; System.out.println(str); }
运行结果是: Hello World!!! 以上代码最终结果实际上 str 对象的内容被改变了。
所谓的字符串的内容根本就没有改变( Java 就定义好了 String 的内容不能够改变),而对于字符串对象内容的改变是利用了引用关系的变化而实现的,但是每一次的变化都会产生垃圾空间。所以 String 类的内容不要频繁的修改。
2.2 StringBuffer
String 类有一个问题:字符串一旦声明了就不能改变,只能改变 String 类对象的引用。为此,Java 里提供了另外一个类:StringBuffer 类,其内容可以修改。String 类的对象可以使用 “+” 进行字符串的连接操作,但是在 StringBuffer 类里必须使用 append() 方法进行追加。
- 方法:public StringBuffer append(数据类型 参数)
public class TestDemo {
public static void main(String args[]){
// String 类可以直接赋值实例化,但是 StringBuffer 类不可以.
StringBuffer buf = new StringBuffer();
buf.append("Hello").append("World").append("!!");
change(buf); // 引用传递
System.out.println(buf);
}
public static void change(StringBuffer temp){
temp.append("\\n").append(true);
}
}
由于 append() 方法的返回值类型仍是 StringBuffer,所以 StringBuffer 类的内容是可以进行修改的,而String 类的内容是不可以修改的。
2.3 StringBuffer、String 类对比
- String 类
public final class String
extends Object
implements Serializable, Comparable<String>, CharSequence
- StringBuffer 类
public final class StringBuffer
extends Object
implements Serializable, CharSequence
发现两个类都是 CharSequence 接口的子类。在以后的开发中,如果看见某些方法的操作上出现的是 CharSequence 接口,那么应该立刻清楚:只需要传递字符串即可。
public class TestDemo {
public static void main(String args[]){
CharSequence seq = "hello"; // 向上转型
System.out.println(seq); // String 类覆写的 toString 方法
}
}
虽然 String 和 StringBuffer 类有着共同的接口,但是这两个类对象之间如果要转换,不能直接转换
2.4 转换
将 String 对象转换为 StringBuffer 对象,有 2 种方式:
- 构造方法:public StringBuffer(String str)
- append() 方法: buf.append(str)。
将 StringBuffer 对象转换为 String 对象,也有两种方式:
- toString() 方法;
- 构造方法: public String(StringBuffer buffer)。
在 String 类里提供了一个和 StringBuffer 比较的方法:public boolean contentEquals(StringBuffer sb)。
2.5 StringBuffer 类常用方法
String 类定义了很多方法便于用户的开发,而在 StringBuffer 类里也定义了许多的操作方法,而且部分方法与 String 类正好互补。
- 字符串反转:public StringBuffer reverse();
- 在指定的索引位置增加数据:public StringBuffer insert(int offset,数据类型 变量);
- 删除部分数据:public StringBuffer delete(int start,int end)。
2.6 StringBuilder 类
在 JDK 1.5 后增加了一个字符串操作类:StringBuilder 类。这个类的定义结构和 StringBuffer 类非常类似,几乎连方法都一样。
面试题:请解释 String、StringBuffer、StringBuilder 的区别?
- String 的内容一旦创建则不可以改变,而 StringBuffer、StringBuilder 声明的内容可以改变;
- StringBuffer 类提供的方法都是同步方法,属于安全的线程操作,而 StringBuilder 方法都属于异步方法,属于非线程安全的操作。
在日后开发中,如果见到了字符串的应用,不需要思考 95% 使用的都是 String 类,只有在需要频繁修改的时候才会使用 StringBuffer 和 StringBuilder 类。
String 类仍然是最常用的字符串描述类,而 StringBuffer 类由于出现的时间较长,所以要比 StringBuilder 类使用的多(习惯了)。
三、String 类的常用方法
对于系统类的方法,一定要去查询文档,一些不常用的方法允许不知道,但是一定要会查。但是对于 String 类的一些方法由于使用的情况比较多,为了方便开发必须背住。
3.1 字符与字符串
| No. | 方法名称 | 说明 | 描述 | | :--: | :--------------------------------------: | :--: | -------------------- |
| 1 | public String(char[] value) | 构造 | 将字符数组变为 String 类对象 |
| 2 | public String(char[] value, int offset, int count) | 构造 | 将部分字符数组变为 String 类对象 |
| 3 | public char charAt(int index) | 普通 | 返回指定索引对应的字符 |
| 4 | public char[] toCharArray() | 普通 | 将字符串变为字符数组 |
很多语言中都是利用了字符数组的概念来描述字符串的信息,这一点在 String 类的方法上也都有所体现。
举例:返回字符串指定索引的字符
String str = "hello"; char s = str.charAt(0); System.out.println(s);
举例:将字符串以字符数组返回
String str = "Hello"; char data[] = str.toCharArray(); for(int i=0;i<data.length;i++){ System.out.print(data[i]+"、"); }
举例:将字符串转大写
public class StringDemo{ public static void main(String args[]){ String str = "hello"; char data[] = str.toCharArray(); for(int i=0;i<data.length;i++){ data[i] -= 32; } str = new String(data); // 将字符数组变为String System.out.println(str); String str1 = new String(data,1,3); // 将字符数组部分内容变为String System.out.println(str1); } }
3.2 字节与字符串
| No. | 方法名称 | 说明 | 描述 | | :--: | :--------------------------------------: | :--: | -------------------- |
| 1 | public String(byte[] bytes) | 构造 | 将全部字节数组变为 String 类对象 |
| 2 | public String(byte[] bytes,int offset,int length) | 构造 | 将部分字节数组变为 String 类对象 |
| 3 | public byte[] getBytes() | 普通 | 将字符串变为字节数组 |
| 4 | public byte[] getBytes(String charsetName) throws UnsupportedEncodingException | 普通 | 进行编码转换 |
字节使用 byte 描述,字节一般主要用于数据的传输或者进行编码转换。将字符串变为字节数组的操作,目的就是为了传输以及编码转换。
举例:观察字符串与字节数组的转换
public class StringDemo{ public static void main(String args[]){ String str = "helloworld"; byte data [] = str.getBytes(); // 将String对象转换为byte数组 for(int i=0; i<data.length; i++){ System.out.print(data[i]+"、"); data[i] -= 32; // 小写字母转为大写字母 } System.out.println(); System.out.println(new String(data)); // 将byte数组转换为String对象 System.out.println(new String(data,1,3)); // 将byte数组部分内容转换为String对象 } }
因为现在操作的是英文字母,所以感觉与字符类似。在以后的IO操作的时候会牵扯到这种字节数组的操作,在后续的开发中会逐步遇到乱码需要转码的问题。
3.3 字符串比较
| No. | 方法名称 | 说明 | 描述 | | :--: | :--------------------------------------: | :--: | ---------------------------------------- |
| 1 | public boolean equals(String anotherString)) | 普通 | 进行相等判断,区分大小写 |
| 2 | public boolean equalsIgnoreCase(String anotherString) | 普通 | 进行相等判断,不区分大小写 |
| 3 | public int compareTo(String anotherString) | 普通 | 判断两个字符串的大小(按照字符编码比较),返回值有如下 3 种:(1) =0 表示要比较的两个字符串内容相等;(2)>0 表示大于的结果;(3)<0 表示小于的结果 |
3.3.1 ==比较内存地址
判断两个基本数据类型的数据是否相等,可以使用 “ == ” 来完成。但是在 String 上也可以使用“ == ” 比较,那比较的结果呢?
举例:
public class StringDemo{ public static void main(String args[]){ String strA = "Hello World"; String strB = new String("Hello World"); String strC = strB; System.out.println(strA == strB); // false System.out.println(strA == strC); // false System.out.println(strB == strC); // true }
从内存关系分析来看,== 比较的是内存地址的数值,并不是字符串包含的内容。所以 == 属于数值比较,比较的是内存地址。
3.3.2 equals比较字符串内容
-
比较内容(与原始定义有一些差别):public boolean equals(String str);
举例:实现内容比较
public class StringDemo{ public static void main(String args[]){ String strA = "Hello World"; String strB = new String("Hello World"); String strC = strB; System.out.println(strA.equals(strB)); // true System.out.println(strA.equals(strC)); // true System.out.println(strB.equals(strC)); // true } }
开发之中,只要是进行字符串的判断,千万不要使用 == 完成。
面试题:请解释在字符串比较中, == 与 equals() 的区别。
-
“ == ” 是 java 提供的关系运算符,主要的功能是进行数值相等判断,如果用在了 String 对象上则表示的是内存地址数值的比较。
-
“ equals() ” 是由 String 类提供的一个方法,此方法专门负责进行字符串内容的比较
3.3.3 其他比较方法
如果要进行字符串内容相等的判断使用 equals(),但是在 String 类里面定义的字符串判断相等的不止这一个。
举例:
public class StringDemo{ public static void main(String args[]){ String strA = "helloworld"; String strB = "HelloWorld"; System.out.println(strB.equals(strA)); // false System.out.println(strB.equalsIgnoreCase(strA)); //true } }
举例:观察 compareTo() 方法
public class StringDemo{ public static void main(String args[]){ String strA = "helloworld"; String strB = "HelloWorld"; System.out.println(strA.compareTo(strB)); // 32 if(strA.compareTo(strB) > 0){ System.out.println("大于"); } } }
3.4 字符串查找
从一个完整的字符串之中要判断某一个子字符串是否存在
| No. | 方法名称 | 说明 | 描述 | | :--: | :--------------------------------------: | :--: | ---------------------------------------- |
| 1 | public boolean contains(String s) | 普通 | 判断指定的内容是否存在 |
| 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) | 普通 | 由指定位置从后向前查找指定字符串的位置,如果查找到了则返回第一个字母的位置索引;如果找不到则返回-1. |
| 6 | public boolean startsWith(String prefix) | 普通 | 判断是否以指定的字符串开头,如果是则返回true,否则返回false |
| 7 | public boolean startsWith(String prefix, int toffset) | 普通 | 在指定位置以指定字符串开始 |
| 8 | public boolean endsWith(String suffix) | 普通 | 以指定的字符串结尾 |
举例:使用 indexOf() 等功能查找
public class StringDemo{ public static void main(String args[]){ String str = "helloworld"; // 返回满足条件单词的第一个字母的索引 System.out.println(str.indexOf("l")); // 2 System.out.println(str.indexOf("l",5)); // 8 // System.out.println(str.indexOf("world",6)); // -1 System.out.println(str.lastIndexOf("l")); // 8 // System.out.println(str.lastIndexOf("orld",4)); // -1 // System.out.println(str.lastIndexOf("orld",7)); //6 } }
上面的功能只返回了位置。但是在一些程序之中需要查找有没有,最早的做法是判断查询结果是否是 -1 来实现的。
public class StringDemo{ public static void main(String args[]){ String str = "helloworld"; if(str.indexOf("world") != -1){ System.out.println("能查询到数据"); }else{ System.out.println("不能查询到数据"); } } }
但是从 JDK 1.5 及之后出现了 contains() 方法,可直接返回 boolean。
public static void main(String args[]){ String str = "helloworld"; if(str.contains("world")){ System.out.println("能查询到数据"); }else{ System.out.println("不能查询到数据"); } }
使用 contains() 更加的简单,并且在整个 java 里面,contains 已经成为了查找的代名词。
举例:开头或结尾判断
public class StringDemo{ public static void main(String args[]){ String str = "##@@helloworld**"; System.out.println(str.startsWith("##")); System.out.println(str.startsWith("@",2)); System.out.println(str.endsWith("*")); } }
这些开头和结尾的判断往往可以作为一些标记在程序之中出现。
3.5 字符串替换
指的使用一个新的字符串替换掉旧的字符串数据,支持的方法有如下几个:
| No. | 方法名称 | 说明 | 描述 | | :--: | :--------------------------------------: | :--: | -------------- |
| 1 | public String replaceAll(String regex,String replacement) | 普通 | 用新的内容替换掉全部旧的内容 |
| 2 | public String replaceFirst(String regex,String replacement) | 普通 | 替换首个满足条件的内容 |
举例:观察替换的结果
public class StringDemo{ public static void main(String args[]){ String str = "##@@helloworld**"; System.out.println(str.replace("l","U")); // ##@@heUUoworUd** System.out.println(str.replaceFirst("@","!")); // ##!@helloworld** } }
参数 regex 是正则
3.6 字符串截取
从一个完整的字符串之中,截取部分子字符串的数据,支持的方法如下:
| No. | 方法名称 | 说明 | 描述 | | :--: | :--------------------------------------: | :--: | ----------- |
| 1 | public String substring(int beginIndex) | 普通 | 从指定索引截取到结尾 |
| 2 | public String substring(int beginIndex,int endIndex) | 普通 | 截取部分子字符串的数据 |
举例:
public class StringDemo{ public static void main(String args[]){ String str = "##@@helloworld**"; System.out.println(str.substring(4)); // helloworld** System.out.println(str.substring(4,13)); // helloworld } }
不能使用负数作为截取的开始点。
3.7 字符串拆分
将一个完整的字符串,按照指定的内容拆分为字符串数组(对象数组,String 类对象),方法如下:
| No. | 方法名称 | 说明 | 描述 | | :--: | :--------------------------------------: | :--: | ---------------------------------------- |
| 1 | public String[] split(String regex) | 普通 | 按照指定的字符串进行全部拆分 |
| 2 | public String[] split(String regex,int limit) | 普通 | 按照指定的字符串进行部分拆分,最后的数组长度由 limit 决定(如果能拆分的结果很多,数组长度才由 limit 决定),即前面拆后面不拆作为整体。 |
举例:全部拆分
String str = "hello world nihao ma"; String data [] = str.split(" "); for(int i=0; i<data.length; i++){ System.out.println(data[i]); }
如果在拆分的时候只是写了一个空字符串(“” 不是null),表示按照每一个字符进行拆分。
举例:部分拆分
String str = "hello world nihao ma"; String data[] = str.split(" ",2); for(int i=0; i<data1.length; i++){ System.out.println(data1[i]); }
举例:实现IPv4地址拆分
String str = "192.68.15.238"; String data[] = str.split("."); for(int i=0; i<data.length; i++){ System.out.println(data[i]); }
如果写成上面,你会发现无法拆分。如果是一些敏感字符(正则标记),严格来讲是拆分不了的。如果真的遇见拆分不了的就使用 " \\ \\(就是 \\ )“,进行转义后才可以拆分。
字符串的拆分是非常常见的,因为很多时候会传递一组数据到程序中。现在有如下的一个字符串:”张三:20|李四:21|王五:22“ (姓名:年龄|)。
String str = "张三:20|李四:21|王五:22"; String data[] = str.split("\\\\|"); for(int i=0; i<data.length; i++){ String temp []= data[i].split(":"); System.out.println("姓名: " + temp[0]+", 年龄: " + temp[1]); }
3.8 其他操作
以上给出的方法是可以归类的,但是 String 类里面有部分代码是没法归类的。
| No. | 方法名称 | 说明 | 描述 | | :--: | :------------------------------: | :--: | ---------------------------- |
| 1 | public String concat(String str) | 普通 | 字符串的连接,与 ” + “ 类似 |
| 2 | public String toLowerCase() | 普通 | 转小写 |
| 3 | public String toUpperCase() | 普通 | 转大写 |
| 4 | public String trim() | 普通 | 去掉字符串中左右两边的空格 |
| 5 | public int length() | 普通 | 取得字符串的长度 |
| 6 | public String intern() | 普通 | 对象入池 |
| 7 | public boolean isEmpty() | 普通 | 判断是否是空字符串(不是 null,而是”“,长度为0) |
举例:转小写与大写
String strA = "hello!!!&&"; System.out.println(strA.toUpperCase());
所有的非字母数据不会进行任何的转换操作。
String str = " hello world "; System.out.println("【" + str + "】"); System.out.println("【" + str.trim() + "】"); String str = " hello world ";
一般在用户进行输入的时候有可能会携带有无用的空格内容,那么接收到这些数据后就需要消除掉所有的无用空格。
举例:取得字符串的长度
String str = " hello world "; System.out.println(str.length());
用途:由于用户输入的数据长度是有限制的,可以利用此方式判断。数组中也有一个 length 属性,但是调用的形式不同。
- 数组对象.length;
- String对象.length();
举例:判断是否是空字符串
String str = " hello world "; System.out.println(str.isEmpty()); // false System.out.println("".isEmpty()); // true
以后如果觉得 isEmpty() 不方便,可以使用 ” ”“.isEquals(str); “。
String 类提供了大量的支持的方法,但是却少了一个重要的方法 —— initcap() 功能:首字母大写其余字母小写,这样的功能只能自己实现。
举例:
public class StringDemo{ public static void main(String args[]){ String str = "hEllo world"; System.out.println(initcap(str)); } public static String initcap(String str){ String strA = str.substring(0,1); String strB = str.substring(1); return strA.toUpperCase() + strB.toLowerCase(); } }
虽然 Java 的类库里没有此功能,但是一些第三方的组件包会提供,例如:Apache。