Kotlin数据类字段声明最佳实践:参数选var/val?是否加private?
Great question—this is a common point of confusion with Kotlin data classes, especially when you need to add custom logic like your non-negative amount check. Let's break down the best practices step by step:
1. Always prefer val for data class parameters (when possible)
Data classes are built for value objects—things like Money, DateRange, or ProductId where the instance's meaning is tied directly to its data. Using val makes these instances immutable, which is a huge win for:
- Thread safety: No race conditions from concurrent modifications.
- Predictability: Once created, the instance's state never changes, so auto-generated methods like
equals(),hashCode(), andtoString()will always behave consistently. - Safer code: You don't have to worry about accidental state changes breaking logic (like a
Moneyinstance suddenly having a negative value elsewhere in your app).
In your case, currency is already a val (perfect!), and your amount validation logic doesn't require mutability—we'll fix that in a second.
2. When to use var in a data class?
Only reach for var if you absolutely need the property to be changeable after the instance is created. But be cautious: mutable data classes can violate value semantics. For example, if you store a mutable Money instance in a HashMap, modifying its amount later would break the map's ability to find it (since hashCode() would change).
If you must have mutability (e.g., a stateful entity that needs updates), var is allowed—but ask yourself if a regular class (not a data class) might be a better fit, since data classes are optimized for immutable values.
3. When to add private to data class parameters?
Add private when you want to hide the parameter from the class's public API. By default, any val/var in a data class's primary constructor becomes a public property that external code can access directly.
Your use of _amount as a backing field for validation is exactly the right scenario for private—you don't want external code to bypass your non-negative check and access the raw, potentially negative value.
4. Optimizing your Money class for best practices
Your current code uses var for both _amount and amount, but you don't actually need mutability here. You can rewrite it with all vals to follow value object best practices:
data class Money(private val _amount: Int, private val currency: String) { val amount: Int get() = if (_amount < 0) 0 else _amount override fun toString(): String { return "Money(amount=$amount, currency='$currency')" } }
This works because:
_amountis aprivate val—set once in the constructor, never modified, and hidden from external code.- The public
amountproperty is avalwith a custom getter that applies your non-negative logic. Since it doesn't need its own backing field, there's no reason to usevar. - You still get all data class perks: auto-generated
equals()/hashCode()based on_amountandcurrency, a workingcopy()method, etc.
Quick Best Practice Cheat Sheet
- Default to
val: Enforce immutability for data classes whenever possible. - Use
varsparingly: Only for mutable state, and consider a regular class instead. - Add
privatefor internal details: Hide backing fields or implementation-specific parameters from the public API. - Custom getters don't need
var: Usevalwith a custom getter if you're transforming a value, not modifying it.
内容的提问来源于stack exchange,提问作者VolodymyrH

