Scala中的None实例不等于None。

10

我在Scala代码中遇到了一个间歇性的问题,我的代码与具有字符串键的不可变map中的值一起工作。以下是基本代码,包括我添加的调试日志:

  val compStruct = subsq.comps get (ident)
  compStruct match {
    ...
    case None =>
      logger.info(s"Found None, of type ${compStruct.getClass.getName}, at position $position (ident $ident)")
      ...
    case x =>
      logger.info(s"Illegal structure of type ${x.getClass.getName} at position $position (ident $ident) - x == None is ${x == None}, x.getClass == None.getClass is ${x.getClass == None.getClass}, x.getClass.getName == None.getClass.getName (${None.getClass.getName}) is ${x.getClass.getName == None.getClass.getName}")
      ...
  }

问题在于当值实际上为None时,有时会使用case x,如(经过消毒的)调试输出所示:

问题在于当值实际上为 None 时,有时会使用 case x,如(经过消毒的)调试输出所示:

  INFO  ...: Found None, of type scala.None$, at position 3000 (ident XX)
  INFO  ...: Illegal structure of type scala.None$ at position 3200 (ident XX) - x == None is false, x.getClass == None.getClass is true, x.getClass.getName == None.getClass.getName (scala.None$) is true

第一行是我预期发生的,通常也确实会发生;其他情况则是错误的。

因此,如果我的日志记录是可信的(并且我没有以某种方式搞砸我的表达式),则我有一个情况,其中地图返回x,其中x是scala.None$类的一个实例(由编译后的代码看到的相同的scala.None$类),但不匹配None案例,x == None为false。

类加载问题可能是明显的原因,但x.class == None.class似乎排除了这种可能性。

添加:如我在评论中所建议的,我可以使用以下代码重现未匹配的None实例:

object Test {
  def main(args: Array[String]): Unit = {
    val none1 = None
    val clas = this.getClass.getClassLoader.loadClass("scala.None$")
    val constr = clas.getDeclaredConstructors()(0)
    constr.setAccessible(true)
    val none2 = constr.newInstance()
    println(s"none1 == none2 is ${none1 == none2}")
    println(s"none1 == None is ${none1 == None}")
    println(s"none2 == None is ${none2 == None}")
  }
}

这意味着:

none1 == none2 is false
none1 == None is false
none2 == None is true

我不认为这与应用程序中正在发生的事情有任何关系。

补充:我修改了实际的None$类文件,使其在构造函数执行时打印消息,并且如果在调用构造函数时None$.MODULE$值非空,则会抛出异常,甚至将存储器移动到静态构造函数块中的静态MODULE$值(原始代码在构造函数中具有此存储器,但我认为这在技术上违反了JVM规则,因为对象在构造函数返回之后才被认为已初始化)。

这确实阻止了对构造函数的反射调用(以上示例代码),该调用复制了问题的症状,但并未改变实际应用程序中的任何内容。 None$.MODULE$的值从一次代码执行到下一次执行会发生变化,尽管类保持不变(System.identityHashCode相同),但构造函数仅调用一次。


也许可以显示subsq.comps的类型声明。此外,您是否从另一个虚拟机序列化任何内容或以某种方式加载整个subsq(网络/磁盘)?还可以尝试显示调试对象的哈希键,并进行比较:scala.None.hashCode,x.hashCode,也许它们不匹配? - LaloInDublin
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Dennis Sosnoski
1
实际上,有人使用反射调用私有构造方法可能会解释这种情况 - 这将把标准的None$更改为新的None。但在这种情况下,奇怪的None是由Map.get返回的,而且这是在从另一个映射返回预期的None之后“立即”发生的。所以仍然令人困惑。 - Dennis Sosnoski
2
你的建议作为一种解决方法是有道理的,但是如果不了解问题,我不愿意开始试图绕过它编码。 - Dennis Sosnoski
2
你也可以尝试使用 System.identityHashCode 来判断是否为另一个实例。 - Odomontois
显示剩余8条评论
2个回答

1
关于你的测试: None 是 Scala 中的一个对象。当你定义 val none1 = None 时,你将这个对象赋值给了 none1,它应该是一个单例。
通过使用反射,你可以绕过私有构造函数并创建 None 类的新实例。如果两个指针指向同一个对象,== 运算符将只返回 true。你可以使用 System.identityHashCode(none1) 来验证这些对象的内存地址并进行比较。
此外,如果你在 Test 对象中运行匹配 none1 的代码,你会遇到匹配错误,因为第二个 None 实例既不匹配 None 也不匹配 x。
我能够重现你的错误。通过运行以下代码:
val a = Map("a" -> "b", "b" -> None)
a.get("b") match { 
   case None => print("None")
   case x => print("X") 
}  // Prints X
a.get("c") match { 
    case None => print("None")
    case x => print("X") 
} // Prints None

我知道这并没有解释为什么会打印X,但至少你知道了何时会打印。
因为你的HashMap具有None值,所以它是一个HashMap [String,java.io.Serializable]而不是HashMap [String,String]。 调用get将返回java.io.Serializable而不是String。
要解决问题并在其为None时匹配,您可以执行以下操作:
case x if(x.isInstanceOf[None$]) => 

这意味着在 acompStruct 的类型注释中添加 HashMap [String,String] 应该会使编译器指出问题,对吗?我想选择 java.io.Serializable 作为 Map 值的公共类型并不是程序员的本意。 - Sven Koschnicke
完全正确,@SvenKoschnicke! - Vinicius Miana

0
请注意,在您的代码上下文中处理None的方式是通过Option[A],如果不指定第二个条件的情况,则意味着您允许None成为第二种情况的一部分。
如果您想要获取不是None的情况,请将map.get处理为以下方式。您应该这样做。
val compStruct = subsqs.comp.get(ident)
compStruct match {
  case None => ...
  case x: Some(_) => ...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接