在Scala(和Java)中,类和类型有什么区别?

58

Scala

在Scala中,类和类型之间的区别在哪里可以观察到,为什么这种区别很重要?

这只是从语言设计角度考虑的问题,还是在编写Scala程序时有“实际”影响的问题?

或者它对“保护类型系统的边界”是基本的(NothingNull让我想到了)?

Java

在Java中,有多少上面提到的考虑/差异/问题同样也可以被识别出来?


(请参见Type和Class之间的区别是什么? 作为一种非特定语言的介绍。)


3
好问题!我会等待看看是否有比我更知道的人回答它,否则我会试着回答它。 - Daniel C. Sobral
4
继续吧,丹尼尔,你知道你想要的……虽然我认为引用的答案已经很准确地解决了问题。如果可以的话,也涉及“种类”的问题 :) - Kevin Wright
5
我很愿意看到Martin Odersky的回复。 - gerferra
4个回答

44
当你说“类型”时,我会假设你主要是指静态类型。但我会简要谈一下动态类型。
静态类型是程序部分的属性,可以在不运行它的情况下静态证明(静态意味着“不运行它”)。在静态类型语言中,每个表达式都有一个类型,无论你是否编写它。例如,在类似于Cish的“int x = a * b + c - d”中,a、b、c和d具有类型,a * b具有类型,a * b + c具有类型,a * b + c - d具有类型。但我们只为x注释了一个类型。在其他语言中,如Scala、C#、Haskell、SML和F#,甚至这也是不必要的。
可证明的确切属性取决于类型检查器。
另一方面,Scala风格的类只是一组对象的规范。该规范包括一些类型信息和许多实现和表示细节,例如方法体和私有字段等。在Scala中,类还指定了一些模块边界。
许多语言都有类型,但没有类,许多语言都有类,但没有(静态)类型。
类型和类之间有几个可观察的差异。List [String]是一种类型,但不是类。在Scala中,List是一个类,但通常不是类型(实际上它是一种更高级的类型)。在C#中,List不是任何类型的类型,在Java中,它是一个“原始类型”。
Scala提供了结构类型。{def foo:Bar}表示具有可证明具有返回Bar的foo方法的任何对象,而与类无关。这是一种类型,但不是类。
类型可以使用类型参数进行抽象。当您编写def foo[T](x: T) = ...时,foo的内部T是一种类型。但是,T不是一个类。
Scala中的类型可以是虚拟的(即“抽象类型成员”),但是目前无法使用Scala使类成为虚拟类(尽管有一种繁琐的方法可以对虚拟类进行编码https://wiki.scala-lang.org/display/SIW/VirtualClassesDesign)。
现在,动态类型。动态类型是对象的属性,在执行某些操作之前运行时会自动检查。在基于类的面向对象动态类型语言中,类型和类之间存在强关联。在诸如Scala和Java等JVM语言中也会发生同样的情况,这些语言具有只能通过动态检查(例如反射和转换)进行的操作。在这些语言中,“类型擦除”或多或少意味着大多数对象的动态类型与其类相同。或多或少。例如,数组通常不会被擦除,以便运行时可以区分Array [Int]和Array [String]之间的区别。但请记住我的广义定义“动态类型是运行时自动检查的对象属性。”当您使用反射时,可以向任何对象发送任何消息。如果对象支持该消息,则一切都能正常工作。因此,即使它不是一个类,将所有可以像鸭子一样嘎嘎叫的对象称为动态类型也是有意义的。这就是Python和Ruby社区所谓的“鸭子类型”的本质。同样,根据我的广义定义,甚至“零值”在大多数语言中也是一种动态类型,因为运行时会自动检查数字以确保您不会除以零。有非常非常少的语言可以通过使零(或非零)成为静态类型来静态证明它。

最后,正如其他人所提到的,有一些类型(例如int)没有类作为实现细节,有一些特殊的类型(例如Null和Any)可以但没有类,还有一些类型(例如Nothing)甚至没有任何值,更别说类了。


根据您上面的解释,我突然想到一个问题。如果我假设由于运行时类型检查,动态类型语言(如Python)比静态类型语言(Scala、Java)慢,那么我的假设是否正确?因为动态类型语言必须在运行时执行广泛的类型检查。 - Archit

27

好的,我来翻译一下... James给出了一个不错的答案,所以我将尝试一个不同的方法,给出更加平易近人的观点。

广义上来说,类是可以被实例化的东西。单例对象(Scala)、特质(Scala)和接口(Scala)通常也被认为是类。这是有道理的,因为单例仍然会通过编译器生成的代码进行实例化,而接口可以作为子类的一部分进行实例化。

这就带我们到了第二点。在大多数面向对象语言中(虽然不包括基于原型的语言,如JavaScript),类是主要的设计单位。多态性和子类化都是以类为基础定义的。类还提供了命名空间和可见性控制。


类型是一种非常不同的存在,系统可以表达的每个可能值都将具有一个或多个类型,有时这些类型可以等同于类,例如:

(Int) => String // both the type and class are Function1[Int,String]
"hello world" // class and type are String    

同时,你也会发现Scala和Java之间存在一些有趣的不同点:

7 // both the class and type are Int in Scala
  // in Java there's no class and the type is Integer.TYPE

println("hello world") // the return type is Unit, of class Unit
                       // Java has void as a type, but no corresponding class

error("oops") // the type and class are both "Nothing"

还有一些非类类型的有趣类型。例如,this.type 总是指向 this 的唯一类型。它是单个实例独有的,甚至与同一类的其他实例不兼容。

还有抽象类型和类型参数。例如:

type A // 'A' is an undetermined abstract type
       // to be made concrete in a subclass

class Seq[T] { ... } // T is a type, but not a class
Seq是一个有趣的类,但不是一个类型。更准确地说,它是一个“类型构造器”,当提供必要的类型参数时,它将构造一个有效的类型。类型构造器的另一个术语是“高阶类型”,我个人不喜欢这个术语,因为“类型构造器”鼓励我考虑像任何其他形式的参数一样提供类型 - 这是一种在Scala中表现良好的心理模型。
“higher-kinded”正确地暗示了Seq具有“kind”,即* => *,此符号表示Seq将接受一个类型并产生一个类型(这类似于用于描述函数的柯里化符号)。相比之下,Map的kind是* => * => *,因为它需要两个类型参数。

“类型构造器”和“高阶类型”的区别在于它们的使用方式。在Java中,List不能作为一种类型(忽略原始类型,它们很糟糕),但是List可以作为一种类型构造器。而在Scala中,它可以两种方式都使用。 - James Iry
@James - 即使有这样的区分,当你开始查看编译器内部表示时,它变得更加错综复杂。特别是当你开始考虑擦除时。 - Kevin Wright
Java有void作为一种类型,但没有相应的类。Java有一种类型来表示void:java.lang.Void。它可以类似于“装箱类型”Integer、Double等进行使用。它在反射和并发API中被广泛使用,并且可以随意在类声明和主体中使用。 - Glen Best

3
一种类型本身可以很有用,即使没有任何实例。一个例子是所谓的“幽灵类型”。这里有一个Java的例子:http://michid.wordpress.com/2008/08/13/type-safe-builder-pattern-in-java/ 在这个例子中,我们有一个public static class Initializer<HA, HB>,其中HAHB取一些类型(由抽象类TRUEFALSE表示),而不需要实例化。
希望这能说明类型和类是不同的东西,类型本身可以很有用。

一个抽象基类不能作为幻影类型吗? - J D
基本上任何东西都可以作为幻影类型,它只是类型系统的一种“标签”。 - Landei

1

(仅适用于Java)我认为,类型是一组对象。如果对象o是集合X的成员,则对象o是类型X。如果集合X是集合Y的子集,则类型X子类型 of Y

对于每个类C(不是接口),都有一组从new C(...)创建的对象。有趣的是,我们很少关心这个集合。(但每个对象确实属于这样一个集合,这可能是有用的事实)

对于每个类C,都有一个类型t(C),通常称为“类型C”,它是可以从new S(...)创建的所有对象的集合,其中S是C或C的子类。

同样,对于每个接口I,都有一个类型t(I),“类型I”,它是可以从new S(...)创建的所有对象的集合,其中S实现了I。

显然,如果类S是类C的子类,则类型S是类型C的子类型。接口I同理。

有一个空类型,即空集。空类型是每种类型的子类型。

有一个包含所有对象的集合,即Object类型。它是每种类型的超类型。

到目前为止,这种形式主义相当无用。类型基本上与类或接口相同,子类型关系基本上是子类/子接口关系。这种平凡性是一件好事,语言易于理解!但是进入泛型时,会出现更复杂的类型和类型的并集和交集等操作。类型不再仅限于类和接口,子类型关系更加丰富和难以理解。


1
即使忽略泛型,它也比那更复杂。一个 int 显然有一种类型,但没有类或接口。你也可以轻松地讨论返回 void 类型的方法,但同样没有类。 - Kevin Wright
那么这几乎是一个循环定义。 - J D

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