In the last article, we looked at the various classes in Kotlin, starting with the Kotlin class, which has properties and methods. The class attributes in Kotlin are very different from the Java class member variables. At the same time, the class attributes also have some things that are difficult to understand, such as: the declaration form of attributes, behind the field, behind the attribute, and so on. In this article, we’ll take a closer look at these things.

1. Foreplay (Kotlin’s common attribute)

In Kotlin, declaring an attribute involves two keywords, var and val.

  • Var declares a mutable property
  • Val declares a read-only property

Declare an attribute with the keyword var:

class Person {
    var name:String = "Paul"// Declare a mutable property, default is Paul
}
Copy the code

You can change the value of an attribute declared by var, as follows:

fun main(args: Array<String>) {
   var person = Person()
   // Prints the value of name for the first time
   println("name:${person.name}")
   // reassign name
   person.name = "Jake"
   // Prints a new value for name
   println("name:${person.name}")}Copy the code

The print result is as follows:

name:Paul
name:Jake
Copy the code

If you change the name property to val, which is declared read-only, what about the value that will be changed?

class Person {
    val name:String = "Paul"
}
Copy the code

Val cannot be reassigned; val cannot be reassigned; val cannot be reassigned.

These are Kotlin’s two ways of declaring properties. Isn’t that easy? One line of code. This line of code contains a lot of things that are just not shown. Let’s look at the full declaration of an attribute:

// Variable attributes
var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]
// Read-only attribute
val <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
Copy the code

A lot more in an instant, initializers, getters, and setters are optional. Attribute types can also be omitted if they can be inferred from the initializer (or from its getter return value, as shown below). The property declaration we saw above, which omits the getter and setter, is provided by default

 var name:String = "Paul" // Use default getters and setters
Copy the code

It initializes a String, so it can be inferred from initialization that the property is of type String, so the property type can be omitted to look like this:

 var name = "Paul" // Can infer the property type, using default getters and setters
Copy the code

1. 2 getter & setter

In Kotlin, getters and setters are part of property declarations. Declaring a property provides getters and setters by default, but you can customize them if you want. To customize, we need to understand what getters and setters are.

In Java, an external method cannot access a class’s private variable. It must provide a setXXX method and a getXXX method to access it. For example, the Java class Person provides getName() and setName() methods for the external method’s private variable name:

public class Person{
    private String name;

    public String getName(a) {
        return name;
    }

    public void setName(String name) {
        this.name = name; }}Copy the code

Getters and setters in Kotlin act like getXX and setXX in Java and are called accessors.

Getters are called read accessors and setters are called write accessors. Variables declared by val have only the getter, and variables declared by var have both.

Q: What is the essence of accessing a property in Kotlin?

A: Read A property, pass. It is essentially a getter accessor that executes a property, for example:

class Person {
    var name:String = "Paul"
}

/ / test
fun main(args: Array<String>) {
   var person = Person()
   // Read the name attribute
   val name = person.name
   println("Print result:$name")}Copy the code

The printed result must be:

Print result :PaulCopy the code

Then we modify the return value of the getter as follows:

class Person {
    var name:String = "Paul"
        get() = "i am getter,name is Jake"
}
/ / test
fun main(args: Array<String>) {
   var person = Person()
   // Read the name attribute
   val name = person.name
   println("Print result:$name")}Copy the code

The result is as follows:

I am getter,name is JakeCopy the code

Thus, the essence of reading a property is to perform a getter, much like Java, which reads a private variable of a Java class through the get method it provides.

Similarly, in Kotlin, the essence of writing a property is to execute the property’s write accessor setter. Again, let’s change the setter:

class Person {
    var name:String = "Paul"
        set(value) {
           println("Execute write accessor with:$value")}}/ / test
fun main(args: Array<String>) {
   var person = Person()
   // Write the name attribute
   person.name = "hi,this is new value"
   println("Print result:${person.name}")}Copy the code

The execution result is as follows:

Execute write accessor with: hi,this is new value Print result :PaulCopy the code

You can see that when you assign a value to a property, you are actually executing the write accessor setter, but why is the result the default value Paul? Because we overwrote the setter, but we didn’t assign the property, which is the default, of course.

So what does the default setter for a property look like? Smart you may immediately think of, this is not simple, with Java setXXX method almost (proud jiao Face). Here it is, as follows:

class Person {
    // Wrong demo
    var name = ""
        set(value) {
            this.name = value
        }
}
Copy the code

I’m sorry, but I can’t find an error when I run StackOverFlow. Convert the Person class to a Java class:

public final class Person {
   @NotNull
   private String name = "Paul";

   @NotNull
   public final String getName(a) {
      return this.name;
   }

   public final void setName(@NotNull String value) {
      this.setName(value); }}Copy the code

See, the method loops, setName is called again in setName, and it loops until it runs out of memory and crashes. The Kotlin code also assigns values to the property in the setter, causing it to execute the setter in an infinite loop until it runs out of memory and crashes. So how do we solve this? This introduces Kotlin to an important thing behind the scenes field

2. Background fields

What is the behind-the-scenes field? There is no exact definition. In Kotlin, if at least one accessor of a property uses the default implementation, Kotlin automatically provides a background field, represented by the keyword field, which is used primarily in custom getters and setters and can only be accessed in getters and setters.

Going back to the custom setter example above, how do I assign a value to a property? The answer is to assign the field behind it as follows:

class Person {
    // Wrong demo
    var name = ""
        set(value) {
            field = value
        }
}
Copy the code

The getter, too, returns the field behind the scenes:

A / / examples
class Person {
    var name:String = ""
        get() = field 
        set(value) {
            field = value
        }
}
2 / / examples
class Person {
    var name:String = ""
}

Copy the code

The above two property declarations are equivalent. The getters and setters in Example 1 are the default getters and setters. The hidden field field refers to the current property, which is not a keyword, but has a special meaning in the special scopes of the setter and getter, just like this in a class, which stands for the current class.

We can do a lot of things in getters and setters with behind-the-scenes fields, which are generally used to make a property have different values under different conditions, such as the following scenario:

Scenario: We can return different names based on gender

class Person(var gender:Gender){
    var name:String = ""
        set(value) {
            field = when(gender){
                Gender.MALE -> "Jake.$value"
                Gender.FEMALE -> "Rose.$value"}}}enum class Gender{
    MALE,
    FEMALE
}

fun main(args: Array<String>) {
    / / gender MALE
    var person = Person(Gender.MALE)
    person.name="Love"
    println("Print result:${person.name}")
    // Gender: FEMALE
    var person2 = Person(Gender.FEMALE)
    person2.name="Love"
    println("Print result:${person2.name}")}Copy the code

Print result:

Jakes.Love Rose.LoveCopy the code

As above, we implement the name property to behave differently with different values of gender. The behind-the-scenes fields are mostly used in similar scenarios.

Will Kotlin have a background field for all properties? Of course not, one of the following conditions needs to be met:

  • Properties that use default getters/setters must have hidden fields. For the var property, as long as one of the getters/setters uses the default implementation, the background field is generated;

  • The field property is used in the custom getter/setter

Here’s an example without a field behind the scenes:

class NoField {
    var size = 0
    //isEmpty has no hidden fields
    var isEmpty
        get() = size == 0
        set(value) {
            size *= 2}}Copy the code

As above, isEmpty has no field behind it. It overwrites the setter and getter and doesn’t use field in it. This might be a little confusing, but let’s convert it to Java code and you’ll see.

public final class NoField {
   private int size;

   public final int getSize(a) {
      return this.size;
   }

   public final void setSize(int var1) {
      this.size = var1;
   }

   public final boolean isEmpty(a) {
      return this.size == 0;
   }

   public final void setEmpty(boolean value) {
      this.size *= 2; }}Copy the code

See, translated into Java code, there’s only one size variable, and isEmpty translates into isEmpty() and setEmpty(). The return value depends on the value of size.

There must be a corresponding Java variable for converting properties into Java code that has fields behind it

3. Behind the scenes attributes

Now that you understand the behind-the-scenes fields, look at the behind-the-scenes properties

Sometimes we want a property that is read-only externally and readable and writable internally to be a behind-the-scenes property. Such as:

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap() // Type parameters have been inferred
        }
        return_table ? :throw AssertionError("Set to null by another thread")}Copy the code

The _table attribute is declared private, so external access is not accessible, internal access is accessible, external access is through the table attribute, and the value of the table attribute depends on _TABLE, where _TABLE is the background attribute.

The size field in the Collection is read only. The value of size changes according to the transformation of the elements in the Collection. This is done inside the Collection.

Kotlin AbstractList SubList

private class SubList<out E>(private val list: AbstractList<E>, private val fromIndex: Int, toIndex: Int) : AbstractList<E>(), RandomAccess {
        // Behind the scenes attributes
        private var _size: Int = 0

        init {
            checkRangeIndexes(fromIndex, toIndex, list.size)
            this._size = toIndex - fromIndex
        }

        override fun get(index: Int): E {
            checkElementIndex(index, _size)

            return list[fromIndex + index]
        }

        override val size: Int get() = _size
    }
Copy the code

Keys and values in the AbstractMap source code also use behind the scenes attributes

 /** * Returns a read-only [Set] of all keys in this map. * * Accessing this property first time creates a keys view from  [entries]. * All subsequent accesses just return the created instance. */
    override val keys: Set<K>
        get() {
            if (_keys == null) {
                _keys = object : AbstractSet<K>() {
                    override operator fun contains(element: K): Boolean = containsKey(element)

                    override operator fun iterator(a): Iterator<K> {
                        val entryIterator = entries.iterator()
                        return object : Iterator<K> {
                            override fun hasNext(a): Boolean = entryIterator.hasNext()
                            override fun next(a): K = entryIterator.next().key
                        }
                    }

                    override val size: Int get() = this@AbstractMap.size
                }
            }
            return _keys!!
        }

    @kotlin.jvm.Volatile
    private var _keys: Set<K>? = null
Copy the code

If you are interested, go to other source code.

4. Summary of this paper

In this article, we cover some of the Kotlin attributes. There are a few points to note:

A property is accessed through its accessors, getters and setters. You can change the visibility of getters and setters. For example, if you add private to the setter, the setter is private.

var setterVisibility: String = "abc"
    private set This setter is private and has a default implementation
Copy the code

2, Kotlin automatically provides the background field is to meet the criteria (meet one of the criteria) :

  • Properties that use default getters/setters must have hidden fields. For the var property, as long as one of the getters/setters uses the default implementation, the background field is generated;

  • The field property is used in the custom getter/setter

3. Scenarios with behind-the-scenes attributes: externally, they are read only, internally, they are read and write.

That’s all for this article. Welcome to the discussion.

More Android dry goods articles, pay attention to the public number [Android technology grocery store]