用switch-case调戏java编译器

最近一部分工作就是将主模块中基础控件、业务逻辑相关代码和资源移至独立的模块(module后者叫做project)中。我们的代码中有不少switch-case里用到了R.id.xxxyyyzzz,但直接把代码移动到新的被依赖模块中时编译器却会报错:case expressions must be constant expressions!

我们都知道,switch-case语法中要求case后面要跟常量,但我们的R.id.xxxyyyzzz的确都是常量啊:

是的,它们的确都是常量,否则编译早就挂了。但资源ID一旦到了子模块中就不再是常量了:

因此,我们必须把子模块中的这些switch-case改成if-else。

不过这不是我们的重点,问题来了:挖掘机技术哪家……不,不!为什么switch-case中一定要用常量?

有同学说了,这是java语法规定!好有道理,我竟然无法反驳……

好了,我还是让代码说话吧。我们从一个简单的method来看看java是怎么处理switch-case的。

	public static void switchcase(int param) {
		switch (param) {
			case 0:
			break;
			case 1:
			break;
			case 2:
			break;
			case 3:
			break;
			case 4:
			break;
			case 5:
			break;
			case 6: 
			break;
			default:
		}
	}

	public static void ifelse(int param) {
		if (param == 0) {
		} else if (param == 1) {
		} else if (param == 2) {
		}
	}

相应的java字节码为:

编译器为switch-case建了一张表,而if-else却要逐个比较(iload是将局部变量压到栈顶,其他命令参照汇编语言猜着看就行J)。Tableswitch是一张key-value表,查找速度嗷嗷快——O(1),执行效率远超if-else的逐个比较。是不是脑海中浮现出了哈希表?

到这里还没完,让我们来调戏一下编译器,把switch-case里的常量改改:

         publicstatic void switchcase(int param) {
                   switch(param) {
                            case0:
                            break;
                            case10:
                            break;
                            case20:
                            break;
                            case30:
                            break;
                            case40:
                            break;
                            case50:
                            break;
                            case60:
                            break;
                            default:
                   }
         }

结果出现了一个新的指令——lookupswitch

Lookupswitch的执行与if-else类似,要逐个比较。但由于数据是排过序的,在执行过程中可以采用二分法等方式进行优化,时间复杂度也可以达到O(logn),实际效率还是很不错的。

编译器对switch-case的处理有2种方式:tableswitch和lookupswitch,那么编译器是怎么做选择的呢?答案是:对于“紧凑”(compact)的使用tableswitch,建立跳转表;对于“稀疏的”(spare)使用lookupswitch,进行比较。对于case中常量比较连续的,就认为是紧凑;常量比较分散,存在“空洞”较多的,则认为是稀疏。至于具体“紧凑”和“稀疏”的定义,就要看编译器实现者的理解了。

有人又会问:既然tableswitch效率高,干脆都用tableswitch好了,干嘛还要有个lookupswitch?好吧,我再回答这一个问题就去搬砖了。

先回忆一下我们的跳转表,一共有6个入口:

接下来再次调戏编译器,把其中2个case注释掉:

         publicstatic void switchcase(int param) {
                   switch(param) {
                            case0:
                            break;
                            case1:
                            break;
                            case2:
                            break;
//                         case3:
//                         break;
//                         case4:
//                         break;
                            case5:
                            break;
                            case6:
                            break;
                            default:
                   }
         }

再来看看结果:

依然是6个入口!只不过3、4跳转的位置和default是一样的。

因为编译器觉得这个分布还是比较“紧凑”的,只不过出现了2个“空洞”而已。为了不因小失大,它就自己补上了2个fake条目,而这2个条目的跳转位置又被设置成和default语句一致的。

如果“空洞”过大,编译器就要填上很多fake条目,从而造成空间的浪费,所以此时就转而使用lookupswitch了。

有兴趣的同学可以继续调戏编译器,看看还有什么惊喜不。


-------------------------------------原创内容,转载请说明出处-----------------------------------------

欢迎联系

微信:oscaryue001


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值