当前位置: 首页 > 知识库问答 >
问题:

scala-泛型中的任何vs下划线

裴存
2023-03-14

Scala中的以下泛型定义有什么不同:

class Foo[T <: List[_]]

class Bar[T <: List[Any]]

我的直觉告诉我它们大致相同,但后者更明确。我发现了一些情况,前者编译了,而后者没有,但我无法确定确切的差别。

谢谢

编辑:

我能再加入一个吗?

class Baz[T <: List[_ <: Any]]

共有2个答案

蔚学真
2023-03-14

前者是存在类型的简写,当代码不需要知道类型是什么或约束它时:

class Foo[T <: List[Z forSome { type Z }]]

此窗体表示 List 的元素类型对于类 Foo 是未知的,而不是您的第二个窗体,它明确表示 List 的元素类型为 Any

看看这篇关于Scala中存在类型的简短解释性博客文章(编辑:此链接现在已死,快照可在archive.org)

蒋英博
2023-03-14

好吧,我想我应该有自己的看法,而不是仅仅发表评论。对不起,如果您想要TL,这将很长;跳到最后。

正如兰德尔·舒尔茨(Randall Schulz)所说,这里_是存在主义的简写。即

class Foo[T <: List[_]]

是 的简写

class Foo[T <: List[Z] forSome { type Z }]

请注意,与Randall Shulz的回答相反(完全披露:我在这篇文章的早期版本中也弄错了,感谢Jesper Nordenberg指出这一点),这与:

class Foo[T <: List[Z]] forSome { type Z }

也不等于:

class Foo[T <: List[Z forSome { type Z }]]

当心,很容易弄错(正如我之前的穿帮所示):Randall Shulz的回答所引用的文章的作者自己弄错了(见评论),后来修复了它。我对这篇文章的主要问题是,在所示的示例中,使用存在主义应该可以将我们从打字问题中解救出来,但事实并非如此。去检查代码,并尝试编译compileAndRun(helloWorldVM("Test"))compileAndRun(intVM(42))。是的,不编译。简单地使compileAndRunA中通用会使代码编译,而且会简单得多。简而言之,这可能不是了解存在主义及其好处的最佳文章(作者本人在评论中承认这篇文章“需要整理”)。

因此,我宁愿推荐阅读本文:http://www.artima.com/scalazine/articles/scalas_type_system.html,特别是名为“存在类型”和“Java和Scala中的方差”的部分。

您应该从本文中得到的重要一点是,在处理非协变类型时,存在性是有用的(除了能够处理泛型java类之外)。这里有一个例子。

case class Greets[T]( private val name: T ) {
  def hello() { println("Hello " + name) }
  def getName: T = name
}

这个类是泛型的(请注意,它是不变的),但我们可以看到<code>hello</code>确实没有使用类型参数(与<code>getName</code>不同),因此如果我得到<code>Greets</code>的实例,我应该总是能够调用它,无论<code>t</code>是什么。如果我想定义一个方法,该方法接受一个问候实例并只调用其你好方法,我可以尝试以下方法:

def sayHi1( g: Greets[T] ) { g.hello() } // Does not compile

果然,这不会编译,因为T在这里不知从哪里冒出来。

好吧,那么,让我们使这个方法通用化:

def sayHi2[T]( g: Greets[T] ) { g.hello() }
sayHi2( Greets("John"))
sayHi2( Greets('Jack))

太好了,这行得通。我们也可以在这里使用存在主义:

def sayHi3( g: Greets[_] ) { g.hello() }
sayHi3( Greets("John"))
sayHi3( Greets('Jack))

也有用。所以总而言之,在类型参数(如< code>sayHi2)上使用存在参数(如< code>sayHi3)并没有什么实际的好处。

但是,如果 Greets 本身显示为另一个泛型类的类型参数,则此情况会发生变化。举个例子,我们希望在一个列表中存储多个问候语(具有不同T)的实例。让我们试试吧:

val greets1: Greets[String] = Greets("John")
val greets2: Greets[Symbol] = Greets('Jack)
val greetsList1: List[Greets[Any]] = List( greets1, greets2 ) // Does not compile

最后一行不会编译,因为< code>Greets是不变的,所以< code>Greets[String]和< code>Greets[Symbol]不能被视为< code>Greets[Any],即使< code>String和< code>Symbol都扩展了< code>Any。

好的,让我们尝试一个存在主义,使用速记符号_

val greetsList2: List[Greets[_]] = List( greets1, greets2 ) // Compiles fine, yeah

这可以很好地编译,您可以按照预期执行:

greetsSet foreach (_.hello)

现在,请记住,我们最初遇到类型检查问题的原因是因为问候语是不变的。如果它变成了一个协变类(类 Greets[T]),那么一切都会开箱即用,我们永远不需要存在主义。

综上所述,存在性对于处理泛型不变类是有用的,但是如果泛型类不需要将自己作为另一个泛型类的类型参数出现,那么很有可能您不需要存在性,只需在方法中添加一个类型参数就可以了

现在回来(终于,我知道!)你的具体问题,关于

class Foo[T <: List[_]]

因为List是协变的,所以对于所有意图和目的来说,这与只是说:

class Foo[T <: List[Any]]

因此,在这种情况下,使用任何一种符号实际上只是风格问题。

但是,如果将 List 替换为 Set,情况会发生变化:

class Foo[T <: Set[_]]

< code>Set是不变的,因此我们的情况与示例中的< code>Greets类相同。因此,上述内容与

class Foo[T <: Set[Any]]
 类似资料:
  • 问题内容: 我正在尝试使用scala json库Circe,将其包装在一个简单的特征中以提供往返于json的转换,我对此具有以下要求: 这样做的目的是简单地能够用任何对象调用JsonConverter并将其转换成json之类的东西,但是当我尝试对其进行编译时,我得到以下信息: 我当然可以拥有一个类,打算通过转换器放入的所有内容都继承自该类,但是我有一个印象,大约可以自动生成编码器/解码器? 问题答

  • 我被泛型子类型搞糊涂了。 在Java中,如果类型是的子类型,则泛型类型和是不变的。例如,不是的子类型。 但是,在Scala中,如果类型是的子类型,则泛型类型和是协变的。那么Scala中泛型类的属性是什么而Java中没有呢?

  • 我读了很多文章,但我不明白这两行之间的区别: 我看到的唯一区别是第一行触发了“未检查的分配”警告。

  • 有四种不同的类型:地点、语言、技术和行业。每个类型都有一个存储库,可以返回这些类型的集合。例如位置列表。每个类型都有一个类型为String的name属性。有一个字符串列表。它可以包含位置、语言等名称。我想编写一个函数来查找那些与字符串列表名称匹配的类型实体(位置、语言、...)。我在想这样的事情: 这是不正确的,那么如何对集合进行查询,然后如何确定name属性是否存在呢?

  • 我定义了一个通用的环境特征: 为此,我提供了以下实现: 此外,我定义了一个通用事件特征,该特征具有一个接受通用环境作为参数的方法: 对于这个事件特征,我提供了以下实现,其中exec()方法接受MyEnvironment类型的参数,使我能够访问MyEnvironment的特定值。 然而,Scala编译器输出了一个错误,从中可以看出MyEnvironment似乎没有被识别为环境[整数]: 错误:方法e

  • 这可能是一个很傻的问题,但我挠头了很久也弄不明白其中的区别。 我正在浏览scala泛型页面:https://docs.scala-lang.org/tour/generic-classes.html 注意:泛型类型的子类型是不变的。这意味着,如果我们有一个stack[Char]类型的字符堆栈,那么它就不能用作stack[Int]类型的整数堆栈。这是不合理的,因为它使我们能够将真整数输入到字符堆栈中