是否有理由在Scala中使用子类型作为类型参数?

时间:2016-11-27 22:40:23

标签: scala

我想知道是否有充分的理由将子类型用作函数类型参数? 让我们考虑以下示例:

scala> trait Animal { def sound: String }
defined trait Animal

scala> def f1[T <: Animal](a: T) = a.sound
f1: [T <: Animal](a: T)String

scala> def f2(a: Animal) = a.sound
f2: (a: Animal)String

f1 f2 相比有哪些优势?

4 个答案:

答案 0 :(得分:5)

我相信你的例子没有任何优势。类型参数通常用于连接代码的不同部分,但有关T的信息实际上已丢失,因为它不匹配任何内容。考虑另一个例子:

def f1[T <: Animal](a: T) = (a.sound, a)

def f2(a: Animal) = (a.sound, a)

现在情况有所不同,因为T被传递给返回类型:

class Dog extends Animal { def sound = "bow-wow" }

f1(new Dog)
//> (String, Dog) = (bow-wow,Dog@141851fd)

f2(new Dog)
//> (String, Animal) = (bow-wow,Dog@5a1fe991)

在这种情况下,您可以将f1视为&#34;实例化&#34;的模板。在编译时,有效地生成基于编译时参数类型的特定方法。因此,如果您想使用需要f1(new Dog)的{​​{1}},则会进行编译,而(String, Dog)则会赢得

答案 1 :(得分:2)

函数f1f2都非常相似。如果输出字节码,您将在常量池中看到它们被表示为:

#71 = Methodref          #12.#70       // Main$.f2:(LMain$Animal;)Ljava/lang/String;
#74 = Methodref          #12.#73       // Main$.f1:(LMain$Animal;)Ljava/lang/String;

就字节码而言,它们是将Animal作为参数并返回String的函数。

当您想要返回特定的T(其中T <: Animal)时,这会变得更有趣。请记住,字节码仍然是相同的,但在编译时,这为T类型参数提供了更多的含义和功能:

想象一下你有:

def f1[T <: Animal](a: T): T = a  // silly, I know
def f2(a: Animal): Animal = a

你试试这个:

val s: Dog = f1(new Dog())
val t: Dog = f2(new Dog()) // NOPE
val u: Dog = f2(new Dog()).asInstanceOf[Dog] // awkward

第二行不会在没有强制转换的情况下编译,这会牺牲编译时类型检查。

答案 2 :(得分:1)

考虑到f1f2具有相同输出类型的示例f2函数在f1之前有优势,如果您想重载方法。

因此对于f1,它会出错:

scala> :paste
// Entering paste mode (ctrl-D to finish)

class Zoo {
  def f1[T <: Animal](a: T) = a.sound
  def f1[T <: Dog](a: T) = "Dog says " + a.sound
}

// Exiting paste mode, now interpreting.

<console>:18: error: method f1 is defined twice
  conflicting symbols both originated in file '<console>'
         def f1[T <: Dog](a: T) = "Dog says " + a.sound
             ^

f2同时有效:

scala> :paste
// Entering paste mode (ctrl-D to finish)

class Zoo {
  def f2(a: Animal) = a.sound
  def f2(a: Dog) = "Dog says " + a.sound
}

// Exiting paste mode, now interpreting.

defined class Zoo

答案 3 :(得分:0)

在Scala和JVM中,他们有以下函数类型规则

  

S1 -> S2T1 -> T2

的子类型      

当且仅当

     

S1T1的子类型,T2S2的子类型

在您的示例中,

   def f1[T <: Animal](a: T): String // T <: Animal -> String
   def f2(a: Animal): String // Animal -> String

根据功能输入规则,f1f2

的子类型

总之 f1f2在实际使用案例中没有区别

请参阅以下链接

https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Function_types