java语法糖

article/2025/9/17 21:27:45

概述

        几乎所有的语言都涉及到语法糖,语法糖时啥呢?语法糖其实就是在开发中方便程序员用的一些语法,该语法和正常语法的区别就在于:语法糖在底层其时是不能够被一次性直接实现的,它们需要依靠一些技术或普通语法实现。当我们编译.java文件为.class文件的时候,底层编译器解析到语法糖时就会进行解语法糖操作,还原为普通语法。

        这些语法虽然不会提供实质性的功能改进,但是能提高我们开发的效率,提供严谨性,但一门语言的语法糖不是越多越好,大量的添加语法糖会让开发者产生依赖,无法知道程序代码的真实行为。

      java中常见的语法糖有默认构造器、泛型、自动拆箱自动装箱、  增强for循环、可变长参数……

泛型

        在jdk5的时候java推出了泛型的功能,我们来谈谈java泛型的故事和其实现原理。

java和c#的泛型

        java和C#在同一年推出了泛型的功能,但是它们两泛型的实现并不相同,java的泛型采用了"类型擦除式泛型",而C#采用的是"具现化式泛型",所谓具现化就是比较具体的,泛型在C#里是真实存在的,在运行环境中泛型充当了一个占位符,其中List<String>和List<int>不是一个类型,因为泛型的存在使得虽然裸类型(泛型前面的类型)相同但是整体却不相同;而类型擦除式泛型是指泛型只在源程序中存在,不会出现在编译后的class文件中,编译时泛型全部会被擦除替换为原来的裸类型,并且在相应的地方加上了强制转换代码,所以List<String>和List<int>其实是一个类型。如下C#可以正常编译,但是java里都是不合法的,因为编译后这些泛型都被擦出了,代码逻辑就不正常了。

public class GenericsType<T> {public void doSomething(Object item){if(item instanceof T){……}T newIntem =new T();T[]  itemArray=new T[10];}
}

        java泛型还有一个缺点就是效率低下,因为它会给程序带来不计其数的拆箱和装箱操作。

        总结一下,因为jdk5才出的泛型,所以java开发人员为了保证jdk5之前开发的java代码也能完美兼容,所以实现泛型有很多束缚,有两种实现方式选择,分别是java和C#两条路。而C#是当时才出现2年时间,完全没有太多的顾虑,它采用了开发一套平行于需要加泛型的类型的一套新的类型,以前的老类型可以用,新的类型只不过是在老类型之上添加了一个泛型标志而已。而java不一样,它在jdk1.2就已经这样选择过了,Vector类型用ArrayList代替、HashTable用HashMap代替……,如果因为泛型而分别在这些老新类型上平行的开发一套带有泛型的类型如Vector<T>、ArrayList<T>等,这就可能导致出现一片开发人员的骂声。

类型擦除

        如ArrayList<T>的裸类型为ArrayList类型,ArrayList<Integer>和ArrayList<String>都是ArrayList的子类型(不是面向对象的那种子类型),系统会允许子类到父类的完美转换。这些子类型在编译时直接被还原为裸类型了,只有元素访问和修改时自动插入一些强制类型转换和检查命令

 ArrayList可以理解为就是ArrayList<Object> 

如下面一段代码,在class文件中其实是另一个模样。

  ArrayList<String> list = new ArrayList<>();list.add("123");list.add("java");System.out.println(list.get(0));System.out.println(list.get(1));

反编译后产看结果:

ArrayList list = new ArrayList();//转为裸类型了
list.add("123");//实际调用的是list.add(Object e)
list.add("java");
System.out.println((String)list.get(0));//添加了强制转换
System.out.println((String)list.get(1));//实际取得是Object类型强转为String类型

        类型擦除方法还有一个缺陷就是对原始类型数据的支持又成了新麻烦,如下一段代码(目前java不支持)

ArrayList<int> ilist = new ArrayList<int>();
ArrayList<long> llist = new ArrayList<long>();
ArrayList list;
list=ilist;
list=llist;

如果当类型擦出了,可以想象得出当往下用到强制类型转换时,代码就往下执行不了了,因为不支持int、long类型和Object类型的转换,只有引用类型才能全是Object的子类,才能转换,所以java简单粗暴的禁止掉了原生类型的泛型,这也导致了无数的装箱拆箱。

还有如下代码,该代码是不能被编译通过的,因为类型擦除后压根就是同一个类型,达不到方法重载。

//'doSomething(ArrayList<String>)' clashes with 'doSomething(ArrayList<Integer>)';both methods have same erasure
public void doSomething(ArrayList<String> item){
}
public void doSomething(ArrayList<Integer> item){
}

擦除法所谓的擦除仅仅是对方法的Code的属性中的字节码擦除,实际上元数据还是保留了泛型信息的,如Signature属性和LocalVariableType都是为泛型而生的。可以看到LocalVariableType Table仍然保留了方法参数泛型的信息。通过反射依然能获取这些信息

ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2=new ArrayList<>();

上面代码的字节码为 

自动装箱、拆箱

        jdk5提供的新功能,因为泛型的加入,一些集合的泛型只支持包装类型,所以会发生很多的类型转换,也就出现了减少这种重复操作的自动拆箱和自动装箱语法糖。

Integer x=1;
int y=x;

如上一段代码,编译器会在编译阶段给我们的代码解语法糖改为如下代码:

Integer x=Integer.valueOf(1);
int y=x.intValue();

其中第一行为装箱操作,第二行为拆箱。

可变参数

可变参数也是JDK 5开始加入的新特性:

public static void main(String[] args) {f("hello","world");
}
public static void f(String... args){String[]arrays=args;//直接赋值System.out.println(arrays);
}

        可变参数String... args实是一个String[] args, 从代码中的赋值语句中就可以看出来。
同样java编译器会在编译期间将上述代码变换为: 

public static void main(String[] args) {f(new String[]{"hello","world"});//其实就是一个数组
}
public static void f(String... args){String[]arrays=args;//直接赋值System.out.println(arrays);
}

如果调用了f() 则等价代码为foo(new String[]{}),创建了一个空的数组,而不会传递null进去

增强for循环

public static void main(String[] args) {int[] array = {1, 2, 3, 4, 5};for (int item : array) {System.out.println(item);}
}

 上面的代码包含了两个语法糖,经过反编译之后,其实就是以下的代码:

public static void main(String[] args) {int[] array = new int[]{1, 2, 3, 4, 5};//第一个语法糖,数组赋初值的简化写法//开始第二个语法糖int[] var2 = array;for(int var4 = 0; var4 < array.length; ++var4) {int item = var2[var4];System.out.println(item);}
}

如果我们遍历的不是数组而是list集合,如

List<String> list= Arrays.asList("1","2","3");
for (String s : list) {System.out.println(s);
}

其实就是如下代码,其中包含了3个语法糖

List<String> list = Arrays.asList(new String[]{"1","2","3"});
Iterator var2 = list.iterator();while(var2.hasNext()) {String s = (String)var2.next();System.out.println(s);
}

 集合是采用的迭代器实现的增强for循环,如果集合要使用增强for循环,那可想而知,肯定是要实现Iterable接口的。Iterable接口需要实现一个iterator()方法,返回一个Iterator迭代器,即接口定义了规范。

switch字符串与枚举

从JDK 7开始,switch 可以作用于字符串和枚举类,这个功能其实也是语法糖,例如:

String str = "hello";
switch (str) {case "hello": {System.out.println("hello");break;}case "world": {System.out.println("world");break;}
}

真实代码为:

    String str = "hello";byte var3 = -1;//该值作为标识符//首先比较字符串的hash值switch(str.hashCode()) {case 99162322://里面的if和equals是为了防止hash冲突的if (str.equals("hello")) {var3 = 0;}break;case 113318802:if (str.equals("world")) {var3 = 1;}}//再根据获得的标识符来选择要执行的具体代码逻辑switch(var3) {case 0:System.out.println("hello");break;case 1:System.out.println("world");}

比较hash值后再equals比较是很严谨的,不会因为hash冲突导致一系列问题。这样做是为了用hash值来提高效率,再让equals来防止hash冲突

用两个switch而不直接输出的原因可能是:直接输出没办法达到用不用break这个操作,有可能用户忘记写break了,但是直接输出不会一直输出下去而是像正常使用的一样,这就有问题。

public enum Sex {MALE,FEMALE
}
public static void main(String[] args) {Sex sex = Sex.MALE;switch (sex) {case MALE:System.out.println("MALE");break;case FEMALE:System.out.println("FEMALE");break;}
}

解语法糖后为:

//定义一个合成类(仅jvm使用,对我们不可见)
//用来映射枚举的ordinal 与数组元素的关系
//枚举的ordinal 表示枚举对象的序号,从0开始
//即MALE的ordinal()=0, FEMALE的ordinal()=1 .
static class $MAP{
//数组大小即为枚举元素个数,里面存储case用来对比的数字static int[]map = new int[2];static{map[Sex.MALE.ordinal()]=1;map[Sex.FEMALE.ordinal()]=2;}
}public static void main(String[] args) {Sex sex = Sex.MALE;int x=$MAP.map[sex.ordinal()]=1;switch(x) {case 1:System.out.println("MALE");break;case 2:System.out.println("FEMALE");}
}

枚举类

JDK 7新增了枚举类,以前面的性别枚举为例:

public enum Sex {MALE,FEMALE
}

就是如下:

public final class Sex extends Enum<Sex>{public static final Sex MALE;public static final Sex FEMALE;public static Sex[] $VALUES;static {MALE=new Sex("MALE",0);FEMALE=new Sex("FEMALE",1);$VALUES =new Sex[]{MALE,FEMALE};}private Sex(String name,int ordinal){super(name,ordinal);}public static Sex[] values(){return $VALUES.clone();}public static Sex valueOf(String name){return Enum.valueOf(Sex.class,name);}
}

try-with-resource

jdk7新增的对关闭资源处理的特殊语法。

try(FileInputStream stream = new FileInputStream(new File("1.txt"))){System.out.println(stream);        
} catch (Exception e) {e.printStackTrace();
}

try中的资源对象需要实现AutoCloseable接口,只要放在try的括号中不需要我们自己去关闭资源,简化开发。

其语法解糖后如下:

try {FileInputStream stream = new FileInputStream(new File("1.txt"));Throwable var2 = null;//临时变量try {//我们的代码逻辑System.out.println(stream);} catch (Throwable var12) {var2 = var12;throw var12;} finally {if (stream != null) {//判断我们的代码中是否有异常    if (var2 != null) {try {stream.close();//关闭流} catch (Throwable var11) {//防止异常丢失var2.addSuppressed(var11);}} else {stream.close();}}}
} catch (Exception var14) {var14.printStackTrace();
}

给我们生成了两个try语句块。

方法重写时的桥接方法

我们都知道,方法重写时对返回值分两种情况:

  • 父子类的返回值完全一致
  • 子类返回值可以是父类返回值的子类(见下面的例子)
public class A {public Number m(){return 1;}
}
class B extends A{@Override//返回值类型必须是父类该方法的子类型public Integer m() {return 2;}
}

该重写时成立的,Override并没有报错。

解糖后类B如下:

class B extends A{public Integer m() {return 2;}public synthetic bridge Number m(){return m();}
}

它会给我们加上实际的方法重写,且间接调用我们原来的方法,这里的两个不认识的关键字我们是看不到的,只在jvm内部存在,这种方法就是桥接方法。

其中桥接方法比较特殊,仅对java虚拟机可见,并且与原来的public Integer m()没有命名冲突,可以用下面反射代码来验证:

for (Method method : B.class.getDeclaredMethods()) {System.out.println(method);
}

结果为:

public java.lang.Integer vm.B.m()
public java.lang.Number vm.B.m()

匿名内部类

public class Candy {public static void main(String[] args) {Runnable run=new Runnable() {@Overridepublic void run() {System.out.println("OK");}};}
}

该代码编译为class文件时会自动生成一个类,名字中有$的一般都是jvm给我们自动生成的类

class Candy$1 implements Runnable{@Overridepublic void run() {System.out.println("OK");}
}

再有,如果该匿名内部类中引用了外部的变量,该变量必须为final类型的

public class Candy {public static void main(String[] args) {int x = 0;//该变量被匿名内部类所引用,jvm会给他自动加上final//x=1; //不能给他赋值,如果赋值了就报错,Variable 'x' is accessed from within inner class, needs to be final or effectively finalRunnable run=new Runnable() {@Overridepublic void run() {System.out.println(x);}};}
}

生成的类为:

class Candy$1 implements Runnable{int val$x;//新增的变量public Candy$1(int val$x) {//构造方法里赋值this.val$x = val$x;}@Overridepublic void run() {System.out.println(this.val$x);}
}

这同时解释了为什么匿名内部类引用局部变量时,局部变量必须是final的:因为在创建candy$1对象时,将x的值赋值给了Candy$1对象的valx属性,所以x不应该再发生变化了,如果变化那么valx属性
没有机会再跟着一起变化。

 


http://chatgpt.dhexx.cn/article/1H961vhi.shtml

相关文章

【python】python语法糖

python 语法糖 1. 什么是语法糖&#x1f36c; 语法糖是由编程语言提供的一种可以让我们写出来的代码在不影响语句执行的功能的条件下&#xff0c;能够让我们的代码看起来更简洁和美观的一种语法。 很多人刚开始接触语法糖的时候&#xff0c;总会说这样一句&#xff1a;“最讨厌…

Java 中的语法糖,真甜。

我把自己以往的文章汇总成为了 Github &#xff0c;欢迎各位大佬 star https://github.com/crisxuan/bestJavaer 我们在日常开发中经常会使用到诸如泛型、自动拆箱和装箱、内部类、增强 for 循环、try-with-resources 语法、lambda 表达式等&#xff0c;我们只觉得用的很爽&…

Python语法糖系列

语法糖说明 语法糖&#xff08;Syntactic sugar&#xff09;&#xff1a;计算机语言中特殊的某种语法这种语法对语言的功能并没有影响对于程序员有更好的易用性能够增加程序的可读性简而言之&#xff0c;语法糖就是程序语言中提供[奇技淫巧]的一种手段和方式而已。 通过这类方…

什么是语法糖?Java中有哪些语法糖?

本文从 Java 编译原理角度&#xff0c;深入字节码及 class 文件&#xff0c;抽丝剥茧&#xff0c;了解 Java 中的语法糖原理及用法&#xff0c;帮助大家在学会如何使用 Java 语法糖的同时&#xff0c;了解这些语法糖背后的原理 1 语法糖 语法糖&#xff08;Syntactic Sugar&…

不了解这12个语法糖,别说你会Java!

阅读本文大概需要 10 分钟。 作者:Hollis 本文从 Java 编译原理角度,深入字节码及 class 文件,抽丝剥茧,了解 Java 中的语法糖原理及用法,帮助大家在学会如何使用 Java 语法糖的同时,了解这些语法糖背后的原理 语法糖 语法糖(Syntactic Sugar),也称糖衣语法,是由英…

什么是语法糖(Syntactic sugar)?

大学时没选修编译原理这门课&#xff0c;不知道什么是语法糖&#xff0c;最近看React的官方文档才接触语法糖的概念&#xff0c;简单查了下资料记录下&#xff0c;以下是来自百度百科的解释&#xff1a; 语法糖&#xff08;Syntactic sugar&#xff09;&#xff0c;也译为糖衣语…

el-steps分布展示页面

<template><div class"app-container automation"><el-steps :active"active" finish-status"success"><el-step title"步骤 1"></el-step><el-step title"步骤 2"></el-step>&l…

038_Steps步骤条

1. Steps步骤条 1.1. Steps步骤条引导用户按照流程完成任务的分步导航条, 可根据实际应用场景设定步骤, 步骤不得少于2步。 1.2. Steps Attributes 参数 说明 类型 可选值 默认值 space 每个step的间距, 不填写将自适应间距。支持百分比。 number / string 无 无 d…

更改el-steps颜色样式

原样式&#xff1a; 目标样式如下&#xff0c;也不追求完全一致&#xff0c;至少得看得过去。 <style scoped>::v-deep .el-step__head.is-success {color: rgb(52, 158, 250);border-color: rgb(52, 158, 250); } ::v-deep .el-step__title.is-success {font-weight: …

vue使用steps步骤条,自定义内容

效果图 一、引入element ui steps <el-steps :active"caseHistoryData.length" finish-status"success" direction"vertical" ><el-step><template slot"description"></template></el-step> </el-…

steps.js 步骤条、时间轴

GitHub地址&#xff1a;https://github.com/fxss5201/steps 介绍 首先看一下目录&#xff1a; 1.0 文件夹和 2.0 文件夹非升级关系&#xff0c;两者仅是着重点方向不一致&#xff0c;1.0 主打双边显示&#xff0c;2.0 主打内容排序&#xff0c;一般功能的话两者均可满足。 …

CSS3中steps()动画的详解

原文作者&#xff1a;Joni Trythall 增修作者&#xff1a;Fegmaybe 一个是雪碧图的实现动画的steps效果。steps&#xff08;&#xff09;阶跃函数&#xff0c;是transition-timing-function和animation-timing-function两个属性的属性值&#xff0c;他是表示两个关键帧之间的动…

css【详解】steps()函数

steps(number, position)number 整数值&#xff0c;表示把动画分成了多少段。position 可选参数&#xff0c;表示动画跳跃执行是在时间段的开始还是结束。 —— start 在时间段的开头处跳跃 —— end 在时间段的结束处跳跃 动画效果见 https://demo.cssworld.cn/new/5/4-7.php…

steps函数--参数意思和用法

图片解释如下&#xff0c;参数意思和用法在代码的注释中 所引用图片共7帧&#xff0c;如下&#xff1a; 尺寸为200*1400&#xff0c;所以设置div为200*200&#xff0c;分为7帧&#xff0c;除去展示帧&#xff0c;需六次步骤跳转&#xff0c;原图如下&#xff1a; 代码&…

element的步骤条整合表单(steps+form)

先上效果图&#xff1a; element ui 组件库官网 使用步骤&#xff1a; 1、页面组合步骤 > 5步 定义出4个步骤&#xff08;看你自己需要几步&#xff09;定义form表单定义4个盒子对象active属性 > 1 到 4放置表单项设置上一步和下一步的按钮 <template>//第一步…

vue、Steps 步骤条、Steps 属性、vue Steps 所有步骤条样式、vue Steps 步骤条全部属性

vue、Steps 步骤条、Steps 属性、vue Steps 所有步骤条样式、vue Steps 步骤条全部属性 设计规则何时使用 代码演示1.基本用法2.迷你版3.带图标的步骤条4.步骤切换5.竖直方向的步骤条6.竖直方向的小型步骤条7.步骤运行错误8.点状步骤条9.自定义点状步骤条 APIStepsSteps.Step 设…

组件封装 - steps组件

首先, 我先来看看效果 steps 组件的封装和 tabs 组件还是相似的 都会去指定两个组件来完成(仿Element UI), 都会去使用 jsx 的语法 让其中一个组件去规定样式和排版, 另外一个组件去接收父组件传入的动态数据 但和面包屑组件还是有区别的(面包屑组件封装): 相同点都是使用两…

ElementUi Steps 步骤条的使用

效果&#xff1a; 实现&#xff1a; <el-steps :active"active" finish-status"success"><el-step title"步骤 1"></el-step><el-step title"步骤 2"></el-step><el-step title"步骤 3"&…

el-steps(步骤条)的入门学习

el-steps(步骤条)的入门学习 适用场景 在有步骤流程的情况下&#xff0c;可以设置步骤条&#xff0c;引导用户向下操作&#xff0c;如四六级的报考 知识点 el-steps嵌套el-step使用el-steps的active设置Number&#xff0c;从零开始el-steps的space设置Number&#xff0c;为…

Vue2步骤条(Steps)

Vue3步骤条&#xff08;Steps&#xff09; 可自定义设置以下属性&#xff1a; 步骤标题数组&#xff08;stepsLabel&#xff09;步骤描述数组&#xff08;stepsDesc&#xff09;步骤总数&#xff08;totalSteps&#xff09;&#xff0c;默认3当前选中的步骤&#xff08;curren…