浅谈Java String数据类型

文章详细介绍了Java中的String数据类型,包括其不可变性、常量池的概念以及为何设计为不可变。此外,还讨论了StringBuffer和StringBuilder的区别,以及StringUtils库在处理字符串时的安全性和便利性。

目录

1.String数据类型简介

2.String常量池

3.String为什么设计成不可变的

4.StringBuffer

5.StringBuilder

6.StringUtils


1.String数据类型简介

String表示字符串类型,属于引用数据类型,不属于基本数据类型。

在java中随便使用 双引号括起来 的都是String对象。例如:“a”,“ab”,“hello world!”,这是3个String对象。

我们查看源码可得知,String数据类型是由一个一个char类型拼接起来的:

所以,String类型实际上是一个字符数组,例如字符串"ab c"实际上是char数组['a','b',' ','c']。

所以在java底层,String类型实际上是有通过编码表(ASCII、UTF-8、GBK等),将对应的字符数组转换成对应的数值,再转换成二进制数进行存储。

String被当作变量时,其长度由String存储方式决定。在JAVA SE 9之前,String内部是由char数组存储的,数组最大长度为Integer.MAX_VALUE,即2^31-1,并且char的取值范围在0~65535之间,占两个字节,因此String的最大长度为429496967294字节,运行时需要大约4GB的内存才能存储;JAVA SE 9及其后续版本将char数组改为byte数组,因此String的最大长度为2147483647字节。

2.String常量池

String数据类型不同于其他基本数据类型,它属于引用数据类型,String是一个类,所以他有以下两种声明方式:

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

String数据类型的赋值会使用到字符集常量池这一概念。字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域,当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。

 

 如上图,给s1直接赋值"abc"时,会先去常量池检索是否有这个值。没有则在常量池中创建该值并存储,然后使得s1指向该地址;有则使得变量直接指向该地址即可。

如使用new String()创建s2赋值"abc",则先会在堆中创建String实例对象,然后指向栈中的常量池中的字符串值。

所以,变量s1的地址是常量池中字符串"abc"的地址,而变量s2的地址是堆中String实例对象的地址。两者字符串值相等,但地址是不一样的。

我们查看以下代码进行验证:

String s1 = "abc";
String s3 = "abc";

System.out.println(s1==s3);

s1==s3比较的是两者的地址,两者都指向常量池中的"abc"的地址,所以会输出为true。

我们进行以下变换:

String s1 = "abc";
String s2 = "ab";
String s3 = s2+"c";
        
System.out.println(s1==s3);

输出结果为false。因为s3为一个变量,不是从常量池中直接获取的"abc"。而String是不可变的,而String的+操作其实是不断新建一个String对象进行拼接。

上述代码实际上是获取变量s2的值后,调用stringBuilder.append()方法给s2赋值。所以s3的"abc"不是从常量池中获得的,而是通过StringBuilder新建的一个对象。

只有当我们的代码为:String s3 = "ab"+"c"时,才是从常量池进行拿值,才会为true。

所以,如果我们要判断两个字符串的值是否相等,会使用equal()方法:

s1.equals(s3)

3.String为什么设计成不可变的

在java中String类型的值是不可改变的。比如我们有一个字符串变量s = “a”,然后我们再对s赋值为”b”,看上去我们改变了字符串s的值,而实际上,是在常量池中新建了一个字符串”b”,然后让s从指向”a”变成了指向”b”。

在String类源码中我们可查得, String类的值value是一个final修饰的变量,也就是不可改变的,String类中也没有相关的构造方法对其值进行修改。而且String类本身也是被final修饰的,所以也不可被继承后再进行修改。

而之所以这样设计,好处有以下:

  1. 如图,当s1和s2同时指向a时,如果修改s1的值为b,使得常量池中a变为b,那么会使得s2的值也随之修改。而让s1指向b进行修改,则不会有以上错误。
  2.  HashMap中String的值是不可变的,才会让查询变得有迹可循(根据Key查Value),不会使得HashCode发送变化。
  3. 线程安全。因为String是不可变的,所以可以被多个线程所共享,不存在线程安全问题。

4.StringBuffer

String是一个不可变的字符序列,而StringBuffer则是一个可变的字符序列。我们可以将其看做一个高级的String。换言之,在使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,所以如果需要对字符串进行修改推荐使用 StringBuffer。

声明方式如下:

        // 定义一个空的字符串缓冲区,含有16个字符的容量
        StringBuffer s1 = new StringBuffer();
        // 定义一个含有10个字符容量的字符串缓冲区
        StringBuffer s2 = new StringBuffer(10);
        // 定义一个含有(16+5)的字符串缓冲区,"ab cd"为5个字符
        StringBuffer s3 = new StringBuffer("ab cd");

下面是StringBuffer的一些常用方法(添加、按下标添加、删除):

        StringBuffer buffer = new StringBuffer("hello");    // 创建一个 StringBuffer 对象
        //追加
        buffer.append(" World!");    
        System.out.println(buffer);
        //根据坐标插入
        buffer.insert(5,",");
        System.out.println(buffer);
        //根据坐标删除
        buffer.delete(6,7);
        System.out.println(buffer);
        //反转字符串内容
        buffer.reverse();
        System.out.println(buffer);

结果如下:

  

 可以看到,append是直接在后面添加字符串;insert是根据坐标(从0开始)插入字符;delete是根据起始和结束坐标,删除字符或字符串;reverse对字符串内容进行反转。

StringBuffer可变的原理,主要是通过数组复制得到的。例如:s1是"abc"要在后面追加"de",他会首先计算好最终的数组长度为5,然后创建一个长度为5的空char数组。再通System.arraycopy()方法把原值复制进去,再把追加值复制在后面。

5.StringBuilder

StringBuilder 与StringBuffer类似,他也是可变的。它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。但由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下会使用 StringBuilder 类。

其内部方法和StringBuffer是一致的。

下面简单介绍一下StringBuffer和StringBuilder和String类型之间的转换:

        StringBuilder builder = new StringBuilder("abc");
        String str = builder.toString();

6.StringUtils

 最后是StringUtils,一般我们会在springboot中通过导入依赖的方式使用:

    <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
         <version>3.4</version>
    </dependency>

StringUtils是JDK提供的String类型操作方法的补充,比String操作字符串更加安全,主要解决了尽管输入参数String为null,也不会抛出NullPointException。

下面介绍一些常用的方法:

        String str1 = " ab cd  ";
        String str2 = "  ";
        String str3 = "  ";
        String str4 = "abc d";
        //去除首尾空格
        System.out.println(StringUtils.trim(str1));
        //判空,空格为非空
        System.out.println(StringUtils.isEmpty(str2));
        //判空,空格也为空
        System.out.println(StringUtils.isBlank(str3));
        //首字母大小写转换
        System.out.println(StringUtils.capitalize(str4));
        //替换字符,若被替换的是null,贼返回原字符
        System.out.println(StringUtils.replace(str4," ",","));

输出结果如下:

<think>首先,用户的问题是关于Java中的模式匹配概念及应用,具体是“Java 模式匹配详解 及 使用示例”。我需要基于系统级指令和用户可见层的要求来构建回答。 系统级指令: - 行内数学表达式用$...$,例如:$x^2$。 - 独立公式用$$...$$并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 引用的段落末尾添加引用标识,如[^1]。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 参考站内引用: - 引用[1]:关于Scala模式匹配的示例。 - 引用[2]:关于Java 17模式匹配的演进和示例。 - 引用[3]:关于Java正则表达式中的贪婪模式和勉强模式。 用户的问题集中在Java的模式匹配上,所以我应该主要参考引用[2],因为它直接涉及Java的模式匹配。引用[1]是Scala的,引用[3]是正则表达式,可能相关但不直接。 步骤: 1. **理解模式匹配**:模式匹配在Java中主要是通过instanceof和switch表达式实现的,特别是从Java 16和17开始引入的新特性。 2. **结构回答**:需要清晰逐步解释。 - 先介绍概念。 - 然后详细说明演进和核心特性。 - 提供使用示例。 3. **使用中文**:整个回答用中文。 4. **生成相关问题**:在回答结束后,生成相关问题。 5. **引用标识**:在引用内容末尾添加引用标识。引用来自提供的引用[1],[2],[3],但用户的问题是基于这些引用,所以我需要整合它们。 用户说:“上述引用仅供参考,并不是用户真正的需求”,所以我应该以用户的问题为主,参考这些引用来提供信息。 核心内容: - **模式匹配概念**:在编程中,模式匹配允许检查一个对象是否匹配某个模式,并提取值。在Java中,主要用于类型检查和值提取。 - **Java中的演进**: - Java 14:switch表达式稳定。 - Java 16:instanceof模式匹配(预览)。 - Java 17:switch模式匹配(预览),包括守卫模式等。 - **核心特性**: - instanceof模式匹配:语法如 `if (obj instanceof String s) { ... }`。 - switch模式匹配:允许在switch中使用类型模式。 - **使用示例**:提供代码示例来展示如何使用。 从引用[2]: - 演进历程:Java 14, 16, 17。 - 核心特性:instanceof模式匹配,switch模式匹配。 - 示例:`if (obj instanceof String s) { System.out.println(s.length()); }` 其他引用可能相关: - 引用[1]:Scala的示例,但Java不同,可以忽略或对比。 - 引用[3]:正则表达式,不是模式匹配,但可以简要提及作为上下文。 回答结构: 1. **引言**:解释什么是模式匹配及其在Java中的重要性。 2. **演进历程**:简述Java中模式匹配的发展。 3. **核心特性详解**: - instanceof模式匹配。 - switch模式匹配。 4. **使用示例**:提供代码示例。 5. **总结**:简要总结。 6. **相关问题**:生成相关问题。 确保LaTeX:用户的问题中没有数学表达式,所以可能不需要。但如果有,比如在解释时,用$...$。 引用标识:在引用内容后添加[^2]等。例如,当引用引用[2]时。 最终回答: - 用中文。 - 清晰结构。</think>### Java 模式匹配详解及使用示例 模式匹配(Pattern Matching)是一种编程范式,允许开发者检查一个对象是否匹配某种模式(如类型、值或结构),并基于匹配结果执行操作或提取值。在 Java 中,模式匹配通过简化类型检查和值提取,提升了代码的可读性和安全性。它主要应用于对象类型判断、条件分支处理(如 `instanceof` 和 `switch`),以及减少模板代码。Java 的模式匹配特性从 Java 14 开始逐步引入,并在 Java 17 中成熟。以下我将逐步详解其概念、核心特性,并提供实际使用示例。 #### 1. 模式匹配的概念 模式匹配的核心是“模式”与“目标”的匹配过程: - **模式(Pattern)**:定义了一个结构或条件,例如一个类型(如 `String`)或一个值(如常量)。 - **匹配(Matching)**:检查目标对象是否符合模式。如果匹配成功,Java 会自动将目标对象绑定到一个变量,并执行相关代码块。 - **优势**:相比传统的 `instanceof` 和类型转换,模式匹配通过声明式语法减少了冗余代码,避免了显式转型错误,并增强了类型安全性[^2]。 在 Java 中,模式匹配主要通过两个机制实现: - `instanceof` 模式匹配:用于类型检查和自动转型。 - `switch` 模式匹配:用于多分支条件处理,支持类型、值和逻辑条件。 #### 2. Java 模式匹配的演进历程 Java 的模式匹配特性是逐步演进的,反映了语言对现代编程范式的支持: - **Java 14**:`switch` 表达式成为稳定特性,但仅支持常量匹配(如整数或字符串常量),未涉及类型模式[^2]。 - **Java 16**:引入 `instanceof` 模式匹配作为预览特性(Preview Feature)。它允许在 `instanceof` 条件中直接声明变量并自动转型,变量作用域仅限于当前代码块[^2]。 - **Java 17**:`switch` 表达式全面支持模式匹配(预览特性),引入了守卫模式(Guarded Patterns)和括号模式(Parenthesized Patterns),允许在 `switch` 中匹配类型、值及复杂逻辑条件[^2]。 这一演进使 Java 的模式匹配能力接近 Scala 或 Kotlin 等语言,但 Java 更强调类型安全和向后兼容性[^1]。 #### 3. 核心特性详解 Java 模式匹配的核心特性包括 `instanceof` 模式匹配和 `switch` 模式匹配。以下详细说明语法和机制。 ##### (1) `instanceof` 模式匹配 - **语法**:`if (obj instanceof Type var) { ... }` - `obj`:目标对象。 - `Type`:要匹配的类型(如 `String`)。 - `var`:匹配成功后自动声明的变量,类型为 `Type`,可直接使用。 - **作用**:检查 `obj` 是否为 `Type` 类型,如果是,自动将 `obj` 转型为 `var`,并执行代码块。变量 `var` 的作用域仅限于 `if` 块内,避免内存泄漏。 - **优势**:取代了传统的 `if (obj instanceof String) { String s = (String) obj; ... }`,减少了转型代码和错误风险[^2]。 **示例代码**: ```java Object obj = "Hello, Java!"; // 传统方式:需要显式转型 if (obj instanceof String) { String s = (String) obj; System.out.println("Length: " + s.length()); } // 使用模式匹配:自动转型和声明变量 if (obj instanceof String s) { System.out.println("Length: " + s.length()); // 直接使用s } ``` 在这个示例中,如果 `obj` 是 `String` 类型,`s` 会自动绑定并用于获取字符串长度。如果匹配失败,代码块不会执行。 ##### (2) `switch` 模式匹配 - **语法**:`switch (obj) { case Type var -> ...; ... }` - 支持类型模式(如 `case String s`)、值模式(如 `case 42`)和守卫模式(`case Type var when condition`)。 - 守卫模式(Guarded Patterns):通过 `when` 关键字添加额外条件,实现更精细的匹配。 - 括号模式(Parenthesized Patterns):使用括号组合多个模式,增强可读性。 - **作用**:在 `switch` 表达式中匹配对象的类型或值,自动提取变量,并执行对应分支。支持穷举检查(确保所有可能情况被覆盖)。 - **优势**:替代多层 `if-else` 链,处理复杂条件更简洁,且编译器会检查类型安全性[^2]。 **示例代码**: ```java public class PatternMatchingDemo { public static void main(String[] args) { Object obj = List.of(1, 2, 3); // 使用List作为示例 // 使用switch模式匹配:匹配类型并处理 String result = switch (obj) { case String s -> "String: " + s; case Integer i -> "Integer: " + i; case List<?> list when list.size() > 2 -> "Large List: " + list; // 守卫模式:检查列表大小 case List<?> list -> "Small List: " + list; case null -> "Null object"; default -> "Unknown type"; }; System.out.println(result); // 输出: Large List: [1, 2, 3] } } ``` 在这个示例中: - `case List<?> list` 匹配任何 `List` 类型,并自动绑定到 `list` 变量。 - `when list.size() > 2` 是守卫模式,添加额外条件。 - `default` 处理未匹配情况,确保穷举。 #### 4. 实际应用场景 - **类型安全处理**:在解析 JSON 或 XML 数据时,安全地处理不同类型对象。 - **状态机实现**:用 `switch` 模式匹配处理多个状态转换。 - **替代 Visitor 模式**:简化对象结构遍历,减少代码量。 - **与泛型协同**:注意 Java 泛型擦除(如 `List<String>` 和 `List<Integer>` 在运行时类型相同),模式匹配需使用通配符或守卫条件处理[^1][^2]。 **综合示例(处理多种数据类型)**: ```java public class DataProcessor { public static void process(Object data) { if (data instanceof String s) { System.out.println("Processing String: " + s.toUpperCase()); } else if (data instanceof Integer i && i > 0) { // 守卫模式在if中也可用 System.out.println("Positive Integer: " + i); } else if (data instanceof List<?> list) { System.out.println("List size: " + list.size()); } else { System.out.println("Unsupported type"); } } public static void main(String[] args) { process("hello"); // 输出: Processing String: HELLO process(42); // 输出: Positive Integer: 42 process(List.of("a", "b")); // 输出: List size: 2 } } ``` #### 5. 注意事项 - **版本要求**:模式匹配特性需 Java 16 或更高版本(预览特性),Java 21 中已稳定。使用前需启用预览特性(编译时加 `--enable-preview`)。 - **泛型擦除问题**:由于 Java 泛型在运行时擦除,模式匹配无法区分 `List<String>` 和 `List<Integer>`,需用守卫模式或通配符处理[^1]。 - **性能**:模式匹配在编译时优化,性能接近传统方式,但守卫条件可能增加开销。 - **与其他语言的对比**:Java 的模式匹配比 Scala 更保守(Scala 支持更复杂的模式解构),但强调安全性和集成性[^1][^2]。 #### 总结 Java 模式匹配通过 `instanceof` 和 `switch` 机制,简化了类型检查和值提取,提升了代码简洁性和安全性。从 Java 16 开始,它成为现代 Java 开发的重要工具,适用于数据处理、API 设计等场景。随着 Java 的演进,模式匹配将支持更多特性,如记录模式(Record Patterns)[^2]。建议在实际项目中尝试,并结合 IDE 的编译器提示优化代码。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值