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。