class 什么时候是数据 class?

When is a class a data class?

我知道 classes 是关于什么的,但为了更好地理解,我需要一个用例。最近我发现了 data classes 的结构。我理解正常 classes 背后的想法,但我无法想象数据 classes.

的真实用例

我什么时候应该使用数据 class 什么时候使用“正常”class?据我所知,所有 classes 都保留数据。

你能提供一个很好的例子来区分数据 class 和非数据 class 吗?

一个data class用来存储数据。它比普通的 class 更轻,可以与 key/value 的数组(字典、散列等)进行比较,但表示为具有固定属性的对象。根据文档,在科特林中,将这些属性添加到 class:

equals()/hashCode() pair

toString() of the form "User(name=John, age=42)"

componentN() functions corresponding to the properties in their order of declaration.

copy() function

它在 class 继承期间也有不同的行为:

If there are explicit implementations of equals(), hashCode(), or toString() in the data class body or final implementations in a superclass, then these functions are not generated, and the existing implementations are used.

If a supertype has componentN() functions that are open and return compatible types, the corresponding functions are generated for the data class and override those of the supertype. If the functions of the supertype cannot be overridden due to incompatible signatures or due to their being final, an error is reported.

Providing explicit implementations for the componentN() and copy() functions is not allowed.

所以在 kotlin 中,如果你想描述一个对象(数据)那么你可以使用数据class,但是如果你正在创建一个复杂的应用程序并且你的 class 需要在构造函数中有特殊行为,具有继承或抽象,那么你应该使用正常的 class.


我不懂Kotlin,但是在Python中,一个dataclass可以看成一个结构化的dict。当你想用字典来存储一个总是具有相同属性的对象时,你不应该把它放在 dict 中,而应该使用 Dataclass.

普通 class 的优点是您不需要声明 __init__ 方法,因为它是“自动”(继承的)。

示例:

这是正常的class

class Apple:
    def __init__(size:int, color:str, sweet:bool=True):
        self.size = size
        self.color = color
        self.sweet = sweet

dataclass

相同 class
from dataclasses import dataclass

@dataclass
class Apple:
    size: int
    color: str
    sweet: bool = True

那么与dict相比的优势就是你确定它有什么属性。它还可以包含方法。

与普通 class 相比的优势在于它更易于声明并使代码更轻便。我们可以看到属性关键字(例如size)在正常的class中重复了3次,但在dataclass.

中只出现了一次

普通 class 的优点还在于您可以个性化 __init__ 方法,(在数据 class 中也是如此,但我认为您失去了它的主要优势)示例:

# You need only 2 variable to initialize your class
class Apple:
    def __init__(size:int, color:str):
        self.size = size
        self.color = color
        # But you get much more info from those two.
        self.sweet = True if color == 'red' else False
        self.weight = self.__compute_weight()
        self.price  = self.weight * PRICE_PER_GRAM

   def __compute_weight(self):
        # ...
        return (self.size**2)*10 # That's a random example

抽象地说,数据class是一种纯粹的惰性信息记录,在复制或传递时不需要任何特殊处理,它仅代表比其字段中包含的内容;它没有自己的身份。一个典型的例子是 3D 中的一个点 space:

data class Point3D(
    val x: Double,
    val y: Double,
    val z: Double
)

只要值有效,数据实例 class 就可以与其字段完全互换,并且可以随意分开或重新实现。通常封装甚至没有什么用处:数据 class 的用户可以直接访问实例的字段。当数据 class 在您的代码中声明为 described in the documentation 时,Kotlin 语言提供了许多便利功能。这些在例如使用数据 classes 构建更复杂的数据结构时很有用:例如,您可以让哈希图将值分配给 space 中的特定点,然后能够使用newly-constructed Point3D.

val map = HashMap<Point3D, String>()
map.set(Point3D(3, 4, 5), "point of interest")
println(map.get(Point3D(3, 4, 5))) // prints "point of interest"

对于 不是 数据 class 的 class 的示例,取 FileReader。在下面,这个 class 可能在私有字段中保留某种文件句柄,您可以假设它是一个整数(因为它实际上至少在某些平台上是这样)。但是你不能期望将这个整数存储在数据库中,让另一个进程从数据库中读取相同的整数,从中重建一个 FileReader 并期望它工作。如果在给定平台上甚至可能的话,在进程之间传递文件句柄需要更多的仪式。 属性 使 FileReader 不是数据 class。 non-data classes 的许多示例都属于这种类型:任何 class 的实例代表瞬时的本地资源,如网络连接、文件中的位置或 运行进程,不能是数据 class。同样,任何 class 不同的实例应该 而不是 被认为是相等的,即使它们包含相同的信息也不是数据 class。

从评论来看,听起来您的问题实际上是关于为什么 non-data classes 存在于 Kotlin 中以及为什么您会选择不制作数据 class。这里有一些原因。

数据 classes 比常规 class:

更严格
  • 他们有一个主构造函数,主构造函数的每个参数是一个属性。
  • 它们不能有一个空的构造函数。
  • 它们不能 open 所以它们不能被子class编辑。

还有其他原因:

  • 有时您不希望 class 具有 copy 功能。如果 class 持有一些复制成本高昂的重状态,也许它不应该通过提供复制函数来宣传它应该被复制。
  • 有时您想在 Set 或 Map 中使用 class 的一个实例,而不是仅仅因为它们的属性具有相同的值而将两个不同的实例视为等效。

数据 classes 的特性特别适用于简单的数据持有者,因此缺点通常是您想要避免的。