1. 程式人生 > >Javac語法糖之EnumSwitch

Javac語法糖之EnumSwitch

environ cnblogs $1 string diag 通過 add 比較 println

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");
  }
}

哈希函數在映射的時候可能存在沖突,多個字符串的哈希值可能是一樣的,所以還要通過字符串比較來保證。  

  

Javac語法糖之EnumSwitch