深入理解Java的String类

简介: 温故而知新~
文章已收录Github精选,欢迎Starhttps://github.com/yehongzhi/learningSummary

String类

在Java中String类的使用的频率可谓相当高。它是Java语言中的核心类,在java.lang包下,主要用于字符串的比较、查找、拼接等等操作。如果要深入理解一个类,最好的方法就是看看源码:

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

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

从源码中,可以看出以下几点:

  • String类被final关键字修饰,表示String类不能被继承,并且它的成员方法都默认为final方法。
  • String类实现了Serializable、CharSequence、 Comparable接口。
  • String类的值是通过char数组存储的,并且char数组被private和final修饰,字符串一旦创建就不能再修改。

下面通过几个问题不断加深对String类的理解。

问题一

上面说字符串一旦创建就不能再修改,String类提供的replace()方法不就可以替换修改字符串的内容吗?

实际上replace()方法并没有对原字符串进行修改,而是创建了一个新的字符串返回,看看源码就知道了。

public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
        int len = value.length;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */
        while (++i < len) {
            if (val[i] == oldChar) {
                break;
            }
        }
        if (i < len) {
            char buf[] = new char[len];
            for (int j = 0; j < i; j++) {
                buf[j] = val[j];
            }
            while (i < len) {
                char c = val[i];
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            //创建一个新的字符串返回
            return new String(buf, true);
        }
    }
    return this;
}

其他方法也是一样,无论是sub、concat还是replace操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。

问题二

为什么要使用final关键字修饰String类?

首先要讲final修饰类的作用,被final修饰的类不能被继承,类中的所有成员方法都会被隐式地指定为final方法。也就是不能拥有子类,成员方法也不能被重写。

回到问题,String类被final修饰主要基于安全性和效率两点考虑。

  • 安全性

因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

String被许多的Java类(库)用来当做参数,比如网络连接地址URL,文件路径path,还有反射机制所需要的String参数等,假若String不是固定不变的,将会引起各种安全隐患。

  • 效率

字符串不变性保证了hash码的唯一性,因此可以放心的进行缓存,这也是一种性能优化手段,意味着不必每次都取计算新的哈希码。

只有当字符串是不可变的,字符串池才有可能实现,字符串常量池是java堆内存中一个特殊的存储区域,当创建一个String对象,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。

字符串常量池

字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,所以在实例化字符串的时候使用字符串常量池进行优化。

池化思想其实在Java中并不少见,字符串常量池也是类似的思想,当创建字符串时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。

我们可以写个简单的例子证明:

public static void main(String[] args) throws Exception {
    String s1 = "abc";
    String s2 = "abc";
    System.out.println(s1 == s2);//true
}

还有一个面试中经常问的,new String(“abc”)创建了几个对象?

这可能就是想考你对字符串常量池的理解,我一般回答是一个或者两个对象。

如果之前"abc"字符串没有使用过,毫无疑问是创建两个对象,堆中创建了一个String对象,字符串常量池创建了一个,一共两个。

如果之前已经使用过了"abc"字符串,则不会再在字符串常量池创建对象,而是从字符串常量缓冲区中获取,只会在堆中创建一个String对象。

String s1 = "abc";
String s2 = new String("abc");
//s2这行代码,只会创建一个对象

字符串拼接

字符串的拼接在Java中是很常见的操作,但是拼接字符串并不是简简单单地使用"+"号即可,还有一些要注意的点,否则会造成效率低下。

比如下面这段代码:

public static void main(String[] args) throws Exception {
    String s = "";
    for (int i = 0; i < 10; i++) {
        s+=i;
    }
    System.out.println(s);//0123456789
}

在循环内使用+=拼接字符串会有什么问题呢?我们反编译一下看看就知道了。

其实反编译后,我们可以看到String类使用"+="拼接的底层其实是使用StringBuilder,先初始化一个StringBuilder对象,然后使用append()方法拼接,最后使用toString()方法得到结果。

问题在于如果在循环体内使用+=拼接,会创建很多临时的StringBuilder对象,拼接后再调用toString()赋给原String对象。这会生成大量临时对象,严重影响性能。

所以在循环体内进行字符串拼接时,建议使用StringBuilder或者StringBuffer类,例子如下:

public static void main(String[] args) throws Exception {
    StringBuilder s = new StringBuilder();
    for (int i = 0; i < 10; i++) {
        s.append(i);
    }
    System.out.println(s.toString());//0123456789
}

StringBuilder和StringBuffer的区别在于,StringBuffer的方法都被sync关键字修饰,所以是线程安全的,而StringBuilder则是线程不安全的(效率高)。

总结

回顾一下,本文介绍了String类的不可变的特点,还有字符串常量池的作用,最后简单地从JVM编译的层面对字符串拼接提出一点建议。所谓温故而知新,即使是一些很基础很常见的类,如果深入去探索的话,也会有一番收获。

这篇文章就讲到这里了,感谢大家的阅读,希望看完大家能有所收获!

我是一个努力让大家记住的程序员。我们下期再见!!!

能力有限,如果有什么错误或者不当之处,请大家批评指正,一起学习交流!
相关文章
|
12天前
|
Kubernetes jenkins 持续交付
从代码到k8s部署应有尽有系列-java源码之String详解
本文详细介绍了一个基于 `gitlab + jenkins + harbor + k8s` 的自动化部署环境搭建流程。其中,`gitlab` 用于代码托管和 CI,`jenkins` 负责 CD 发布,`harbor` 作为镜像仓库,而 `k8s` 则用于运行服务。文章具体介绍了每项工具的部署步骤,并提供了详细的配置信息和示例代码。此外,还特别指出中间件(如 MySQL、Redis 等)应部署在 K8s 之外,以确保服务稳定性和独立性。通过本文,读者可以学习如何在本地环境中搭建一套完整的自动化部署系统。
38 0
|
14天前
|
安全 Java API
告别繁琐编码,拥抱Java 8新特性:Stream API与Optional类助你高效编程,成就卓越开发者!
【8月更文挑战第29天】Java 8为开发者引入了多项新特性,其中Stream API和Optional类尤其值得关注。Stream API对集合操作进行了高级抽象,支持声明式的数据处理,避免了显式循环代码的编写;而Optional类则作为非空值的容器,有效减少了空指针异常的风险。通过几个实战示例,我们展示了如何利用Stream API进行过滤与转换操作,以及如何借助Optional类安全地处理可能为null的数据,从而使代码更加简洁和健壮。
42 0
|
2天前
|
Java
java的类详解
在 Java 中,类是面向对象编程的核心概念,用于定义具有相似特性和行为的对象模板。以下是类的关键特性:唯一且遵循命名规则的类名;描述对象状态的私有属性;描述对象行为的方法,包括实例方法和静态方法;用于初始化对象的构造方法;通过封装保护内部属性;通过继承扩展其他类的功能;以及通过多态增强代码灵活性。下面是一个简单的 `Person` 类示例,展示了属性、构造方法、getter 和 setter 方法及行为方法的使用。
|
6天前
|
Java API 开发者
【Java字节码操控新篇章】JDK 22类文件API预览:解锁Java底层的无限可能!
【9月更文挑战第6天】JDK 22的类文件API为Java开发者们打开了一扇通往Java底层世界的大门。通过这个API,我们可以更加深入地理解Java程序的工作原理,实现更加灵活和强大的功能。虽然目前它还处于预览版阶段,但我们已经可以预见其在未来Java开发中的重要地位。让我们共同期待Java字节码操控新篇章的到来!
|
4天前
|
Java
Java 对象和类
在Java中,**类**(Class)和**对象**(Object)是面向对象编程的基础。类是创建对象的模板,定义了属性和方法;对象是类的实例,通过`new`关键字创建,具有类定义的属性和行为。例如,`Animal`类定义了`name`和`age`属性及`eat()`、`sleep()`方法;通过`new Animal()`创建的`myAnimal`对象即可调用这些方法。面向对象编程通过类和对象模拟现实世界的实体及其关系,实现问题的结构化解决。
|
4天前
|
Java API 开发者
【Java字节码的掌控者】JDK 22类文件API:解锁Java深层次的奥秘,赋能开发者无限可能!
【9月更文挑战第8天】JDK 22类文件API的引入,为Java开发者们打开了一扇通往Java字节码操控新世界的大门。通过这个API,我们可以更加深入地理解Java程序的底层行为,实现更加高效、可靠和创新的Java应用。虽然目前它还处于预览版阶段,但我们已经可以预见其在未来Java开发中的重要地位。让我们共同期待Java字节码操控新篇章的到来,并积极探索类文件API带来的无限可能!
|
15天前
|
机器学习/深度学习 人工智能 算法
探索人工智能在医疗诊断中的应用与挑战Java编程中的对象和类:基础与实践
【8月更文挑战第27天】随着人工智能(AI)技术的飞速发展,其在医疗领域的应用日益广泛。本文深入探讨了AI技术在医疗诊断中的具体应用案例,包括图像识别、疾病预测和药物研发等方面,并分析了当前面临的主要挑战,如数据隐私、算法偏见和法规限制等。文章旨在为读者提供一个全面的视角,理解AI在改善医疗服务质量方面的潜力及其局限性。
|
16天前
|
Java Spring 容器
Java获取接口的所有实现类方法
这篇文章介绍了在Java中获取接口所有实现类的方法,包括使用JDK的ServiceLoader(SPI机制)和Spring Boot中的@Autowired自动注入及ApplicationContextAware接口两种方式。
37 1
|
2天前
|
Java 程序员
Java编程中的对象和类: 初学者指南
【9月更文挑战第9天】在Java的世界中,对象和类构成了编程的基石。本文将引导你理解这两个概念的本质,并展示如何通过它们来构建你的程序。我们将一起探索类的定义,对象的创建,以及它们如何互动。准备好了吗?让我们开始这段Java的旅程吧!
|
8天前
|
存储 C++
C++(五)String 字符串类
本文档详细介绍了C++中的`string`类,包括定义、初始化、字符串比较及数值与字符串之间的转换方法。`string`类简化了字符串处理,提供了丰富的功能如字符串查找、比较、拼接和替换等。文档通过示例代码展示了如何使用这些功能,并介绍了如何将数值转换为字符串以及反之亦然的方法。此外,还展示了如何使用`string`数组存储和遍历多个字符串。