首页 > 编程语言 >java中的语法糖

java中的语法糖

时间:2022-09-01 20:25:22浏览次数:74  
标签:java String int System 语法 static public out

Java 语法糖

语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin(彼得·兰丁) 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用,当然也仅仅是方便程序员的使用,因为java虚拟机并不支持语法糖,因此在语法糖在编译阶段就被还原成简单的基础语法结构。
语法糖就是对现有语法的一个封装。简而言之,语法糖让程序更加简洁,有更高的可读性

在编程领域,除了语法糖,还有语法盐和语法糖精的说法,有兴趣可以百度了解一下。

解语法糖

语法糖的存在主要是方便开发人员使用。但是,Java虚拟机并不支持这些语法糖。这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖
因此,Java中的语法糖只存在于编译期,在编译器将 .java 源文件编译成 .class 字节码时,有一个步骤就是调用desugar(),这个方法就是负责解语法糖而实现的。
如果将语法糖先编译后(解语法糖)在反编译成.java文件,那么所写的语法糖就不会存在了。

Java 中最常用的语法糖主要有switch语句支持String与枚举、泛型和类型擦除、自动装箱与拆箱、方法边长参数、枚举、内部类、条件编译、断言、数值字面量、增强for循环、try-with-resources语句、Lambda表达式等。

糖块一:switch 支持String与枚举

在JDK1.5之前,switch循环只支持byte short char int四种数据类型.

JDK1.5 在switch循环中增加了枚举类与byte short char int的包装类,对四个包装类的支持是因为java编译器在底层手动进行拆箱,而对枚举类的支持是因为枚举类有一个ordinal方法,该方法实际上是一个int类型的数值.

jdk1.7开始支持String类型,但实际上String类型有一个hashCode算法,结果也是int类型.而byte short char类型可以在不损失精度的情况下向上转型成int类型.所以总的来说,可以认为switch中只支持int.

java 对switch的支持

public class SwitchDemoString {
    public static void main(String[] args) {
        String str = "world";
        switch (str) {
        case "hello":
            System.out.println("hello");
            break;
        case "world":
            System.out.println("world");
            break;
        default:
            break;
        }
    }
}

反编译后内容:

public class SwitchStringDemo {

    public static void main(String[] args) {
        String str;
        String string = str = "world";
        int n = -1;
        switch (string.hashCode()) {
            case 99162322: {
                if (!string.equals("hello")) break;
                n = 0;
                break;
            }
            case 113318802: {
                if (!string.equals("world")) break;
                n = 1;
            }
        }
        switch (n) {
            case 0: {
                System.out.println("hello");
                break;
            }
            case 1: {
                System.out.println("world");
                break;
            }
        }
    }

}

字符串的switch是通过equals()和hashCode()方法来实现的。

进行switch比较的实际是哈希值,然后通过使用equals方法比较进行安全检查,这个检查是必要的,因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行switch或者使用纯整数常量,但这也不是很差。

糖块二、泛型和类型擦除

很多语言都是支持泛型的,但是不同的编译器对于泛型的处理方式是不同的。

通常情况下,一个编译器处理泛型有两种方式:Code specialization和Code sharing。

C++和C#是使用Code specialization的处理机制,而Java使用的是Code sharing的机制

Code sharing方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除(type erasue)实现的。

也就是说,对于Java虚拟机来说,他根本不认识Map<String, String> map这样的语法。需要在编译阶段通过类型擦除的方式进行解语法糖。

类型擦除的主要过程如下:

  • 将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。
  • 移除所有的类型参数。
Map<String, String> map = new HashMap<String, String>();  
map.put("name", "JourWon");  
map.put("wechat", "JourWon");  
map.put("blog", "https://blog.csdn.net/ThinkWon");  

// 解语法糖后
Map map = new HashMap();  
map.put("name", "JourWon");  
map.put("wechat", "JourWon");  
map.put("blog", "https://blog.csdn.net/ThinkWon");   

以下两个方法编译出错

import java.util.List;  
public class FanxingTest{  
    public void method(List<String> list){  
        System.out.println("List String");  
    }  
    public void method(List<Integer> list){  
        System.out.println("List Int");  
    }  
}  

糖块三、自动装箱与拆箱

自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱

原始类型byte, short, char, int, long, float, double 和 boolean 对应的封装类为Byte, Short, Character, Integer, Long, Float, Double, Boolean。

自动装箱:

public static void main(String[] args) {
    int i = 10;
    Integer n = i;
}
// 反编译后
public static void main(String args[]) {
    int i = 10;
    Integer n = Integer.valueOf(i);
}

自动拆箱:

public static void main(String[] args) {
    Integer i = 10;
    int n = i;
}

// 反编译后
public static void main(String args[]) {
    Integer i = Integer.valueOf(10);
    int n = i.intValue();
}

从反编译得到内容可以看出,在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。

糖块四 、方法变长参数

可变参数(variable arguments)是在Java 1.5中引入的一个特性。它允许一个方法把任意数量的值作为参数。

public static void main(String[] args) {
    print("aa", "bb", "cc");
}

public static void print(String... strs) {
    for (int i = 0; i < strs.length; i++) {
        System.out.println(strs[i]);
    }
}

// 反编译后
public static void main(String[] args) {
    print(new String[]{"aa", "bb", "cc"});
}

public static void print(String[] strs) {
    for (int i = 0; i < strs.length; i++)
        System.out.println(strs[i]);
}

从反编译后代码可以看出,可变参数在被使用的时候,他首先会创建一个数组,数组的长度就是调用该方法时传递实参的个数,然后再把参数值全部放到这个数组当中,再把这个数组作为参数传递到被调用的方法中。

糖块五 、枚举

为什么说枚举也是语法糖?

Java SE5提供了一种新的类型-Java的枚举类型

未有枚举前,定义季节

//分别用1 表示春天,2表示夏天,3表示秋天,4表示冬天。
public class Season {
    public static final int SPRING = 1;
    public static final int SUMMER = 2;
    public static final int AUTUMN = 3;
    public static final int WINTER = 4;
}

这种方法称作int枚举模式。可这种模式有什么问题呢,我们都用了那么久了,应该没问题的。通常我们写出来的代码都会考虑它的安全性易用性可读性。 首先我们来考虑一下它的类型安全性。当然这种模式不是类型安全的。比如说我们设计一个函数,要求传入春夏秋冬的某个值。但是使用int类型,我们无法保证传入的值为合法。

private String getChineseSeason(int season){
        StringBuffer result = new StringBuffer();
        switch(season){
            case Season.SPRING :
                result.append("春天");
                break;
            case Season.SUMMER :
                result.append("夏天");
                break;
            case Season.AUTUMN :
                result.append("秋天");
                break;
            case Season.WINTER :
                result.append("冬天");
                break;
            default :
                result.append("地球没有的季节");
                break;
        }
        return result.toString();
    }

public void doSomething(){
        System.out.println(this.getChineseSeason(Season.SPRING));//这是正常的场景

        System.out.println(this.getChineseSeason(5));//这个却是不正常的场景,这就导致了类型不安全问题
    }

接下来我们来考虑一下这种模式的可读性。使用枚举的大多数场合,我都需要方便得到枚举类型的字符串表达式。如果将int枚举常量打印出来,我们所见到的就是一组数字,这是没什么太大的用处。我们可能会想到使用String常量代替int常量。虽然它为这些常量提供了可打印的字符串,但是它会导致性能问题,因为它依赖于字符串的比较操作,所以这种模式也是我们不期望的。 从类型安全性程序可读性两方面考虑,intString枚举模式的缺点就显露出来了。

// 定义一个枚举类
// enum就和class一样,只是一个关键字,他并不是一个类
public enum T {
    SPRING,SUMMER;
}
// 反编译
// 通过反编译后代码我们可以看到,public final class T extends Enum,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。
public final class T extends Enum<T> {
    public static final /* enum */ T SPRING = new T("SPRING", 0);
    public static final /* enum */ T SUMMER = new T("SUMMER", 1);
    private static final /* synthetic */ T[] $VALUES;

    public static T[] values() {
        return (T[])$VALUES.clone();
    }

    public static T valueOf(String name) {
        return Enum.valueOf(T.class, name);
    }

    private T(String string, int n) {
        super(string, n);
    }

    static {
        $VALUES = new T[]{SPRING, SUMMER};
    }
}


糖块六 、内部类

内部类又称为嵌套类,可以把内部类理解为外部类的一个普通成员。

内部类之所以也是语法糖,是因为它仅仅是一个编译时的概念。

outer.java里面定义了一个内部类inner,一旦编译成功,就会生成两个完全不同的.class文件了,分别是outer.class和outer$inner.class。所以内部类的名字完全可以和它的外部类名字相同。

当jad对OutterClass.class文件进行反编译的时候,它会把两个文件全部进行反编译,然后一起生成一个OutterClass.jad文件

public class OutterClass {

    private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public static void main(String[] args) {

    }

    class InnerClass {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

糖块七 、条件编译

—般情况下,程序中的每一行代码都要参加编译。但有时候出于对程序代码优化的考虑,希望只对其中一部分内容进行编译,此时就需要在程序中加上条件,让编译器只对满足条件的代码进行编译,将不满足条件的代码舍弃,这就是条件编译。

public class ConditionalCompilation {
    public static void main(String[] args) {
        final boolean DEBUG = true;
        if(DEBUG) {
            System.out.println("Hello, DEBUG!");
        }

        final boolean ONLINE = false;

        if(ONLINE){
            System.out.println("Hello, ONLINE!");
        }
    }
}

反编译后代码如下:

public class ConditionalCompilation {
    public static void main(String[] args) {
    boolean DEBUG = true;
    
    System.out.println("Hello, DEBUG!");
    
    boolean ONLINE = false;
}

首先,我们发现,在反编译后的代码中没有System.out.println(“Hello, ONLINE!”);,这其实就是条件编译。

所以,Java语法的条件编译,是通过判断条件为常量的if语句实现的。根据if判断条件的真假,编译器直接把分支为false的代码块消除。

通过该方式实现的条件编译,必须在方法体内实现,而无法在正整个Java类的结构或者类的属性上进行条件编译。

糖块八 、断言

在Java中,assert关键字是从JAVA SE 1.4 引入的,为了避免和老版本的Java代码中使用了assert关键字导致错误,Java在执行的时候默认是不启动断言检查的(这个时候,所有的断言语句都将忽略)。

如果要开启断言检查,则需要用开关-enableassertions或-ea来开启。

格式:

assert <布尔表达式>;
assert <布尔表达式> : <错误信息>;

assert 后面跟个冒号表达式。如果冒烟前为true,则冒号后面的被忽略,否则抛出AssertionError,错误内容为冒号后面的内容。

含有断言的代码:

public class AssertTest {
    public static void main(String args[]) {
        int a = 1;
        int b = 1;
        assert a == b;
        System.out.println("CSDN-ThinkWon");
        assert a != b : "ThinkWon";
        System.out.println("博客:https://blog.csdn.net/ThinkWon");
    }
}

反编译后代码如下:

public class AssertTest {
	static final /* synthetic */ boolean $assertionsDisabled;

    public static void main(String[] args) {
        boolean a = true;
        boolean b = true;
        if (!$assertionsDisabled && a != b) {
            throw new AssertionError();
        }
        System.out.println("CSDN-ThinkWon");
        if (!$assertionsDisabled && a == b) {
            throw new AssertionError((Object)"ThinkWon");
        }
        System.out.println("\u535a\u5ba2:https://blog.csdn.net/ThinkWon");
    }

    static {
        $assertionsDisabled = !AssertTest.class.desiredAssertionStatus();
    }
}

很明显,反编译之后的代码要比我们自己的代码复杂的多。所以,使用了assert这个语法糖我们节省了很多代码。

其实断言的底层实现就是if语言,如果断言结果为true,则什么都不做,程序继续执行,如果断言结果为false,则程序抛出AssertError来打断程序的执行。

糖块九 、数值字面量

在java 7中,数值字面量,不管是整数还是浮点数,都允许在数字之间插入任意多个下划线。这些下划线不会对字面量的数值产生影响,目的就是方便阅读。

比如:

public class Test {
    public static void main(String[] args) {
        int i = 10_000;
        System.out.println(i);
    }
}

// 反编译后:
// 反编译后就是把_删除了。也就是说编译器并不认识在数字字面量中的_,需要在编译阶段把他去掉。
public class Test {
    public static void main(String[] args) {
        int i = 10000;
        System.out.println(i);
    }
}

糖块十 、增强for循环

for-each的实现原理其实就是使用了普通的for循环和迭代器。

public static void main(String args[]) {
    String[] strs = {"aa", "bb", "cc"};
    for (String s : strs) {
        System.out.println(s);
    }
    System.out.println();

    List<String> strList = Arrays.asList(strs);
    for (String s : strList) {
        System.out.println(s);
    }
}

反编译后:

public static void main(String args[]) {
    String[] strs;
    String[] arrstring = strs = new String[]{"aa", "bb", "cc"};
    int n = arrstring.length;
    for (int i = 0; i < n; ++i) {
        String s = arrstring[i];
        System.out.println(s);
    }
    System.out.println();

    List<String> strList = Arrays.asList(strs);
    Iterator<String> iterator = strList.iterator();
    while (iterator.hasNext()) {
        String s = iterator.next();
        System.out.println(s);
    }

}

糖块十一 、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:\\hello.xml"));
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        // handle exception
    } 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:\\ hello.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) {
    try {
        BufferedReader br = new BufferedReader(new FileReader("d:\\ hello.xml"));
        Throwable throwable = null;
        try {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (Throwable line) {
            throwable = line;
            throw line;
        } finally {
            if (br != null) {
                if (throwable != null) {
                    try {
                        br.close();
                    } catch (Throwable line) {
                        throwable.addSuppressed(line);
                    }
                } else {
                    br.close();
                }
            }
        }
    } catch (IOException br) {
        // empty catch block
    }
}

其实背后的原理也很简单,那些我们没有做的关闭资源的操作,编译器都帮我们做了。

所以,再次印证了,语法糖的作用就是方便程序员的使用,但最终还是要转成编译器认识的语言。

糖块十二、Lambda表达式

Labmda表达式不是匿名内部类的语法糖(内部类在编译之后会有两个class文件,但是,包含Labmda表达式的类编译后只有一个文件),但是他也是一个语法糖。实现方式其实是依赖了几个JVM底层提供的Labmda相关api。

/**
 * lambda表达式
 * option: --decodelambdas false
 */
public void lambdaTest() {
    String[] qingshanli = {"haha", "qingshan", "helloworld", "ceshi"};  
    List<String> list =  Arrays.asList(qingshanli);
    // 使用lambda表达式以及函数操作
    list.forEach((str) -> System.out.print(str + "; "));
    // 在JDK8中使用双冒号操作符
    list.forEach(System.out::println);  
}

lambda表达式参考:https://blog.csdn.net/weixin_43691058/article/details/113816211

标签:java,String,int,System,语法,static,public,out
From: https://www.cnblogs.com/ah-grass/p/16647686.html

相关文章

  • 正则表达式在Java中的使用
    正则表达式在Java中的使用不仅限于String类中的match()方法!!!正则中的^与$首先我们来了解这两个符号在正则表达式中的作用:^符号放在表达式头部表示开始匹配$符号放......
  • 【学习底层原理系列】Java底层-synchronized锁-3轻量级锁篇
     一.上一篇回顾1.偏向锁中的批量撤销和批量重偏向2.偏向锁发生了竞争,锁就要升级二.轻量级锁拆解1.加锁流程构建LockRecord,将LockRecord的obj指向当前锁对象,然后将无......
  • 【Java基础】类的结构之一:属性(变量)
    1.成员变量与局部变量二者的区别成员变量局部变量声明的位置直接声明在类中方法形参或内部、代码块内、构造器内等修饰符private、public、static、fin......
  • java中的一维数组数组
    数组(array):是一种用于存储多个相同数据类型的存储模型(可以理解为容器)数组定义和静态初始化数组的两种定义格式:  格式1:    数据类型[]变量名;    范例......
  • 【Java基础】匿名对象
    1.匿名对象在创建对象时,没有显式的赋给一个变量名,匿名对象只能调用一次。Phonephone=newPhone();//正常的对象mail.show(phone);mall.show(newPhone());2.......
  • 学习日记Day4. Java 4
    Java个人总结4对象三大特性封装、继承、多态面向对象.以类的方式组织代码,以对象的组织(封装)数据对象内存分析多态存在条件有继承关系子类重写父类方法父类引......
  • 学习日记 Day3. Java 3
    title:Day3Java3date:2022-08-2922:20:14tags:JavaJava个人总结3位运算符位运算效率极高。A=00111100B=00001101---------------------------A&B......
  • Python环境和语法
    Python环境搭建一.python安装在浏览器打开网址:www.python.org,下载python。在C盘创建一个python3的文件夹。如图所示  3.双击下载的安装包python-3.9.5.e......
  • JAVA常用集合解析
    JAVA常用集合解析常用集合属性详解集合底层实现原理常用集合适用场景分析集合属性详解集合是一个存放对象的引用的容器,在Java中它存在于java.util包下,List、Set......
  • 【Java面试】面试如何让面试官面的很爽,看完这道面试题,finally块一定会执行吗?
    “finally块一定会执行吗?”这是最近一个工作3年的小伙伴去面试的时候遇到的问题。你遇到这个问题会怎么回答呢?大家好,我是Mic,一个工作了14年的Java程序员对于这个问题,......