博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
细数Java的语法糖(一): 用于字符串拼接的 "+" 运算符
阅读量:5893 次
发布时间:2019-06-19

本文共 4906 字,大约阅读时间需要 16 分钟。

前言

语法糖(Syntactic Sugar),又称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法。这种语法对语言的功能并没有影响,但往往能让程序更加简洁,并有更高的可读性,从而方便程序员使用,减少代码出错的机会,并提升开发效率。简单来说,语法糖就是对现有语法的一种包装。

很多编程语言中都有语法糖,Java 也是如此。要明白,语法糖仅存在于编译时,JVM 并不认识这些语法糖。因此,在编译阶段,Java 编译器会将语法糖还原为基础的语法结构,有些文章将这个过程称为 "脱糖",也有称 "解语法糖"(Reference 2)。在 的 compile 方法中,有一个步骤(compile2)会调用它的 desugar 方法,这个方法就是用来实现脱糖处理的。

顺便提下,从 Java 6 开始,Java 提供了编译器API,使得开发者们可以更灵活地使用编译。如果你有兴趣,它的入口在 接口(从父接口继承来)的 run 方法。 是它的一个实现,它最终会将编译委托给 com.sun.tools.javac.main.JavaCompiler 的 compile 方法。

从本文开始,笔者将试图总结和介绍 Java 语言中的常见语法糖,并将尽量按照由简到繁、并将相似或相关内容放在一起的顺序来组织。对于涉及到的关联知识,也会稍做介绍,但可能不会深究太多细节。欢迎有兴趣的同学一起探讨,如有不足,还请指正。

这个系列的目录如下(为了不剧透,先只列出部分):

注: 如果没有特别指明,所有的介绍及示例均基于 Java 8。

用于字符串拼接的 "+" 运算符

在Java中,字符串是最常使用的类型,字符串的拼接又是字符串最常用的操作。有些语言(如: C++) 允许程序员对运算符做重载,从而实现定制化的操作。Java 不支持运算符重载,但它为程序员简化了字符串拼接操作,允许通过二元操作符 "+" 完成字符串拼接。

更进一步地,根据 Java 语言规范,"+" 操作符的运算规则如下:

  1. 如果有一个操作数是 String,则将另一个操作数也转为 String。
  2. 否则,(如有必要,插入拆箱转换),如果任意一个操作数是 double,则将另一个转为 double。
  3. 否则,如果任意一个操作数是 float,则将另一个转为 float。
  4. 否则,如果任意一个操作数是 long,则将另一个转为 long。
  5. 否则,将两个操作数都转为 int。

这条规则的最后一句也就是两个 byte 相加,结果是 int 的原因。

回到正题,先看一个字符串拼接的例子:

String t = "b";       String s = "a" + t + "c";复制代码

反编译这段代码,得到:

0: ldc           #2                  // String b       2: astore_1       3: new           #3                  // class java/lang/StringBuilder       6: dup       7: invokespecial #4                  // Method java/lang/StringBuilder."
":()V 10: ldc #5 // String a 12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: aload_1 16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: ldc #7 // String c 21: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 27: astore_2复制代码

简单解释,首先将字符串 "b" 存入第一个变量(即: t) 中。接着,构造一个 StringBuilder,并通过其 append 方法存入字符串 "a"。然后,将第一个变量(t)中的字符串("b")取出,并再次通过在刚才的 StringBuilder 对象上调用 append 方法存入。接着,以类似的方法再存入字符串 "c"。最后,通过在 StringBuilder 对象上调用 toString 方法得到最后的结果,存入第二个变量(即: s)中。

可见,在编译过后,Java 的字符串拼接其实是通过构造 StringBuilder 对象,并不断调用其 append 方法将字符串放入,再通过 toString 得到最终结果。这样的做的好处在于可以避免产生很多无用的中间字符串对象。

事实上,Java 编译器做的还不止于此,如果字符串是常量值,那么在编译期,Java 会直接将常量字符串替换为其字面值。考虑如下代码:

final String t = "b";       String s = "a" + t + "c";复制代码

与前述例子相比,这里仅仅是将 t 声明为了 final。反编译后的代码如下:

0: ldc           #2                  // String b       2: astore_1       3: ldc           #3                  // String abc       5: astore_2复制代码

看,由于所有的字符串都是常量值,Java 可以在编译阶段就直接计算出结果。

不过,需要指出,Java 编译器也聪明得有限。考虑下面字符串连续拼接的场景:

String s = "a";        s += "b";        s += "c";复制代码

反编译后得到如下代码:

0: ldc           #2                  // String a       2: astore_1       3: new           #3                  // class java/lang/StringBuilder       6: dup       7: invokespecial #4                  // Method java/lang/StringBuilder."
":()V 10: aload_1 11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 14: ldc #6 // String b 16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: astore_1 23: new #3 // class java/lang/StringBuilder 26: dup 27: invokespecial #4 // Method java/lang/StringBuilder."
":()V 30: aload_1 31: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 34: ldc #8 // String c 36: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 39: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 42: astore_1复制代码

可以看出,相比之前的例子,这个过程多了很多中间计算。首先将字符串 "a" 存入变量1(即: s)中。接着,在拼接字符串 "b" 时,构造了一个 StringBuilder,依次放入变量1中的值("a")、字符串 "b"。然后通过 StringBuilder#toString 得到结果并再次存入变量1中。此时变量1中存的值是 "ab"。接着,再次构造一个 StringBuilder,依次放入变量1的值("ab")、字符串 "c"。然后又一次调用 StringBuilder#toString,得到最终结果 "abc"。

也就是说,每执行一条拼接语句,就会构造一个 StringBuilder,并对之前的结果和当前要拼入的后续字符串依次调用 append 放入,然后再用 toString 得到该语句的结果。如果你有类似于下面这样的拼接,那么可能会产生极大的浪费:

String result = ...;        List
strsToAppend = ...; for (String s : strsToAppend) { result += s; }复制代码

总结一下,如果我们要在一条语句中进行字符串拼接,那么可以直接使用 "+" 运算符,这样做的代码十分简洁,同时也具有很高的可读性。但如果拼接涉及多条语句,那么就需要考虑使用类似于 StringBuilder 的技术来避免或减少先创建然后又丢弃中间对象的事情发生,以提高拼接的性能。

References

转载地址:http://fpisx.baihongyu.com/

你可能感兴趣的文章
go微服务框架go-micro深度学习(三) Registry服务的注册和发现
查看>>
python 重载方法有哪些特点 - 老王python - 博客园
查看>>
在Fedora8上安装MySQL5.0.45的过程
查看>>
设计模式之命令模式
查看>>
android 测试 mondey
查看>>
Spring AOP项目应用——方法入参校验 & 日志横切
查看>>
使用REST-Assured对API接口进行自动化测试
查看>>
王潮歌跨界指导HUAWEI P20系列发布会 颠覆传统 眼界大开!
查看>>
王高飞:微博已收购一直播 明年一季度重点是功能与流量打通
查看>>
趣头条发行区间7至9美元 预计9月14日美国上市
查看>>
新北市长侯友宜:两岸交流应从隔壁最亲近的人开始
查看>>
全面屏的Nokia X即将上线,不到2000元的信仰你要充值吗?
查看>>
HTML5音频audio属性
查看>>
ES6学习
查看>>
Centos7搭建Django环境
查看>>
序列化一个Intent
查看>>
JavaScript数据类型及语言基础--ife
查看>>
进阶 Nginx 高手必须跨越的 5 座大山
查看>>
部署P2P升级的脚本
查看>>
jenkins--ant持续集成测试build文件脚本 测试报告
查看>>