2011-08-17 26 views
0

考虑这个(非常丑陋的代码)工作:NullPointerException异常而有状态PartialFunction和collectFirst

object ExternalReferences2 { 
    import java.util.regex._ 

    implicit def symbol2string(sym: Symbol) = sym.name 

    object Mapping { 
    def fromXml(mapping: scala.xml.NodeSeq) = { 
     new Mapping(mapping \ 'vendor text, 
        mapping \ 'match text, 
        mapping \ 'format text) 
    } 
    } 
    case class Mapping(vendor: String, 
        matches: String, 
        format: String) extends PartialFunction[String, String] { 
    private val pattern = Pattern.compile(matches) 
    private var _currentMatcher: Matcher = null 
    private def currentMatcher = 
     { println("Getting matcher: " + _currentMatcher); _currentMatcher } 
    private def currentMatcher_=(matcher: Matcher) = 
     { println("Setting matcher: " + matcher); _currentMatcher = matcher } 

    def isDefinedAt(entity: String) = 
     { currentMatcher = pattern.matcher(entity); currentMatcher.matches } 

    def apply(entity: String) = apply 

    def apply = { 
     val range = 0 until currentMatcher.groupCount() 
     val groups = range 
        map (currentMatcher.group(_)) 
        filterNot (_ == null) 
        map (_.replace('.', '/')) 
     format.format(groups: _*) 
    } 
    } 

    val config = 
    <external-links> 
     <mapping> 
     <vendor>OpenJDK</vendor> 
     <match>{ """^(javax?|sunw?|com.sun|org\.(ietf\.jgss|omg|w3c\.dom|xml\.sax))(\.[^.]+)+$""" }</match> 
     <format>{ "http://download.oracle.com/javase/7/docs/api/%s.html" }</format> 
     </mapping> 
    </external-links> 

    def getLinkNew(entity: String) = 
    (config \ 'mapping) 
     collectFirst({ case m => Mapping.fromXml(m)}) 
     map(_.apply) 

    def getLinkOld(entity: String) = 
    (config \ 'mapping).view 
     map(m => Mapping.fromXml(m)) 
     find(_.isDefinedAt(entity)) 
     map(_.apply) 
} 

我试图通过对如图getLinkNew使用collectFirst改善getLinkOld的方法,但我总是得到NullPointerException因为_currentMatcher是仍设置为null

scala> ExternalReferences2.getLinkNew("java.util.Date") 
Getting matcher: null 
java.lang.NullPointerException 
    at ExternalReferences2$Mapping.apply(<console>:32) 
    at ExternalReferences2$$anonfun$getLinkNew$2.apply(<console>:58) 
    at ExternalReferences2$$anonfun$getLinkNew$2.apply(<console>:58) 
    at scala.Option.map(Option.scala:131) 
    at ExternalReferences2$.getLinkNew(<console>:58) 
    at .<init>(<console>:13) 
    at .<clinit>(<console>) 
    at .<init>(<console>:11) 
    at .<clinit>(<console>) 

,同时它与getLinkOld完美。

这里有什么问题?

回答

3

你的匹配器作为isDefined副作用产生。将副作用函数传递给例程(例如map)通常是灾难的秘诀,但这甚至不会发生在这里。您的代码需要isDefinedapply之前被调用,具有相同的参数。这使得你的代码非常脆弱,这就是你应该改变的。

PartialFunction的客户通常不必遵循该协议。想象一下,例如

if (f.isDefinedAt(x) && f.isDefinedAt(y)) {fx = f(x); fy = f(y)}. 

这里调用apply代码甚至没有你的,但是集合类,所以你无法控制会发生什么。

getLinkNew你的具体问题是:isDefined简直是从来没有的collectFirst called.The PartialFunction参数{case m => ...}。被调用的isDefined是这个函数的isDefined。由于m是一个无可辩驳的模式,它总是真实的,collectFirst将始终返回第一个元素,如果有的话。部分函数返回另一个不在m处定义的部分函数(Mapping)是无关紧要的。

编辑 - 可能的解决方法

一个很轻的变化将是检查matcher是否可用,并创建它,如果它不是。更好的是,保留已用于创建它的字符串entity,以便您可以检查它是否正确。只要没有多线程,这应该使副作用是良性的。但方法是,不要使用null,使用Option,所以编译器不会让你忽略它可能是None的可能性。

var _currentMatcher : Option[(String, Matcher)] = None 
def currentMatcher(entity: String) : Matcher = _currentMatcher match{ 
    case Some(e,m) if e == entity => m 
    case _ => { 
    _currentMatcher = (entity, pattern.matcher(entity)) 
    _currentmatcher._2 
    } 
} 

再次编辑。愚蠢的我

对不起,所谓的解决方法确实使类更安全,但它不会使collectFirst解决方案的工作。同样,case m =>部分功能始终定义(注意:entity甚至不出现在您的getLinkNew代码中,这应该令人担忧)。问题是人们需要一个NodeSeq的PartialFunction(不是实体,它将被函数所知,但不会作为参数传递)。 isDefined将被调用,然后应用。模式和匹配器取决于NodeSeq,因此它们不能事先创建,但只能在定义和/或应用中使用。本着同样的精神,您可以缓存isDefined中计算的内容以在Apply中重复使用。这绝对是不漂亮

def linkFor(entity: String) = new PartialFunction[NodeSeq, String] { 
    var _matcher : Option[String, Matcher] = None 
    def matcher(regexp: String) = _matcher match { 
    case Some(r, m) where r == regexp => m 
    case None => { 
     val pattern = Pattern.compile(regexp) 
     _matcher = (regexp, pattern.matcher(entity)) 
     _matcher._2 
    } 
    } 
    def isDefined(mapping: NodeSeq) = { 
    matcher(mapping \ "match" text).matches 
    } 
    def apply(mapping: NodeSeq) = { 
    // call matcher(...), it is likely to reuse previous matcher, build result 
    } 

} 

您使用与

+0

谢谢!你认为什么样的解决方案不会计算两次? – soc

+0

提出了一种解决方法,可以对代码进行最小限度的更改,而不是作为有更多空间的评论。 –