193 lines
7.7 KiB
Plaintext
193 lines
7.7 KiB
Plaintext
h1. Field accessors in Java
|
|
|
|
In *Java* world, we are so accustomed to writing _getters_ and _setters_ for our _fields_, that the advice by [Effective Java |http://java.sun.com/docs/books/effective/] Item 14: *In public classes, use accessor methods, not public fields* sort of goes without saying. All major IDEs help us here: they generate getters and setters, so that it is not that hard to produce a class like this:
|
|
|
|
{code}
|
|
// Java
|
|
public class Address {
|
|
|
|
private String name;
|
|
private String street;
|
|
private String city;
|
|
private String state;
|
|
private String zip;
|
|
|
|
public String getName() {
|
|
return name;
|
|
}
|
|
|
|
public void setName(String name) {
|
|
this.name = name;
|
|
}
|
|
|
|
public String getStreet() {
|
|
return street;
|
|
}
|
|
|
|
public void setStreet(String street) {
|
|
this.street = street;
|
|
}
|
|
|
|
public String getCity() {
|
|
return city;
|
|
}
|
|
|
|
public void setCity(String city) {
|
|
this.city = city;
|
|
}
|
|
|
|
public String getState() {
|
|
return state;
|
|
}
|
|
|
|
public void setState(String state) {
|
|
this.state = state;
|
|
}
|
|
|
|
public String getZip() {
|
|
return zip;
|
|
}
|
|
|
|
public void setZip(String zip) {
|
|
this.zip = zip;
|
|
}
|
|
}
|
|
{code}
|
|
Most of the lines in this class are pure boilerplate code.
|
|
|
|
h1. Getting rid of field/get/set triples
|
|
|
|
In [Kotlin], there's no way to declare a _field_. All you have is _properties_. Read/write properties are declared with the {{var}} keyword, and read-only ones -- with {{val}} keyword. Thus, the class above can be rewritten as follows:
|
|
{jet}
|
|
public class Address() { // parentheses denote a _primary constructor_
|
|
public var name : String = ...
|
|
public var street String = ...
|
|
public var city : String = ...
|
|
public var state : String? = ...
|
|
public var zip : String = ...
|
|
}
|
|
{jet}Here we have five _mutable_ properties, each of which has a _backing field_ that stores the value, and two accessors: getter and setter. Thus, the byte-code generated from this class will be almost equivalent to the one for the Java class above. The only difference will be property initializers, see [Null-safety].
|
|
|
|
(For even better option look [here|Classes and Inheritance#bean-class]).
|
|
|
|
To use a property, one simply refers to it by name, as if it were a field in Java:
|
|
{jet}
|
|
fun copyAddress(address : Address) : Address {
|
|
val result = Address() // there's no 'new' keyword in Kotlin
|
|
result.name = address.name // accessors are called
|
|
result.street = address.street
|
|
// ...
|
|
return result
|
|
}
|
|
{jet}
|
|
|
|
|
|
h2. Declaring properties and accessors
|
|
|
|
The full syntax for a _mutable_ property declaration is as follows:
|
|
{jet}
|
|
var <propertyName> : <PropertyType> [= <property_initializer>]
|
|
<getter>
|
|
<setter>
|
|
{jet}
|
|
The initializer, getter and setter are optional. Property type is optional if it can be inferred from the initializer or from the base class member being [overridden |#Overriding properties].
|
|
|
|
Examples:
|
|
{jet}
|
|
var <error:>allByDefault : Int? // error: explicit initializer required, default getter and setter implied
|
|
var initialized = 1 // has type Int, default getter and setter
|
|
var setterVisibility : String = "abc" // Initializer required, not a nullable type
|
|
private set // the setter is private and has the default implementation
|
|
{jet}
|
|
Note that types are not inferred for properties exposed as parts of the public API, i.e. *public* and *protected*, because changing the initializer may cause an unintentional change in the public API then. For example
|
|
{jet}
|
|
public val <error:>example = 1 // A public property must have a type specified explicitly
|
|
{jet}
|
|
|
|
The full syntax of an _immutable_ property declaration differs from a _mutable_ one in two ways: it starts with {{val}} instead of {{var}} and does not allow a setter:
|
|
{jet}
|
|
val simple : Int? // has type Int, default getter, must be initialized in constructor
|
|
val inferredType = 1 // has type Int and a default getter
|
|
{jet}
|
|
|
|
We can write custom accessors, very much like ordinary functions, right inside a property declaration. Here's an example of a custom getter:
|
|
{jet}
|
|
val isEmpty : Boolean
|
|
get() = this.size == 0
|
|
{jet}
|
|
Since this property is purely derived from others, the compiler will not generate a _backing field_ for it.
|
|
|
|
A custom setter looks like this:
|
|
{jet}
|
|
var stringRepresentation : String
|
|
get() = this.toString()
|
|
set(value) {
|
|
setDataFromString(value) // parses the string and assigns values to other properties
|
|
}
|
|
{jet}
|
|
|
|
h2. Backing fields
|
|
|
|
As we mentioned above, some properties have _backing fields_, i.e. from the client's point of view, a property is a pair of accessors (or just one getter), but physically the accessors may read and write data from/to a real _field_. One can not _declare_ a field explicitly in [Kotlin], the compiler figures it out for us.
|
|
|
|
In the simple cases, when we do not provide custom accessor implementations, it is obvious that a property must have a backing field, otherwise what should the default accessors do in the following case?
|
|
{jet}
|
|
var counter : Int = 0
|
|
{jet}
|
|
|
|
But when there is a custom accessor, it _may_ or _may not_ rely on a backing field.
|
|
To access a backing field of a property {{x}}, one says {{$x}} (the dollar sign cannot be used as a part of an identifier in [Kotlin]):
|
|
{jet}
|
|
var counter = 0 // the initializer value is written directly to the backing field
|
|
set(value) {
|
|
if (value >= 0)
|
|
$counter = value
|
|
}
|
|
{jet}
|
|
The {{$counter}} field can be read or written inside the accessors of {{counter}} and can only be assigned to inside constructors. In other places, there's no access to {{$counter}}.
|
|
|
|
The compiler looks at the accessors' bodies, and if they use the backing field (or the accessor implementation is left by default), a backing field is generated, otherwise it is not.
|
|
|
|
For example, in the following case there will be _no backing field_:
|
|
{jet}
|
|
val isEmpty : Boolean
|
|
get() = this.size > 0
|
|
{jet}
|
|
The backing field is not needed because the only accessor does not refer to it.
|
|
|
|
h3. What if I want to do ... ?
|
|
|
|
If you want to do something that does not fit into this "implicit backing field" scheme, you can always fall back to having a "backing property":
|
|
{jet}
|
|
private var _table : Map<String, Int>? = null
|
|
public val table : Map<String, Int>
|
|
get() {
|
|
if (_table == null)
|
|
_table = HashMap() // Type parameters are inferred
|
|
return _table ?: throw AssertionError("Set to null by another thread")
|
|
}
|
|
{jet}
|
|
In all respects, this is just the same as in Java since access to private properties with default getters and setters is optimized so that no function call overhead is introduced.
|
|
|
|
h2. Overriding properties
|
|
|
|
See [Overriding properties|Classes and Inheritance#Overridable properties and accessors].
|
|
|
|
h1. Best practices related to this feature
|
|
|
|
[J. Bloch. Effective Java Second Edition |http://java.sun.com/docs/books/effective/]
|
|
*Item 14*: In public classes, use accessor methods, not public fields
|
|
|
|
See also: [JavaBeans |http://www.oracle.com/technetwork/java/javase/tech/index-jsp-138795.html]
|
|
|
|
h1. Similar features in other languages
|
|
|
|
Java IDEs generate accessors [automatically|http://www.jetbrains.com/idea/features/code_generation.html#link1].
|
|
|
|
For *Java*, there's [Project Lombok |http://projectlombok.org/]: the syntax for properties is based on Java annotations.
|
|
|
|
In [*C#*|http://msdn.microsoft.com/en-us/library/x9fsa0sw.aspx], [*Groovy* Beans |http://groovy.codehaus.org/Groovy+Beans] and [*Gosu*|http://gosu-lang.org/doc/wwhelp/wwhimpl/api.htm?&context=gosu&src=classes&topic=Properties] one still writes getters and setters alongside their backing fields that are declared explicitly, although the access looks like property access. [*Scala*|http://www.scala-lang.org/node/29] does not distinguish between a field and a property, but to customize a setter one needs to write a separate function named by convention.
|
|
|
|
h1. What's next
|
|
|
|
* [Basic types] |