语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语。指的是,在计算机语言中添加某种语法,这些语法糖虽然不会对语言的功能产生任何影响,却能使程序员更方便的使用语言开发程序,同时增强程序代码的可读性,避免出错的机会。但是如果只是大量添加和使用语法糖,却不去了解他,容易产生过度依赖,从而无法看清语法糖的糖衣背后,程序代码的真实面目。

总而言之,语法糖可以看做是编译器实现的一些“小把戏”,这些“小把戏”可能会使得效率“大提升”,但我们也应该去了解这些“小把戏”背后的真是世界,这样才能更好地利用它们,而不是被它们迷惑。

下面我们就 泛型,自动拆箱/装箱、遍历循环和条件编译做简单的介绍和分析。了解他们背后的真相。

1、泛型与类型擦除

​ 泛型是 JDK 1.5 的一项新增类型,它的本质是参数化类型(Parametersized Type)的应用,也就是说所操作的数据类型被指定为一个参数。这种参数可以用在类、接口和方法的创建中,分别成为泛型类、泛型接口和泛型方法。

​ 泛型思想早在 C++ 语言的模板(Template) 就开始生根发芽。在 Java 语言还处于没有出现泛型的版本的时候,只能通过 *Object 类*是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化。 例如,在 HashMap 的存取中,get() 方法返回的就是一个 Object 对象,由于 Java 语言所有的类型都继承于 java.lang.Object , 所以 Object 转型成任何对象都是有可能的。但是也因为有无限的可能性,就只有程序员和运行期的虚拟机知道这个Object 到底是什么类型的对象。在编译期间,编译器无法检查这个 Object 的强制转型是否成功**,如果仅仅依赖于程序员去保障这项操作的正确性,许多 ClassCastException 的风险就会转嫁到程序运行期中。

​ 但是,泛型技术在 C# 和 Java 之中的使用方式看似相同,在实现上却有着根本性的分歧,C# 的泛型无论是在源码中、编译后的 IL 中(Intermediate Language , 中间语言买这时候泛型符是一个占位符),或是运行期的 CLR 中,都是切实存在的, List 和 List 就是两个不同的类型,它们在运行期间,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型称为****真实泛型****。

​ Java 中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型(Raw Type , 也成为裸类型)了,并且在相应的地方插入了强制类型转换代码,因此,对于运行期的 Java 语言来说,ArrayList 和 ArrayList 就是同一个类,所以泛型技术实际上是 Java 语言的一颗语法糖,Java 语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型

下面我们来看看一段简单的泛型擦除的例子:

 public static void main(String[] args) {Map<String, String> map = new HashMap<String, String>();map.put("hello", "你好");map.put("how are you?", "聊天止于呵呵");System.out.println(map.get("hello"));System.out.println(map.get("how are you?"));}

上面这段代码经过编译后,我们可以借助反编译软件 XJad 来查看编译后的代码,如下:

 public static void main(String args[]){Map map = new HashMap();map.put("hello", "你好");map.put("how are you?", "聊天止于呵呵");System.out.println((String)map.get("hello"));System.out.println((String)map.get("how are you?"));}

可以看到,所有的泛型代码都不见了,程序又变回了 Java 泛型出现之前的写法,泛型类型都变回了原生类型。也因为这个原因,当使用泛型类型做方法参数的时候,

无法根据泛型来实现重载

。如下列代码:

 public static int method(List<String> list){return 1;}public static int method(List<Integer> list){return 2;}

编译器会拒绝进行编译。

2、自动装箱、拆箱与遍历循环

​ 从技术上来讲,自动装箱、拆箱与遍历循环(Foreach循环) 这些语法糖,无论是从实现上还是从思想上都不能和放行相比,两者的难度和深度都有很大的差距。我们通过代码来看看这些语法糖的本质:

 public static void main(String[] args) {List<Integer> list = Arrays.asList(1,2,3,4);//如果是在 JDK 1.7 还有另外一颗语法糖// List<Integer> list = [1,2,3,4];int sum =0;for(int i : list){sum+=i;}System.out.print(sum);}

上面代码一共包含了 泛型、自动装箱、自动拆箱、遍历循环与变长参数 5 种语法糖。经过反编译后得到如下代码:

 public static void main(String args[]) {List list = Arrays.asList(new Integer[] { Integer.valueOf(1),Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4) });int sum = 0;for (Iterator iterator = list.iterator(); iterator.hasNext();) {int i = ((Integer) iterator.next()).intValue();}System.out.print(sum);}

可以看到,

​ 自动箱、拆箱在编译之后被转化成了对应的包装和还原方法。如本例中的 Integer.valueof() 与 Integer.intValue() 方法,而遍历循环则把代码还原成了 ****迭代器****的实现,这也是为何遍历循环需要被遍历的类实现 Iterable 接口的原因。

变长参数则在调用的时候变成了一个***\*数组类型\****的参数,在变长类型出现之前,程序员就是使用数组来完成类似功能的。

​ 有的时候,不节制的或不正确的使用装箱和拆箱会给我们带了极大的困扰,如下列代码:

public static void main(String[] args) {Integer a = 1;Integer b = 2;Integer c = 3;Integer d = 3;Integer e = 321;Integer f = 321;Long g = 3L;System.out.println(c == d);System.out.println(e == f);System.out.println(c == (a + b));System.out.println(c.equals(a + b));System.out.println(g == (a + b));System.out.println(g.equals(a + b));}

读完上面代码,不妨自己思考一下,这六条输出语句的输出结果是什么? 这些语法糖解除之后的参数会是什么样子的呢?

下面来揭晓答案吧!

​ 首先第一个:c ==d,因为“ ==” 号是用来判断两个引用指向的是否是同一个对象,因为在Integer 在构造对象的时候,128内的整数做了一个缓存优化,构造相同数值的代码会指向同一个对象,所以这个输出是true。

​ 第二个, e == f ,类似于第一条,只是因为 e 与 f 不在优化范围之内,所以输出是 false。

​ 第三个,c == ( a + b) , Java 中 == 号只有遇见运算符的情况下才会发生拆箱操作,这行代码被编译成 c.intValue() == a.intValue() + b.intValue(),比较的是值是否相等,所以输出结果为 true。

​ 第四个,c.equals( a + b ),类似于上一条,会发生拆箱操作,编译为 c.equals(Integer.valueOf(a.intValue() + b.intValue())) ,输出结果为 true。

​ 第五个,g == (a + b),类似于第三条,发生拆箱,同时因为有"=="号的存在,会发生强制类型转换, 被编译为 g.longValue() == (long)(a.intValue() + b.intValue()) ,输出结果为 true。

​ 第六个,g.equals( a+b) ,类似于第四条,会发生拆箱操作,但是没有”==“号,不会放生强制类型转换。而第四条比较的都是Integer类型,而这里却是 Long 和 Integer 类型,equals 只能比较同种类型的对象,所以返回自然就是 false 了。

你做对了几道?

所以程序猿们做好少用,用者需谨慎!

3、条件编译

​ 许多程序设计语言都提供了条件编译的途径,如C、C++ 中使用预处理器指示符(#ifdef)完成条件编译。C、C++ 的预处理器最初的任务是解决编译时的代码依赖关系(如#include),而在 Java 语言之中并没有使用预处理器,因为 Java 语言天然的编译方式(不一个个编译 Java 文件,而是将所有编译单元的语法树顶级结点输入到待处理列表后再进行编译,因此各个文件之间能够互相提供符号信息) 无需使用预处理器。

​ Java 语言也可以进行条件编译,方法就是使用条件为常量的 if 语句。如下面代码:

public static void main(String[] args) {if(true){System.out.println("True");}else{System.out.println("False");}}

经过编译后会变成如下代码:

public static void main(String args[]){System.out.println("True");}

而有的时候使用常量的判断语句会提示错误,被拒绝编译:

while(false){System.out.println("True");}

​ Java 语言中条件编译的实现,也是 Java 语言的一颗语法糖,根据布尔常量的真假,编译器将会把分支中不成立的代码块消除掉,这一工作将在编译器接触语法糖阶段完成。由于这种条件编译的实现方式使用了 if 语句,所以它必须遵循最基本的Java 语法,只能写在方法体内部,因此它只能实现语句基本块级别的条件编译,而无法根据条件调整 Java 类的结构。

Java语言中还有其它的一些语法糖,如内部类、枚举类、断言语句、对枚举和字符串的switch 支持、try语句中定义和关闭资源等,读者可以跟踪 Javac源码、反编译 Class 文件等方式了解他们的本质实现。

Jvm 系列(十一)Java 语法糖背后的真相相关推荐

  1. jvm系列(十一):Java 8-从持久代到metaspace

    原文出处:http://blog.csdn.net/wang8118/article/details/45765869 Java 8介绍了一些新语言以及运行时新特点.其中一个特点便是完全移除了持久代( ...

  2. Java 语法糖详解

    语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序 ...

  3. JAVA语法糖(全)

    JAVA语法糖(全) 目录 概述 字符串拼接 条件编译 断言 枚举与Switch语句 字符串与Switch语句 可变参数 自动装箱/拆箱 枚举 内部类 泛型擦除 增强for循环 lambda表达式 t ...

  4. Java语法糖1:可变长度参数以及foreach循环原理

    语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的 ...

  5. java语法糖效率高吗_打包 Java将持续向“高糖”方向发展,你真的了解Java语法糖吗? _好机友...

    Java语法糖概念 1. 语法糖Syntactic Sugar 糖衣语法,方便开发人员使用,JVM并不识别,会在编译阶段解语法糖,还原为基础语法. 2. com.sun.tools.javac.mai ...

  6. 65.Java语法糖

    65.Java语法糖 语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没 ...

  7. JVM系列之Java是解释性语言还是编译型语言?(一)

    JVM系列之Java是解释性语言还是编译型语言? 1.什么是Java语言? java语言是一门面向对象的计算机高级编程语言.编程语言(英语:programming language),是用来定义计算机 ...

  8. Java中switch参数传null会引起异常——Java 语法糖

    问题 switch 参数不能是null,swicth(null)会报java.lang.NullPointerException异常 查找原因 为什么会这样呢,查找一下原因: 找到编译后的class文 ...

  9. java 语法糖 字符串,java中的一些语法糖

    Java中的语法糖 语法糖的定义:语法糖指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,并没有给语言添加什么新东西,但是更方便程序员使用.通常来说使用语法糖能够增加程序的可读性,从而减少 ...

最新文章

  1. openCV 图像相加,位运算,协方差,绝对值,比较
  2. 遮罩层中的相对定位与绝对定位(Ajax)
  3. SAP Spartacus基于travis的持续集成
  4. poj 2777 Count Color(线段树区区+染色问题)
  5. 【es】es 分布式一致性原理剖析(三)-Data篇
  6. python中copytree的用法_python复制文件的方法实例详解
  7. mysql 加密 en_以极少的停机时间加密运行 MySQL 或 MariaDB 的 Amazon RDS 数据库实例...
  8. Selenium与Cypress的比较
  9. ultilize什么意思_utilize是什么意思_utilize在线翻译_读音_用法_例句_含义-查字典网...
  10. 商业计划书范文3000_商业计划书范文(精选)
  11. CAD看图软件的快速搜索功能怎么用?
  12. 没有计算器的日子怎么过——手动时期的计算工具
  13. PyTorch实践系列(二):GPU与CPU运行对比
  14. mtk处理器和骁龙对比_3500元以内手机的绝杀?首款MTK 天玑1000处理器手机IQOO Z发布...
  15. Python + Selenium实现163邮箱的自动登录和发送邮件
  16. Electron MAC 打包签名生成
  17. 如何利用极致业务基础平台构建一个通用企业ERP之十四生产任务单设计
  18. will-change的使用
  19. PHP使用swagger-php自动生成api文档(详细附上完整例子)
  20. git操作提示warning: redirecting to git@github.com:XXXXX

热门文章

  1. 最终版本Science级组合图表绘制
  2. 真菌其实是长歪了的动物
  3. Gut:人体最初的微生物起源与生殖健康
  4. Python使用matplotlib可视化时间序列数据、并为时间序列曲线添加误差带、使用95%置信区间(Time Series Error Bands with confidence interval
  5. R语言ggplot2可视化时间序列数据并突出标注重要时间点数据实战:特殊节点标签标注、特殊区域标注
  6. R语言axis函数自定义可视化的坐标轴刻度线(axis ticks)
  7. gif加文字 php,gif动态图片添加文字 gif制作软件 怎样给gif动态图片添加文字
  8. 孪生素数 java代码_科学网—孪生素数猜想——利用 Java + 正则表达式 输出孪生素数对 - 马廷灿的博文...
  9. cad打印样式ctb丢失_我的第一次打印:cad模型空间套图框打印图纸
  10. 三代测序纠错软件汇总篇