DEV Community

loading...
Cover image for Let's Code Kotlin: Goodbye Builders, Hello Data Classes

Let's Code Kotlin: Goodbye Builders, Hello Data Classes

gatlingxyz profile image Tavon Originally published at gatling.xyz ・4 min read

If it wasn't for auto-generation, I can't say I'd be a good programmer. Back when Java was my sole language used for Android apps, manually writing each and every getter and setter would have been the bane of my existence, and eventually my downfall. Potentially constructors, too. Android Studio luckily can generate them all for me, which is why I'm still alive today.

When I switched to Kotlin, I was amazed. So many things I had been creating for years with too so* many lines of code were now being reduced to a small handful for the necessities.

I've mentioned before about my love for this Kotlin feature, but it deserves its own post: Let's talk about Data Classes.

The Java Way

In Java, you need to be verbose.

class Pizza {

    private Dough dough;
    private Sauce sauce;
    private Cheese cheese;
    private List<Topping> toppings = new ArrayList<>();

    private Pizza(Dough dough, Sauce sauce, Cheese cheese, List<Topping> toppings) {
        this.dough = dough;
        this.sauce = sauce;
        this.cheese = cheese;
        this.toppings = toppings;
    }

    static class Builder {
        private Dough dough;
        private Sauce sauce;
        private Cheese cheese;
        private List<Topping> toppings = new ArrayList<>();

        public Builder() {

        }

        public Builder(Pizza pizza) {
            this.dough = pizza.dough;
            this.sauce = pizza.sauce;
            this.cheese = pizza.cheese;
            this.toppings = pizza.toppings;
        }

        public Builder setDough(Dough dough) {
            this.dough = dough;
            return this;
        }

        public Builder setSauce(Sauce sauce) {
            this.sauce = sauce;
            return this;
        }

        public Builder setCheese(Cheese cheese) {
            this.cheese = cheese;
            return this;
        }

        public Builder setToppings(List<Topping> toppings) {
            this.toppings = toppings;
            return this;
        }

        public Pizza build() {
            return new Pizza(dough, sauce, cheese, toppings);
        }
    }

}
Enter fullscreen mode Exit fullscreen mode

I never want to see that type of code again in my life. To build a pizza, we would do this:

Pizza newOrder = new Pizza.Builder()
                .setDough(Dough.HAND_TOSSED)
                .setSauce(Sauce.EXTRA_NORMAL)
                .setCheese(Cheese.MOZZARELLA)
                .setToppings(Arrays.asList(Topping.BACON, Topping.JALEPENOS))
                .build();
Enter fullscreen mode Exit fullscreen mode

All of that work to get something, admittedly, pretty. I can't really argue with the results: the builder pattern produces some nice looking code. It's been worth it in the past, but the amount of boilerplate code has always frustrated me.

If I wanted to add one more configuration to it — let's say we split up toppings to be Meats and Vegetables — then I have to:

  1. Change private List<Topping> toppings = new ArrayList<>(); in the class to reflect the split
  2. Change the private constructor to handle the split
  3. Duplicate the toppings split in the Builder
  4. Remove setToppings() in the builder
  5. Add two more sets to the builder to handle meats and vegetables.

Heaven forbid I want to add uncookedToppings to the configuration.

It's entirely too much and my brain hurts just thinking about it.

The Kotlin Way

In Kotlin, there's a much simpler way to handle builders: not having one at all.

data class Pizza(val dough: Dough,
                  val sauce: Sauce,
                  val cheese: Cheese,
                  val toppings: List<Topping>) {
}
Enter fullscreen mode Exit fullscreen mode

That entire Java class gets reduced to 5 lines; it could be 1 but this how I like to format my classes.

The code to make a pizza now turns into this:

val newOrder = Pizza(
                Dough.HAND_TOSSED, 
                Sauce.EXTRA_NORMAL, 
                Cheese.MOZZARELLA, 
                listOf(Topping.BACON, Topping.JALEPENOS)
)
Enter fullscreen mode Exit fullscreen mode

Again, this could all end up on one line, but this is how I format my object initializations if three or more parameters being provided.

To be fair, you can do this in Java as well by removing the builder and allowing you to use the public constructor. And for simple cases, sure, that could be fine. But then if you have defaults for each value, you'd probably end up in a case where you have a million constructors letting you configure the pizza however you want: just set the dough and cheese, or just the sauce and dough. At this point, the Builder provides you with more flexibility by allowing you to set the defaults there, and if they aren't provided with one of the setters, it'll fallback to the default.

But then we're back to using the Builder, and that's where my frustration comes in.

In Kotlin, you can provide the default value, too:

data class Pizza(val dough: Dough = Dough.NORMAL,
                  val sauce: Sauce = Sauce.NORMAL,
                  val cheese: Cheese = Cheese.MOZZARELLA,
                  val toppings: List<Topping> = listOf()) {
}
Enter fullscreen mode Exit fullscreen mode

This is your standard pizza: regular dough, regular sauce, mozzarella cheese, and no toppings.

val defaultPizza = Pizza()
Enter fullscreen mode Exit fullscreen mode

Our plain cheese pizza is a simple Pizza().

Extra cheese? We can set just that.

val extraCheese = Pizza(cheese = Cheese.EXTRA_MOZZARELLA)
Enter fullscreen mode Exit fullscreen mode

Hand-tossed with mushrooms? Easy, and you can call the toppings first then the dough, using Kotlin's Named Arguments.

val handTossedMushroom = Pizza(toppings = listOf(Topping.MUSHROOMS), dough = Dough.HAND_TOSSED)
Enter fullscreen mode Exit fullscreen mode

.copy

As an added bonus of data classes, they have a built-in .copy() method. In our Java example, we had to manually make a separate constructor that took in an old Pizza and set the values it held into our new builder, allowing us to change them.

Pizza updatedOrder = new Pizza.Builder(newOrder)
                .setCheese(Cheese.EXTRA_MOZZARELLA)
                .build();
Enter fullscreen mode Exit fullscreen mode

In Kotlin, however, it's all baked in. (You get it? Baked? lol) And with the help of Named Arguments, you can only change the values you want to change.

val updatedOrder = newOrder.copy(cheese = Cheese.EXTRA_MOZZARELLA)
Enter fullscreen mode Exit fullscreen mode

Data classes are absolutely amazing. For more information on them, visit the official documentation.

Anyone else love data classes as much as me? Leave a comment below and let me know how your life is better because of them. 😉

Discussion (7)

pic
Editor guide
Collapse
favanso profile image
Fernando Branco

The first language that I learn was Python but than I started to learn Java, and I was so frustrated, because it was so complicated. Now I’m learning kotlin and a love it. It’s an amazing language.

Collapse
gatlingxyz profile image
Tavon Author

Java is SO frustrating now. I never really understood it until I switched. Now I cringe whenever I have to do something in Java. (Writing this blog post made me shiver, too. lol)

Collapse
ddaypunk profile image
Andy Delso

As mentioned in my own comment, Java 15 has this same concept now called records: baeldung.com/java-15-new

Thread Thread
gatlingxyz profile image
Tavon Author

I've already been scarred! lol But I had to look at that link; maybe I won't hate it as much if I have to go back to Java at some point. Thanks for that insight!

Thread Thread
ddaypunk profile image
Andy Delso

It’s useful in its own ways. Especially when starting a new position and the backend is in it. It has allowed me to contribute outside my SDET role which has been awesome for learning.

Collapse
ddaypunk profile image
Andy Delso

I had been primarily working in Java in my previous role, but then had the chance to work with Kotlin in my current role, as well as Groovy. I would say that out of the three, Kotlin is the one I enjoy working in the most.

Data classes are the best!

Collapse
ddaypunk profile image
Andy Delso

Though, recent versions of Java have a similar concept called records I believe.