我在scala中遇到了一些非常奇怪的行为。 我写了一个泛型方法,它将一个容易出错的代码和一个“有效异常”列表作为参数,它应该执行代码,同时在抛出“有效异常”时重试代码。
该方法效果很好,我在几个地方使用它
但是,其中一个方法调用在编译时失败了。
原因是例外列表的初始化。
我在REPL中尝试过,以确保没有其他原因,您可以自己查看结果:
me@my-lap:~$ scala -cp /path/to/maven/repository/org/apache/httpcomponents/httpclient/4.1.1/httpclient-4.1.1.jar:/path/to/maven/repository/commons-httpclient/commons-httpclient/3.1/commons-httpclient-3.1.jar:/path/to/maven/repository/me/my-utils/1.0-SNAPSHOT/my-utils-1.0-SNAPSHOT.jar:/path/to/maven/repository/org/apache/httpcomponents/httpcore/4.1/httpcore-4.1.jar
Welcome to Scala version 2.9.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_17).
Type in expressions to have them evaluated.
Type :help for more information.
scala> import org.apache.http.conn.{HttpHostConnectException,ConnectTimeoutException}
import org.apache.http.conn.{HttpHostConnectException, ConnectTimeoutException}
scala> import me.util.exceptions.RetryException
import me.util.exceptions.RetryException
scala> val validEx = classOf[ConnectTimeoutException] :: classOf[RetryException] :: classOf[HttpHostConnectException] :: Nil
<console>:9: error: inferred type arguments [java.lang.Class[_ >: _1 with org.apache.http.conn.ConnectTimeoutException <: java.lang.Exception]] do not conform to method ::'s type parameter bounds [B >: java.lang.Class[_ >: org.apache.http.conn.HttpHostConnectException with me.util.exceptions.RetryException <: java.lang.Exception]]
val validEx = classOf[ConnectTimeoutException] :: classOf[RetryException] :: classOf[HttpHostConnectException] :: Nil
^
从所有3种异常类型初始化列表时,代码失败并出现这种奇怪的错误。 所以我试图用这3个中的2个例外的每个子集初始化:
scala> val validEx = classOf[ConnectTimeoutException] :: classOf[RetryException] :: Nil
validEx: List[java.lang.Class[_ >: me.util.exceptions.RetryException with org.apache.http.conn.ConnectTimeoutException <: java.lang.Exception]] = List(class org.apache.http.conn.ConnectTimeoutException, class me.util.exceptions.RetryException)
scala> val validEx = classOf[ConnectTimeoutException] :: classOf[HttpHostConnectException] :: Nil
validEx: List[java.lang.Class[_ >: org.apache.http.conn.HttpHostConnectException with org.apache.http.conn.ConnectTimeoutException <: java.io.IOException]] = List(class org.apache.http.conn.ConnectTimeoutException, class org.apache.http.conn.HttpHostConnectException)
scala> val validEx = classOf[RetryException] :: classOf[HttpHostConnectException] :: Nil
validEx: List[java.lang.Class[_ >: org.apache.http.conn.HttpHostConnectException with me.util.exceptions.RetryException <: java.lang.Exception]] = List(class me.util.exceptions.RetryException, class org.apache.http.conn.HttpHostConnectException)
它有效!我还尝试使用List()
而不是使用::
运算符来创建所有3种异常类型的列表。它也有效:
scala> val validEx = List(classOf[ConnectTimeoutException], classOf[RetryException], classOf[HttpHostConnectException])
validEx: List[java.lang.Class[_ >: org.apache.http.conn.HttpHostConnectException with me.util.exceptions.RetryException with org.apache.http.conn.ConnectTimeoutException <: java.lang.Exception]] = List(class org.apache.http.conn.ConnectTimeoutException, class me.util.exceptions.RetryException, class org.apache.http.conn.HttpHostConnectException)
顺便说一下,RetryException
的实施是:
class RetryException extends Exception {}
那为什么会这样?这是scala的::
运营商的错误吗?
为什么编译器不能推断出比List[java.lang.Class[_ >: org.apache.http.conn.HttpHostConnectException with me.util.exceptions.RetryException with org.apache.http.conn.ConnectTimeoutException <: java.lang.Exception]]
更好的类型
(我的方法除了一个类型为validExceptions: List[Class[_ <: java.lang.Throwable]]
的参数,这是一个更简洁的类型。我会除了编译器之外的某些东西,如:List[Class[_ <: java.lang.Exception]]
(我使用Scala版本2.9.2(Java7 oracle 1.7.0_17)在ubuntu 12.04 64bit上运行
答案 0 :(得分:3)
这看起来像是一个推理问题。
尝试将Nil
替换为List.empty[Exception]
:
val validEx = classOf[ConnectTimeoutException] :: classOf[RetryException] :: classOf[HttpHostConnectException] :: List.empty[Class[_ <: Exception]]
这应该解决它。
现在让我们看看原始代码中会发生什么:
val validEx = classOf[ConnectTimeoutException] :: classOf[RetryException] :: classOf[HttpHostConnectException] :: Nil
这是从右到左评估的,因为::
是右关联的。
首先,编译器评估classOf[HttpHostConnectException] :: Nil
。推断类型为List[Class[HttpHostConnectException]]
。
让我们调用此表达式tmp1: List[Class[HttpHostConnectException]]
然后编译器评估classOf[RetryException] :: tmp1
。类型推断尝试统一左侧和右侧的类型,并提出List[_ >: RetryException with HttpHostConnectException]
。
你可能想知道它为什么不推断List[Class[Exception]]
。这是因为Class
不是协变的(它是不变的),因此Class[HttpHostConnectException]
和Class[RetryException]
不是Class[Exception]
的子类型。
现在您可能还认为编译器可以推断出更简单(但不太精确)的类型List[AnyRef]
。但编译器只是尽量精确,这显然是非常有用的。但在这种情况下,它将在下一步产生问题。
因此,让我们调用此表达式tmp2: List[_ >: RetryException with HttpHostConnectException]
的结果,看看接下来会发生什么。
最后,编译器评估classOf[ConnectTimeoutException] :: tmp2
。
此处::
的签名要求classOf[ConnectTimeoutException]
是RetryException with HttpHostConnectException
的子类型(到目前为止列表元素的类型)。换句话说,ConnectTimeoutException
需要扩展RetryException
和HttpHostConnectException
,显然情况并非如此,因此会出现编译错误
答案 1 :(得分:1)
当您多次调用cons运算符(::
)时,类型推断器会尝试在每一步为您评估List
的正确类型参数。
在第一次合作之后
classOf[RetryException] :: classOf[HttpHostConnectException] :: Nil
推理类型与第三个cons调用不兼容。
错误消息告诉您上面的值具有
java.lang.Class[_ >: org.apache.http.conn.HttpHostConnectException with me.util.exceptions.RetryException <: java.lang.Exception]
所以它期望额外的前置元素必须是上述的超类型。
您可以通过执行
在控制台上验证这一点val intermediateList = classOf[RetryException] :: classOf[HttpHostConnectException] :: Nil
查看结果类型,然后尝试添加
classOf[ConnectTimeoutException] :: intermediateList
获得与OP中相同的错误
正如@RégisJean-Gilles建议的那样,如果您创建List
,指定您构建的第一个元素是您期望的类型
List.empty[Exception]
编译器没有问题。
使用List(..., ...)
对象工厂时会发生同样的情况,因为方法调用的类型推断一次执行一个参数列表(一组parens)。
这意味着编译器使用所有参数来定义正确的List
类型