JVM(二):Java中的语法糖

上文讲到在语义分析中会对Java中的语法糖进行解糖操作,因此本文就主要讲述一下Java中有哪些语法糖,每个语法糖在解糖过后的原始代码,以及这些语法糖背后的逻辑。

语法糖

语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。 -----百度百科

在编程领域中,除了语法糖的概念还有语法盐,语法糖精,语法海洛因这些新奇的概念,感兴趣的读者自行Google一下,本文篇幅有限,就不展开来说了.

从百度百科的描述来看,语法糖只是作为一种更快捷的语法,其并不会改变所要实现的功能,因此本文就以Java中的语法糖来验证一下是否如此.

循环迭代

源代码如下所示

public class TestForEach {

public static void main(String[] args) {
// 验证 循环迭代
ArrayList<Integer> test = new ArrayList<Integer>();
test.add(1);
for (Integer integer : test) {
System.out.println(integer);
} Integer[] array = new Integer[test.size()];
array = test.toArray(array);
for (Integer integer : array) {
System.out.println(integer);
}
}
}

用jad反编译后如下所示

public class TestForEach
{ public TestForEach()
{
} public static void main(String args[])
{
ArrayList arraylist = new ArrayList();
arraylist.add(Integer.valueOf(1));
Integer integer;
for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); System.out.println(integer))
integer = (Integer)iterator.next(); Integer ainteger[] = new Integer[arraylist.size()];
ainteger = (Integer[])arraylist.toArray(ainteger);
Integer ainteger1[] = ainteger;
int i = ainteger1.length;
for(int j = 0; j < i; j++)
{
Integer integer1 = ainteger1[j];
System.out.println(integer1);
} }
}

根据上面反编译后的代码可以看出集合元素的循环迭代底层是通过迭代器来实现的.而数组的循环则是通过原始的for循环来实现的.

泛型

通过上面的代码我们还可以看出泛型这个概念在javac编译时是不存在的,编译器会将所有的泛型替换掉,在使用时,直接采用类型转换的方式来得到结果.也正是泛型的这个特征可能出现下面这个问题.

函数重载

  public void test1(ArrayList<Integer> a){}
public void test1(ArrayList<String> a){}

以上代码是无法编译通过的,因为根据上文得到的结论,泛型在编译时,会消除所有的泛型的限定,那么上面两个方法的签名都会一致,不满足函数重载的条件.

自动拆装箱

Java支持自动拆装箱,即将基本类型和其包装类型之间进行自动替换,那这种方式又是如何实现的呢.

原始代码如下所示:

// 自动拆装箱
Integer a = 1;
int b = new Integer(a);

经过反编译后,代码如下所示

Integer integer = Integer.valueOf(1);
int i = (new Integer(integer.intValue())).intValue();

可以看到Java实现基本类型 -- >包装类型,是通过XXX.valueOf()来实现的,而包装类型 --> 基本类型是通过xxxValue()来实现的.

switch

我们都知道switch--case只对int和char类型的数据有效,但从java7开始switch已经可以支持String类型了,这背后的逻辑又是什么,下面我们反编译一下代码看看其本质是如何实现的.

    String hello = "1";
switch (hello){
case "hello":
System.out.println("Hello");
break;
case "world":
System.out.println("World");
break;
default:
System.out.println("HelloWorld");
break;
}

初始代码如上所示,我们在switch中比较了字符串,根据字符串的不同来实现不同的分支,那这种逻辑是如何实现的呢.

    String s = "1";
String s1 = s;
byte byte0 = -1;
switch(s1.hashCode())
{
case 99162322:
if(s1.equals("hello"))
byte0 = 0;
break; case 113318802:
if(s1.equals("world"))
byte0 = 1;
break;
}
switch(byte0)
{
case 0: // '\0'
System.out.println("Hello");
break; case 1: // '\001'
System.out.println("World");
break; default:
System.out.println("HelloWorld");
break;
}
}

可以看到字符串是通过比较字符串的hashcode来进行比较,当两个字符串的hashCode值相同时,再通过equals()来确定其是否真正相同.

因此 Switch 比较 String 的本质还是比较 int 类型的数据。

变长参数

变长参数,即允许在方法调用时传入不定数量的参数.具体使用如下所示:

public static void unSignedArgs(String... a){
for (String s : a) {
System.out.println(s);
}
} public static void main(String[] args) {
unSignedArgs("1","3","4");
}

unSignedArgs()方法中,我们定义了一个变长参数,然后在方法调用的时候,传入3个参数.那么这种方法是如何实现的.

public static transient void unSignedArgs(String as[])
{
String as1[] = as;
int i = as1.length;
for(int j = 0; j < i; j++)
{
String s = as1[j];
System.out.println(s);
}
} public static void main(String args[])
{
unSignedArgs(new String[] {
"1", "3", "4"
});
}
}

可以看到变长参数,本质是通过数组来实现的,首先在方法定义时,将变长参数转换为了数组.然后在方法调用的时候是将传入的参数转换成数组然后再传入定义的方法中。

try-with-resource

在过去操作资源时,使用try-catch-finally语句,需要开发人员手动在finally中关闭资源。但现在官方提倡使用 try-with-resource 来操作资源,那么该语法是如何使用的呢。

源代码如下所示:

   try (FileInputStream fileInputStream = new FileInputStream("dfd")){

       fileInputStream.read();
}catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

反编译后代码如下所示:

FileInputStream fileinputstream;
Throwable throwable;
Exception exception;
fileinputstream = new FileInputStream("dfd");
throwable = null;
try
{
fileinputstream.read();
}
catch(Throwable throwable2)
{
throwable = throwable2;
throw throwable2;
}
finally
{
if(fileinputstream == null) goto _L0; else goto _L0
}
if(fileinputstream != null)
if(throwable != null)
try
{
fileinputstream.close();
}
catch(Throwable throwable1)
{
throwable.addSuppressed(throwable1);
}
else
fileinputstream.close();
break MISSING_BLOCK_LABEL_104;
if(throwable != null)
try
{
fileinputstream.close();
}
catch(Throwable throwable3)
{
throwable.addSuppressed(throwable3);
}
else
fileinputstream.close();
throw exception;
Object obj;
obj;
((FileNotFoundException) (obj)).printStackTrace();
break MISSING_BLOCK_LABEL_104;
obj;
((IOException) (obj)).printStackTrace();
}

可以看到编译器自动帮助我们进行资源的关闭,减少了编程人员出错的可能.

数值字面量

数值字面量即在多位数值中穿插入_,方便开发人员快速掌握数值的大小.

int a = 10_000;
System.out.println(a+1);

那么这个语法糖的含义是什么呢,反编译后如下所示

char c = '\u2710';
System.out.println(c + 1);

从结果可以看到编译器是不会管下划线的,其只会将数值正常的读写出来.

断言

    int a = 1;
int b = 2;
assert a == b;
System.out.println(a+b);

翻译后代码如下所示

 {
int i = 1;
byte byte0 = 2;
if(!$assertionsDisabled && i != byte0)
{
throw new AssertionError();
} else
{
System.out.println(i + byte0);
return;
}
}

从代码中可以清楚地看到断言的底层实现机制是用if语句来实现,如果条件不符合,则抛出异常.

条件编译

Java中的条件编译,是通过永真或永假if来实现的,编译器会判断条件是否符合,从而来判断是否进行编译.

源代码如下所示:

    // 条件编译
if (true){
System.out.println("true");
}else{
System.out.println("false");
}

反编译后代码如下:

 {
System.out.println("true");
}

从代码中我们可以看到,编译器对永远不会执行的代码进行了不编译的处理,从而达到了条件编译的效果.但其实笔者感觉条件编译在Java中用处不大,作用就是在不同的模式或机器下,可以编译执行不同的代码.不过有总比没有好.

内部类

在这里我们说内部类是一个语法糖,是因为其仅仅是一个编译时的概念,在编译阶段,编译器会将外部类和内部类进行编译,从而生成两个不同的文件,如下所示:

public class TestForEach {
public class Children{
}
} [260259@localhost src]$ ll
总用量 16
-rw-rw-r--. 1 260259 260259 331 5月 21 11:04 TestForEach$Children.class
-rw-rw-r--. 1 260259 260259 335 5月 21 11:04 TestForEach.class
-rw-rw-r--. 1 260259 260259 506 5月 21 11:04 TestForEach.jad
-rw-rw-r--. 1 260259 260259 2206 5月 21 11:06 TestForEach.java

反编译后,如下所示:

public class TestForEach
{
public class Children{ final TestForEach this$0; public Children(){
this$0 = TestForEach.this;
super();
}
}

枚举

枚举是一种特殊的数据接口,其中包含了一种特殊的数据接口,以key-value 的形式来存储数据,那么 enum 是一种类吗,其内部又是如何实现的呢。

首先我们定义一个枚举:

public enum testEnum {
SPRING,SUMMER,AUTUMN,WINTER
}

此时,我们对这个枚举进行反编译,

public final class testEnum extends Enum
{ public static testEnum[] values()
{
return (testEnum[])$VALUES.clone();
} public static testEnum valueOf(String s)
{
return (testEnum)Enum.valueOf(testEnum, s);
} private testEnum(String s, int i)
{
super(s, i);
} public static final testEnum SPRING;
public static final testEnum SUMMER;
public static final testEnum AUTUMN;
public static final testEnum WINTER;
private static final testEnum $VALUES[]; static
{
SPRING = new testEnum("SPRING", 0);
SUMMER = new testEnum("SUMMER", 1);
AUTUMN = new testEnum("AUTUMN", 2);
WINTER = new testEnum("WINTER", 3);
$VALUES = (new testEnum[] {
SPRING, SUMMER, AUTUMN, WINTER
});
}
}

从结果,我们可以看出,首先枚举是一个编译时的概念,这也说明了其是Java的一个语法糖,编译器在编译的时候自动生成了一个类继承自Enum,同时声明为final,这也为枚举是不可继承的提供了理论基础.

lambda

lambda作为Java8新出的一个功能点其实也是一种语法糖.因为笔者这边没有Java8及以上版本的反编译工具,因此这边就不详细描述了,粗略说一下,lambda作为一个语法糖,其内部其实是通过相关的两个底层Api来实现的.

总结

在本文中,笔者介绍了Java中的12个语法糖,作为开发人员了解这些语法糖的用法以及其内部的含义,可以让我们更加高效地开发业务代码,同时也可以让我们了解编译器的优化逻辑.从而提高程序的编写效率和运行效率.

文章在公众号"iceWang"第一手更新,有兴趣的朋友可以关注公众号,第一时间看到笔者分享的各项知识点,谢谢!笔芯.

本系列文章主要借鉴自<深入分析JavaWeb技术内幕>和<深入理解Java虚拟机-JVM高级特性与最佳实践>.

JVM(二):Java中的语法糖的更多相关文章

  1. [转]谈谈Java中的语法糖

    *该博客转自 http://blog.csdn.net/danchu/article/details/54986442 语法糖(Syntactic Sugar),也称糖衣语法,指在计算机语言中添加的某 ...

  2. 浅析java中的语法糖

    概述 编译器是一种计算机程序, 它主要的目的是将便于人编写.阅读.维护的高级计算机语言所写的源代码程序, 翻译为计算机能解读.运行的低阶机器语言的程序, 即可执行文件.而 javac 就是java语言 ...

  3. 【Java基础】Java中的语法糖

    目录 Java中的语法糖 switch对String和枚举类的支持 对泛型的支持 包装类型的自动装箱和拆箱 变长方法参数 枚举 内部类 条件编译 断言 数值字面量 for-each try-with- ...

  4. Java 中的语法糖

    百度百科对语法糖的定义 语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这 ...

  5. Java 中的语法糖,真甜。

    我把自己以往的文章汇总成为了 Github ,欢迎各位大佬 star https://github.com/crisxuan/bestJavaer 我们在日常开发中经常会使用到诸如泛型.自动拆箱和装箱 ...

  6. Java中的语法糖

    一.范型 1. C#和Java范型的区别 在C#中范型是切实存在的,List<int>和List<String>就是两种不同的类型,它们在系统运行期间生成,有自己的虚方法表和类 ...

  7. Java 中的语法糖(7/15整个周六上午总结)

    语法糖定义指的是,在计算机语言中添加某种语法,这种语法能使程序员更方便的使用语言开发程序,同时增强程序代码的可读性,避免出错的机会:但是这种语法对语言的功能并没有影响.Java中的泛型,变长参数,自动 ...

  8. Java初认识--Java中的语法结构

    Java中的语法结构(程序流程控制) Java的语法结构有四种: 1.顺序结构. 顺序结构很简单,就是按顺序执行,输出就可以了. 2.判断结构. 判断结构的一个代表性的语句是if:if语句有三种格式体 ...

  9. JVM:Java中的引用

    JVM:Java中的引用 本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记 在原来的时候,我们谈到一个类的实例化 Person p = new Person() 在 ...

随机推荐

  1. SceneAction$$FastClassByCGLIB$$7330f7b9.invoke(int, Object, Object[]) line: not available

    现象:在调试状态下,断点可以进入ACTION ,当调用service的时候,发现无法进入service中的断点,就报了题目中的错误. 过程:1.降低JDK.因为本工程是用JDK1.6编译的,maven ...

  2. PMP项目管理学习笔记(2)——组织、约束和干系人

    (一)组织 这里所说的组织,就是我们所说的团队组织架构. 1.组织的类型 职能型: 在这种组织中,项目团队成员总是向职能经理报告,所有事务都有职能经理全权负责. 项目经理的决策需要与职能经理确认. 项 ...

  3. mysql 插入多条记录,重复值不插入

    只去除主键与唯一索引的字段,字段为null时 是可以重复插入的domo: insert ignore into table_name(email,phone,user_id) values('test ...

  4. .Net Core 真能令微软的.Net 跨平台“蔓延”?

    什么是.Net .Net 本身就是基于公共语言基础架构(CLI)实现的平台独立的公共语言开发平台,只是自2006年成为规范以来的CLI,只有Windows自己支持罢了(mono除外).微软的.Net ...

  5. scrapy 请求传参

    class MovieSpider(scrapy.Spider): name = 'movie' allowed_domains = ['www.id97.com'] start_urls = ['h ...

  6. _bbox_pred函数

    fast中的_bbox_pred函数和faster中的bbox_transform_inv是一样的,是将框进行4个坐标变换得到新的框坐标.fast中是将selective search生成的框坐标进行 ...

  7. h5开发app,移动端 click 事件响应缓慢的解决方案

    造成点击缓慢的原因 从点击屏幕上的元素到触发元素的 click 事件,移动浏览器会有大约 300 毫秒的等待时间.为什么这么设计呢? 因为它想看看你是不是要进行双击(double tap)操作. 使用 ...

  8. 216种Web安全颜色

    216种Web安全颜色 全部 JavaScript HTML5 jQuery CSS EXT Ajax Web综合 界面设计 DWR   锁定老帖子 主题:216种Web安全颜色 精华帖 (0) :: ...

  9. php更改wampserver的站点目录

    我都wampserver安装在f盘 F:\wamp\bin\apache\Apache2.4.4\conf文件夹下的hhtpd.conf文件ctrl+f查找DocumentRoot,第二次的位置修改即 ...

  10. python 3 廖雪峰博客笔记(二) python解释器

    python 解释器用于理解 python代码,存在多种python解释器 CPython 官方版本python解释器,用C语言开发,使用最广泛 IPython 基于CPython,在交互方式上有所增 ...