Writing cleancode withmodern Java. Miro Cupak, Dnastack

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so there is the last talk for today now we have Mira speaking about the clean code and how to write the perfect coding in Java thank you and good evening everybody [Applause] thank you for being here still last talk of the day I admire your commitment to learning my name is Mira Tupac with the Toronto based company called DNA stack where we've built a cloud platform for biomedical data access search and analysis and if health tech is something that you're interested in or curious about please come find me after the talk I do enjoy speaking about it but this talk is not about the domain it's about the underlying technology stack which of course is Java now if you solve the agenda for the conference you may have noticed that I have a couple of talks here as well as a workshop and they're all kind of on the same topic right they're always about writing clean code anyway and they're always about new features in the JDK so when I say new I mean essentially things that we got in the JDK in the last two years or so basically since Java switched to the six month release cycle so we're looking at Java 9 10 11 and 12 so the difference between the talk today and tomorrow is essentially what we're focusing on today we're talking about the language features and tomorrow we're talking about api's and good API design so if that's something that you're interested in please come join me tomorrow as well now these are the things that we're going to talk about today of course we're going to cover a local variable type inference which without a doubt is the biggest language feature they've got Java got in the last couple of years so it's certainly worth spending some time on and that of course is Java 10 and Java yaadon we're also going to take a look at private medicine interfaces which is a Java 9 feature improved drive with resources which is also a Java 9 feature and switch expressions which is the Java 12 edition but before we get to that I think I should clarify what it means to write clean code and I think intuitively we kind of all understand what this is about right many of you have probably seen these two cartoons that summarize clinco pretty well I also like the definition from Williams in sir who actually has nothing to do with coding it's an American writer and he wrote a lot about writing in English particularly English as a second language and he came up with these four principles for writing well which is also a title of his book actually so the four principles are quite applicable to code two and they are clarity simplicity brooding and humanity so in other words we want our code to be clear we want it to be simple we're going to be short and we want it to be readable by humans so it's a lot about focusing on the reader right it's a well-known fact that we spend more time reading code and we've been writing it so it makes sense to optimize for that there are two very good books on the writing clean code one of them is actually called clean code that's an older book but still very much relevant the other one is called effective Java and that will actually just got a new edition two years or so ago so certainly worth checking out they are very well known so odds are you've have read them before if not I would definitely highly recommend doing that they're probably two of the best software development books that I've ever read and they provided a lot of context and inspiration for this talk as well so I like learning for examples and I'm going to write many of them throughout this talk and I'm going to write all my examples in Jay shell which is javis implementation of rebel that we've had since Java since version 9 if you haven't used a show before don't worry about it too much to do is extremely easy to figure out as a tool like this should be so you will get a pretty good sense for how to use it throughout this talk or maybe you've used the rifle in a different technology stack improving Scala you name it it's basically the same thing so let me just switch to my terminal here and let's take a look at the first feature which are private methods in interfaces so what you're seeing here is Jay shell I'm actually running gel from open JDK 12 and I know that this is a big room so if there are any issues of readability like font size or anything like that please do let me know the vast majority of this dog is taking place in my terminal so it's important you guys can read it everything good so far okay we can do it so since Java 8 the statement that interfaces don't contain implementation logic is no longer true right because Java 8 introduced default methods and the idea was to allow you to evolve the API without breaking backwards compatibility so you would provide an implementation directly in the interface and then everybody who implements this interface would automatically get it or they can choose to override the method of course so here's what that looks like I'm going to create an interface here let's call it my interface and I'm just going to make it empty that's enough for now and I will create a simple class what's called my class that implements my interface now this is a very simple API right but imagine your API evolves and at some point you decide to add a default method to your interface so I can just go here and create a default method here let's say default void default methods and it's going to be very simple I will just make it print out that the method has been called so system.out.print line the default method called now of course if I instantiate the class I have a multi-car modify the class in any way I can call the default method here right because it's inherited from the interface that sort of standard way of how default methods work now imagine your API evolves even more right your default method has grown and it's no longer readable maybe you want to split it into smaller methods or maybe you have added a second default method and they share some core and you would like to extract that to a helper method if you are in Java 8 there's nothing you can do right but starting with Java and I we have private methods that allow me to do precisely that so now in Java 9 I can modify my interface here and I can extract some functionality from my default methods into a private method so let's just call it private method error call here and then actually implement this method private void private method and I think you can guess what the method is going to do it's going to do the same thing just print out then it's been called private method called okay now if I find my class again I can call the default method of course this works calls the private method in the background right and if I try to call the private method directly this will not work at all right so the private method is in the interface but it's not the part of my public API so it's a very nice sort of little addition to the JDK that allows us to evolve our API without polluting them so to recap some best practices around this in terms of writing clean code a good rule is that all your methods should be short right and default methods are not an exception so use private methods to keep them short also don't repeat yourself don't duplicate functionality between default methods extract it to a private method instead I think one aspect of default methods that's also often forgotten is that even though they were designed for providing functionality an API is in backward-compatible ways they are not completely safe and that's that's important to realize right so what deformities are good for is providing default behavior in new ap is that you're creating well they're not very good for is extending existing api's right because you don't know how people have implemented or extended your API and there's no way you can maintain all the possible invariants that people might impose in their code right so you can potentially break things for people so it's just something to be careful about you know keep this in mind so this brings us to the next feature which are which is improved tribe of resources now in Java 6 or before closing resources like input streams output streams connections that sort of thing was quite painful right we had to use the try finally construct which can get complicated pretty quickly especially if you're juggling multiple resources sure there were finalized errs as well but those are just terrible for closing things so let's let's not even bring this up the Java seven was introduced right and Java seven introduced tried with resources statement which suddenly improved the situation quite a lot so now as long as you have a resource that implements the auto closeable interface you can just give it to the try statement directly and it's going to manage it for you right so it's going to be automatically closed when you're done with it and you can use a pretty in universally nowadays pretty much everything in the JDK that implements some sort of a resource that can be closed does implement the auto closeable interface anyway here's the how this works first in Java 7 style so let's create a method here and a method that reads the contents of a file and prints it to standard output ok so we're basically trying to copy a file to standard output so I'm going to start by creating a method let's call it read file and that's going to take a string referring to my file and now I'm going to try and implement this so what do I need to do to actually read a file well I'm going to need an input stream right so let's obtain a file input stream to my file and then I need to do sort of standard Java boilerplate so probably if I want to read from this I would wrap it in an input stream reader and wrap this in a bufferedreader say like this and finally let's store it in a variable for future use so bufferedreader and let's call this reader so something like this now what else can we do here well I can now use the try with resources construct but because I have a reader from before I cannot reuse it here right try requires a new variable so that you can manage it so a very common idiom that people use is basically just create a second bufferedreader you often see this being named reader too and you just do a trig you assign the existing reader to it and then you're good to go right so now let's try to implement the code for actually copying the file so I'm going to read it line by line so what I need at the beginning is the string to store the line and then while I'm not at the end of the file I'm going to keep reading it so while line assign my reader to read line method and while this is not now so we're not at the end of the file and I probably need a bracket here and at the beginning here so while I'm not at the end of the file let's just print out the line system.out.print line fine so something like this should probably do the trick and I forgot that this can throw an exception of course so froze IO exception just like that let's take a look at this okay this is looking reasonable so let's try to use this and I prepared a file before hopefully it's still there I can just go to TMP and my hello file and this works just fine but you can see that this is not exactly written very cleanly right and the unclean part here is of course the second bufferedreader that I had to create just for the purpose of having it managed by the Tri construct that is no longer the case since Java 9 so since Java and I what supported here our final variables as well as effectively final variables effectively final means that it's variable that's not explicitly defined as a final but it just doesn't change after you initialize it so what that means is that I can just modify my code here and I can completely get rid of the second reader now I'm just going to remove it here and rename this to reader anyway if I try to run the read file method this still works ok so just a tiny little addition to the API that allows us to clean up our code a little bit by the way there is another little feature added in Java 9 that allows me to simplify this girlcode even more in our code what we're doing we're essentially just copying a file from an input stream to an output stream right and it turns out that input stream now has a chameleon's method to do exactly that and that method is called transfer to so I could actually replace all of this code just by doing something like file input stream to my file TMP hello and I can just transfer to system out and that does the same thing right so significant simplification here just a little bit nicer so let's recap some best practices around this basically always prefer the tribe of resources construct right don't use try finally anymore and certainly don't use finalized errs to do these things be aware of convenience methods in other API is to deal with i/o that can simplify your things such as the input stream transfer to method and don't create unnecessary helper objects so remember you can now use final and effectively final variables directly in your try construct so no need to create all these extra objects that we used to do so I was Java 9:00 lunch let's jump to Java twelve and one feature that they introduced which is called switch expressions switch expressions is not actually even a full feature it's something that's called a preview feature and just out of curiosity who here knows what preview features are in Java okay basically no one so preview features are essentially like a staging environment for language and VM features in Java so it is a way for the JDK developers to give you access to some stuff that you can use but the stuff is no longer finalized there are no requirements on backward compatibility it might disappear hopefully it will be promoted to a full feature in the JDK but it might change in back we're incompatible ways in a way this is similar to incubator modules so preview features are two language features and VM features what incubator modules are two AP is right don't worry these things are not available by default you have to pass a special flag to the launcher or the compiler if to access this otherwise things would break and in fact we can see that flag here if I go to my back to my gesture and take a look at the environment it's the enable preview flag so switch expressions are a preview feature and now I'm actually allowed to use it here switch expressions are essentially an extension of the switch statement that also allows it to function as an expression so this also solves two kind of I wouldn't say bugs but slightly annoying aspects of switch right the default scoping of switch box the blog is treated as a single scope and the default control flow behavior so the fact that things sort of fall through and you need to use breaks right this is basically done by introducing switch labels that kind of resemble lambdas so I can just initialize a variable here that I'm going to use in my switch and I can then use switch on this variable and implement a couple of a couple of cases so let's say if this is zero just return zero otherwise return one and this works and there are a couple of interesting things about it right first of all you can see that this returns zero so it returns the value from the first branch without using any breaks right second of all we're only using expressions inside the branches not statement and finally this whole thing is an expression right this has a return value in this case is zero so that's very nice there's also a convenience aspect to it the syntax is very concise it's very readable and it's kind of flexible as well I can for example aggregate multiple cases here so I could say okay if this is 0 or 1 return 0 otherwise return 1 so kind of a nice simplification here we can also still use this consistent syntax with statements let me just simplify this so that I don't have to type as much I'm just going to keep one branch here and I can replace this with a statement I can do something like this print line my variable and you can see that now the value is printed out but there is no return value right so now this works as a statement we can also do the trick that we do with lambdas so if you want to execute a couple of statements but still keep keep it an expression still keep a return value you can wrap them in a block so we can do things like okay wrap this in a block and then I can use the break statement to return a value just like this so you can see that now the statement was executed it prints something out but it also has a return value so I just wanted to use this example to point out that the break statement was also extended to accept the value now so essentially it kind of functions as a return for non void functions so that's pretty cool so let's recap what we have here so remember that switch expressions are a preview feature right so most likely not a good idea to use in a serious production system it's an extension of the switch statement so that it can be used as either a statement or an expression both forms are available and both forms can use the traditional as well as the simplified scoping and control flow behavior so a good rule of thumb is to prefer the simplified form now that we used two lambdas this was very natural and very readable there's also a convenience aspect to it the syntax is very concise and we can aggregate multiple cases the cases of a switch expression are exhaustive they must be exhaustive in other words what that means is that you have to use the default Clause unless you're dealing with an enum and if you exhausted all the possible options in that case the compiler can actually insert the default branch for you the break statement has also been extended to take an argument which becomes the value of the enclosing switch expression and one final thing to note is that switch expressions are similar to land us in a way that there are also poly expressions what that means is that if the target type of the such expression is known it's pushed down to each branch of the switch and the type of the switch expression is the target type if the target type is not known a new type is computed based on what you have all the branches so this creates some very interesting things for type inference speaking of type inference this is like I said certainly the most interesting language feature in the Java language that affects every one of us right and as is typical with Java features this is not a new revolutionary concept right Java tends to be very conservative when it comes to the option of new things and this is not an exception this is something that has been available in other languages for quite a while you might have seen it in in c-sharp or in Scala and starting with Java 10 we have it in Java as well in fact the local variable type inference that we have in Java is still very limited basically what it does is whenever you have a variable declaration with an assignment on the right side all it does is it takes a look at the initializer and it infers the type on the left based on the type of the initializer that's really all there is to it so what this means in practice is that starting with Java 10 I can declare variables using the VAR keyword actually calling it a keyword is not correct it's not the keyword it's something called a reserved type name but you get the idea and we'll see what that means later so I can create a variable like this and if we take a look at this variable you can see that it's actually stored as a string so this was correctly inferred as a string right we're not getting rid of static types or anything like that we're just inferring based on the surrounding context this is a string and will always be a string so if you're saying this for the first time you might be thinking this seems a bit sketchy right there's probably some case of polymorphism that I can construct that would allow me to break things in weird ways and that's actually right which is why there's a very specific set of rules that you need to obey when you want to use this and that creates confusion for the reader and it's a clear sign of unclean code so let's examine these situations that might seem confusing situation number one our declarations without explicit initialization right if I just do something like this it turns out that this actually doesn't work and it should be noted that this is purely a design decision right this this could have worked the compiler sees all of our source code if we don't initialize it here it could easily go further down other source code determine what the right type should be interesting for it it was purely a design decision not to do that they basically just decided that they want to you know avoid this type of action at a distance inference errors so this is not supported we have a similar case if we try to initialize it with something like now this also doesn't work and it might seem obvious you know after seeing the previous case but I think it's worth noting that even if we don't want to infer the actual type as a performance optimization or whatever there are still other things that we could have done here right for example we know that this is a now so we could have just inferred a null type a null type is a very special type in Java right it's the only type that's a subtype of every reference type what also makes it special is that now is its only value right so we could infer it here but then wouldn't be very useful like we would never be able to reassign the variable because now is the only possible value there so that's not very helpful for us so how about we go on the other side right how about we try to go to the most general route and we try to infer the most general thing possible which would be an object of course we can do that as well but then wouldn't be very pragmatic would it if I infer this as an object and later I assign a string into it I would expect that string to behave like a string so I expect to be able to call all the string methods on it but then wouldn't be the case I would have to cast it everywhere which is gonna be very practical so essentially we ran out of options there is nothing good that we can infer here so we're just not going to allow that it should be noted that this is purely a matter of not telling it the type on this line there's nothing like inherently weird with now for example if I tell it that specifically I mean that this is an integer now this is going to work just fine right so things like this can be a little bit confusing when you're reading this for the first time we have an interesting situation with compound declarations as well if I try to do something like this and initialize multiple variables on the same line which of course is valid Java code it doesn't work with Barr and again this is just a design decision it was essentially deemed too confusing and of course they're trying to encourage people not to do that just not allowed it also doesn't work in methods or classes if I create a very simple method here let's say I'm just going to create a method called increment which takes an integer and returns integer plus 1 if I try to use var in place of an argument of the method this will not work var is not allowed there if I try to use it as a return value also not allowed there right so doesn't work anywhere in method signatures which kind of makes sense if you think about it right when you're thinking of method signatures you're essentially finding your API is you probably don't want to have bars in your API eyes especially in the case of an argument right that's where you're dealing with overloading so it kind of makes sense not to rely on type inference there there's a similar situation in a class case if I create a new class and just have like a class level attribute there I'm going to use var initialize it correctly and everything this still doesn't work we're still not allowed there and the reason is essentially this is meant for local variables this meant for variables with very limited scope so Jesus just specifically decided not to allow it at the class level it also doesn't quite work when you're dealing with trying catch specifically the catch block if I use var here in place on an exception this is not going to compile right also if you think about it kind of makes sense right with exceptions you want to be very specific when it comes to error handling basically and since all the exception inherit from the same class anyway you want to be extra careful when it comes to inference you don't want to accidentally end up inferring some sort of a parent and then having a wrong error things get even trickier when it comes to arrays I can for example use var in place of an element of an array so maybe just something like this and initialize it with something and you might be surprised that this actually doesn't work right and it tells you you you just kind of use it as an element type of an array so a natural question now is okay not as an element how about I use it as the whole array and it turns out this also doesn't work and it actually gives you quite an interesting message it tells you array initializer needs an explicit target type and this might sound weird when you see it for the first time but it's actually a very accurate description of what what's going on if you think about it when we take a look at this array we don't actually know what type that is right we don't know if these are Short's bytes or integers this can be anything so actually what's happening with arrays is the right side is trying to infer the type from the left side but now we've added another layer of type inference where the left side is trying to infer from the right side so how kind of these conflicting type inference is going on which is why this case is just not allowed however if I try to initialize this array differently maybe by using something like this it turns out this actually works so it's not the case it just doesn't work with arrays you just have to initialize it the right way right so in this case of course I'm fixing the Earth from before because I'm explicitly telling it that the target type is an inch so a little bit of a tricky situation especially trying to refactor and replace things with bar things are tricky when it comes to primitive types as well of course this is the valid Java code but if I try to replace this short with far it's going to work but taking a look at this X it's actually inferred as an integer right not as a short so that's also kind of a tricky thing to keep in mind there are several primitive types that all infer to the same thing and then think is an end where things get really tricky our land us so let me let me define a quick lambda here I'm just going to create a consumer and it's going to take something and just print it out something like this and this of course is valid Java code but if I try to replace this with bar I'm not going to be very successful right and it gives me an interesting error message something that might seem familiar right now lambda expressions need an explicit target type right we've seen this before with race and that's the exact same problem that we have here essentially just by looking at the lambda you don't know what the type it should be so there's already a type inference from the right side to the left and now we've added another one so just kind of allow both which is why this doesn't work the situation would be the same if we made this method reference of course method references are just lambdas right if I try to do something like this this this works but replacing the consumer with bar will prevent it from working it's interesting what happens if you try and introduce bar to lambda parameters if I for example took this implicit parameter and just added power here and of course I want to have a consumer here so that this doesn't fail it turns out that this actually works and this might seem like it's obvious but this actually only works in Java eleven and upwards this would not work if we were running Java 10 so this is an extension type it for us that was added in release 11 right they basically saw okay so we can use the bar pretty much everywhere with local variables for consistency it makes sense so that we can use it with lambdas as well so now this is allowed it gets a little bit tricky when you have more of these things so if I change this to be a bank consumer and add another variable maybe something like this this of course is fine but if I try to keep one of these arguments implicit and use var for the other one this will not work and it tells you you cannot mix bar with implicitly typed pattern mirrors okay so it's telling me I cannot use it with implicitly type parameters how about explicitly type parameters right how about I do this well turns out this doesn't work either Cannell use with explicit I'd parameters as well so basically once you decide to use var you cannot mix it with anything else that's what this comes down to another fun thing to look at is naming right we now have this new identifier in our code we now have bar which wasn't there before so a natural question to ask is can i still name things bar in other words can i for example name a variable or can I do this and it turns out that you can I certainly would not recommend doing that because from readability perspective this is obviously not good but it is fully possible and this is why it matters that var is not a keyword but it's a reserved type name if Mar was a keyword this would not work right but because it's a research type name this works just fine in fact I can use it with methods as well if I created the method just like this this would also work if I created a class that's also called var for whatever reason now things break right because you may remember it's a reserved type name so you cannot use it with types everything else fair game so this is the tricky thing here and it actually tells you that okay this is a breaking change as of release time but you know if you name your class far you're basically breaking so many naming conventions already that we just kind of accepted this as as like a sacrifice that we can make so yeah that's how this works so let's take a look at something even more complex than this let's take a look at generics okay so I'm going to create a list make it an array list and let's say ArrayList of string and this of course is going to work right this is in fact one of the cases that this feature was specifically designed for types can get very complex when you're dealing with generics so this just makes sense if you take a look at the title of this list what this got inferred - you can see that it's correctly an ArrayList of string but one important thing to note here right this is not how you would write this if you were writing this manual from scratch you would make this a list you would make this an interface you would make it an ArrayList that's not how type inference works type inference always infers the most specific type right so that's something to keep in mind don't just you know upgrade to Java 10 and then change everything toolbar because it can potentially break your code for example if I was relying on this being a more general implementation of a list and later I assigned a different implementation of a list into it now this would break so it should be noted that you're very unlikely to it this case just because the feature was designed for local variables with very limited scope so it's actually quite unlikely that you would reassign a different implementation into it and if you do you know things break within like the three lines of where you are in your code base but it is something to keep in mind what you might find very surprising though is that this keeps working even if I remove the type even if I just keep the diamond operator here this is going to go through right and this is interesting this was probably out of all the cases I've tried this is the most confusing one to me if we take a look at this list it's actually inferred as an ArrayList of object and the strange thing about this is that we have so many you know seemingly arbitrary rules around type inference already where clearly they put these rules in place to prevent us from creating something that's not very useful to us but in this case we're getting a data structure that's not useful at all ArrayList of object is almost never what you want right so it's a tricky question you know why is something like this allowed and I don't actually have a good answer for this but I can help hypothesize I would imagine they basically introduced this for consistency reasons right the diamond operator was available in Java prior to local variable type inference and there were already cases when you could end up with an object right that's just how the diamond operator works so then after they introduced local variable type inference for consistency they kind of had to keep telling the same story right so I think that's why this is allowed so maybe if both features were introduced in the same release of the platform this would be different maybe this wouldn't work but I guess we'll never know an interesting thing here is that if I actually keep the diamond operator but pre initialize this list with something of a known type maybe like an integer and then take a look at the type of this list this will actually infer correctly so it is smart enough to look inside the arguments of the constructor and determine the type right so now we're getting into really extreme cases right and one of these really extreme cases are capture variables capture variables may be you know known the term but you've certainly seen this around it's the thing where we use question mark to denote a type right so essentially wildcards or in particular collections of unknowns are a relatively common so I can for example create a list of unknown let's call it a list and I'm going to initialize it with something let's say an integer now if I take a look at this list here you can see that it's exactly what I typed right it's a list of unknown but now the tricky thing is that if I try to extract an element from this list which clearly it is there I can see that one is there this works without problems correctly instructed extracted one but if I try to add it back to my list without transforming in any way it's actually fails and it tells me that object cannot be converted to a capture so that's pretty strange right and the reason why this doesn't work is actually the type of the thing that I extracted so the problem is not with the insertion the problem is with the extraction here it turns out this actually got inferred as an object so of course in the next step when I try to edit to I want to try to add an object to a list of something that's potentially more specific than an object of course this doesn't go through right so there is a sort of a special rule for local variable type inference when it comes to capture variables essentially they always get projected to their super types which most often is going to be an object so just something to to keep in mind here another kind of tricky thing here are anonymous classes one thing that's really known about Java is that they don't have an implementation of a pair or a table all right so we can just create one ourselves and I can just a very easy way of doing that is just using an object right and I try to create an anonymous class based on my object and essentially just extend it with a couple of attributes maybe something like this and so let's put in another attribute just like that so this is a completely valid anonymous class right representing Interpol no the question is can I actually access something here and the answer is yes of course no problem here and this seems like it's not a big deal right but it actually turns out this is a very very big deal because this would not work prior to Java 10 that's why you've never seen an implementation of Topo done this way right this just the only works with type inference and the reason is oh well it only works in Tibet ferrets again lies in the type of the thing that was inferred so we take a look here it's something really weird it's an anonymous class extending an object right anonymous classes belong to a group of types that are called nandi notable types there are basically types that you cannot name so they are supported by the compiler but they're not exposed to the user but var actually exposes these types to us so what's happening here is that I was able to infer something more specific than an object right whereas if I was in a previous release of the platform I would have to name the class here but anonymous classes are nandi notable there's nothing to name in a way if the only option I have here is an object and then of course if I try to access this this doesn't exist this is just the regular object right an object doesn't have an attribute called a but with local variable type inference I was able to infer something more specific that's aware of these extra attributes so it's a very nice trick that you can certainly take advantage of but again it's one of those things you know just don't use it for readability reasons right if somebody was trying to understand your code they basically have to be aware of the specific trick to be able to understand what's going on and still we're at the point where this is not super well known I'm going to show what one final thing here just to tie it back to switch expressions that we've seen before so let me just fine find my switch from before maybe a simpler one maybe something like this oh and I need to reinitialize my variable it's a different type now so this is my switch right and we know that it's an expression we know that it returns a value so naturally I can use it in combination with type inference and I can do something like this right and this of course works if we take a look at this variable it's actually inferred as an integer so nothing too surprising here except this switch statement is not a method right like we've never committed to a specific return type nothing prevents me from doing this right nothing prevents me from returning different types in different branches but it turns out that this still actually works right this is still zero even though I've done these different types here but if we take a look at this variable you know there are a couple of options what you can expect here it can be an int or it can be an object right it turns out it's actually something completely different something a lot more complex and this is a little bit hard to read because of how small my terminal eyes and there's a lot of things going on here but essentially what's happening here is I have an intersection type combining four different interfaces and some of them are parent riced which makes the line even longer but I have a serializable here a comparable a constable and a constant desc and you're probably thinking okay it's two realizable comparable I know those right maybe you don't know the reigning two and that's completely fine those are part of the JVM constants API which was also introduced in Java 12 but that's completely out of scope for this talk so basically what's happening here is the same situation we had with anonymous classes right he was able to infer an on the notable type that's more specific at an object essentially it found the parent of string an integer which is a combination of these four interfaces this is what they have in common so that's pretty interesting right so let's just recap some of the best practices around using local variable type inference well keep in mind we're not getting rid of static typing here which is pretty universal recognized is a good feature of Java we're merely inferring if you don't have experience with a language that has had that feature you might feel like this feature actually reduces readability and that's completely fair in fact that's how many people including including myself felt when lambdas were first introduced for quite a while after lambdas were available I was writing lambdas but I was specifying the explicit type everywhere because I just felt that the type information actually improved readability but then after a while I got used to lambdas and I suddenly realized that the shorter form is actually more readable so I think it's going to be the same case with this you might not be completely on board right now this is the first time you're seeing this but I think it's going to be one of those things where you get used to it then it's going to make sense this feature reduces boilerplate and improves readability so it basically reduces clutter and lets more important information stand out it's particularly useful when you're dealing with generics and types further parametric generic types basically whenever the type information is really complex because it's because it allows you to shorten it just three characters right in a very simple case just imagine iterating through something like an entry set of a map that uses some complex types it's also not only about readability this helps with maintenance and refactoring as well right for example let's say I have some domain class and I replace it with another class normally when I refactor I would have to rename all the occurrences of this class in my code as well but if you're using bar those just getting further automatically right so refactoring is much simpler with this this works well with local variables with initializers and for loops including for each so certainly a good idea to see there use it when you're initializing the variable with a static factory method or a constructor so something that has a name that already tells you what this is about right for example the name of the constructor is by definition equal to the name of the class so it doesn't make sense to have that type information on both sides such as use bar the left it's a great tool for substituting complex types particularly containers like collections or or optionals especially when used with generics it's also really good when you're trying to break sort of long pipe lines or chains of code like sometimes people put streams chain onto other streams and they don't break them apart they just keep one long pipeline because the intermediate types that you would end up with after breaking it would be super complex and it actually would be really hard to read so it's just easier to keep it as a single long pipeline well you don't have that excuse anymore right now you can break it apart and just use bar and it's going to work very nicely note that this doesn't work with a bunch of constructs it doesn't work with methods constructors method return types fields catch or any other kind of variable declaration so it's pretty limited particularly we know that this doesn't work for method signatures right you you don't want to have war in your API and force the contract there keep in mind it's not a silver bullet it works for coming use cases and it's good but sometimes having an explicit type can help readability so just evaluate it on a case-by-case basis essentially it's particularly you know the case when the type isn't obvious from the expression that generates it be careful when using this with primitive types they might not infer to what you expect them to and remember that the program to the interface rule doesn't work with type inference anymore right so that's a common idiom in Java is to construct an instance of a concrete type but assign it to the variable of the interface type which is a good pattern right because it binds your code to the abstraction rather than the implementation so it's good for maintainability in the future you know you're basically not creating a dependency on a concrete implementation this of course does not work with type inference type inference always infers the concrete type not the interface type but in this case it's generally considered acceptable it wouldn't be okay probably if we were doing this for fields or you know methods but because it only works for local variables and variable with a very small scope even if something breaks it only affects the surrounding few lines so it's kind of a compromise that were generally willing to accept and we're not only sacrificing flexibility right we're also gaining some flexibility in terms of refactoring like we mentioned be very careful about combining type inference with diamond operator or generic methods because things can get pretty weird right remember the aerialist example the problem essentially is that the inferred type can change with arguments that you give on the right side so if somebody is maintaining your code and it's just removing an argument things can break all over the place because the types change so just be very careful about that and also be extremely careful when using this with nandi notable types right it works for intersection types it works for capture variables with the specific rule and usually very carefully when you're using this with anonymous classes just remember the example where we had an attribute in an object pay attention to naming having variable names that provide useful information is now more important than ever because of the absence of types right when you're naming a variable just really think about it and input don't use bar as part of variable names even though you can with methods as well avoid Hungarian notation you might be tempted to encode the type name in the name of the variable now that we don't have types essentially there's still no place for it just leave that to the compiler don't rely on IDs right every ID has a feature where it can show you what the in first type is but it often involves the keyboard shortcut so it's not just visible if you're reading the code and then of course people read code in other places people Rico and github all the time right so keep that in mind though just you know rely on the type information rather use a very explicit name a very good pattern for writing clean code as minimizing the scope of local variables right that increases readability increases maintainability of your code reduces the likelihood of error and one technique for doing that is declaring the variable when it's first used right because otherwise it just distracts the reader in fact if you can't use the bar because you don't have an initializer it might be a sign that you're trying to use it too early so it might be an indicator of bad designers so just think about it almost every local variable declaration should contain an initializer there are very few exceptions one notable one is you're trying it to use it with try and catch and finally construct right sometimes you need the variable from the try block to use it afterwards in that case of course it's valid to extract it before but it's one of the few exceptions a related fact is that you should prefer for loops too while loops in terms of you know cleanliness of your code because for loops allow you to keep the scope of variables minimal right you can just define them within the scope of that for loop also var is particularly usable for for loops both traditional and enhanced for each keep your methods small and focused if your method does more than one thing you might have local variables that are related to one activity to kind of get in the scope of the other activity and you might end up reassigning things of different types of course this doesn't work with var and can be a sign that maybe your code could use better design so yeah that's it for me if you have any questions we're basically out of time but be around after the dog so just come up and say hi and otherwise if you want to know what else is new in Java and how we can write clean api's come join me tomorrow I think it's 12:20 and in this same room and otherwise thank you and enjoy the rest of the conference any questions so it seems to be there are no questions thank you very much
Info
Channel: Devoxx
Views: 31,509
Rating: 4.7890773 out of 5
Keywords:
Id: uEHJ5CHaF08
Channel Id: undefined
Length: 49min 13sec (2953 seconds)
Published: Thu May 30 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.