Javac语法糖之EnumSwitch

Enum在Java中也被当作一个类来处理,并且Enum也算是Java中的语法糖,主要通过Javac中的Lower类来处理枚举这个语法糖的。

Java7中允许在swith中使用enum类,如下:

public class TestEnumClass {
	public void test() {
		Color color = Color.RED;
		switch (color) {
		case RED:
			System.out.println("red");
			break;
		case GREEN:
			System.out.println("green");
			break;
		case BLUE:
			System.out.println("blue");
			break;
		default:
			System.out.println("unknow");
		}
	}
}
 

现在就来看看编译器javac是怎么对enum switch进行解语法糖的。如果要处理switch语句,肯定要调用visitSwitch()方法,具体代码如下:

 public void visitSwitch(JCSwitch tree) {
        Type selsuper = types.supertype(tree.selector.type);
        boolean enumSwitch = selsuper != null &&  (tree.selector.type.tsym.flags() & ENUM) != 0;
        boolean stringSwitch = selsuper != null &&  types.isSameType(tree.selector.type, syms.stringType);

        Type target = enumSwitch ?
                        tree.selector.type :
                        (stringSwitch? syms.stringType : syms.intType);

        tree.selector = translate(tree.selector, target); // Translate a single node, boxing or unboxing if needed.
        tree.cases = translateCases(tree.cases);  //  translate a list of case parts of switch statements.
        if (enumSwitch) {
            result = visitEnumSwitch(tree);
        } else if (stringSwitch) {
            result = visitStringSwitch(tree);
        } else {
            result = tree;
        }
    }

由上可以知道,switch中使用的类型有3种,枚举类、字符串和整数类型。传入的参数tree的各种值如下截图。

    

下面访问的是visitEnumSwitch()方法,代码如下:

 public JCTree visitEnumSwitch(JCSwitch tree) {
        TypeSymbol enumSym = tree.selector.type.tsym;
        EnumMapping map = mapForEnum(tree.pos(), enumSym);
        make_at(tree.pos());
        Symbol ordinalMethod = lookupMethod(tree.pos(),
                                            names.ordinal,
                                            tree.selector.type,
                                            List.<Type>nil());
        JCArrayAccess selector = make.Indexed(map.mapVar,
                                        make.App(make.Select(tree.selector,
                                                             ordinalMethod)));
        ListBuffer<JCCase> cases = new ListBuffer<JCCase>();
        for (JCCase c : tree.cases) {
            if (c.pat != null) {
                VarSymbol label = (VarSymbol)TreeInfo.symbol(c.pat);
                JCLiteral pat = map.forConstant(label);
                cases.append(make.Case(pat, c.stats));
            } else {
                cases.append(c);
            }
        }
        JCSwitch enumSwitch = make.Switch(selector, cases.toList());
        patchTargets(enumSwitch, tree, enumSwitch);
        return enumSwitch;
    }

调用后的结果是生成了新的switch()语句,如下:

switch (com.test03.TestEnumClass$1.$SwitchMap$com$test03$Color[color.ordinal()]) {
case 1:
    System.out.println("red");
    break;

case 2:
    System.out.println("green");
    break;

case 3:
    System.out.println("blue");
    break;

default:
    System.out.println("unknow");

}

  

代码调用了mapForEnum()方法,其具体的代码实现如下:

    Map<TypeSymbol,EnumMapping> enumSwitchMap = new LinkedHashMap<TypeSymbol,EnumMapping>();

    EnumMapping mapForEnum(DiagnosticPosition pos, TypeSymbol enumClass) {
        EnumMapping map = enumSwitchMap.get(enumClass);
        if (map == null)
            enumSwitchMap.put(enumClass, map = new EnumMapping(pos, enumClass));
        return map;
    } 

其中会初始化一个EnumMapping类的对象,这个类的代码如下:

/** This map gives a translation table to be used for enum  switches.
     *
     *  For each enum that appears as the type of a switch
     *  expression, we maintain an EnumMapping to assist in the
     *  translation, as exemplified by the following example:
     *
     *  we translate
     *          switch(colorExpression) {
     *          case red: stmt1;
     *          case green: stmt2;
     *          }
     *  into
     *          switch(Outer$0.$EnumMap$Color[colorExpression.ordinal()]) {
     *          case 1: stmt1;
     *          case 2: stmt2
     *          }
     *  with the auxiliary table initialized as follows:
     *          class Outer$0 {
     *              synthetic final int[] $EnumMap$Color = new int[Color.values().length];
     *              static {
     *                  try { $EnumMap$Color[red.ordinal()] = 1; } catch (NoSuchFieldError ex) {}
     *                  try { $EnumMap$Color[green.ordinal()] = 2; } catch (NoSuchFieldError ex) {}
     *              }
     *          }
     *  class EnumMapping provides mapping data and support methods for this translation.
     */
    class EnumMapping {
        EnumMapping(DiagnosticPosition pos, TypeSymbol forEnum) {
            this.forEnum = forEnum;
            this.values = new LinkedHashMap<VarSymbol,Integer>();
            this.pos = pos;
            Name varName = names
                .fromString(target.syntheticNameChar() + // 系统指定为‘$‘
                                "SwitchMap" +
                                target.syntheticNameChar() +
                                writer.xClassName(forEnum.type).toString()
                                .replace(‘/‘, ‘.‘)
                                .replace(‘.‘, target.syntheticNameChar()));
            ClassSymbol outerCacheClass = outerCacheClass();
            this.mapVar = new VarSymbol(STATIC | SYNTHETIC | FINAL,
                                        varName,
                                        new ArrayType(syms.intType, syms.arrayClass),
                                        outerCacheClass);
            enterSynthetic(pos, mapVar, outerCacheClass.members());
        }

        DiagnosticPosition pos = null;

        // the next value to use
        int next = 1; // 0 (unused map elements) go to the default label

        // the enum for which this is a map
        final TypeSymbol forEnum;

        // the field containing the map
        final VarSymbol mapVar;

        // the mapped values
        final Map<VarSymbol,Integer> values;

        JCLiteral forConstant(VarSymbol v) {
            Integer result = values.get(v);
            if (result == null)
                values.put(v, result = next++);
            return make.Literal(result);
        }

        // generate the field initializer for the map
        void translate() {
           // ...
        }
    }  

查看enumSwitchMap中的值,如下:

  

下面还需要对Color这个Enum类型进行一些处理,通过translateTopLevelClass()方法中循环调用EnumMapping中的translate()方法后,Color如下:

/*synthetic*/ class TestEnumClass$1 {
    /*synthetic*/ static final int[] $SwitchMap$com$test03$Color = new int[Color.values().length];
    static {
        try {
            com.test03.TestEnumClass$1.$SwitchMap$com$test03$Color[Color.RED.ordinal()] = 1;
        } catch (NoSuchFieldError ex) {
        }
        try {
            com.test03.TestEnumClass$1.$SwitchMap$com$test03$Color[Color.GREEN.ordinal()] = 2;
        } catch (NoSuchFieldError ex) {
        }
        try {
            com.test03.TestEnumClass$1.$SwitchMap$com$test03$Color[Color.BLUE.ordinal()] = 3;
        } catch (NoSuchFieldError ex) {
        }
    }
}

  

下面继续来看switch对于字符串的处理。

 The general approach used is to translate a single string switch statement into a series of two chained switch statements:  the first a synthesized statement switching on the argument string‘s hash value and computing a string‘s position in the list of original case labels, if any, followed by a second switch on the computed integer value.  The second switch has the same code structure as the original string switch statement except that the string case labels are replaced with positional integer constants starting at 0.

The first switch statement can be thought of as an inlined map from strings to their position in the case label list.  An alternate implementation would use an actual Map for this purpose, as done for enum switches.

With some additional effort, it would be possible to use a single switch statement on the hash code of the argument, but care would need to be taken to preserve the proper control flow in the presence of hash collisions and other complications, such as fallthroughs.  Switch statements with one or two alternatives could also be specially translated into if-then statements to omit the computation of the hash code.

The generated code assumes that the hashing algorithm of String is the same in the compilation environment as in the environment the code will run in.  The string hashing algorithm in the SE JDK has been unchanged since at least JDK 1.2.  Since the algorithm has been specified since that release as well, it is very unlikely to be changed in the future.

Different hashing algorithms, such as the length of the strings or a perfect hashing algorithm over the particular set of case labels, could potentially be used instead of String.hashCode.

举一个具体的例子,如下:

public class TestStringSwitch {

	public void test(String status) {
		switch (status) {
		case "killed":
		case "alive":
			System.out.println("killed or alive");
			break;
		case "sacrificed":
			System.out.println("sacrificed");
			break;
		case "die":
			System.out.println("die");
			break;
		default:
			System.out.println("default");
			break;
		}
	}
}

调用visitStringSwitch()方法后解语法粮的结果如下:

{
    /*synthetic*/ final String s97$ = status;
    /*synthetic*/ int tmp97$ = -1;
    switch (s97$.hashCode()) {
    case -1131353987:
        if (s97$.equals("killed")) tmp97$ = 0;
        break;

    case 92903629:
        if (s97$.equals("alive")) tmp97$ = 1;
        break;

    case 1585581907:
        if (s97$.equals("sacrificed")) tmp97$ = 2;
        break;

    case 99456:
        if (s97$.equals("die")) tmp97$ = 3;
        break;

    }
    switch (tmp97$) {
    case 0: 

    case 1:
        System.out.println("killed or alive");
        break;

    case 2:
        System.out.println("sacrificed");
        break;

    case 3:
        System.out.println("die");
        break;

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

    }
}

通过class进行反编译后的结果如下:

public class TestStringSwitch
{
  public void test(String status)
  {
    String str;
    switch ((str = status).hashCode())
    {
    case -1131353987:
      if (str.equals("killed")) {
        break;
      }
      break;
    case 99456:
      if (str.equals("die")) {}
      break;
    case 92903629:
      if (str.equals("alive")) {
        break;
      }
      break;
    case 1585581907:
      if (!str.equals("sacrificed"))
      {
        break label129;
        System.out.println("killed or alive");
        return;
      }
      else
      {
        System.out.println("sacrificed");
        return;

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

哈希函数在映射的时候可能存在冲突,多个字符串的哈希值可能是一样的,所以还要通过字符串比较来保证。  

  

时间: 2024-08-06 15:58:50

Javac语法糖之EnumSwitch的相关文章

Javac语法糖之增强for循环

加强的for循环有两种,遍历数组和实现了Iterable接口的容器.javac通过visitForeachLoop()方法来实现解语法糖,代码如下: /** Translate away the foreach loop. */ public void visitForeachLoop(JCEnhancedForLoop tree) { if (types.elemtype(tree.expr.type) == null) visitIterableForeachLoop(tree); else

Javac语法糖之TryCatchFinally

Optionally replace a try statement with the desugaring of a try-with-resources statement. The canonical desugaring of try ResourceSpecification Block is { final VariableModifiers_minus_final R #resource = Expression; Throwable #primaryException = nul

第十二章-语义分析之语法糖去除

参考以下博文: (1)Javac语法糖之内部类 (2)Javac语法糖之EnumSwitch (3)Javac语法糖之TryCatchFinally (4)Javac语法糖之增强for循环 (5)Javac语法糖之其它 (6)解语法糖之私有构造函数 (7)lower类的accessCode解读 (8)invokespecial与invokevirtual指令的区别 原文地址:https://www.cnblogs.com/extjs4/p/9744078.html

深入理解java虚拟机(十二) Java 语法糖背后的真相

语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语.指的是,在计算机语言中添加某种语法,这些语法糖虽然不会对语言的功能产生任何影响,却能使程序员更方便的使用语言开发程序,同时增强程序代码的可读性,避免出错的机会.但是如果只是大量添加和使用语法糖,却不去了解他,容易产生过度依赖,从而无法看清语法糖的糖衣背后,程序代码的真实面目. 总而言之,语法糖可以看做是编译器实现的一些"小把戏",这些"小

[C#]剖析异步编程语法糖: async和await

一.难以被接受的async 自从C#5.0,语法糖大家庭又加入了两位新成员: async和await. 然而从我知道这两个家伙之后的很长一段时间,我甚至都没搞明白应该怎么使用它们,这种全新的异步编程模式对于习惯了传统模式的人来说实在是有些难以接受,不难想象有多少人仍然在使用手工回调委托的方式来进行异步编程.C#中的语法糖非常多,从自动属性到lock.using,感觉都很好理解很容易就接受了,为什么偏偏async和await就这么让人又爱又恨呢? 我想,不是因为它不好用(相反,理解了它们之后是非常

Java中的10颗语法糖

语法糖(Syntactic Sugar):也称糖衣语法,指在计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用.通常来说,使用语法糖能够增加程序的可读性,减少程序代码出错的机会. 相对于C#及许多其他JVM来说,Java在现代编译语言之中属于“低糖语言”.尤其是JDK1.5之前的版本,“低糖”语法也是java语言被怀疑已经“落后”的一个表现方面. 在Javac的源码中,解语法糖的过程由desugar()方法触发,在com.sun.tools.javac.comp.Tr

Java 语法糖详解

语法糖 语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法. 这种语法对语言的功能并没有影响,但是更方便程序员使用.简而言之,语法糖让程序更加简洁,有更高的可读性. 有意思的是,在编程领域,除了语法糖,还有语法盐和语法糖精的说法,篇幅有限这里不做扩展了. 我们所熟知的编程语言中几乎都有语法糖.作者认为,语法糖的多少是评判一个语言够不够牛逼的标准之一. 很多人说 Java 是一个 "低糖语言&qu

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

本文从 Java 编译原理角度,深入字节码及 class 文件,抽丝剥茧,了解 Java 中的语法糖原理及用法,帮助大家在学会如何使用 Java 语法糖的同时,了解这些语法糖背后的原理 语法糖 语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用.简而言之,语法糖让程序更加简洁,有更高的可读性. 有意思的是,在编程领域,除了语法糖,还有语法盐和

JVM(二):Java中的语法糖

JVM(二):Java中的语法糖 上文讲到在语义分析中会对Java中的语法糖进行解糖操作,因此本文就主要讲述一下Java中有哪些语法糖,每个语法糖在解糖过后的原始代码,以及这些语法糖背后的逻辑. 语法糖 语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用.通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会.