Kotlin Data Class - mariamaged/Java-Android-Kotlin GitHub Wiki

Kotlin - Data Class

One of the reasons why Kotlin has taken off in popularity - particularly for Android app development - is that Java can be tedious to write.

  • One way that developers have tried to reduce that tedium is through annotation processors that generate Java code for you.
  • A class example of that is Google's AutoValue.
    • AutoValue is for immutable objects, ones whose fields have getters but no setters.
    • In particular, AutoValue can code-generate things like an equals() methods that takes all of the fields into account.

How You Declare It

  • All you do is add the data keyword to the class declaration.
  • The class needs to have 1+ parameters in its constructor.
    • Those parameters need to be declared as val or var.
    • And typically you will use val.
  • That's pretty much it.
    • You are welcome to have whatever functions you need on the class, but nothing else is required.
data class Animal(val species: String, val ageInYeards: Float)

What You Gain?

Standard Java Methods

  • Kotlin will automatically supply implementation of:
    • equals(), which compares each of the properties to see if they are equal.
    • hashCode(), which generates an Int to be used in place like HashSet and HashMap.
    • toString(), mostly for debugging purposes, showing the values of each of the properties.
  • For Kotlin/JVM, these methods are the standard Java Object methods that ideally get overriden on most classes...yet you do not, because we skip them if we think that we do not need them.
     
  • This is a subset of the Java equivalent of the Animal data class shown above:
public final class Animal {
	@NotNull 
	private final String species;
	private final float ageInYears;

	@NotNull
	public final String getSpecies() {
		return this.species;
	}

	public final float getAgeInYears() {
		return this.ageInYears;
	}

	public Animal(@NotNull String species, float ageInYears) {
		this.species = species;
		this.ageInYears = ageInYears;
	}

	public String toString() {
		return "Animal(species=" + this.species + ", ageInYears=" + this.ageInyears +")";
	}

	public int hashCode() {
		return (this.species != null ? this.species.hashCode() : 0)* 31 + Float.floatToIntBits(this.ageInYears);
	}

	public boolean equals(Object var1) {
		if(this != var1) {
			if(var1 instanceof Animal) {
				Animal var2 = (Animal) var1;
				if(this.species.equals(var2.species) && Float.compare(this.ageInYears), var2.ageInYears) == 0) return true;
		}
		return false;
		}
		else return true;
	}
}
  • Note 1: If you implement these methods yourself in your data class, Kotlin will assume that you know what you are doing.
  • Note 2: If your data class extends from other class, Kotlin will skip any of those functions that the class implements and marks as final.

copy()

Kotlin also generates a copy() function.

  • As the name suggests, this creates a copy of an instance of your data class.
  • However, what the function really does is:
    • Accept all of the properties as function parameters.
    • Defaulted to the current values from the instance.
  • This allows you to selectively override values in the copy, which is a great place to use named parameters.
  • So, out second Animal copied the species but has a different ageInYears, courtesy of the value we provided to copy().
data class Animal(val species: String, val ageInYears: Float)

val critter = Animal("frog", 3.14F)
val youngerCriter = critter.copy(ageInYears = 0.1F)

println(youngerCritter) // Prints Animal(species=frog, ageInYears=0.1)

What You Lose

  • You cannot subclasses of data classes.
  • A data class cannot be marked with open to allow it to be extended.
     
  • If subclasses were allowed, it is possible that subclasses would change the class definitions in ways that might make the code-generated equals() and other methods be invalid.
     
  • A side effect of the no-subclasses limitation is that data classes cannot be abstract.

Data Classes With Other Properties

  • You are welcome to have other properties in your data classes, beyond those in the constructor.
  • However, code generated functions, like equals() and copy() will ignore these properties.
  • Those functions only incorporate the properties defined in the primary constructor.
  • Sometimes,
    • This can be a feature: you might want some properties to be ignore for equals() and hashCode().
    • This can be a bug: you might not realize that copy() only copies a subset of your properties.
data class Animal(val species: String, val ageInYears: Float) {
	var is Friendly = true
	var isHungry = true
	val is CommonlySeenFlyingInTornados = false
}