Conf42 Enterprise Software 2021 - Online

Effective Java with Groovy & Kotlin - How Languages Influence Adoption of Good Practices

Video size:

Abstract

If you are curious to find out how the selection of language (Groovy & Kotlin in this case) not only improves developer productivity but also influences developers to adopt good practices, this talk is the right place to be in. ​ Effective Java’ presents the most effective ways of using language. However, the adoption of these practices among Java developers is less than satisfactory. Over the years, Java has evolved as a platform, and it hosts several languages. Since Groovy and Kotlin run on JVM, most of the suggestions from Effective Java are equally relevant for Groovy and Kotlin developers. Moreover, these languages simplify the implementation by providing out of the box constructs for many of the recommended practices which can boost developer productivity.

Summary

  • Presentation on effective Java with Groovy and Kotlin. Java started as a platform independent technology. But later that situation changed and we had more programming languages targeting JVM. Now making the JVM a more promising place for developers.
  • effective Java is one of the most influential books that helped Java developers. When languages like Groovy and Kotlin were developed, they were already aware of effective Java practices. But when we try to reuse these Java implementation in groovy or Kotlin, we might end up with the certain problems.
  • The culprits are equals and hash code. In order to maintain consistency, they should rely on the same set of attributes. There are two approaches what you could take in Java. One is asking your ide to generate the code. Two, use a library like Apache commerce language.
  • In these languages, instead of for each loop, we could go on to the next level. We should favor internal iterators to external iterators, wherein you could make your code have even fewer moving parts. What helps these languages to enable that is closures or higher order functions.
  • Groovy and Kotlin take quite different approach for you. Instead of using the null literal, it replaced it with the null object. Using null objects for instead of null literals saved you from a lot of trouble. Life is too short for null checks. You would rather spend it wisely for solving some real business problems.
  • Effective Java says minimize mutability. Let's see how modern JVM languages like Groovy and Kotlin overcome this. In groovy the compiler takes care of rearranging and calling the constructor in the right order. In Kotlin the area that this really helps is the readability aspect.
  • Effective Java says favor composition over inheritance. Some of the effective Java suggestions are already baked into the language like Groovy and Kotlin. Compiler generated code is much better than IDE generated. Modern languages are really helping developers embrace good practices.

Transcript

This transcript was autogenerated. To make changes, submit a PR.
Hello and a warm welcome to the presentation on effective Java with Groovy and Kotlin. I started using Java on several projects and after that I went on to use Groovy and Kotlin languages on few more JVM projects. This journey had some interesting lessons and I had some cool observations on how program languages influences developers to adopt good practices. And in this presentation I'm going to share them with you. My name is Naresha and I help teams to get better at applying technology to solve their business problems. Let's get started by setting up a common ground. You know, Java started as a platform independent technology. Do you remember that famous acronym Vora which meant for right ones run anywhere? Interestingly, Java was the only language that you could use to run or develop applications on top of JVM. But later that situation changed and we had more programming languages targeting JVM. Like Groovy is such a language. And later on we had Kotlin which is another language which could run on JVM. Now making the JVM a more promising place for developers because they could choose the language, what helps them better to create their application and the information or the key that JVM is. These most important piece in the whole ecosystem has been endorsed by people like James Gosling, who has been attributed as the father of Java. With that, let's move on to effective Java. I'm sure you would consider effective Java as one of the most influential books that helped Java developers. When I read it like 15 years ago, it really changed the way I wrote Java programs. Not only that, it really changed some of my fundamental beliefs on what is the right approach or right technique for developing applications on JVM. A quick look at groovy programming language one of the problems faced by several developers when they were developing on Java language was that developer productivity developers weren't that productive because they were spending a lot of time writing a very mundane verbals code. So Groovy was an attempt to solve such problem. So here you could see what James Strachen who created Groovy language said, that the whole idea was to make developers really productivity with the new language and at that time the approach was dynamic language. On a similar note, Kotlin is another JVM language and jetbrains developed this language to make sure that their development experience on JVM is better. And they had certain things in mind when they developed this languages. And interestingly, it was at a later point in time than Ruby, so more of like static typing was the most widely preferred choice at that point in time. With that, let's explore a few wisdoms from effective Java and see how they're applicable in languages like Groovy and Kotlin. So here are a few icons that will help you to navigate this. In case you feel lost, you will always see these icons and that will help you to see where you are. So we will take this approach. We will start with these problem, right? When we have a problem, what do we do? Effective Java comes to the rescue. So let's go and check what does effective Java say in that context or how to solve that particular problem. Then we know what is said in effective Java is good for Java languages. But when we take those ideas or when we try to reuse these Java implementation in groovy or Kotlin, we might end up with the certain problems. Those are the traps what you need to be aware of. Then we will see for Groovy or Kotlin what is the idiomatic solution. And then finally we will see lessons what lessons did we learn by going through these good examples? Note here that effective Java was initially written to improve the Java implementation. And when languages like Groovy and Kotlin were developed, they were already aware of effective Java practices. So they incorporated lot of these ideas in the language itself. But in a way, when we look at how these effective Java suggestions are used in Groovy and Kotlin, we get another idea of how these languages, groovy and Kotlin make developers life easy and really encourage developers to embrace good practices. So let's take few examples and explore this since you would see good examples covering three languages. Here I have the icons of respective languages so that it becomes easy for you to identify which language has been used in writing that particular piece of code. So without further delay, let's get started with the first item. In this case, I have a class written in groovy, which is product which has three attributes, namely sku, description and price. So what I'm going to do is I'm creating two instances of the product class with the same set of attributes, and I'm trying to check if these two objects are equal. Please note that double equals in groovy is like similar to calling equals method in Java. So typically you would expect them to be equal, but they are not equals. On a very similar note, what I'm going to do is create hash map with the variable name as stock. Note the native syntax of hash map in groovy and I'm using book one as the key to store a value 100 in that and then I'm trying to retrieve the value using boost two as these key. Ideally, since they have the same value, we would expect to retrieve the value 100, but that's not the case. I'm sure you have had these surprise before and you'd have rightly guessed it. The culprits are equals and hash code. I mean the developers, we are the culprits. We were supposed to override equals and hash code because hash code is unique for each instance and equals method by default would check if they are pointing to the same object, same instance. But in this case we had two instances, right? So it becomes our developer's responsibility to override these methods. So these are these related wisdoms from effective Java book. Essentially what you need to do is maintain that consistency between equals and hash code methods. That's like whenever two objects are equals, they should have the same hash code, which means that when we override equals and hash code method, they should depend on the same set of attributes. You can't have equals depending on few set of attributes and hash code depending on another set of attributes. The typical approach without or there are two approaches what you could take in Java. Approach one, ask your ide to generate the code approach. Two, use a library like Apache commerce language which will provide you something like equals builder and hash code builder. You would see an example of using equals builder in action here. Essentially you need to supply what are the fields that the equals method would depend upon. I see two problems here. One is like if you use IDE to generate the code, let's say take this example and now say I'm going to add one more attribute, date of manufacturer after some time, say the requirement changes and now I need to incorporate date of expiry. Also, who is going to update equals and hash code? Ide code generation works great for the first time. Then if the developer forgets to update or to regenerate that code, you will end up in trouble, right? That's the problem with the IDE code generation. On the other hand, what happens with the approach, what we are using is like if you use equals and hash code builder, there are duplicate representations, right? Since we told in order to maintain the consistency between equals and hash code, they should rely on the same set of attributes. However, there were two different classes, one equals builder and one hash code builder instead of a single equals and has code builder. That's what groovy does. And you would see that it looks like an annotation at equals and hash code, which is called as ast transformation in groovy. Now what happens is compiler will look at this instruction, that is the ast transformation equals and hash code, which will automatically generate the equals and hash code whenever you compile your code. Now just by adding that extra annotation we will rerun our code. Now you would see book one equals book two, and you would see that when we use the second object as these key, we are getting the right value and the code is working as expected. Now however, a couple of things that you need to be taking care. In the previous case the class, what we looked is more like value object has against an entity. But in the case of an entity what you would do is like say you might have can autogenerated id. So you have to be careful about the situation wherein like object before persisting versus object after persisting. So that's the reason why typically relying upon an auto generated key can lead you to trouble. Instead of using that, you should typically rely upon the business key, which in this case happens to be sKu, so that there is always consistency even if you perform a save in between. If you had question on how do I instruct these groovy compiler to generate equals and hash code methods based on subset of fields? I'm sure the example also tells you how to do that. Here I have used influences in a very similar way. You have excludes options as well. With that, let's move on to Kotlin and recreate a very similar situation here. You are very much familiar with this and what approach Kotlin takes as against groovy is that Kotlin uses more implicit approach. That is like you use data class. By default you get all the equals hash code methods generated for you. Now just by adding making it a data class rather than an ordinary class, it works as expected because compiler generates equals and hash code methods for you. And now let's move on to the second problem of customizing. In this case I wanted to have a field like Id which gets changed later on when you're saving the value. So now again you're back to the problem like both they are not equals and you're not retrieving the right value using the second object as these key. So how do we solve these kind of problems? Pretty simple. In Kotlin convention you use the fields that are to be considered for equals and hash code in your primary constructor, and the remaining fields you declare inside the body. That's the approach you have to take. With that, let's move on to see what lessons did we learn. So groovy used ASD transformation, you have to be explicit on what fields to be used for equals and hash code. You say that by default all fields are considered. If you just put the transformation unlike that, Kotlin uses more default convention like okay, data classes will generate equals and hash code with the fields to be considered are to be part of the primary constructor. That's the more like implicit approach of Kotlin versus slightly more explicit approach of groovy. And interesting point is, both these languages save you, the developers, from committing the mistake of violating dry principle, right? That's like single point of representation for any knowledge which you might commit in your Java code. That's the real benefit you are going to get. And that's how these two languages help you to embrace good practices. So the good practice here is dry principle and you have learned how groovy and Kotlin help you to embrace good practices. With that, let's move on to see these piece of code. Well, how does it look? Very complex, right? Why does it look complex? Because it has too many moving parts. I'm sure many times when we see our code we get the same feeling represented by these image. So let's take this code, piece of code simple like Java code, wherein we iterate through a list. What do you think is the problem or the difference between the first very conventional for loop versus the for each loop? You would readily notice that the second piece of code here has fewer moving parts compared to the first, which makes the second code simpler and less error prone. So what does effective Java say? It says prefer for each loop to traditional for loops. And let's move on to Kotlin this time. And let's use that for each loop. Very similar to that in Kotlin. You could use this piece of code. And now let's say we want to perform some sort of map operation, multiply each by two. So instead of using a typical loop, I'm using map transformation here. That's why you have higher order function map available which accepts closure as the argument. Very similar. I can perform a reduce operation using fold. Now groovy also provides a very similar operation. This is a regular like for each loop and I could use map transformation. The method is collect and very similar to fold. You have inject in groovy. So notice that in these languages, instead of for each loop, we could go on to the next level. So when it comes to interpreting favoring for each loop against traditional for loops in these languages, what we need to do is that we have to interpret it in a slightly better way. That is like what we need to do is we should favor internal iterators to external iterators, wherein you could make your code have even fewer moving parts. And what helps these languages to enable that is closures or higher order functions. Since Java nowadays also has support for lambda expressions. This advice is very much applicable to Java also. But in Java you'll have to use streams API to take advantage of this, while these languages have higher order functions supported right in their collections. With that, let's move on to these next item. If I ask you a question, what would be a million dollar or billion dollar effort spent in every Java project, or money wasted in Java presents writing redundant code? My favorite would be definitely null check, right? A lot of null checks people write, but still ending up with null pointer exception in several places. So let's see an example. Imagine I have a method called get speakers, which accepts a conference name, has arguments, and returns the list of speakers who speak at the conference. What I do is I go and return null directly from this method. What is the implication of this? Whenever a developers calls this method, he has to make sure that he checks if the written value is null, which is going to be very counterproductive, and every call would require a null check, which is not recommended. So as per effective, Java says return empty arrays are collections instead of nulls, right? So you should never return nulls whenever the return type is a collection. Let's call the same piece of code in groovy. So imagine in groovy you return null, and let's try to invoke that method. On that return method, we'll perform subsequent operations and see if we get null pointer exception, which is the main problem, right? So what I do is I invoke the collect method on the return value, which is null, which is like calling null collect, right? These again in the next line I'm calling null find all. And interestingly, you would note that I'm not going to get a null pointer exception, I'm going to get an empty collection, empty list in this case. Well, so now your friend is trying to create trouble for you by returning null in that method, but groovy is going to save you from all the trouble that your friend is going to cause to you. How did that happen? Groovy, instead of using the null literal, it replaced it with the null object. Null object. What it does is whenever you invoke iterator method on the null object, it returns an empty list. In this case, instead of ending up with these null pointer exception. If you invoke any other method it will throw a null pointer exception. So using null objects for instead of null literals saved you from a lot of trouble. Now in the case of Kotlin, let's see how different approach Kotlin takes. So you have these method get speakers returning a list of speakers and I say return null. What happens is that the code is not using to compile because by default type is not null level. If you think that these method can return null, then you should explicitly say that by suffixing the question mark in this case. In the second example it will allow you to return null. The first example it will not allow you to return null. So developer has to ask that question to himself, hey, do you really want to return null or not? Accordingly, the compiler will help you with the extra safety checks. So note that in this case groovy and Kotlin take quite different approach for you. Both were pretty much helpful to the developers, but they were fundamentally different in the approach they took. So what did we learn here? Technique used was null object pattern in case of groovy and type system itself, asking that question whether it should allow null or not. Null was what has been favored by Kotlin and essentially helping you to write fewer boilerplate code and more safety, which is the value what these features deliver to the users of these languages. And in fact, life is too short for null checks. You would rather spend it wisely for solving some real business problems. Well, what comes to your mind when you hear the word side effect? It's mutability, right? Interestingly, effective Java says minimize mutability. So in the beginning when I said effective Java fundamentally changed a lot of my thoughts about how to approach programming in JVM. Definitely minimizing mutability is one of them. I was really surprised to see this in the book, because before that I always used to think programming in Java is pretty much about mutability. However, reading this really changed my thought process and under that point the book talks about it gives list of a checklist how to make a class immutable, which is long enough as you would see. Usually what happens when such a prescription is given to the developers is that they happily ignore this, right? They don't implement that. That was these challenge happened with Java. So let's see how modern JVM languages like Groovy and Kotlin overcome this. Let's start with groovy in this case, as you would see, I could go and take the Java implementation and reuse it in groovy. But again I'll have to write a lot of code. And there is second challenge that the constructor has so many fields here, and in order to invoke that I have to remember the order of these parameters, which is like the second challenge, in addition to making fields final, et cetera. So groovy provides can est transformation which is called immutable here. So I apply that to my class rectangle and now all the fields are made final and there is no setters, there is only getter and the way in which I invoke the constructor. Also you could use something like in the named argument style, like length as ten and breadth as five. With this you really don't have to remember the order of parameters. Let's see how this works under these hood in groovy. So if you look at the generated code by groovy compiler, you would see that both length and breadth were made final and it generated regular constructor as expected. Note that it created constructor which takes a java util map as the argument. That's how my named argument style works. So whenever, especially you say you provide the named argument kind of syntax under the hood, it is converted to map and map constructor gets called. That's how groovy achieves not remembering the argument order part. Let's see what Kotlin does, which is very similar, but there is a slight change there. Of course you will have to use, I mean, by making it a data class you get immutability in addition to equals and hash code, what we saw in the beginning. But what happens is that again you would see the generate making the field final having only getters, no setters, and in this case you would see the regular constructs. You won't see a map constructor with the map argument here. But what happens is that whenever you invoke in a named argument style in this I say length 20 and breadth ten. Note that compiler took care of rearranging and calling the constructor in the right order. So that's the difference between Groovy and Kotlin. In groovy. What happened is it was map constructor, and in this case compiler took care of rearranging or rather deciding the order based on the named arguments what you providing to it. So ac transformation again is a predominant technique used in groovy as you would see repeatedly. And syntactic sugar is that of providing a named argument style in turn results in a map argument. That's what happens. That's a syntactic sugar and in Kotlin you would see the compiler doing the work and the area that this really helps is the readability aspect. Well it's just time for another question. What do you think is these golden hammer with respect to Java the language? Well most of you would have guessed it right, it's inheritance, right people, or I would say inheritance is the number one abused feature in Java language. So effective Java says favor composition over inheritance and let's take an example. So imagine I have a list of string here which I want to be considered as like phone numbers. So I want to perform all the operations that are available in a list. In addition to that I want it to have a few more operations like say I want to check if it is like a particular phone number belongs to indian. So what I could do is how would I design these phone numbers type, the first thought usually comes is okay, array list has all the implementations of list operations, why not I extend from array list and add that additional one method which I need. But if you look at it closely, right. The case for using inheritance is typically substitutability which is like Liskov substitution principle. These consumer doesn't have to know the details without knowing that it gets a common contract or interface and they can invoke with these different types of behavior getting executed. Not really to reuse something is available in case class in the check class. So typically the option is to use composition in this case. But you would know that writing a composition would involve typically writing lot of WordPress code when it comes to Java. Let's take these case of groovy. What you could do is you could use the delegate at delegate ast transformation. Now I have defined an instance variable called phone numbers of type list which is marked as delegate and whatever any other custom methods. What I need, I can write it inside the class. So now what happens is that even though my class four numbers does not implement any list methods on its own due to the delegate instruction, compiler will make sure that it generates the necessary code that is required to delegate into the realist implementation. That's what happens in groovy. And let's quickly check Kotlin. Kotlin also provides a very similar feature. So you would use the delegate like using that by keyword. That's what we are using it here. And when it comes to composition there is also a composition of behavior, something like called as trait in groovy which also is very much applicable. But I'll not be going to the details of composing through traits. So again groovy supports this feature with the ASD transformation. And essentially we need to make sure that developer doesn't have to write, or the language has to make sure that developers don't have to write a lot of code to achieve some simple tasks. In that case they might be discouraged, whereas if you simplify things for them, they might be really encouraged towards embracing good practices. Right, because the moment you say doing right thing is easy and doing the right thing is difficult and the wrong thing is easy, people might choose these wrong way, which is why we should be careful. And I'm happy that modern languages are really helping developers embrace good practices. Now let me conclude by summarizing the takeaways. Well, as you would have noticed that some of the effective Java suggestions are already baked into the language like Groovy and Kotlin, making it easy for the developers because that's available right out of the box. I understand that Java is also slowly catching up something like records for data classes. Kotlin what we saw, those are coming up. I mean these are already there, but not yet in the LTS version as such. So that's one of these benefit what you would see readily. And another aspect what we saw is that compiler generated code is much better than IDE generated because it's my firm belief that any practice that assumes that code does not change is fundamentally flawed in software development, because we know change is the only thing that is constant. If you use IDE to generate the code, you may not get an opportunity, or the IDE doesn't get an opportunity to update the code right, you will have to do it yourself. Whereas whenever you make a change, obviously you're going to compile your code. Compiler can regenerate the code as per these changes you have made. That's why I believe these days it's much better to favor compiler generated code than using the id generated code. And we have saw several examples where copying implementation from usage of effective Java, like the Java code into groovy or Kotlin, might not be the best practice. You may fall into traps. So understanding the idiomatic approach in these languages is really really important to be effective. And with several examples I'm sure you have understood that programming languages can really reduce these friction to implement good practices by making developers really productivity. And with this we saw that Kotlin and groovy in several cases achieve the same value with different set of approaches or different techniques, which really helps a developer to understand that there could be really multiple right solution. There's nothing like one perfect solution. So always what fits better in your context. You can go and pick one of such solutions which is really thing to consider when you are designing anything. And very important, the way we code right, is not just influenced by the language in which we write the code, but also depends on our knowledge of other languages, which will really open our mind towards new possibilities and new design. So even if you're not using Kotlin Groovy or any other language for your day to day work, knowing those languages will definitely help you to come up with better designs. And one thing what we saw is that while effective Java in the beginning was very, very helpful for Java developers to learn better ways of writing their Java code. But then what happened later is that these effective Java ideas helped language designers to develop languages in better ways. And today, as a developer, when you use these languages, I think an important question to ask is like can we use effective Java as litmus test for modern JVM languages? What do you think? With that, I hope I managed to give you a few thoughts to ponder up on later and you found them useful. I would like to thank the organizers of Conf 42 Java for providing me this opportunity. Thank you one and all and happy coding.
...

Naresha K

Independent Consultant

Naresha K's LinkedIn account Naresha K's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways