首页 > 其他分享 >第九章《字符串》第3节:String类对象的存储方式

第九章《字符串》第3节:String类对象的存储方式

时间:2022-12-31 16:31:21浏览次数:55  
标签:abc String 第九章 池中 对象 字符串 常量

​大多数情况下,程序员都会用String类对象表示一个字符串。虚拟机在存储String类对象时会创建一个常量池,把符合条件的对象都存储到常量池中。所谓常量池是指一块用于保存对象的内存区域,这个区域中存储的对象可以被反复利用。这就如同把使用过的工具存放到工具箱,当下次使用工具的时候直接从工具箱中取出就可以,不需要重新创造一个工具。

当程序刚启动时,常量池中没有任何对象,在程序的执行过程中,虚拟机会把程序中出现的字符串常量放入常量池中,而那些不是字符串常量的String类对象则不会被保存到常量池中,例如:​

Scanner sc = new Scanner(System.in);​
String s1 = "abc";//①​
"xyz".charAt(2);//②​
String s2 = sc.nextLine();//③​

在这段代码中,出现了两个字符串常量,分别是语句①中的“abc”和语句②中的“xyz”。前文讲过:一个字符串常量就是一个String类对象,因此“abc”和“xyz”都是String类对象,它们以字符串常量的形式出现,所以都会被存放到常量池中。而语句③中的s2虽然也是String类对象,但它是用户从控制台上输入的字符串,不是字符串常量,所以s2不会被存放到常量池中。常量池中的字符串不会出现重复,所以如果常量池中已经有了字符串“abc”,即使程序中再次出现了“abc”,虚拟机也不会在常量池中再次创建一个一模一样的“abc”字符串。​

如果一个字符串的值能够在编译阶段就能够确定下来,那么这个字符串也会被加入到常量池中,请看下面的代码:​

String s = "abc" + "xyz";​

在这段代码中,字符串s是由“abc”和“xyz”这两个字符串常量组成的,因此“abc”和“xyz”这两个字符串都会被加入到常量池中。而由“abc”和“xyz”拼接而成的字符串“abcxyz”也会被加入到常量池中。这是因为字符串常量“abc”和“xyz”的拼接结果只能是“abcxyz”,这个拼接结果在编译阶段就能确定。​

如果字符串在拼接过程中出现了引用,只要引用指向的是一个字符串常量,并且在引用前面添加了final关键字,那么这个拼接出来的字符串也会被加入到常量池中,例如:​

final String s1 = "abc";​
final String s2 = "xyz";​
String s = s1 + s2;​

在这段代码中,字符串s是通过引用s1和s2拼接而成,s1和s2都指向了字符串常量,并且这两个引用前面都添加了final关键字,这样的话s1和s2拼接而成的字符串“abcxyz”也会被加入到常量池中。s1和s2拼接成的字符串之所以会被加入常量池,是因为String类是不可变类,所以s1和s2所指向的字符串的值不会发生改变,而s1和s2的前面又添加了final关键字,这使得s1和s2的指向也不会发生改变,以上两个条件保证了s1和s2的拼接结果在编译阶段就能够被确定下来,所以这个拼接结果会被加入常量池。​

如果是通过String类的构造方法去创建字符串对象,那么每次调用构造方法都会创建出一个新的字符串对象,并且创建出来的字符串对象不属于字符串常量,这个对象自然也不会被加入常量池。例如:​

String s = new String(new char[]{'a','b','c'}) ;​

在这条语句中,字符串s是通过构造方法创建出来的,所以它不是字符串常量,也不会被加入常量池中。但如果在创建对象时以字符串常量作为构造方法的参数,就会导致有时候用一条语句却能创建出两个字符串对象的现象,例如:​

String s = new String("abc");​

在这条语句中,使用new关键字创建了字符串对象s,s不是字符串常量,而创建字符串s时使用的参数“abc”却是一个字符串常量,这个字符串常量也是被虚拟机所创建出的字符串对象,并且这个对象与s不是同一个对象。因此,这条语句就会一次性创建两个字符串对象,字符串常量“abc”会被存放在常量池中,字符串s则被存放在常量池之外。​

前文讲过:常量池中任意两个字符串的值都不相等,也就是说,如果常量池中已经有了一个值为“abc”的字符串,就不会出现第二个值为“abc”的字符串,因此以下代码会不创建出4个字符串对象:​

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

代码中的这两条语句只能创建出3个字符串对象,它们分别是s1、s2以及字符串常量“abc”,由于常量池中不会出现两个值想相同的字符串对象,所以代码中的两个字符串常量“abc”实际上是同一个对象。​

如果一个字符串对象不在常量池中,并且这个字符串的值在常量池中没有出现过,那么使用intern()方法可以把这个字符串对象加入到常量池中。例如:​

String s = new String(new char[]{'a','b','c'}) ;​

这条语句中字符串对象s是通过new关键字创建出来的,所以s不在常量池中,如果常量池中不存在值为“abc”的字符串对象,那么执行了“s.intern();”之后,s就会出现在常量池中。​

本小节重点讲解了常量池的概念和原理,下面的【例09_15】能够帮助读者深刻理解哪些字符串会被存入常量池中。​

【例09_15 字符串比较】

Exam09_15.java​

public class Exam09_15 {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
final String s3 = "abc";
String s4 = new String("abc");
String s5 = new String("abc");
String s6 = "xyz";
final String s7 = "xyz";
String s8 = "abc"+"xyz";
String s9 = "ab"+"cxyz";
String s10 = new String(new char[] {'h','e','l','l','o'});
String s11 = new String(new char[] {'h','e','l','l','o'});
s10.intern();
s11.intern();
System.out.println(s1==s2);//①true
System.out.println(s1==s4);//②false
System.out.println(s4==s5);//③false
System.out.println(s8=="abcxyz");//④true
System.out.println(s8==s9);//⑤true
System.out.println(s1+s6=="abcxyz");//⑥false
System.out.println(s3+s7=="abcxyz");//⑦true
System.out.println(s10=="hello");//⑧true
System.out.println(s11=="hello");//⑨false
}
}

【例09_15】对多个字符串进行了比较操作,通过比较结果就能看出哪些字符串被加入到常量池中。为方便读者阅读程序,本例的代码中直接以注释的形式把比较结果标注到了语句的后面。下面就逐条分析这些比较结果都能证明哪些结论。​

语句①用==对两个字符串常量“abc”做比较,比较结果为true。这证明值相同的字符串常量都是同一个对象,并且它们都会被加入常量池中。​

前文讲过:字符串常量一定会被加入到常量池中,这就可以推导出:如果一个字符串对象与一个字符串常量用==进行比较的结果为true,就说明这个字符串对象与字符串常量是同一个对象,进而说明这个字符串对象一定在常量池中。根据“常量池中不会出现值相同的字符串”能推导出:如果一个字符串对象的值与一个字符串常量的值相同,但这个字符串对象与字符串常量用==进行比较的结果为false,那么这个字符串对象一定不在常量池中。​

语句②用一个字符串常量s1和一个用构造方法创建出的字符串对象s4做比较,虽然s1和s4的值相同,但比较结果为false,这就充分证明用构造方法创建出的字符串对象一定不在常量池中,​

语句③用两个用构造方法创建出的字符串做比较,比较结果为false。这证明每次使用构造方法都会创建出一个新的字符串对象,虽然它们的值完全相同,但它们却是两个不同的对象。​

语句④用s8和字符串常量“abcxyz”做比较,比较结果为true。s8是一个由字符串常量拼接而成的字符串,它的值在编译阶段就能被确定。比较结果为true就能证明编译阶段就能确定值的拼接字符串也会被加入常量池中。​

语句⑤用s8和s9做比较,比较结果为true。虽然这两个字符串都是由字符串常量拼接而成,但s8由“abc”和“xyz”,而s9由“ab”和“cxyz”拼接而成,比较结果为true就能证明即使用不同的字符串常量进行拼接,只要拼接结果相同,并且这个拼接结果在编译阶段就能确定,那么拼接起来的字符串都是同一个对象,这个对象也会被加入到常量池中。​

语句⑥用拼接而成的字符串与字符串常量“abcxyz”做比较,比较结果为false。这个比较结果证明:如果在拼接过程中出现了引用,每个引用虽然也都指向了字符串常量,但引用前面没有加final关键字,那么通过引用拼接起来的字符串不会被加入到常量池中。​

语句⑦是语句⑥的反例,虽然也是由拼接而成的字符串与字符串常量“abcxyz”做比较,但比较结果为true,这证明如果在拼接过程中出现了引用,每个引用都指向了字符串常量,并且引用前面添加了final关键字,那么通过引用拼接起来的字符串就能在编译阶段把值确定下来,而这个拼接而成的字符串也一定会被加入到常量池中。​

语句⑧用一个字符串对象与字符串常量“hello”做比较,比较结果为true。这个比较结果证明如果常量池中没有出现值相同的字符串,那么一个用构造方法创建出的字符串对象在调用了intern()方法后会被加入常量池。​

语句⑨与语句⑧本质上是相同的,但比较结果为false。这是因为s10在执行了intern()方法后,常量池中已经有了值为“hello”的字符串,那么s11这个值为“hello”的字符串就不会被重复加入到常量池中。​

【例09_15】通过实际的运行结果证明了哪些字符串会被加入常量池中,各位读者需仔细体会字符串被加入常量池的各种条件。通过这个示例也可以看出:常量池中的字符串不会重复,并且可以反复使用,因此最大程度的节约了存储空间。因此各位读者在创建对象时要尽量使用字符串常量赋值的方式创建,而不是使用构造方法创建。

本文字版教程还配有更详细的视频讲解,小伙伴们可以点击这里观看。

标签:abc,String,第九章,池中,对象,字符串,常量
From: https://blog.51cto.com/mugexuetang/5982151

相关文章

  • 第九章《字符串》第1节:String类的几个基本常识
    ​一串连续的字符被称为字符串。为了与程序中的变量名相区别,字符串的两端都会加上双引号,所以在程序中如果出现了“abc”,那么它就代表一个字符串而不是一个变量的名称。被双......
  • cf-1767C-Count Binary Strings(区间dp)
    题面https://codeforces.com/problemset/problem/1767/C下面展示带注释的ac代码在代码里解释思路Ac代码#include<bits/stdc++.h>#defineioios::sync_with_stdio(f......
  • 自定义工具类之“分割所有类型的字符串”
    自定义工具类之“分割所有类型的字符串”/***<p>默认根据,,\t\n\r分隔符分隔出list</p>**<pre>*tokenizeToStringArray(null)......
  • 流的使用之“如何将List<String>转为Map”
    流的使用之“如何将List<String>转为Map” ProductIllegalCustomquery=newProductIllegalCustom();query.setUnionSkus(unionSkus);Map<String,......
  • 写一个函数,实现字符串的逆序
    #include<stdio.h>#include<string.h>voidreverse_string(char*str){//assert(arr);intlen=strlen(str);char*left=str;char*right=str+len-......
  • 递归实现字符串的逆序
    编写一个函数reverse_string(char*string)将参数字符串中的字符反向排序要求:不能使用c函数库中的字符串操作函数#include<stdio.h>intmy_strlen(char*str){intcount......
  • 【学习笔记】字符串后缀算法学习笔记
    后缀数组\(\text{SuffixArray}\)参考资料:洛谷日报#273浅谈后缀数组算法、常见字符串算法byAlex_Wei后缀排序使用一种基数排序结合倍增的方法,将一个字符串的所有后......
  • 字符串全家桶
    ChangeLog2022.12.30.开坑。0.前言字符串学得很辣鸡,被ktq_cpp神仙吊着打,所以就开了坑。但是不知道什么时候能填完,嘻嘻。1.Manacher算法1.1算法介绍Manach......
  • 字符串转数字
    //ConsoleA.cpp:定义控制台应用程序的入口点。//#include"stdafx.h"#include<string>usingnamespacestd;//ascii0-9:48~57//A:65//a:97//空格不等于\0//单引号的......
  • C++字符串分割字符串
    #include<iostream>#include<vector>std::vector<std::string>split_str(std::string&str,conststd::string&separator){size_tpos=0;std::vecto......