一,如何使用String
public static void main(String[] args) {
// 使用常量串构造
String s1 = "hello bit";
System.out.println(s1);
// 直接newString对象
String s2 = new String("hello bit");
System.out.println(s1);
// 使用字符数组进行构造
char[] array = {'h','e','l','l','o','b','i','t'};
String s3 = new String(array);
System.out.println(s1);
}
即三个方法都可以
那我们如何让系统读取我们从键盘输入的话呢?很简单,使用scanner就可以
String的[注意事项]
1. String是引用类型,内部并不存储字符串本身,

public static void main(String[] args) {
// s1和s2引用的是不同对象 s1和s3引用的是同一对象
String s1 = new String("hello");
String s2 = new String("world");
String s3 = s1;
System.out.println(s1.length()); // 获取字符串长度---输出5
System.out.println(s1.isEmpty()); // 如果字符串长度为0,返回true,否则返回false
}
2. 在Java中“ ”引起来的也是String类型对象。
// 打印"hello"字符串(String对象)的长度
System.out.println("hello".length());
二, String对象的比较
1,引用数据类型储存的是地址
不同于基础数据类型储存的是值,引用数据类型储存的是地址
因此我们要注意使用常量串和使用new来创建对象还是有区别的,什么区别呢,咱们直接上代码
可以看到,明明是一样的内容,在逻辑运算符比较之下却输出的是 false,这是为什么?
我们都知道,在 Java 中,==
运算符用于比较两个引用是否指向内存中的同一个对象 ,也就是比较两个对象的内存地址是否相同。如果两个引用指向同一个对象,==
运算的结果为 true
;如果指向不同的对象,结果则为 false
。
而String就是引用类型,所以两个对象之间比较的是地址而不是内容
就这么简单吗,让我们再来看一段代码
我们可以看到,这次用常量串来构造对象,结果居然是true,这是为什么?
因为JVM 为节省内存,会将字面量创建的字符串放入常量池。若已有相同内容的字符串,则直接复用。因此地址是相同的
而对于new就不一样了,通过new String("abc")
创建的对象会在堆内存中生成新实例,而非直接使用常量池中的对象。因此用new来创建的对象,地址就不一样了
那么我们如何比较内容呢?这时候那我们就要用到equals方法
2,equals方法(比较字符串内容)
通过使用equals方法我们可以比较两个字符串的值,话不多说我们直接看代码
那么又引申出了一个新的问题----我们如何比较字符串的大小呢?
3,compareTo方法(比较字符串大小)
我们先看一段代码:
在这段代码中,我们通过compareTo方法对s1和s2进行了比较
要注意的是,是 s1 和 s2 比较,而不是s2 与 s1比较 因为 e > c 所以 s1 > s2 结果返回的是正数
至于为什么结果是2而不是什么别的正数,是因为在ASCLL码比较下c与e刚好差2(无论大小写)
但是要注意,大写字母+32 = 小写字母
刚才两个字符串的长度相同,那么在长度不相等的情况下,结果会改变吗?
可以看到结果仍然是2,因此实际比较的还是e与c的值
那么,如果两个字符串前面的内容也相同呢?
这次两个字符串前面的内容相同但长度不相同,结果如何?
可以看到,结果是负数,也就是说s1<s2
那么如果大小写不同呢?
此时,s1>s2,因为小写字母 = 大写字母+32(ASCLL码值)
那如果我就是想忽略大小写,我们应该怎么办?
4,compareToIgnoreCase方法(忽略大小写)
代码运行结果为0,说明两个字符串无差值,即 s1 = s2
三,字符串的查找
1,chatAt方法
因为charAt的返回值是char,所以我们用char类型来接受
代码运行结果是第一个字符 ' a '
注意这种给出下标的我们不要越界
一旦越界就会报错
事实上为了防止越界,我们经常用遍历的方式来获取字符,就像这样:
当然如果想让输出结果在同一行,只需要把println改为print就好啦
2,index方法
index方法可以获取到我们字符串的下标
他返回的是所查找字符第一次出现的位置,没有的话就返回-1
此时的"L"默认是hello中的第一个字母L,因此会输出数字2,即第二个下标
此外,index方法还可以构成重载,index方法不仅只能查找第一个字符,还可以指定数位查找
这段代码中我们指定查找了处于第三个数位的"L",代码运行结果是3
当然如果没有找到,结果就会返回-1
那如何构成重载?
可以看到代码运行结果是2,也就是输出的是重载之后的结果
当然我们也可以查找字符串
输出的是第一个字符出现的位置
当然如果没有找到的话依旧返回的是-1
3,lastIndexOf方法
lastIndexOf方法是寻找字符从后往前找
找到的是最后一个"L"的所在位置,即输出3
当然我们也可以指定让他从哪个位置开始找
通过这种方法它会找到字符从后往前找时第一次出现的位置,没有返回-1
很清楚了吧
四,转换
引出:valueOf方法
valueOf方法是一个静态方法,可以把其他类型转化为字符串类型,该方法有多种重载形式,能够转换多种不同类型的数参数
我们打开valueof方法的源代码可以看到确实是由static所修饰的
补充一下,如果你发现一个方法或者成员由类名调用,那么这一定会是一个静态方法或静态成员
比如我们常见的System.out,这就是一个类名点一个方法
我们看一下源代码发现确实是由static修饰,也是静态方法
1,把基本数据类型转换为字符串类型
2,将对象转换为字符串
代码运行结果是 20
但是要注意的是
当传入一个对象作为参数时,valueOf()
方法会调用对象的toString()
方法。要是对象为null
,则会返回字符串"null"
。
3,把字符数组转换为字符串
注意,该方法不会抛出NullPointerException异常,如果对象是null值,那么会直接输出null
4,将字符串转换为数字
我们可以通过Integer.parseInt方法将字符串转换为数字.
这里要注意,因为是s1是实例对象,所以要使用非静态方法,如果要使用静态方法,就要在静态方法中创建一个新的Cheer对象(一个静态对象)或者直接将静态方法外面的实例对象变为静态对象
当然除了parseInt方法还有parseDouble方法
5,小写转大写
在字符串中将小写字母转换为大写字母我们需要用到toUpperCase方法
在源代码中,toUpperCase方法返回的是String类型,所以我们要用String类型的对象来接收这个方法
除此之外要注意:
在字符串转换时,一切转变都不是在原字符串上改变,而是创建了一个新的字符串
6,字符串转数组
使用toCharArray方法可以将字符串转换为数组
这是代码运行结果.
要注意的是,在将数组转换为字符串时
这两种写法均可
针对字符数组转字符串,在确保数组非 null
时,二者都能实现转换;考虑到对 null
情况的不同处理,根据实际场景选择即可,若希望更好地处理可能为 null
的数组,String.valueOf
更合适
7,格式化
在 Java 中,格式化(Formatting) 指的是将数据(如数字、日期、字符串等)按照指定的规则转换为特定格式的字符串,以便于展示、存储或传输。格式化的核心目的是让数据呈现更符合人们的阅读习惯,或满足特定场景的格式要求。
格式化主要分为 数字格式化 , 时间格式化 和 字符串格式化 ,这里我们主要说一下字符串格式化
什么是字符串格式化?
简单来说就是按 指定 占位符规则 拼接 字符串,类似 C 语言的 printf
。
怎么进行字符串格式化?
使用format方法可以进行格式化
例如:
- 动态拼接含变量的字符串,如
"姓名:%s,年龄:%d"
填充后为"姓名:张三,年龄:20"
五,字符串的替换
1,replace方法
1,字符的替换
2,字符串的替换
2,replaceFirst方法
该方法的作用是替换第一个字符/字符串
3,replaceAll方法
和replace方法作用相同,但也有一些区别
replace
方法:- 有两种重载形式,一种接收两个
char
类型的参数 ,用于将指定的字符替换为另一个字符;另一种接收两个CharSequence
类型的参数,用于将指定的字符序列(字符串)替换为另一个字符序列(字符串)。 - 它进行的是普通的字符或字符序列匹配,不支持正则表达式。例如,在字符串中遇到目标字符或字符序列就进行替换,不会对字符序列进行特殊的正则解析。
- 有两种重载形式,一种接收两个
replaceAll
方法:- 接收两个
String
类型的参数,第一个参数是一个正则表达式,第二个参数是用于替换匹配项的字符串。 - 会按照正则表达式的规则对字符串进行匹配,然后将匹配到的部分替换为指定的字符串。这意味着可以实现更复杂的匹配和替换逻辑,比如匹配特定格式的字符串、数字、单词等。
- 接收两个
- 比如对于字符串
"1abc2abc3abc"
,执行"1abc2abc3abc".replaceAll("\\d", "*")
,这里\\d
是匹配数字的正则表达式,最终会将所有数字替换为*
,得到结果"*abc*abc*abc"
。
总体而言,如果只是简单的字符或字符串替换,使用replace
方法即可;如果需要基于正则表达式进行复杂的匹配和替换,replaceAll
方法则更为合适。
六,字符串的拆分
1,将字符串全部拆分
String[] split(String regex)
这串代码实现了将
"wangyuntong = student & wangkangrui = student & wangyuntong"
这段字符串拆分成几段
通过split方法实现拆分
但是还有一些特殊的符号作为分隔符要拆分还需要转义
举个例子:



2,将字符串以指定方式,拆分成limit组
String[] split(String regex, int limit)
七,字符串的截取
字符串的截取要用到substring方法
在上述代码中展示了substring的两个用法,即
可以指定索引截取到末尾
也可以制定截取片段,但要注意截取的部分是[ )
八,字符串的trim方法
trim方法可以去掉字符串左右两边的空格,但无法去掉中间的
九,字符串的不可变性
1,String不可变性的原因
什么是字符串的不可变性,在前面我们对字符串进行各种操作,比如转换大小写,格式化,替换等等,这些操作并没有改变字符串原本的值,而是创建了一个新的字符串修改,也就是说原本的字符串没有变化.那么为什么字符串具有不可变性呢?
很多书上有写关于字符串不可变的原因是由于final
说是因为final修饰String所以字符串具有不可变性,但其实这并不是关键
因为fianl所修饰的String类,只能说明这个String类是不可以被继承的,并没有说明是不可变
这其实是我们String底层存储带来的不可变性
真正的原因是我们的值value被private和final修饰
被private修饰保证了外部无法直接访问该数组
被final修饰保证了数组引用一旦初始化后就不能指向其他数组对象。即不能引用其它字符数组,但是其引用空间中的内容可以修改。
被private修饰的值只能在当前类中使用,拿不到value,并且被final修饰也无法引用其他字符数组,那我们也就无法修改原来的值,这就是不可变的原因
因此纠正一下,
2,String不可变性存在的意义
十,对字符串进行修改
真正对字符串进行修改我们要用到映射,这个方法我们以后再提


我们来看一个方法,appened方法,但要注意,这个方法不是对字符串'本身'进行修改,他依然具有字符串的不可变性.
appened常用于字符串构建和数据拼接,主要定义于StringBuilder和StringBuffer中
它的主要功能是将指定数据追加到当前对象的末尾,并返回当前对象本身(便于链式调用)
StringBuilder
是非线程安全的,效率较高,适合单线程场景。StringBuffer
是线程安全的(方法加了synchronized
),效率稍低,适合多线程场景。
十一,StringBuilder与StringBuffer
1,为什么要引入这两个类
首先要明确,StringBuilder和StirngBuffer都不是String类型,但是他们都可以操纵字符串
我们先来看一段代码
输出结果:
可以看到,最后在打印的时候,我们使用了toString方法,为什么要使用这个方法?
首先,在 Java 中StringBuilder
是一个可变的字符序列类,用于高效地创建和操作字符串。它属于引用类型
而String
是一个引用类型,用于表示字符串,即一系列字符的有序集合。
因此StringBuilder和String类型不一致,只有 toString()
能把 StringBuilder
转换成真正的 String
类型,满足类型要求。
详细一点说,StringBuilder
重写了 Object
类的 toString
方法,调用 builder.toString()
后,会把 StringBuilder
内部维护的可变字符序列,转换成一个不可变的 String
字符串,这样就能适配 System.out.println
的入参要求,顺利打印出拼接好的字符串内容。
当然,字符串的拼接我们之前学过一段更容易理解的
这样也能输出同样的结果
但是
这样的效率是远远不及StirngBuilder的(对象多时)
我们来用代码说明
这串代码分别计算了符号拼接,StringBuffer拼接和StringBuilder拼接的效率,我们运行一下代码
效率高低一目了然,为什么会这样?
因为符号拼接会不断地创建对象,销毁对象(for循环),而StringBuffer拼接和StringBuilder拼接是在创建后的对象上进行操作,省去了销毁对象的过程,因此效率大大提高
看到了吗,返回值都是this,这是从底层代码来解释的
2,StringBuffer和StringBuilder的联系与区别
首先,StirngBuffer和StirngBuilder这两个类里面的方法几乎是一摸一样的,他们都包含了Stirng类本身没有的方法
那么区别是什么,别着急,现在我们就通过底层代码来看一下(找不同)
StringBuilder:
StringBuffer:
对比一下,有两处不同,最主要的,来看一下StringBuffer多了一个Synchronized
这个单词的意思是 同步
这个单词在javaEE中我会着重提到,现在不过多讲述
StringBuffer主要用于多线程情况下,可以保证线程安全
StringBuilder主要用于单线程情况下,无法保证线程安全
那为什么不直接用StringBuffer?
我们可以把Synchronized看作是一把锁
把多线程情况比作,有一个公共厕所,很多人去上,那么进去的人要上锁,上完厕所出来再解锁,进去的人再上锁这样的一个过程,这样是有必要的,是可以保证安全的
把单线程情况比作, 放假了 你家里只有你一个人,你要上厕所,那你为什么要上锁解锁呢,这就不存在安全问题了,这样会导致效率没有意义的下降,这是一种资源的浪费
因此在单线程情况下请尽量使用StringBuilder
小总结
3,StringBuffer和StringBuilder特有的方法(部分)
1,字符串逆置(反转)
我们要知道String是没有逆置这个方法的,这是StringBuffer和StringBuilder特有的方法
这里的toString可加可不加
可以看到我们没有给reverse返回值,这是因为他不需要返回值
从他的底层代码来看,方法调用之后直接返回他自己,因此不需要返回值
2,字符串插入
同样不需要返回值,原因也是一样的
十二,String对象创建数量
为了更深一步的了解String对象的创建过程,我们来做一个小练习
请大家先思考一下
解答:
这是java中关于String对象创建数量的经典问题,需要结合字符串常量池和new关键字的特性分析
案例 1:String str = new String("ab");
执行逻辑拆解(分步骤分析)
1,常量池对象创建:
字符串字面量"ab"会触发常量池检查,由于题目要求"不考虑常量池之前是否存在",因此会在字 符串 常量池中创建1个内容为"ab"的String对象
2,堆内存对象创建:
new String ("ab")会在堆内存中再创建1个String对象,这个对象的初识内容拷贝自常量池 的"ab"
3,最终赋值:
变量str指向堆内存中新建的String对象
结论:共创建 2 个 String
对象(常量池 1 个 + 堆内存 1 个 )。
案例 2:String str = new String("a") + new String("b");
执行逻辑拆解(分步骤分析)
第一步:
new String("a")
- 常量池创建:字面量
"a"
在常量池创建 1 个String
对象。 - 堆内存创建:
new String("a")
在堆内存创建 1 个String
对象(拷贝常量池的"a"
)。 - 共创建 2 个对象(常量池 1 个 + 堆内存 1 个 )。
- 常量池创建:字面量
第二步:
new String("b")
- 常量池创建:字面量
"b"
在常量池创建 1 个String
对象。 - 堆内存创建:
new String("b")
在堆内存创建 1 个String
对象(拷贝常量池的"b"
)。 - 共创建 2 个对象(常量池 1 个 + 堆内存 1 个 )。
- 常量池创建:字面量
第三步:
new String("a") + new String("b")
+
运算符在 Java 中会触发隐式创建StringBuilder
,用于拼接字符串。虽然StringBuilder
不是String
,但拼接过程会间接影响String
对象数量:- 拼接时,
StringBuilder
会先将两个堆内存的String
对象("a"
和"b"
)的内容取出,拼接成"ab"
。 - 由于是非编译期确定的拼接(运行时动态拼接),因此不会在常量池自动创建
"ab"
。 - 最终通过
StringBuilder.toString()
会在堆内存新建 1 个String
对象(内容为"ab"
)。
- 拼接时,
最终赋值:
变量str
指向堆内存中通过StringBuilder
拼接后新建的String
对象(内容"ab"
)。
逐步骤统计:
new String("a")
:2 个对象(常量池 1 + 堆 1 )new String("b")
:2 个对象(常量池 1 + 堆 1 )+
拼接与toString()
:堆内存新增 1 个String
对象(内容"ab"
)
结论:共创建 5 个 String
对象(常量池 2 个 + 堆内存 3 个 )。
关键注意点
编译期 vs 运行期拼接:
如果是编译期确定的拼接(如String str = "a" + "b";
),结果会直接优化为"ab"
,且只在常量池创建 1 个对象;但本案例是运行期动态拼接(涉及new String
),因此需走StringBuilder
逻辑,且不会在常量池自动创建"ab"
。StringBuilder
不计数:
案例中StringBuilder
是中间临时对象,题目只统计String
对象,因此无需计入。
总结:
new String("ab")
→ 2 个String
对象new String("a") + new String("b")
→ 5 个String
对象