不可变性
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 ...
随机推荐
- FPGA两种寄存器的使能
在FPGA中,寄存器的使能设计一般有两种方式: 1.直接使用寄存器的使能端口. 2.使用一个数据选择器连接寄存器的D端口,通过数据选择器的sel端口做使能.如下图 这个方式与直接使用寄存器的CE端口有 ...
- Mac主机映射到域名
1, control+space 打开spotlight, 搜索"终端" 2, 打开终端 3, 在"终端"界面中输入: sudo vi /etc/hosts ...
- docker核心概念及centos6下安装
Docker三大核心概念 镜像 容器 仓库 镜像 docker镜像类似于虚拟机镜像,可以将它理解为一个面向Docker引擎的只读模板,包含了文件系统. 容器 1.容器是从镜像创建的应用运行实例,容器和 ...
- C语言之循环结构
程序结构: 顺序结构 条件结构(分支结构) if结构,if-else结构 ,多重if分支结构,switch结构 循环结构:做重复的事情 while循环,do..while循环和for循环. 写循环结构 ...
- 华为A199:近期不会再买华为的手机了
为了支持国货,也省点钱,买了个华为A199: 缺点: 没有google play market很复杂的刷机后才能装Gmail 不过也有亮点: 自带录音功能,老htc通过软件也只能录单方向的哦关机闹 ...
- 【数据库】Mean web开发 04-MongoDB在Linux上的安装及遇到的问题
简介 Mean是JavaScript的全栈开发框架.更多介绍 用MongoDB实现持久数据的存储是Mean Web全栈开发中的一部分. MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非 ...
- C# 调用cmd.exe的方法
网上有很多用C#调用cmd的方法,大致如下: [c-sharp] view plaincopy private void ExecuteCmd(string command) { Proces ...
- Ext表格分页
pageSize:配置表格或者数据的数量, autoLoad: { start: 0, limit: 2 }:自动加载时候的参数, proxy中:params: {start: 0,limit: 2} ...
- Python处理Excel文件
因为工作需求,需要审核一部分query内容是否有效,query储存在Excel中,文本内容为页面的Title,而页面的URL以HyperLink的格式关联到每个Cell. 于是本能的想到用Python ...
- C++11 单例类实现
单例类: (1) 单例类保证全局只有一个唯一的实例对象. (2) 单例类保证只有唯一的接口获取这唯一实例. 非线程安全的单例类举例: class CSingleton { public: ~CSing ...