Java.lang.String类型

本文详细介绍了Java中字符串的创建、特性和比较方式,并对比了String、StringBuffer和StringBuilder的区别。

1.字符串的创建

常量池

Java中的常量池分为两种形态:静态常量池和运行时常量池。
**静态常量池:*.class文件中的常量池,class文件中的常量池不仅仅包括字符串,还包括类、方法的信息,占用class文件绝大部分空间。
**运行时常量池:**JVM虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中。
我们常说的常量池,就是指方法区中的运行时常量池。

String a="Hello World";

此种创建方式下,编译器首先检查常量池中是否含有此字符串常量,没有则创建,然后JVM会开辟一块堆内存存放一个隐式对象指向该字符串常量,接着在栈空间里创建a变量,a变量指向该块堆内存首地址。

 String a = new String("Hello World");

此种创建方式下,直接在堆内存new一个对象,并在常量池中创建“Hello World”字符串,该对象指向常量池中的“Hello World”字符串,然后创建a变量,并将a变量指向该块堆内存首地址。

2.字符串的特性

String是一个不可变的字符序列

String a1 = new String("Hello");
String a2 = new String("World");
a1 += a2;

此段代码执行时,首先在内存中分配一块空间,它的大小为a1和a2空间大小之和,然后将a1和a2的内容复制到该内存空间相应位置,最后将变量a1指向该内存空间。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
{
    /** The value is used for character storage. */
    private final char value[];

    /** The offset is the first index of the storage that is used. */
    private final int offset;

    /** The count is the number of characters in the String. */
    private final int count;

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    ........
}

从String源码中分析可知,String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法。并且String类是通过char数组来存储字符串。

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > count) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    if (beginIndex > endIndex) {
        throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
    }
    return ((beginIndex == 0) && (endIndex == count)) ? this :
        new String(offset + beginIndex, endIndex - beginIndex, value);
}

分析String类中的substring方法可以发现,该方法的操作并不改变原有字符串的值,而是返回了一个新的字符串对象,这也与String类是一个不可改变的字符序列相吻合。

3.字符串的比较

“==”比较的是两个字符串地址是否相同,而.equals()方法比较的是两个字符串内容是否相同。

情况一:

String s1 = new String("java");
String s2 = new String("java");

System.out.println(s1==s2);            //false
System.out.println(s1.equals(s2));    //true

此段代码创建的是两个对象,他们有不同的堆空间,因此“==”为false;但内容相同,因此.equals()为true。

情况二:

String s1 = new String("java");
String s2 = s1;

System.out.println(s1==s2);            //true
System.out.println(s1.equals(s2));    //true

此段代码中变量s1,s2指向的为同一堆空间,并且字符串内容相同,因此均为true。

情况三:

String s1 = "java";
String s2 = "java";

System.out.println(s1==s2);            //true
System.out.println(s1.equals(s2));    //true

此段代码中,字符串创建方式为隐式创建,即,将String视为基本数据类型,因此,若字符串值相同,则s1,s2指向同一对象;若不同,则指向不同对象。

情况四:

String s0="helloworld";
String s1="helloworld";
String s2="hello"+"world";
System.out.println(s0==s1); //true 可以看出s0跟s1是指向同一个对象 
System.out.println(s0==s2); //true 可以看出s0跟s2是指向同一个对象

此段代码中的s0和s1中的"helloworld”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而"hello”和"world”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中"helloworld”的一个引用。

情况五:

 String s0="helloworld"; 
 String s1=new String("helloworld"); 
 String s2="hello" + new String("world");
 String s3="aaa";
 String s4=s0+s3;
 
 System.out.println( s0==s1 ); //false  
 System.out.println( s0==s2 ); //false 
 System.out.println( s1==s2 ); //false
 System.out.println( s4=="helloworldaaa" ); //false

用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。

s0还是常量池中"helloworld”的引用,s1因为无法在编译期确定,所以是运行时创建的新对象"helloworld”的引用,s2因为有后半部分new String(”world”)所以也无法在编译期确定,所以也是一个新创建对象"helloworld”的引用。

String s4=s0+s3是运行时才可知的,而"helloworldaaa"是在编译时就存放在常量池中的。在之前说过,String的所有函数都是返回的一个新字符串而不改变原字符串,因此+运算符会在堆中建立来两个String对象,这两个对象的值分别是"helloworld"和"aaa",也就是说从字符串池中复制这两个值,接着在堆中创建两个对象,然后再在栈空间建立变量s4,将 "helloworldaaa"的堆地址赋给s4。

情况六:

 String s0 = "ab"; 
 final String s1 = "b"; 
 String s2 = "a" + s1;  
 
 System.out.println((s0 == s2)); // true

s1字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + s1和"a" + "b"效果是一样的。故上面程序的结果为true。

情况七:

String s0 = "ab"; 
final String s1 = getS1(); 
String s2 = "a" + s1; 

System.out.println((s0 == s2)); // false 

private static String getS1() {  
    return "b";   
}

这里虽然将s1用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此s0和s2指向的不是同一个对象,故上面程序的结果为false。

4.String、StringBuffer、StringBuilder的区别

(1)可变与不可变:String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变)。

(2)是否多线程安全:String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是StringBuffer 中的方法大都采用了synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是非线程安全的。

5.关于String str = new String(“abc”)创建了多少个对象?

网上流传的以及一些面试书籍上都说是2个对象,这种说法是片面的。
首先必须弄清楚创建对象的含义,创建是什么时候创建的?这段代码在运行期间会创建2个对象么?毫无疑问不可能,用javap -c反编译即可得到JVM执行的字节码内容:
在这里插入图片描述
很显然,new只调用了一次,也就是说只创建了一个对象。而这道题目让人混淆的地方就是这里,这段代码在运行期间确实只创建了一个对象,即在堆上创建了"abc"对象。而为什么大家都在说是2个对象呢,这里面要澄清一个概念,该段代码执行过程和类的加载过程是有区别的。在类加载的过程中,确实在运行时常量池中创建了一个"abc"对象,而在代码执行过程中确实只创建了一个String对象。

因此,这个问题如果换成 String str = new String(“abc”)涉及到几个String对象?
合理的解释是2个。

<think>嗯,用户的问题是关于Java中将java.lang.String转换为String的方法。这个问题有点奇怪,因为java.lang.String就是String,两者是同一个类型。 用户可能实际遇到了类型转换异常,比如把其他类型错误当成String处理。从提供的引用看,用户之前遇到的是Integer不能转String、BigDecimal不能转String、以及String数组不能转String的问题。 用户引用的三个案例都是关于类型转换错误的解决方案: 1. 用toString()或String.valueOf()转换数值 2. 用Integer.decode()处理对象转换 3. 正确处理String数组的转换 用户现在的查询表述不太准确,可能实际是想问如何正确进行类型转换,避免ClassCastException。需要重点解释java.lang.StringString是等同的,并给出常见的类型转换场景解决方案。 考虑到用户之前遇到的错误案例,应该强调: - 基本类型String的转换 - 对象转String的两种方式 - 数组类型的特殊处理 - 类型检查的重要性 用户可能是Java初学者,对类型系统理解不够深入。需要明确区分"转换"和"强制类型转换"的区别,指出很多情况下的类型错误是因为误用强制转换造成的。 需要特别注意引用中提到的Map参数处理案例,这是Web开发中常见错误点。要提醒用户getParameterMap()返回的是String[]而不是String。 </think> 在 Java 中,`java.lang.String` 就是 `String` 类型本身,它们是**完全等同**的。`String` 是 `java.lang.String` 的简写形式(通过 `import java.lang.*;` 自动导入)。因此,**不存在将 `java.lang.String` “转换”为 `String` 的需求或方法**,因为它们指的是同一个类。 您遇到的错误(例如 `Integer cannot be cast to String`, `BigDecimal cannot be cast to String`, `[Ljava.lang.String; cannot be cast to java.lang.String`)都是由于**试图将其他类型(非 `String` 类型)的对象强制当作 `String` 类型使用**造成的。 根据您提供的引用内容和常见场景,以下是**正确将其他类型转换为 `String` 类型**的方法,这些才是您实际需要解决的**核心问题**: 1. **使用 `toString()` 方法:** * 适用于:对象本身重写了 `toString()` 方法(如 `Integer`, `BigDecimal`, 自定义对象等)。 * 示例: ```java Integer num = 123; String str1 = num.toString(); // 调用对象的 toString() 方法 BigDecimal decimal = new BigDecimal("10.55"); String str2 = decimal.toString(); ``` * **注意:** 如果对象是 `null`,调用 `toString()` 会抛出 `NullPointerException`。 2. **使用 `String.valueOf(Object obj)` 静态方法:** * 适用于:将任何对象(包括 `null`)转换为 `String`。这是**最通用和安全**的方式,尤其当对象可能为 `null` 时。 * 原理:内部处理了 `null` 情况。如果传入 `null`,则返回字符串 `"null"`;否则调用传入对象的 `toString()` 方法。 * 示例: ```java Integer num = 123; String str1 = String.valueOf(num); // "123" BigDecimal decimal = new BigDecimal("10.55"); String str2 = String.valueOf(decimal); // "10.55" Object obj = null; String str3 = String.valueOf(obj); // "null" (不会抛出异常) ``` 3. **处理数组类型(如 `String[]` -> `String`):** * **错误做法:** 直接强制转换 `(String) stringArray` 会导致 `[Ljava.lang.String; cannot be cast to java.lang.String` 错误。 * **正确做法:** 需要根据需求将数组元素连接成一个字符串(拼接)或选择特定元素。 * **示例 (引用[^3]中的解决方案):** ```java Map map = request.getParameterMap(); Set key = map.keySet(); for (Object aaa : key.toArray()) { String parakey = aaa.toString(); // 关键!getParameterMap() 返回的值是 String[] 类型 String paravalue = ((String[]) map.get(aaa))[0]; // 取数组的第一个元素 requestpath = requestpath + "?" + parakey + "=" + paravalue; } ``` * **其他数组处理方式:** * 使用 `Arrays.toString(Object[] a)`: 返回包含数组内容的字符串表示(带 `[` 和 `]`, 元素用 `, ` 分隔)。 * 使用 `String.join(CharSequence delimiter, CharSequence... elements)` 或 `String.join(CharSequence delimiter, Iterable elements)`:将数组元素用指定分隔符合并成一个字符串。 * 手动循环拼接。 4. **处理数值类型转换 (如 `Integer`, `BigDecimal` -> `String`):** * 使用上面提到的 `toString()` 或 `String.valueOf()` 是最直接的方式(引用[^1], [^2])。 * **引用[^2]中额外的方法:** `Integer.decode(String nm)` * 这不是*转换*为 `String`,而是*从 `String` 解析出 `Integer`*。主要用于将表示数字的字符串(如 `"10"`, `"0xFF"`, `"#FF"`, `"077"`)转换成 `Integer` 对象。 * **不适用于**将已有的 `Integer` 对象转成 `String`。 **总结与关键点:** * `java.lang.String` **就是** `String`,无需转换。 * 您遇到的错误源于**试图将非 `String` 类型(`Integer`, `BigDecimal`, `String[]`)强制当作 `String` 类型使用**。 * **正确转换非 `String` 类型为 `String` 的方法:** * `obj.toString()`:对象非 `null` 时安全高效。 * **`String.valueOf(obj)`:最通用、安全(处理 `null`),推荐首选。** * 处理数组:**不能强制转换整个数组**,需取特定元素或拼接元素(如 `arr[0]`, `Arrays.toString(arr)`, `String.join(", ", arr)`)。 * 在从 `Map`(特别是 `request.getParameterMap()`)获取值时,**务必注意返回值的类型**(通常是 `String[]`),并使用正确的方式访问(如 `((String[])value)[0]`)[^3]。 **
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值