Java中的语法糖

  • 内容
  • 评论
  • 相关

之前说到了Swift、Java、C#等的泛型,Java的泛型是一种伪泛型,是一种语法糖而已,所以我们今天就来说一下Java的语法糖都有哪些。首先需要说的是,什么是语法糖。语法糖指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

Java中得语法糖包括但不限于泛型与类型擦除、自动装箱和拆箱、遍历循环、变长参数、条件编译、内部类、枚举类、对枚举和字符串的switch支持、断言语句、在try语句中定义和关闭资源。接下来我们会对上面的每一项进行说明。

一、泛型与类型擦除
为了说明Java的泛型为什么是伪泛型,我们来举一个特别简单的例子:

可以看到一个提示,是说这两个传参的类型是一样的,导致两个方法实际是一样的,无法重载。这很显然了,因为泛型是语法糖,只是在编译期的一个功能,实际情况下String与Integer都会被擦除掉,导致最后的传参类型变成ArrayList,所以这俩方法无法重载。接下来我们就来验证一下:

ArrayList stringList = new ArrayList();
ArrayList integerList = new ArrayList();
stringList.add("1");
integerList.add(1);

这段代码非常正常,如果我们编译后再去返回去查看class文件的反编译信息就会发现,里面是这样的:

ArrayList var1 = new ArrayList();
ArrayList var2 = new ArrayList();
var1.add("1");
var2.add(Integer.valueOf(1));

是不是什么都没有了,只剩下了ArrayList,添加的时候也是以Object的类型添加的。同样,在Map中也是这样的道理:

Map map = new HashMap();
map.put("hello", "world");
System.out.println(map.get("hello"));

反编译后的结果是:

HashMap var1 = new HashMap();
var1.put("hello", "world");
System.out.println((String)var1.get("hello"));

二、自动装箱和拆箱

我们知道,基本类型都有对应的包装类,以使得基本类型能够成为对象使用,而Java中还提供了自动让两者转换的功能,不过我们看似自动的这个功能,也是一个语法糖,内部实际还是由其他的基本方法完成的,例如:

Integer a = 100;
int b = a;

最后查看反编译后的结果是:

Integer var1 = Integer.valueOf(100);
int var2 = var1.intValue();

可以看到,说是自动装箱拆箱功能,实际内部还是通过Integer的valueOf和intValue两个方法来实现的。

三、遍历循环

忘了从哪个版本开始Java支持了foreach这样的写法,看似是加了一个功能,实际上它也是一个语法糖:

ArrayList list = new ArrayList();
for(String str:list){

}

反编译后的结果是:

ArrayList var1 = new ArrayList();
String var3;
for(Iterator var2 = var1.iterator(); var2.hasNext(); var3 = (String)var2.next()) {
            ;
}

可以看到,它的内部还是使用了Iterator来实现的,所以并没有提供什么新的功能。

四、变长参数
变长参数是个有意思的东西,但是内部是如何实现的呢?反编译一下就知道其中的内幕了:

public void function(String...arg){

}

这次我们不能简单的直接用工具查看class文件了,因为可能编辑器太高级,导致编译回去还能看到变长参数。所以呢,我们需要用jad反编译然后查看结果,可能JDK本身没有带jad工具,所以我这里提供一个我自己的下载链接:

http://pan.baidu.com/s/1hrGZJ3Q

这样反编译后会发现原来变长参数是这样的:

public transient void function(String as[]){

}

五、条件编译

Java中没有提供像C++那样的条件编译的功能,但是我们可以借助一种特殊的方式来实现:

if(true){
    System.out.println("true");
}else{
    System.out.println("false");
}

反编译后可以看到,实际上false的东西本身就没有:

System.out.println("true");

六、内部类

内部类就是在一个类内部的类,但是呢,实际情况下,查看编译后的结果发现,两者还是独立的,例如:

class A{
    class B{}
}

可以看一下编译后的class文件:


可以看到,其实A和B还是两个独立的class,只不过在命名上有一些关联,我们用jad反编译后查看一下结果:

class A
{
    class B
    {

        final A this$0;

        B()
        {
            this$0 = A.this;
            super();
        }
    }


    A()
    {
    }
}

class A$B
{

    final A this$0;

    A$B()
    {
        this$0 = A.this;
        super();
    }
}

可以看到,A中的B持有A的引用,但是它们是两个独立的文件。

七、枚举

一直以来很多人都有疑问,枚举到底是什么,是值?是类?这里揭示枚举这个语法糖后就知道它到底是什么了:

enum TestEnum {
    W,C
}

上面定义了一个枚举,jad工具查看可以发现:

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 W;
    public static final TestEnum C;
    private static final TestEnum $VALUES[];

    static 
    {
        W = new TestEnum("W", 0);
        C = new TestEnum("C", 1);
        $VALUES = (new TestEnum[] {
            W, C
        });
    }
}

Enum本身也是一个类,那么定义枚举的时候就是去继承它,枚举内部的元素实际就是这个子类的对象而已。

八、switch中得枚举与字符串

我们知道,整形、字符串、枚举都可以在switch中使用,Java是如何来支持字符串和枚举的switch功能呢?来看一个例子:

TestEnum te = TestEnum.W;
switch(te){
    case W:
        System.out.println("W");
        break;
    case C:
        System.out.println("C");
        break;
    default:
        System.out.println("Null");
}

用jad反编译后查看一下:

TestEnum testenum = TestEnum.W;
static class _cls1{

    static final int $SwitchMap$TestEnum[];

    static {
        $SwitchMap$TestEnum = new int[TestEnum.values().length];
        try{
            $SwitchMap$TestEnum[TestEnum.W.ordinal()] = 1;
        }catch(NoSuchFieldError nosuchfielderror) { }
        try{
            $SwitchMap$TestEnum[TestEnum.C.ordinal()] = 2;
        }
        catch(NoSuchFieldError nosuchfielderror1) { }
    }
}

switch(_cls1..SwitchMap.TestEnum[testenum.ordinal()]){
    case 1: // '\001'
        System.out.println("W");
        break;

    case 2: // '\002'
        System.out.println("C");
        break;

    default:
        System.out.println("Null");
        break;
}

可以看到,枚举在switch中实际已经变成了其内部代表的int型的变量,所以说枚举的switch实际就是int的switch。再来看字符的:

String str = "A";
switch(str){
    case "A":
        System.out.println("A");
        break;
    case "B":
        System.out.println("B");
        break;
    default:
        System.out.println("Null");
}

jad反编译后:

String var1 = "A";
byte var3 = -1;
switch(var1.hashCode()) {
    case 65:
        if(var1.equals("A")) {
            var3 = 0;
        }
        break;
    case 66:
        if(var1.equals("B")) {
            var3 = 1;
        }
}

switch(var3) {
    case 0:
        System.out.println("A");
        break;
    case 1:
        System.out.println("B");
        break;
    default:
        System.out.println("Null");
}

可以看到实际字符串使用其本身的hashcode来做的switch,所以说字符串的switch也是int的switch。

九、try-with-resources

说实话,这个功能我也是才刚刚知道,它是在jdk1.7中加进来的,就是在try的大括号之前的小括号里写和io有关的代码,这些io流在最后就会自动close,而不用我们去手动调用。我们来看看它是怎么实现的。

try (BufferedReader br = new BufferedReader(new FileReader("A.java"))) {
    System.out.println(br.readLine());
} catch (Exception e) {
    e.printStackTrace();
}

反编译后:

try {
    BufferedReader var1 = new BufferedReader(new FileReader("A.java"));
    Throwable var2 = null;

    try {
        System.out.println(var1.readLine());
    } catch (Throwable var12) {
        var2 = var12;
        throw var12;
    } finally {
        if(var1 != null) {
            if(var2 != null) {
                try {
                    var1.close();
                 } catch (Throwable var11) {
                     var2.addSuppressed(var11);
                 }
            } else {
                var1.close();
            }
        }

    }
} catch (Exception var14) {
    var14.printStackTrace();
}

可以看到,虽然我们没有调用,但是实际系统已经帮我们调用了。

十、断言

断言这个东西我们用的少,它就是为了方便我们少些一些if-else,不过呢,实际情况下它还是这么干的:

class A {
    void test() {
        String str = null;
        assert str!=null:"the string is null";
    }
}

jad反编译后:

class A{

    A(){}

    void test(){
        Object obj = null;
        if(!$assertionsDisabled && obj == null)
            throw new AssertionError("the string is null");
        else
            return;
    }

    static final boolean $assertionsDisabled = !A.desiredAssertionStatus();

}

可以看到,它自己还是通过if-else来实现的。

评论

0条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注