2015-03-30 45 views
14

我很好奇,想看看的Java和Scala如何实现对字符串开关:接通字符串

class Java 
{ 
    public static int java(String s) 
    { 
     switch (s) 
     { 
     case "foo": return 1; 
     case "bar": return 2; 
     case "baz": return 3; 
     default: return 42; 
     } 
    } 
} 
object Scala { 
    def scala(s: String): Int = { 
    s match { 
     case "foo" => 1 
     case "bar" => 2 
     case "baz" => 3 
     case _ => 42 
    } 
    } 
} 

看起来像Java接通的哈希码,然后做一个字符串比较:

0: aload_0  
1: dup   
2: astore_1  
3: invokevirtual #16 // Method java/lang/String.hashCode:()I 
6: lookupswitch { // 3 
      97299: 40 
      97307: 52 
      101574: 64 
     default: 82 
    } 
40: aload_1  
41: ldc   #22 // String bar 
43: invokevirtual #24 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 
46: ifne   78 
49: goto   82 
52: aload_1  
53: ldc   #28 // String baz 
55: invokevirtual #24 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 
58: ifne   80 
61: goto   82 
64: aload_1  
65: ldc   #30 // String foo 
67: invokevirtual #24 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 
70: ifne   76 
73: goto   82 
76: iconst_1  
77: ireturn  
78: iconst_2  
79: ireturn  
80: iconst_3  
81: ireturn  
82: bipush  42 
84: ireturn  

相比之下,斯卡拉似乎可以与所有情况进行比较:

0: aload_1  
1: astore_2  
2: ldc   #16 // String foo 
4: aload_2  
5: invokevirtual #20 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z 
8: ifeq   16 
11: iconst_1  
12: istore_3  
13: goto   47 
16: ldc   #22 // String bar 
18: aload_2  
19: invokevirtual #20 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z 
22: ifeq   30 
25: iconst_2  
26: istore_3  
27: goto   47 
30: ldc   #24 // String baz 
32: aload_2  
33: invokevirtual #20 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z 
36: ifeq   44 
39: iconst_3  
40: istore_3  
41: goto   47 
44: bipush  42 
46: istore_3  
47: iload_3  
48: ireturn  

是否可以说服Scala采用散列码技巧?我宁愿O(1)解决方案的O(n)解决方案。在我的真实代码中,我需要比较33个可能的关键字。

+0

我不认为Java会一直这样做,保证所有给出的字符串都有不同的哈希码?这个检查可能是在解释器(JVM)中完成的,如果所有字符串都计算为不同的散列值,则只选择散列码解决方案 – smac89 2015-03-30 20:12:19

+1

@ Smac89散列冲突根本没有问题。 Java将简单地跳转到相同的地方,然后做2个字符串比较而不是1.另外,编译器知道所有的字符串以及它们的所有hashcode。 JVM不需要动态分析情况。 – fredoverflow 2015-03-30 20:14:17

+0

我也很好奇......现在Java 8出来了,Scala仍然有用吗? – 2015-03-30 20:21:09

回答

5

当然,这种情况似乎是缺乏来自Scala编译器的优化。当然,match结构比Java中的开关/情况要强大得多,而且对它进行优化要困难得多,但是它可以检测出这些特殊情况,在这些情况下可以应用简单的散列比较。

另外,我不认为这种情况会在惯用的Scala中多次显示,因为除了具有不同的值之外,您总是与具有一些含义的案例类匹配。

2

我认为问题在于你从Java的角度思考Scala(我认为你也过早地优化了,但是嘿)。

我会认为你想要的解决方案是,而不是记忆你的映射。 你有一个从String - > Int映射的函数,对吧?所以这样做:

class Memoize1[-T, +R](f: T => R) extends (T => R) { 
    import scala.collection.mutable 
    private[this] val vals = mutable.Map.empty[T, R] 

    def apply(x: T): R = { 
    if (vals.contains(x)) { 
     vals(x) 
    } 
    else { 
     val y = f(x) 
     vals += ((x, y)) 
     y 
    } 
    } 
} 

object Memoize1 { 
    def apply[T, R](f: T => R) = new Memoize1(f) 
} 

(这memoizing代码是从here采取

然后你就可以memoize的代码是这样的:!

object Scala { 
    def scala(s: String): Int = { 
    s match { 
     case "foo" => 1 
     case "bar" => 2 
     case "baz" => 3 
     case _ => 42 
    } 
    } 

    val memoed = Memoize1(Scala.scala) 

    val n = memoed("foo") 
} 

田田现在你正在做的哈希值进行比较。虽然我会补充说大多数memoization示例(包括这个)都是玩具,并且在大多数使用情况下都不会存在。真实世界memoization should include an upper limit达到您愿意缓存的数量,并且在代码的情况下,您拥有一个小小的可能有效的案例数量和大量的无效案例,我会考虑制作一个预制地图的普通类,并进行专门的查询,说“在我的缓存中,你赢了,而不是在我的缓存中,默认值”,这可以做得很好可以通过调整备忘录轻松地将输入的List预缓存并更改“不进入缓存”代码以返回默认值。

+0

是啊......或者我可以把字符串开关放在一个'.java'文件中并从Scala中使用它:) – fredoverflow 2015-03-30 20:40:04

+2

真诚的,和我一样喜欢memoization,我不认为这是一个很有价值的答案。案件... – 2015-03-30 21:07:53

2

这个问题激发了我学习Scala宏,我不妨分享我的解决方案。

这里是我如何使用宏:

switch(s, 42, "foo", "bar", "baz") 

相关的值会自动累加。如果这不是你想要的,你可以改变实现来接受ArrowAssoc s,但这对我来说太复杂了。

这里是宏是如何实现的:

import scala.language.experimental.macros 
import scala.reflect.macros.blackbox.Context 
import scala.collection.mutable.ListBuffer 

object StringSwitch { 

    def switch(value: String, default: Long, cases: String*): Long = 
    macro switchImpl 

    def switchImpl(c: Context)(value: c.Expr[String], default: c.Expr[Long], 
          cases: c.Expr[String]*): c.Expr[Long] = { 
    import c.universe._ 

    val buf = new ListBuffer[CaseDef] 
    var i = 0 
    for (x <- cases) { 
     x match { 
     case Expr(Literal(Constant(y))) => 
      i += 1 
      buf += cq"${y.hashCode} => if ($x.equals($value)) $i else $default" 

     case _ => throw new AssertionError("string literal expected") 
     } 
    } 
    buf += cq"_ => $default" 

    c.Expr(Match(q"$value.hashCode", buf.toList)) 
    } 
} 

请注意,此解决方案不处理哈希冲突。由于在我的实际问题中,我关心的特定字符串不会相互碰撞,所以我还没有穿过那个特定的桥。