Cho lớp Kotlin sau:
data class Test(val value: Int)
Làm cách nào để ghi đè Int
getter để nó trả về 0 nếu giá trị âm?
Nếu điều này là không thể, một số kỹ thuật để đạt được kết quả phù hợp là gì?
Cho lớp Kotlin sau:
data class Test(val value: Int)
Làm cách nào để ghi đè Int
getter để nó trả về 0 nếu giá trị âm?
Nếu điều này là không thể, một số kỹ thuật để đạt được kết quả phù hợp là gì?
Câu trả lời:
Sau khi dành gần một năm để viết Kotlin hàng ngày, tôi thấy rằng cố gắng ghi đè các lớp dữ liệu như thế này là một việc làm không tốt. Có 3 cách tiếp cận hợp lệ cho điều này và sau khi tôi trình bày chúng, tôi sẽ giải thích tại sao cách tiếp cận mà các câu trả lời khác đã đề xuất là không tốt.
Yêu cầu logic nghiệp vụ của bạn tạo ra data class
thay đổi giá trị thành 0 hoặc lớn hơn trước khi gọi hàm tạo có giá trị xấu. Đây có lẽ là cách tiếp cận tốt nhất cho hầu hết các trường hợp.
Không sử dụng a data class
. Sử dụng một class
IDE thông thường và để IDE của bạn tạo ra các equals
và hashCode
phương thức cho bạn (hoặc không, nếu bạn không cần chúng). Có, bạn sẽ phải tạo lại nó nếu bất kỳ thuộc tính nào bị thay đổi trên đối tượng, nhưng bạn có toàn quyền kiểm soát đối tượng.
class Test(value: Int) {
val value: Int = value
get() = if (field < 0) 0 else field
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Test) return false
return true
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
}
Tạo một thuộc tính an toàn bổ sung trên đối tượng thực hiện những gì bạn muốn thay vì có một giá trị riêng tư bị ghi đè hiệu quả.
data class Test(val value: Int) {
val safeValue: Int
get() = if (value < 0) 0 else value
}
Một cách tiếp cận tồi mà các câu trả lời khác đang đề xuất:
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
Vấn đề với cách tiếp cận này là các lớp dữ liệu không thực sự dành cho việc thay đổi dữ liệu như thế này. Chúng thực sự chỉ để giữ dữ liệu. Trọng các getter cho một lớp dữ liệu như thế này sẽ có nghĩa là Test(0)
và Test(-1)
sẽ không equal
lẫn nhau và sẽ có khác nhau hashCode
s, nhưng khi bạn gọi .value
, họ sẽ có kết quả tương tự. Điều này không nhất quán và mặc dù nó có thể hiệu quả với bạn, nhưng những người khác trong nhóm của bạn xem đây là một lớp dữ liệu, có thể vô tình sử dụng sai mà không nhận ra bạn đã thay đổi nó như thế nào / khiến nó không hoạt động như mong đợi (nghĩa là cách tiếp cận này sẽ không ' t hoạt động chính xác trong a Map
hoặc a Set
).
data class class(@JsonProperty("iss_position") private val position: Map<String, Double>) { val latitude = position["latitude"]; val longitude = position["longitude"] }
, và tôi coi nó khá tốt cho trường hợp của tôi, tbh. Bạn nghĩ gì về điều này? (có OFC các lĩnh vực khác và do đó tôi tin rằng nó làm cho không có ý nghĩa với tôi để tái cấu trúc json lồng nhau trong mã của tôi)
parsing a string into an int
bạn, rõ ràng bạn đang cho phép logic nghiệp vụ của việc phân tích cú pháp và xử lý lỗi các Chuỗi không phải số vào lớp mô hình của bạn ...
List
và MutableList
không có lý do.
Bạn có thể thử một cái gì đó như sau:
data class Test(private val _value: Int) {
val value = _value
get(): Int {
return if (field < 0) 0 else field
}
}
assert(1 == Test(1).value)
assert(0 == Test(0).value)
assert(0 == Test(-1).value)
assert(1 == Test(1)._value) // Fail because _value is private
assert(0 == Test(0)._value) // Fail because _value is private
assert(0 == Test(-1)._value) // Fail because _value is private
Trong một lớp dữ liệu, bạn phải đánh dấu các tham số của hàm tạo chính bằng val
hoặc var
.
Tôi gán giá trị của _value
để value
để sử dụng tên mong muốn cho tài sản.
Tôi đã xác định một trình truy cập tùy chỉnh cho thuộc tính với logic bạn đã mô tả.
Câu trả lời phụ thuộc vào những khả năng bạn thực sự sử dụng data
cung cấp. @EPadron đã đề cập đến một thủ thuật tiện lợi (phiên bản cải tiến):
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
Điều đó sẽ hoạt động như mong đợi, ei nó có một lĩnh vực, một tầng, đúng equals
, hashcode
và component1
. Cái bắt là vậy toString
và copy
thật kỳ lạ:
println(Test(1)) // prints: Test(_value=1)
Test(1).copy(_value = 5) // <- weird naming
Để khắc phục sự cố, toString
bạn có thể xác định lại nó bằng tay. Tôi không biết cách nào để sửa việc đặt tên tham số nhưng hoàn toàn không sử dụng data
.
Tôi biết đây là một câu hỏi cũ nhưng có vẻ như không ai đề cập đến khả năng đặt giá trị riêng tư và viết getter tùy chỉnh như thế này:
data class Test(private val value: Int) {
fun getValue(): Int = if (value < 0) 0 else value
}
Điều này phải hoàn toàn hợp lệ vì Kotlin sẽ không tạo getter mặc định cho trường riêng tư.
Nhưng nếu không, tôi chắc chắn đồng ý với spierce7 rằng các lớp dữ liệu là để lưu giữ dữ liệu và bạn nên tránh mã hóa logic "nghiệp vụ" ở đó.
val value = test.getValue()
và không giống như các getters khác val value = test.value
.getValue()
Tôi đã xem câu trả lời của bạn, tôi đồng ý rằng các lớp dữ liệu chỉ dùng để lưu trữ dữ liệu, nhưng đôi khi chúng ta cần tạo ra một cái gì đó từ chúng.
Đây là những gì tôi đang làm với lớp dữ liệu của mình, tôi đã thay đổi một số thuộc tính từ val thành var và ghi lại chúng trong hàm tạo.
như vậy:
data class Recording(
val id: Int = 0,
val createdAt: Date = Date(),
val path: String,
val deleted: Boolean = false,
var fileName: String = "",
val duration: Int = 0,
var format: String = " "
) {
init {
if (fileName.isEmpty())
fileName = path.substring(path.lastIndexOf('\\'))
if (format.isEmpty())
format = path.substring(path.lastIndexOf('.'))
}
fun asEntity(): rc {
return rc(id, createdAt, path, deleted, fileName, duration, format)
}
}
fun Recording(...): Recording { ... }
). Ngoài ra, có thể một lớp dữ liệu không phải là thứ bạn muốn, vì với các lớp không phải dữ liệu, bạn có thể tách các thuộc tính của mình khỏi các tham số phương thức khởi tạo. Tốt hơn là bạn nên rõ ràng với ý định thay đổi trong định nghĩa lớp của bạn. Nếu các trường đó cũng có thể thay đổi được, thì một lớp dữ liệu vẫn ổn, nhưng hầu như tất cả các lớp dữ liệu của tôi là bất biến.
Đây dường như là một trong những nhược điểm khó chịu của Kotlin.
Có vẻ như giải pháp hợp lý duy nhất, hoàn toàn giữ được tính tương thích ngược của lớp là chuyển đổi nó thành một lớp thông thường (không phải lớp "dữ liệu") và triển khai bằng tay (với sự hỗ trợ của IDE) các phương thức: hashCode ( ), bằng (), toString (), copy () và componentN ()
class Data3(i: Int)
{
var i: Int = i
override fun equals(other: Any?): Boolean
{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Data3
if (i != other.i) return false
return true
}
override fun hashCode(): Int
{
return i
}
override fun toString(): String
{
return "Data3(i=$i)"
}
fun component1():Int = i
fun copy(i: Int = this.i): Data3
{
return Data3(i)
}
}
Tôi nhận thấy những điều sau đây là cách tiếp cận tốt nhất để đạt được những gì bạn cần mà không bị phá vỡ equals
và hashCode
:
data class TestData(private var _value: Int) {
init {
_value = if (_value < 0) 0 else _value
}
val value: Int
get() = _value
}
// Test value
assert(1 == TestData(1).value)
assert(0 == TestData(-1).value)
assert(0 == TestData(0).value)
// Test copy()
assert(0 == TestData(-1).copy().value)
assert(0 == TestData(1).copy(-1).value)
assert(1 == TestData(-1).copy(1).value)
// Test toString()
assert("TestData(_value=1)" == TestData(1).toString())
assert("TestData(_value=0)" == TestData(-1).toString())
assert("TestData(_value=0)" == TestData(0).toString())
assert(TestData(0).toString() == TestData(-1).toString())
// Test equals
assert(TestData(0) == TestData(-1))
assert(TestData(0) == TestData(-1).copy())
assert(TestData(0) == TestData(1).copy(-1))
assert(TestData(1) == TestData(-1).copy(1))
// Test hashCode()
assert(TestData(0).hashCode() == TestData(-1).hashCode())
assert(TestData(1).hashCode() != TestData(-1).hashCode())
Tuy nhiên,
Đầu tiên, lưu ý rằng _value
là var
, khôngval
, nhưng mặt khác, vì nó tin và các lớp dữ liệu không thể được thừa hưởng từ, nó là khá dễ dàng để chắc chắn rằng nó không được sửa đổi trong lớp.
Thứ hai, toString()
tạo ra một kết quả hơi khác so với nó nếu _value
được đặt tên value
, nhưng nó nhất quán và TestData(0).toString() == TestData(-1).toString()
.
_value
đang được sửa đổi trong khối init equals
và hashCode
không bị hỏng.