Java——String 类

一、String

1、简要介绍

String 是一个用于表示字符串的类,它是Java中最常用的数据类型之一。它用来保存一个字符串,也就是一串字符序列。

同时,String 类是一个 final 类,不能被继承。

2、继承与实现

String 类直接继承 Object 类,同时,String 类实现了 Serializable、Comparable、CharSequence 接口。

实现 Serializable 接口用途:

Serializable 接口是一个标记接口,表示实现该接口的类可以被序列化。序列化是将对象的状态转换为字节流的过程,以便于存储或传输。

对于 String 类来说,实现 Serializable 接口使得字符串对象可以被安全地保存到文件中,或通过网络传输。将字符串对象序列化后存储到文件中或者通过网络发送时,可以方便地恢复原始的字符串对象。

实现 Comparable 接口用途:

Comparable 接口定义了一个比较两个对象的顺序的标准。

实现这个接口允许字符串对象按照字典顺序进行比较,这在排序和搜索时非常有用。

使用 Collections.sort() 方法对字符串列表进行排序时,String 类中的 compareTo(String anotherString) 方法将被调用,以确定字符串的自然顺序。

实现 CharSequence 接口用途:

CharSequence 接口允许 String 类提供对字符序列的访问。

这个接口定义了一组可以用于处理字符序列的方法,如 length()、charAt(int index)、subSequence(int start, int end) 等。

实现这个接口使得String类能够与其他处理字符序列的类(如 StringBuilder、StringBuffer 等)兼容。可以将 String 对象直接传递给需要 CharSequence 类型参数的方法,比如 StringBuilder 的构造函数。

3、构造器

String 类有许多构造器,这里只简要介绍几个常用的构造器:

1)String()

创建一个空字符串对象。

String str = new String();

2)String(String original)

通过复制指定字符串的内容来创建一个新的字符串对象。

String str = new String("Hello");

3)String(char[] value)

通过字符数组创建字符串对象。

char[] chars = {'H', 'e', 'l', 'l', 'o'};
String str = new String(chars);

4)String(byte[] bytes)

通过字节数组创建字符串对象,使用平台的默认字符集。

byte[] bytes = {72, 101, 108, 108, 111}; // 对应 "Hello"
String str = new String(bytes);

5)String(byte[] bytes, int offset, int length)

通过字节数组的一部分创建字符串对象,从指定偏移量开始,指定长度,使用平台的默认字符集。

byte[] bytes = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100};
String str = new String(bytes, 0, 5); // "Hello"

4、字符串存储

String 对象的内容是存储在一个名为 value 的字符数组中,而且这个数组是使用 final 修饰的,意味着一旦被初始化,它的引用不能被改变(只是引用不能修改,value 的每一个元素是可以修改的)。这个设计决定了 String 对象的不可变性,以及其在内存中的表现。

    private final char value[];

当创建一个 String 对象时,JVM 会为其分配内存,并在 value 数组中存储字符。value 数组会根据字符串的字符数动态大小化。字符串的每个字符会被存储为 char 类型,Java 使用 UTF-16 编码来表示字符,因此每个字符占用两个字节。

5、不可变性

由上面的 String 对象的内容是被存储在一个 final 字符数组 value 中的,我们可知,String 对象是不可变的(immutable)。

这意味着一旦创建了一个 String 对象,它的值就不能被改变。如果你对字符串进行任何修改操作,比如拼接、替换等,实际上会生成一个新的 String 对象。

上面说到 value 被 final 修饰只是 value 的引用不能修改,而 value 的每一个元素值可以修改。如果你想要对 String 对象的某个字母进行修改,可以使用 replace 方法,但是这个方法实际上还是会返回一个新的对象:

String original = "Hello";
String modified = original.replace('H', 'J');
// original 仍为 "Hello"
// modified 为 "Jello"

所以说,String 对象设计理念就是不改动 value 数组的内容,即使 final 修饰只是使 value 的引用不能修改,而 value 的每一个元素可以修改。

其实不直接修改源字符串的 value 的某个元素还有一个原因,因为 value 可能指向字符串常量池的某个字符串常量,字符串常量是没法被修改的。

二、字符串常量

1、字符串常量介绍

字符串常量是指在程序中直接使用的固定字符串值。

String str = "Hello, World!";

这里,"Hello, World!" 就是一个字符串常量。字符串常量一旦创建,它的内容不能被修改。

2、字符串常量池

字符串常量池(String Constant Pool)是用于存放字符串常量的特殊内存区域。这个池的主要作用是避免重复创建相同内容的字符串对象,从而节省内存。

3、字符串常量池测试

public class Example {
	public static void main(String[] args) {
		String str1 = "Hello";
		String str2 = "Hello";

		System.out.println(str1 == str2);
	}
}

运行结果:

可以证明 str1 和 str2 指向的是同一个对象。

三、字符串创建方式

这里记住一点:

只要使用了字符串常量,JVM 会先查找常量池中是否有相同的字符串常量,如果有,则什么也不做,如果没有的话,这个字符串常量会放到常量池。

1、直接使用字符串常量赋值

如果常量池没有相同的字符串常量的话,这个字符串常量会被放到常量池中,然后 String 引用变量会指向这个字符串常量。如果常量池中有相同的字符串常量,则 String 引用变量直接指向这个相同的字符串常量。 

	String str = "Hello";

所以这里的 "Hello" 会被放到常量池中。

然后 str 会指向常量池的这个字符串常量,具体到图则是:

2、使用字符串常量加构造器创建

	String str = new String("Hello");

JVM 会先在常量池中找是否有 "Hello" 这个字符串常量

  • 如果有的话,在堆区创建一个 String 对象,然后这个对象的 value 字段指向常量池的 "Hello" 字符串常量。
  • 如果常量池没有 "Hello" 这个字符串常量的话,则会在常量池中创建一个 "Hello" 字符串常量,然后在堆区创建一个 String 对象,然后这个对象的 value 字段指向常量池的 "Hello" 字符串常量。

这里为什么要有找这个字符串常量的动作呢,因为这里使用构造器创建时使用了字符串常量,我们上面提到过使用字符串常量就会导致这一系列动作。

对于上面的例子,我们可以作出以下图:

对于以下例子,

	String str1 = "Hello";
	String str2 = new String("Hello");

我们可以绘出相应的内存布局图:

3、不使用或间接使用字符串常量创建

	char[] chars = {'H', 'e', 'l', 'l', 'o'};
	String str = new String(chars);

这种方式会直接在堆区中创建 String 对象,然后 String 对象的 value 数组是从以上代码中的 chars 数组中复制得到的。

对于这个例子,内存分布图是这样的:

	String str1 = new String("Hello");
	String str2 = new String("World");

	String str = str1 + str2;

这种方式创建,对于 str1 和 str2 的创建方式是上面的第二种方式,也就是使用字符串常量加构造器的创建方式。

对于 str 的创建方式,则比较特别:

		// StringBuilder s = new StringBuilder;
		// s.append(str1);
		// s.append(str2);
		// str = s.toString();

最后 toString 方法会创建一个新的 String 对象,而且常量池中没有与这个 String 对象内容一样的 字符串常量。

四、intern() 方法

1、intern() 方法机制

在 JDK7 之前:

当调用 intern() 方法时,JVM 会检查字符串常量池中是否有与当前字符串内容一样的字符串常量:

  • 如果存在,intern() 方法将返回常量池中那个一样的字符串的引用。
  • 如果不存在,intern() 方法会在常量池中创建一个内容与当前字符串相同的字符串对象,并返回这个新创建字符串的引用。

自 JDK7 向后:

当调用 intern() 方法时,JVM 会查看字符串常量池中是否有与当前字符串内容一样的字符串常量:

  • 如果存在,则直接返回这个字符串常量的引用。
  • 如果不存在,则将当前的 String 对象的引用放到常量池中,然后返回这个引用(这里只是将 当前的 String 对象的引用放到了常量池中,没有创建新的对象)。

这里在 JDK7 之前和 JDK7 及之后有区别的原因是,JDK7 时将字符串常量从方法区移动到了堆区中。

下面举出几个以 JDK7 为环境的案例:

2、案例

1)案例一:常量池中存在一样的字符串常量

public class Example {
	public static void main(String[] args) {
		String str = new String("Hello");
		// 这里使用了字符串常量"Hello",所以它会被放到常量池

		System.out.println(str == str.intern()); // false
		// 由于字符串常量池中有"Hello"常量,所以这里str.intern()返回的
		// 是常量池中的"Hello"常量的引用,而str指向的是堆区的字符串对象,
		// 所以这里的结果是false
	}
}

运行结果:

2)案例二:常量池中没有一样的字符串常量

public class Example {
	public static void main(String[] args) {
		String str1 = new String("Hello");
		String str2 = new String("World");

		String str = str1 + str2;
        // 这里是上面字符串创建方式提到的第三种创建方式
        // 间接使用构造器创建,所以这里的str指向的是一个堆区的
        // 字符串对象,而且常量池中没有与str内容一样的字符串常量

		System.out.println(str == str.intern()); // true
		// 这里由于str指向的是一个新的在堆区的String对象,内容为"HelloWorld"
		// 而在常量池中没有与"HelloWorld"内容一样的字符串常量,所以,str的引用
		// 会被放入到常量池中,然后被返回,所以这里str.intern()返回的还是str,所以
		// 这里会输出true
	}
}

 运行结果:

3)案例三:综合案例

public class Example {
	public static void main(String[] args) {
		String a = "123"; 
		// a指向常量池的"123"
		String b = new String("123"); 
		// b指向堆区的String对象,
		// String对象的value指向常量池的"123"

		System.out.println(a.equals(b)); // true
		System.out.println(a == b); // false
		System.out.println(a == b.intern()); // true
        // 由于常量池中已经有了"123"常量,所以返回的是这个常量的引用,
        // 而a的引用就是这个常量的引用,所以这里结果为true
		System.out.println(b == b.intern()); // false
        // 这里b.intern()返回的是常量池中的"123"常量,对于b指向的则是堆区中的
        // 对象,所以这里是结果false
	}
}

运行结果:

五、String 常用方法

  1. equals(Object obj): 比较两个字符串的内容是否相同,返回布尔值。

  2. equalsIgnoreCase(String anotherString): 比较两个字符串的内容是否相同,忽略大小写,返回布尔值。

  3. length(): 返回字符串的长度,即字符的数量。

  4. indexOf(String str): 返回指定子字符串在字符串中首次出现的位置,如果未找到则返回-1。

  5. lastIndexOf(String str): 返回指定子字符串在字符串中最后一次出现的位置,如果未找到则返回-1。

  6. substring(int beginIndex): 从指定的开始索引返回一个子字符串。

  7. substring(int beginIndex, int endIndex): 返回从指定的开始索引到结束索引之间的子字符串(不包括结束索引)。

  8. trim(): 返回去除字符串前后空格的新字符串。

  9. charAt(int index): 返回指定索引处的字符。

  10. toUpperCase(): 返回一个新的字符串,将原字符串中的所有字符转换为大写。

  11. toLowerCase(): 返回一个新的字符串,将原字符串中的所有字符转换为小写。

  12. concat(String str): 将指定字符串连接到原字符串的末尾,返回新的字符串。

  13. compareTo(String anotherString): 按字典顺序比较两个字符串,返回一个整数,表示它们的相对顺序。

  14. toCharArray(): 将字符串转换为一个字符数组。

  15. format(String format, Object... args): 使用指定的格式字符串和参数生成格式化的字符串。

六、String 的特性

1、常量折叠

public class Example {
	public static void main(String[] args) {
		String str = "Hello" + "World";
	}
}

由于在编译时已经确定了这个字符串的内容,Java 编译器在编译阶段会对这些字符串进行优化,将其拼接成一个字符串常量 "HelloWorld" 。最终 JVM 只会在字符串常量池中只创建一个字符串对象 "HelloWorld" 。

因此,最终的结果是,str 引用的是常量池中唯一的 "HelloWorld" 对象,而不是创建两个不同的字符串对象,最后再产生一个拼接的新的字符串对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值