【Java】String类

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【Java】String类

1.字符串常量池

1.1 创建对象的思考

public class Test {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "hello";
        String str3 = new String("hello");
        String str4 = new String("hello");
        System.out.println(str1 == str2);
        System.out.println(str3 == str4);
        System.out.println(str1 == str3);
    }
}

运行结果:

去.png



大家看到运行结果后,可能会想这样一个问题,为什么 str1 == str2 是 true,而 str3 == str4 却是false 呢?


这就涉及到了常量池的概念,学习了常量池,这个问题也就迎刃而解了


在Java程序中,为了让程序的运行速度更快、更节省空间,Java 为 8 种基本数据类型和 String类都提供了常量池


常量池,肯定是存放常量的,那究竟什么是池了?


给大家举一个例子,大家也就明白了


比如: 小和尚打水


情况一:假设寺庙里面没有水缸,每次小和尚需要水的时候都要下山去打水


情况二:假设寺庙里面有水缸, 水缸里面打满了水,每次小和尚需要水的时候直接去水缸中取


情况二,就是池化技术的一种示例,水放在水缸中,随用随取,效率非常高


为了让程序的运行速度更快、更节省空间,Java中引入了:


Class文件常量池:每个 .Java 源文件编译后生成 .class 文件中都会保存当前类中的字面常量以及符号信息

运行时常量池:在 .class 文件被加载时,.class 文件中的常量池被加载到内存中称为运行时常量池,运行时常量池每个类都有一份

字符串常量池

我们本期讲的字符串所以主要讲的是字符串常量池


1.2 字符串常量池

字符串常量池 在 JVM 中是 StringTable 类,实际是一个固定大小的 HashTable,在不同 JDK 版本下字符串常量池的位置以及默认大小是不同的:

请.png



1.3 再谈创建对象

现在大多数人用的 jdk 版本都是 java8,那我们就拿 java8 来详谈


1.直接使用字符串常量进行赋值


public class Test {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "hello";
        System.out.println(str1 == str2);
    }
}

运行结果:

其.png



分析图:


前.png


"hello" 它是一个字符串常量,所以它需要放入字符串常量池。因为我们用的 jdk 版本都是 java8,所以它字符串常量池的位置在堆中。我们还知道双引号引起来的字符串是字符串对象,并且字符串在内存中是按数组存放的,所以编译器会自动创建一个字符串对象,并指向这个按数组存放的字符串。因为它是字符串常量所以需要放入字符串常量池中,但是不会在字符串常量池中直接存入编译器自动生成的字符串对象的地址,而是借助一个叫哈希桶的工具去存放这个字符串对象的地址,然后把哈希桶的地址存放在字符串常量池中。


当执行 str1="hello" 的时候直接把 "hello" 放进了字符串常量池中。当执行到 str2 = "hello" 的时候会去字符串常量池中看有没有这个字符串常量,如果有直接存放它的字符串对象的地址,如果没有就去把这个字符串常量入池,然后在存放它的字符串对象的地址


注:只会开辟一块堆内存空间,保存在字符串常量池中,然后str共享常量池中的String对象


2.通过new创建String类对象


public class Test {
    public static void main(String[] args) {
        String str1 = new String("hello");
        String str2 = new String("hello");
        System.out.println(str1 == str2);
    }
}

运行结果:


我.png


分析图:


为.png


编译器会自动为字符串常量生成一个字符串对象然后指向这个存储字符串的数组,然后利用哈希桶存放这个字符串对象的地址,将这个哈希桶的地址放入字符串常量池中。


String str1 = new  String("hello") ,new 肯定会创建一个对象,这个对象 vlue 属性中存放的是"hello"这个字符串常量的数组地址,str1里面存放的就是这个new出来的对象地址。


String str2 = new  String("hello") ,new 肯定会创建一个对象,这个对象 vlue 属性中存放的是"hello"这个字符串常量的数组地址,str2里面存放的就是这个new出来的对象地址。


注:只要是 new 就会产生一个新的对象 ,直接使用字符串常量进行赋值自动创建String类型对象的效率更高,而且更节省空间


学习完 直接使用字符串常量进行赋值 和 通过new创建String类对象,我们也就知道了上述提到的为什么 str1 == str2 是 true,而 str3 == str4 却是false 呢?


答:因为str1和str2是直接使用字符串常量进行赋值,str1和str2里面存储是编译器自动生成的字符串常量的对象,所以 str1 == str2 是 true。str3和str4是通过new创建String类对象,会分别创建一个对象,然后指向存储字符串的数组,str1和str2里面存储是通过new创建String类对象的地址,所以 str3 == str4 是 false


注:会开辟两块堆内存空间,字符串"hello"保存在字符串常量池中,然后用常量池中的String对象给新开辟 的String对象赋值


3.intern方法


intern 方法的作用是手动将创建的String对象添加到常量池中


①未手动将创建的String对象添加到常量池中


public class Test {
    public static void main(String[] args) {
        char[] arr = new char[]{'a','b','c'};
        String str1 = new String(arr);
        String str2 = "abc";
        System.out.println(str1==str2);
    }
}

运行结果:


五.png


解析:将数组作为参数去实例化一个 String 对象,因为数组不是字符串常量,所以不会放入常量池,那么str1里面存储的就是实例化的那个String对象地址。str2="abc","abc"是个字符串常量所以会入常量池,str2里面存放的是编译器自动生成的字符串常量对象,所以当 str1==str2 返回的是false


②手动将创建的String对象添加到常量池中


public class Test {
    public static void main(String[] args) {
        char[] arr = new char[]{'a','b','c'};
        String str1 = new String(arr);
        str1.intern();
        String str2 = "abc";
        System.out.println(str1==str2);
    }
}

运行结果:

玩.png



解析:手动将str1对象添加到常量池,我们知道字符串常量是用数组存储的,所以当我们String str2 = "abc"这个代码时,会先去常量池里面看有没有"abc"这个字符串常量,因为字符串常量是用数组存储的,所以常量池中有这个字符串常量那么就不会入池,str2直接存储指向"abc"地址的对象地址

2.字符串的不可变性

String是一种不可变对象,字符串中的内容是不可改变,字符串不可被修改


阿.png


String类中的字符实际保存在内部维护的value字符数组中:


String类被final修饰,表明该类不能被继承

value被final修饰,表明value自身的值不能改变,即不能引用其它字符数组,但是其引用空间中的内容可以修改。

value被private修饰,表明value只能在本类使用,而且String类没有对外提供 getValue 和 setValue 的方法,所以在外部无法访问和修改value数组的内容

很多人会认为 value 不能被修改的原因是final,final修饰的变量不能改值,那下面我们就来证明value 不能修改跟 final 无关


证明 value 不能被修改跟 final 无关:


呃.png


通过上面的代码也就证明了被 final 修饰的数组是可以修改的,因为被 final 修饰的不能改变里面存储的内容,arr 里面存储的是 new 一个数组对象的地址,那么 arr 也就不能修改成其他的数组对象地址了,但是数组的内容可以改变,因为 arr 里面存的是地址


注:final修饰类表明该类不想被继承,final修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的。


为什么 String 要设计成不可变的?


方便实现字符串对象池,如果 String 可变,那么对象池就需要考虑写时拷贝的问题了

不可变对象是线程安全的

不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.

3.字符串修改

尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率非常低


public class Test {
    public static void main(String[] args) {
        String str = "abc";
        str += "def";
        System.out.println(str);
    }
}

如果要对String类型对象修改建议尽量使用StringBuffer或者StringBuilder


3.1 StringBuilder

由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffer类。这两个类大部分功能是相同的,这里介绍 StringBuilder常用的一些方法


StringBuff append(String str) :在尾部追加,相当于String的+=,可以追加:boolean、char、char[]、 double、float、int、long、Object、String、StringBuff的变量


rcharAt(int index) :cha获取index位置的字符


int length():获取字符串的长度


StringBuffer reverse():反转字符串


StringBuffer deleteCharAt(int index):删除index位置字符


StringBuilder 在修改字符串内容的时候会直接在该对象中修改,不会重新创建新对象:

の.png



画图解析:

额.png



解析:"abc"是一个字符串常量对象,所以它会入池,当把"abc"作为实例化 StringBuilder 的参数的时候,会自动创建一个比"abc"大一些的数组空间然后把abc拷贝进去,这个拷贝的数组不会入池。所以它可以直接在改变里面的值,也不会创建一个新的对象。当"abc"没有使用的时候编译器会自动回收


StringBuffer和StringBuilder的区别:StringBuffer采用同步处理,属于线程安全操作。StringBuilder没有采用同步处理,属于线程不安全操作。这两个类大部分功能是相似的(更详细的内容会放在多线程部分讲解)



相关文章
|
14天前
|
Java 测试技术 开发者
Java零基础-indexOf(String str)详解!
【10月更文挑战第14天】Java零基础教学篇,手把手实践教学!
102 65
|
5天前
|
存储 安全 Java
java.util的Collections类
Collections 类位于 java.util 包下,提供了许多有用的对象和方法,来简化java中集合的创建、处理和多线程管理。掌握此类将非常有助于提升开发效率和维护代码的简洁性,同时对于程序的稳定性和安全性有大有帮助。
33 17
|
1天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
19 4
|
2天前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
9 2
|
6天前
|
存储 安全 Java
如何保证 Java 类文件的安全性?
Java类文件的安全性可以通过多种方式保障,如使用数字签名验证类文件的完整性和来源,利用安全管理器和安全策略限制类文件的权限,以及通过加密技术保护类文件在传输过程中的安全。
|
10天前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
|
10天前
|
Java API Maven
如何使用 Java 字节码工具检查类文件的完整性
本文介绍如何利用Java字节码工具来检测类文件的完整性和有效性,确保类文件未被篡改或损坏,适用于开发和维护阶段的代码质量控制。
|
10天前
|
存储 Java 编译器
java wrapper是什么类
【10月更文挑战第16天】
18 3
|
13天前
|
Java 程序员 测试技术
Java|让 JUnit4 测试类自动注入 logger 和被测 Service
本文介绍如何通过自定义 IDEA 的 JUnit4 Test Class 模板,实现生成测试类时自动注入 logger 和被测 Service。
20 5
|
14天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
13 3