不可变性
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 ...
随机推荐
- [原创]CentOS下Mysql的日志回滚
一. 环境: a) Centos-6.5-x64位操作系统. b) 安装mysql.命令:yum install mysql* 二. 配置 a) ...
- docker核心概念及centos6下安装
Docker三大核心概念 镜像 容器 仓库 镜像 docker镜像类似于虚拟机镜像,可以将它理解为一个面向Docker引擎的只读模板,包含了文件系统. 容器 1.容器是从镜像创建的应用运行实例,容器和 ...
- SpringMVC——数据校验
数据校验在web应用里是非常重要的功能,尤其是在表单输入中.在这里采用Hibernate-Validator进行校验,该方法实现了JSR-303验证框架支持注解风格的验证. 一.导入jar包 若要实现 ...
- 网络编程4之UDP协议
一.定义 UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种[无 ...
- 刨根究底字符编码之十二——UTF-8究竟是怎么编码的
UTF-8究竟是怎么编码的 1. UTF-8编码是Unicode字符集的一种编码方式(CEF),其特点是使用变长字节数(即变长码元序列.变宽码元序列)来编码.一般是1到4个字节,当然,也可以更长. 为 ...
- vs2015未安装 Style 的 Visual Studio 语言支持
解决方案:在浏览器搜索下载安装Microsoft ASP.NET and Web Tools即可 下载地址:https://www.microsoft.com/en-us/download/confi ...
- iOS中动态注入JavaScript方法。动态给html标签添加事件
项目中有这样一种需求,给html5网页中图片添加点击事件,并且弹出弹出点击的对应的图片,并且可以保持图片到本地 应对这样的需求你可能会想到很多方法来实现. 1. 最简单的方法就是在html5中添加图片 ...
- linux系统必学-部分链接
vi操作笔记 vim操作笔记 好网站,临时收藏 SEO十万个为什么 9RIA.com天地会--中国最大的专业Flash论坛 ===互联网=============================== ...
- SQL Server使用导入导出向导导入超过4000个字符的字段的数据
在使用SQL Server导入导出向导导入数据的时候,我们经常会碰到某个单元格的数据超长而被截断报错的情况.本文针对这种场景给出相应的解决方案. 环境描述:SQL Server 2012,文件源: ...
- Phpcms·二次开发
PHPCMS V9产品介绍 PHPCMS V9(简称V9)采用PHP5+MYSQL做为技术基础进行开发.V9采用OOP(面向对象)方式进行基础运行框架搭建.模块化开发方式做为功能开发形式.框架易于功能 ...