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. hihocoder offer收割编程练习赛12 B 一面砖墙

    思路: 就是求哪个长度出现的次数最多. 实现: #include <iostream> #include <cstdio> #include <algorithm> ...

  2. Vue.js学习笔记--4. 组件的基本使用

    整理自官网教程 -- https://cn.vuejs.org/ 1. 所有Vue组件同时也都是Vue实例,分为全局组件和局部组件,注册方式如下. <div id="app" ...

  3. Android动态权限申请

    Android系统中,目前Dangerous级别的权限都需要动态申请.步骤如下: 1.AndroidManfiest.xml中申明需要的动态权限 <?xml version="1.0& ...

  4. Android学习笔记(六) Activity和View基础

    一.Activity的启动流程 操作系统首先查看AndroidManifest.xml来决定启动哪一个Activity. 生成对应的Activity对象(系统自动完成). 调用它的onCreate() ...

  5. git介绍与使用

    git是去中心化版本管理工具,项目创建一个远程版本,将其clone到本地,会在本地创建一个本地仓,一个开发项目,在没有网络的时候依然可以提交修改到本地仓,当有网络的时候再提交到远程仓库.git可以快速 ...

  6. Swift protocol extension method is called instead of method implemented in subclass

    Swift protocol extension method is called instead of method implemented in subclass protocol MyProto ...

  7. http 1.0 http 1.1 http 2.0的区别

    HTTP/1.0 版的主要缺点是,每个TCP连接只能发送一个请求.发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接. http1.1优点: 1.HTTP 1.1 版引入了持久连接( ...

  8. 解决java web项目导入后出现的问题 ---cannot be read or is not a valid ZIP file

    导入以前的web项目后会出现以下三个错误: 1. Archive for required library: ‘WebContent/WEB-INF/lib/readme.txt’ in projec ...

  9. promise的简单使用

    var p = new Promise(function (resolve,reject) { /*setTimeout(function () { resolve('success') },3000 ...

  10. Java格式化CST时间(mysql date类型)

    在从mysql导入数据时候,mysql里的日期是格林威治时间,普通格式化不行,这里总结一下格式化格林威治时间的方法: Date date = new Date(); System.out.printl ...