不可变性
String可以说是最常用的类型了,即字符串类型,String是常量(final关键词修饰的),他的值不能被创建后更改,因为字符串是不可被改变的,所以可以被用来共享。
Java语言为String提供了同基本数据类型相似的操作符(+,+=),这里请注意,由于String是不可被改变的,所以每次操作都会会重新生成一个String类型。
String a = "a";
System.out.println(a.hashCode());
a += "b";
System.out.println(a.hashCode());
输出的结果是不一致的,此外String的所有方法都是返回一个新的字符串对象。
共享性
这里就需要提到jvm的内存模型了,jvm中将内存大致分为两大块(细致不止两大块,这里重点不是这个,所以就大致的解释下),引用《深入java虚拟机》作者的原话, 经常有人把Java 内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,Java内存区域的划分实际上远比这复杂。这种划分方式的流行只能说明大多数程序员最关注的、与对象内存分配关系最密切的内存区域是这两块。
堆内存呢主要存放所有对象的实例(包括数组)、而栈主要存放的是方法、局部变量等信息,而在此之外还有一个常量池用来存放字面常量和符号引用,而每当我们创建一个字符串类型时就会先在常量池中查找是否存在该字符串,如果存在则直接返回实例引用,否则将会实例化一个新的字符串存放到常量池中,从而实现了共享。
以下例子来源《Effective》一书
String a = new String("a");
这个例子中呢,创建了两个对象,第一个对象“a”实例化以后被存储到常量池中(常量池中不存在的情况下),第二个a是堆(因为a的一个对象实例)中的字符串对象,该对象是在运行时创建的。
所以聪明的人才创建了另一种写法:
String a = "a";
这种写法比上一种更为简洁而且是直接创建了对象实例,只创建了一个对象,编译时完成该分配。
String a1 = new String("a");
String a2 = new String("a");
String a3 = "a";
String a4 = "a";
System.out.println(a1 == a2);//false 因为a1跟a2都是运行时创建返回的新实例对象。
System.out.println(a1 == a3);//false
System.out.println(a3 == a4);//true a3、a4都是直接指向常量池中的内存地址。
讲到这里,我们可以拓展开继续讲下去,关于final时字符串的工作表现。
String a = "a";
String bc = "bc";
String abc1 = a + bc;
String abc2 = "abc";
String abc3 = "a" + "bc";//编译器会在编译阶段将其转化成"abc",因为这两个值在编译时就确定了。
String abc4 = a + "bc";//由于a是变量,a的值只能在运行时才能确定
System.out.println( abc1 == abc2);//false
System.out.println( abc2 == abc3);//true
System.out.println( abc2 == abc4);//false
那么我们再来看下面这个情况,加入了final修饰符,变成常量(这里要区分下,String是的值是常量类型,表示的是他的值,而String表示的是最终类)
final String a = "a";
final String bc = "bc";
String abc1 = a + bc;
String abc2 = "abc";
System.out.println( abc1 == abc2);//true 因为final修饰的常量在编译阶段的值就确定了,编译器会将该常量全部"宏替换"成指向该常量池中的实例,final修饰符在编译完成之后是不存在的(这里有点绕,要理解)。
可能刚才有的朋友会很好奇,不是说字符串类型是不可被改变的么,怎么编译器还将其优化合并成一个对象哇,有这种疑问非常好,接下来就是揭秘时刻。
该例子来着《Think in java》一书。
当我们使用“+”操作符进行连接时,编译器会帮我们对代码进行优化将其操作转换成StringBuilder(1.5版本之前是StringBuffer),由于StringBuffer是不安全的,所以1.5之前对其优化处理可能会出现并发问题,这也是为什么编译器会对其进行合并。
而对于“+=”操作符,例如:a += b;会将进行拆分成a = a + b;这是基础不废话了。
这里是java代码
package top.yangchudong.main;
public class App {
public static void main(String[] args){
String a = "a" + "b" + "c";
}
}
以下是编译结果
E:\JavaProject\GradleTest\src\main\java>javac top\yangchudong\main\App.java
E:\JavaProject\GradleTest\src\main\java>javap top\yangchudong\main\App.class
Compiled from "App.java"
public class top.yangchudong.main.App {
public top.yangchudong.main.App();
public static void main(java.lang.String[]);
}
E:\JavaProject\GradleTest\src\main\java>javap -c top\yangchudong\main\App.class
Compiled from "App.java"
public class top.yangchudong.main.App {
public top.yangchudong.main.App(); //这是默认构造方法
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":
()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String a
2: astore_1
3: new #3 // class java/lang/StringBuilder 这里可以清楚的看到定义了一个StringBuilder对象
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<
init>":()V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6 // String b
16: invokevirtual #5 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
22: astore_1
23: new #3 // class java/lang/StringBuilder 这里又创建了一个
26: dup
27: invokespecial #4 // Method java/lang/StringBuilder."<
init>":()V
30: aload_1
31: invokevirtual #5 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: ldc #8 // String c
36: invokevirtual #5 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
39: invokevirtual #7 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
42: astore_1
43: getstatic #9 // Field java/lang/System.out:Ljava/
io/PrintStream;
46: aload_1
47: invokevirtual #10 // Method java/io/PrintStream.printl
n:(Ljava/lang/String;)V
50: return
}
这个例子我是故意要这样写的,因为很多人认为既然编译帮我们做了优化,那么他们就可以不注意String的使用,但这其实就是一个编程陷阱。
我们理想上面例子编译器会帮我们编译成下面这个样子:
StringBuilder a = new StringBuilder("a");
a.append("b");
a.append("c");
System.out.println(a.toString());
可现实编译器帮我们做到的只能是这样:
String a = "a";
StringBuilder builder1 = new StringBuilder(a);
builder1.append("b");
a = builder1.toString();
StringBuilder builder2 = new StringBuilder(a);
builder1.append("c");
a = builder1.toString();
System.out.println(a);
所以大家以后在编写的时候要多注意这个问题的存在,当较多的使用操作符操作字符串时还是使用StringBuilder或者StringBuffer。
- java字符数组char[]和字符串String之间的转换
java字符数组char[]和字符串String之间的转换 觉得有用的话,欢迎一起讨论相互学习~Follow Me 使用String.valueOf()将字符数组转换成字符串 void (){ cha ...
- Java基础——数组应用之字符串String类
字符串String的使用 Java字符串就是Unicode字符序列,例如串“Java”就是4个Unicode字符J,a,v,a组成的. Java中没有内置的字符串类型,而是在标准Java类库中提供了一 ...
- Java使用占位符拼接字符串-String.format()的使用
String domain = "www.ykmaiz.com"; int iVisit = 0; String info = String.format("该域名%s被 ...
- JAVA中数组(Array)、字符串(String)、集合(List、Set)相互转换
1.数组转List String[] arr = new String[]{"A", "B", "C"}; List list = Arra ...
- Java内存管理-探索Java中字符串String(十二)
做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 一.初识String类 首先JDK API的介绍: public final class String extends O ...
- Java入门篇(五)——Java的字符串/String类
前面在举例时有出现过String的例子,当时肯定有一部分朋友不知道这个是做什么用的.其实String类是Java中一个比较特殊的类,字符串即String类,它不是Java的基本数据类型之一,但可以像基 ...
- Java入门篇(五)——字符串/String类
前面在举例时有出现过String的例子,当时肯定有一部分朋友不知道这个是做什么用的.其实String类是Java中一个比较特殊的类,字符串即String类,它不是Java的基本数据类型之一,但可以像基 ...
- Java字符串String类操作方法详细整理
关于String类的基本操作,可分为以下几类: 1.基本操作方法 2.字符串比较 3.字符串与其他数据类型之间的转换 4.字符与字符串的查找 5.字符串的截取与拆分 6.字符串的替换与修改 我觉得在整 ...
- JAVA笔记3__字符串String类/对象一对一关联
import java.lang.String; import java.util.Scanner; public class Main { public static void main(Strin ...
随机推荐
- NopCommerce(3.9)作业调度插件
NopCommerce(3.9)作业调度插件视频教程录制完成,下面是插件源码下载地址和插件视频教程下载地址:插件下载地址: http://www.nopcommerce.com/p/2752/jobs ...
- Java 开发中如何正确踩坑
为什么说一个好的员工能顶 100 个普通员工 我们的做法是,要用最好的人.我一直都认为研发本身是很有创造性的,如果人不放松,或不够聪明,都很难做得好.你要找到最好的人,一个好的工程师不是顶10个,是顶 ...
- (原创) Maven查看JAR包的依赖关系
如果是用命令行,可进入项目所在目录,然后输入: mvn dependency:tree ,来查看jar包依赖关系. 另外还可以在eclipse操作,如下图所示: 点击run后,开始输出JAR包依赖树. ...
- php+mysql 除了设置主键防止表单提交内容重复外的另一种方法
感觉好久没有更新博客了,一直在做网站及后台,也没有遇到让我觉得可以整理的内容,之前做的一个系统,已经完成了,后来客户又要求加一个功能,大概就是表单提交的时候,约束有一项不能和以前的内容重复,如图 比如 ...
- 使用 libdvm.so 内部函数dvm* 加载 dex
首先要清楚,odex只是对代码段(我将dex文件与elf文件类比,大家都将执行文件分成不同的段)作优化,而其它用于类反射信息的段都应用原来的dex,所以odex文件内部还包含了一个dex. 打开一个d ...
- python读写文件相关内容
1.python读取文件 f = open('E:/info.txt', 'r')#用read的方式打开a = 0for line in f.readlines():读取没一行,就是读取所有文件的意思 ...
- JavaScript基础之注释,类型,输出,运算符
JavaScript是一种依托于网页为宿主的脚本语言,JavaScript是一门非常强大的语言,尤其对于web端,用途广泛,好用,偏向于操作网页,可以操作网页中的任何一个元素,JavaScript的缺 ...
- PHP·笔记(函数总结)
PHP 指 PHP:超文本预处理器(译者注:PHP: Hypertext Preprocessor,递归命名) PHP 是一种服务器端的脚本语言,类似 ASP PHP 脚本在服务器上执行 PHP 支持 ...
- response.getWriter().write()产生乱码
Struts1中通过action, return到页面的时候,如果代码中使用到response.getWriter().write(),那么有可能jsp页面中汉字会变成乱码.百度上有人说改为respo ...
- [1] Spring.Net
开发框架之Spring.Net