(Crazy busy day today, so simple article to meet my quota. Sorry)
I mentioned before about Kotlin being significantly streamlined as compared to Java. And one of the most obvious places it achieves this, that you are likely to see very early on, is in Data Classes.
Data classes are just classes. There's nothing special about them from that point. The actual "data" keyword was originally nothing more than an annotation that the compiler saw and reacted to. (I'm honestly not sure if this is still the case I'm afraid). As such, they can do (almost) anything that a normal class can do.
The point of Data Classes is that - believe it or not - they are meant to represent data, and not functionality. This is the definition of a Java Bean from Java-land. And that is exactly what they are. Only significantly more streamlines. When you add the "data" keyword (or annotation, or whatever it is) in front of the class definition, what you are telling the compiler to do is:
- Generate a class
- With this set of fields
- With getters (and optionally setters) for every field
- With an
equals
andhashCode
method that is correct - With a
toString
method that is correct - With a
copy
method - With a set of
componentN
methods
The first three of those are just part of how Kotlin defines classes anyway. That's nothing special. It's the other 4 that are where data classes really come into it.
Writing equals
, hashCode
and toString
methods isn't hard. Writing them correctly can be though. But even then, most of the time people either have their IDE generate them automatically or else they use something like Commons-Lang Equals/HashCode/ToStringBuilder classes. And that's fine, but not without cost. The generated ones from the IDE will get stale as soon as the class changes, unless you remember to keep them updated. The Builder classes will either suffer the same problems, or else will use reflection with the performance cost that incurs.
The final set of methods are simply some helper methods. The copy
method lets you take one object, and create a new one with the same values. Or with some of the same values. You can selectively pick and choose which values you want to replace with new ones. For example:
val user = User(username = "graham", email = "my.email@example.com", banned = false)
val copiedUser = user.copy(email = "my.real.email@example.com)
In this case, copiedUser has:
- username = "graham"
- email = "my.real.email@example.com"
- banned = false
And you only had to specify one of those values.
The componentN
methods are used by Destructuring, which is a topic in itself. Simply put though, it lets you take a class and decompose it into individual fields. This is very useful in a few places - such as pattern matching - when you don't care about the class itself but just the individual fields.
In short though, data classes aren't anything special. They don't really do anything. They just allow you to not write a lot of boilerplate for what are essentially generated methods. You can write a Java Bean with a number of fields, and get all of the correct JavaBean methods for everything you need, and a few more on top, in a single line of code. To steal an example from Reddit, it's turning this:
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String component1() {
return name;
}
public int component2() {
return age;
}
public User copy() {
return new User(name, age);
}
public User copy(String newName) {
return new User(newName, age);
}
public User copy(String newName, int newAge) {
return new User(newName, newAge);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
into this:
data class User(val name: String, val age: Int)
Top comments (2)
Most of my articles will be way way simpler than this, for meeting my 100 days quota 😊
Yeah - turns out I don't know how to write short articles... ;) My emails at work are similar. I'm well known there for how long they can get!