兰州市做网站的企业有哪些,ag亚游平台网站开发,北京网站优化服务商,做家教中介网站赚钱吗目录 面试回答
知识扩展
如何解语法糖#xff1f;
糖块一、swith 支持 String 与枚举
糖块二、泛型
糖块三、自动装箱与拆箱
糖块四、枚举
糖块五、条件编译
糖块六、断言
糖块七、数值字面量
糖块八、for-each
糖块九、try-with-resource
可能遇到的坑
泛型
自…目录 面试回答
知识扩展
如何解语法糖
糖块一、swith 支持 String 与枚举
糖块二、泛型
糖块三、自动装箱与拆箱
糖块四、枚举
糖块五、条件编译
糖块六、断言
糖块七、数值字面量
糖块八、for-each
糖块九、try-with-resource
可能遇到的坑
泛型
自动装箱与拆箱
总结 面试回答 语法糖Syntactic sugar,指在计算机语言中添加的某种语法这种语法对语言的功能并没有影响但是更方便程序员使用。 虽然 Java 中有很多语法糖但是 Java 虚拟机并不支持这些语法糖所以这些语法糖在编译阶段就会被还原成简单的基础语法结构这样才能被虚拟机识别这个过程就是解语法糖。 如果看过 Java 虚拟机的源码就会发现在编译过程中有一个重要的步骤就是调用 desugar() ,这个方法就是负责解语法糖的实现。 常见的语法糖有 switch 支持枚举及字符串、泛型、条件编译、断言、可变参数、自动装箱/拆箱、枚举、内部类、增强 for 循环、try-with-resources 语句、lambda 表达式等。 知识扩展 如何解语法糖 语法糖的存在主要是方便开发人员使用。但其实Java 虚拟机并不支持这些语法糖。这些语法糖在编译阶段就会被还原成简单的基础语法结构这个过程就是解语法糖。 说到编译大家肯定都知道Java 语言中 javac 命令可以将后缀名为 .java 的源文件编译为 .class 的可以运行于 java 虚拟机的字节码。如果你去看 com.sun.tools.javac.main.JavaCompiler 的源码你会发现在 compile() 中有一个步骤就是调用 desugar(),这个方法就是负责解语法糖的实现的。 糖块一、swith 支持 String 与枚举 从 Java 7 开始Java 语言中的语法糖在逐渐丰富其中一个比较重要的就是 Java 7 中 swith开始支持 String。 在开始 coding 之前先科普下Java 中的 switch自身原本就支持基本类型。比如 int、char等。对于 int类型直接进行数值的比较。对于 char类型则是比较其 ascii 码。所以对于编译器来说switch 中其实只能使用整型任何类型的比较都要转换成整型。比如 byte、short、char 以及int (ascii 码是整型)。 那么接下来看下 switch 对 String 的支持如以下代码 public class main {public static void main(String[] args) {String strword;switch (str){case hello:System.out.println(hello);break;case world:System.out.println(world);break;default:break;}}
} 反编译后内容如下 public class main
{public main(){}public static void main(String args[]){String str word;String s str;byte byte0 -1;switch(s.hashCode()){case 99162322: if(s.equals(hello))byte0 0;break;case 113318802: if(s.equals(world))byte0 1;break;}switch(byte0){case 0: // \0System.out.println(hello);break;case 1: // \001System.out.println(world);break;}}
} 看到这个代码你知道原来字符串的 switch 是通过 equals() 和 hashCode()方法来实现的。还好 hashCode() 方法返回的是 int ,而不是 long。 仔细看下可以发现进行 switch的实际是哈希值然后通过使用 equals方法比较进行安全检查这个检查是必要的因为哈希可能发生碰撞。因此它的性能是不如使用枚举进行 switch 或者使用纯整数常量但这也不是很差。 糖块二、泛型 我们都知道很多语言都是支持泛型的但是很多人不知道的是不同的编译器对于泛型的处理方式是不同的通常情况下一个编译器处理泛型有两种方式Code specialization和 Code sharing。C 和 C# 是使用 Code specialization的处理机制而 Java 使用的是 Code sharing的机制。 Code sharing 方式为每个泛型类型创建唯一的字节码表示并且将泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类型实例映射到唯一的字节码表示是通过类型擦除type erasue实现的。 也就是说对于 Java 虚拟机来说他根本不认识 MapString,String map 这样的语法。需要在编译阶段通过类型擦除的方式进行解语法糖。 类型擦除的主要过程如下
1.将所有的泛型参数用其最左边界最顶级的父类型类型替换。
2.移除所有的类型参数。 以下代码 MapString,String mapnew HashMapString,String();map.put(name,tango);map.put(wechat,Tango);map.put(blog,https://www.baidu.com); 解语法糖之后会变成 Map map new HashMap();map.put(name, tango);map.put(wechat, Tango);map.put(blog, https://www.baidu.com); 以下代码 public static A extends ComparableA A max(CollectionA xs) {IteratorA xi xs.iterator();A w (Comparable)xi.next();while(xi.hasNext()) {A x (Comparable)xi.next();if (w.compareTo(x) 0) {w x;}}return w;} 类型擦除之后会变成 public static Comparable max(Collection xs){Iterator xi xs.iterator();Comparable w (Comparable)xi.next();do{if(!xi.hasNext())break;Comparable x (Comparable)xi.next();if(w.compareTo(x) 0)w x;} while(true);return w;} 虚拟机中没有泛型只有普通类和普通方法所欲泛型类的类型参数在编译时都会被擦除泛型类并没有自己独有的 Class类对象。比如并不存在 ListString.class 或是 ListInteger.class 而只有 List.class 。 糖块三、自动装箱与拆箱 自动装箱就是 Java 自动将原始类型值转换成对应的对象比如将 int 的变量转换成 Integer 的对象这个过程叫做装箱反之将 Integer 对象转换成 int 类型值这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换所以就称作为自动装箱和拆箱。原始类型 byte、short、char、int、long、float、double 和 boolan 对应的封装类为 Byte、Short、Character、Integer、Long、Float、Double、Boolean。 先来看个自动装箱的代码 public static void main(String[] args) {int i 0;Integer n i;} 反编译后代码如下 public static void main(String args[]){int i 0;Integer n Integer.valueOf(i);} 再来看个自动拆箱的代码 public static void main(String[] args) {Integer n 0;int i n;} 反编译后代码如下 public static void main(String args[]){Integer n Integer.valueOf(0);int i n.intValue();} 从反编译得到内容可以看出在装箱的时候自动调用的是 Integer的 valueOf方法。而在拆箱的时候自动调用的是 Integer的 intValue的方法。 所以装箱过程是通过调用包装器的 valueOf 方法实现的而拆箱过程是通过调用包装器的 xxxValue 方法实现的。 糖块四、枚举 在 Java 中枚举是一种特殊的数据类型用于表示有限的一组常量。枚举常量是在枚举类型中定义的每个常量都是该类型的一个实例。Java 中的枚举类型是一种安全而优雅的方式来表示有限的一组值。 要想看源码首先得有一个类吧那么枚举类型到底是什么类呢是 enum 吗答案很明显不是enum就和 class一样只是一个关键字他并不是一个类那么枚举是由什么类维护的呢我们简单的写一个枚举 public enum t {SPRING,SUMMER;
} 然后我们使用反编译看看这段代码到底是怎么实现的反编译后代码如下 public final class t extends Enum
{public static t[] values(){return (t[])$VALUES.clone();}public static t valueOf(String name){return (t)Enum.valueOf(com/chiyi/test/t, name);}private t(String s, int i){super(s, i);}public static final t SPRING;public static final t SUMMER;private static final t $VALUES[];static {SPRING new t(SPRING, 0);SUMMER new t(SUMMER, 1);$VALUES (new t[] {SPRING, SUMMER});}
}通过反编译代码我们可以看到public final class t extends Enum说明该类是继承了 Enum类的同时 final关键字告诉我们这个类也是不能被继承的。当我们使用 enum 来定义一个枚举类型的时候编译器会自动帮我们创建一个 final类型的类继承 Enum类所以枚举类型不能被继承。 糖块五、条件编译 一般情况下程序中的每一行代码都要参加编译。但有时候出于对程序代码优化的考虑希望只对其中一部分内容进行编译此时就需要在程序中加上条件让编译器只对满足条件的代码进行编译将不满足条件的代码舍弃这就是条件编译。 如在 C 或 CPP 中可以通过预处理语句来实现条件编译。其实在 Java 中也可实现条件编译。我们先来看一段代码 public static void main(String[] args) {final boolean DEBUGtrue;if (DEBUG){System.out.println(Hello,DEBUG!);}final boolean ONLINEfalse;if (ONLINE){System.out.println(Hello,ONLINE!);}} 反编译后代码如下 public static void main(String args[]){boolean DEBUG true;System.out.println(Hello,DEBUG!);boolean ONLINE false;} 首先我们发现在反编译后的代码中没有 System.out.println(Hello,ONLINE!);这其实就是条件编译。当 if (ONLINE)为 false 的时候。编译器就没有对其内的代码进行编译。 所以Java 语法的条件编译是通过判断条件为常量的 if 语句实现的。其原理也是 Java 语言的语法糖。根据 if 判断条件的真假编译器直接把分支为 false 的代码块消除。通过该方式实现的条件编译必须在方法体内实现而无法在整个 Java 类的结构或类的属性上进行条件编译这与 C/C 的条件编译相比确实更有局限性。在 Java 语言设计之初并没有引入条件编译的功能。虽有局限但是总比没有更强、 糖块六、断言 在 Java 中 assert关键字是从 JAVA SE 1.4 引入的为了避免和老版本的 Java 代码中使用了 assert 关键字导致错误 Java 执行的时候默认是不启动断言检查的这个时候所有的断言语句都将忽略如果要开启断言检查则需要用开关 -enableassertions或 -ea来开启。 看一段包含断言的代码 public static void main(String[] args) {int a 1;int b 1;assert a b;System.out.println(公众号Tango);assert a ! b : Tango;System.out.println(百度https://www.baidu.com);} 反编译后代码如下 public static void main(String args[]){int a 1;int b 1;if(!$assertionsDisabled a ! b)throw new AssertionError();System.out.println(\u516C\u4F17\u53F7\uFF1ATango);if(!$assertionsDisabled a b){throw new AssertionError(Tango);} else{System.out.println(\u767E\u5EA6\uFF1Ahttps://www.baidu.com);return;}} 很明显反编译之后的代码要比我们自己的代码复杂得多。所以使用了 assert 这个语法糖我们节省了很多代码。其实断言的底层实现就是 if 语句如果断言结果为 true则什么都不做程序继续执行如果断言结果为 false则程序抛出 AssertError 来打断程序的执行。-enableassertions 会设置 $assertionsDisabled 字段的值。 糖块七、数值字面量 在 java 7 中数值字面量不管是整数还是浮点数都允许在数字之间插入任意多个下划线。这些下划线不会对字面量的数值产生影响目的就是方便阅读。 比如 public static void main(String[] args) {int i10_000;System.out.println(i);}反编译后 public static void main(String args[]){int i 10000;System.out.println(i);} 反编译后就是把 _ 删除了。也就是说 编译器并不认识在数字字面量的 _需要在编译阶段把他去掉。 糖块八、for-each 增强 for 循环for-each相信大家都不陌生日常开发经常会用到的它会比 for 循环要少写很多代码那么这个语法糖背后是如何实现的呢 public static void main(String[] args) {String [] strs{南京,合肥,深圳,北京};for (String s:strs){System.out.println(s);}ListString strings ImmutableList.of(南京,合肥,深圳,北京) ;for (String s:strings){System.out.println(s);}} 反编译后代码如下 public static void main(String args[]){String strs[] {\u5357\u4EAC, \u5408\u80A5, \u6DF1\u5733, \u5317\u4EAC};String args1[] strs;int i args1.length;for(int j 0; j i; j){String s args1[j];System.out.println(s);}List strings ImmutableList.of(\u5357\u4EAC, \u5408\u80A5, \u6DF1\u5733, \u5317\u4EAC);String s;for(Iterator iterator strings.iterator(); iterator.hasNext(); System.out.println(s))s (String)iterator.next();} 代码很简单for-each 的实现原理其实就是使用了普通的 for 循环和迭代器。 糖块九、try-with-resource Java 里对于文件操作 IO 流、数据库连接等开销非常昂贵的资源用完之后必须及时 close 方法将其关闭否则资源会一直处于打开状态可能会导致内存泄漏等问题。 关闭资源的常用方式就是在 finally 块里是释放即调用 close方法。比如经常会写这样的代码 public static void main(String[] args) {BufferedReader br null;try {String line;br new BufferedReader(new FileReader(D:\\youth\\java\\javacode\\base\\src\\main\\resources\\application.properties));while ((line br.readLine()) ! null) {System.out.println(line);}} catch (IOException e) {} finally {try {if (br ! null) {br.close();}} catch (IOException ex) {//handle exception}}} 在 Java 7 开始jdk 提供了一种更好的方式关闭资源使用 try-with-resources语句改写一下上面的代码效果如下 public static void main(String[] args) {try ( BufferedReader br new BufferedReader(new FileReader(D:\\tango.xml))){String line;while ((line br.readLine()) ! null) {System.out.println(line);}} catch (IOException e) {//handle exception}} 看这简直是一大福音啊虽然我之前一般使用 IOUtils去关闭流并不会使用在 finally中写很多代码的方式但是这种新的语法糖看上去好像优雅很多呢。看下他的背后 public static void main(String args[]){BufferedReader br;Throwable throwable;br new BufferedReader(new FileReader(D:\\tango.xml));throwable null;String line;try{while((line br.readLine()) ! null) System.out.println(line);}catch(Throwable throwable2){throwable throwable2;throw throwable2;}if(br ! null)if(throwable ! null)try{br.close();}catch(Throwable throwable1){throwable.addSuppressed(throwable1);}elsebr.close();break MISSING_BLOCK_LABEL_113;Exception exception;exception;if(br ! null)if(throwable ! null)try{br.close();}catch(Throwable throwable3){throwable.addSuppressed(throwable3);}elsebr.close();throw exception;IOException ioexception;ioexception;} 其实背后的原理也很简单那些我们没有做的关闭资源的操作编译器都帮我们做了。所以再次印证了语法糖的作用就是方便程序员的作用但最终还是要转成编译器认识的语言。 可能遇到的坑 泛型 一、当泛型遇到重载 比如 public static void print(ListString list){System.out.println(invoke print(ListString list));}public static void print(ListInteger list){System.out.println(invoke print(ListString list));} 上面这段代码有两个重载的函数因为他们的参数类型不同一个是 ListString另一个是 ListInteger但是这段代码是编译通不过的。因为我们前面讲过参数 List 和 List 编译之后都被擦除了变成了一样的原生类型 List擦除动作导致这两个方法的特征签名变得一模一样。 二、当泛型遇到 catch 泛型的类型参数不能用在 Java 异常处理的 catch 语句中。因为异常处理是由 JVM 在运行时刻来进行的。由于类型信息被擦除JVM 是无法区分两个异常类型 MyExceptionString和 MyExceptionInteger的。 三、当泛型内含静态变量 public static void main(String[] args) {GTInteger gtinew GTInteger();gti.var1;GTString gtsnew GTString();gts.var2;System.out.println(gti.var);}static class GTT{public static int var0;public void nothing(T x){}} 以上代码输出结果为2由于经过类型擦除所有的泛型类实例都关联到同一份字节码上泛型类的所有静态变量是共享的。 自动装箱与拆箱 对象相等比较 public static void main(String[] args) {Integer a 1000;Integer b 1000;Integer c 100;Integer d 100;System.out.println(a b is (a b));System.out.println(c d is (c d));} 输出结果 a b is false
c d is true 在 Java 5 中在 Integer 的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。 适用于整数值区间 -128 至 127。
只适用于自动装箱。使用构造函数创建对象不适用。 总结 前面介绍了 9 种 Java 中常用的语法糖。所谓语法糖就是提供给开发人员便于开发的一种语法而已。但是这种语法只有开发人员认识。想要被执行需要进行解糖即转成 JVM 认识的语法。当我们把语法糖解糖之后你就会发现其实我们日常使用的这些方便的语法其实都是一些其他更简单的语法构成的。 有了这些语法糖我们在日常开发的时候可以大大提升效率但是同时也要避免过度使用。使用之前最好了解下原理避免掉坑。