Optimizing equals() Methods with Pattern Matching

Optimizing your equals() methods with Pattern Matching - JEP Cafe #21

Estimated read time: 1:20

    Summary

    In this episode of JEP Cafe, the focus is on optimizing the equals() methods in Java using pattern matching. The creator delves into the pitfalls of relying solely on IDE-generated methods, and how potentially better-performing patterns exist that developers may not be aware of. The session explains the importance of well-designed equals() and hashCode() methods, especially when considering performance impacts. Pattern matching, instance checking, and predictive branching are explored as strategies for optimizing these methods. Various examples and benchmarks show that, in many cases, traditional practices might lead to performance hits, and a more nuanced approach could yield better results. The creator emphasizes measuring performance accurately for specific use cases as crucial for making the right optimization choices.

      Highlights

      • Using pattern matching can lead to more efficient equals() methods. 📊
      • Traditional instance checks can sometimes slow down performance. ⏳
      • Understanding and leveraging JEP 394 can optimize code significantly. 🤓
      • Measure your specific performance needs before deciding on an optimization strategy. ⚖️
      • Predictive branching can cause unexpected performance hits if not accounted for. 🚦

      Key Takeaways

      • Don't just rely on your IDE for equals() and hashCode() methods, explore pattern matching for optimization! 🌟
      • A well-designed hashCode() is crucial for performance, especially avoiding common pitfalls like returning constant values. 🚀
      • Instance checking isn't always beneficial; in some cases, it can degrade performance, especially with modern setups. 🎯
      • Predictive branching impacts performance; understanding it can help optimize equals() designs. 🔍
      • Pattern matching can outperform traditional approaches, especially with the recent Java advancements. ☕

      Overview

      In this engaging episode of JEP Cafe, the creator takes us through the intricacies of optimizing equals() methods in Java, shedding light on the often-overlooked performance impacts that poorly designed methods can have. The journey begins with a deeper look into the equals() and hashCode() methods — the foundational companions to any Java class.

        The discussion highlights the power of pattern matching introduced in JEP 394 and how it can be leveraged effectively to write performant Java code. We're guided through practical examples, comparing traditional instance checks with the modern, streamlined approaches pattern matching offers. The benchmarks provided show compelling evidence for reconsidering the default code we might take for granted.

          Amidst the Java jibber-jabber, the creator brings out a critical message: the importance of measuring performance based on real-world use cases. As technology evolves, so too should our approaches. The episode wraps up with a call to rethink old patterns and embrace more efficient options, like those available in Java 21 with pattern matching, to ensure your Java applications run smoother and faster.

            Chapters

            • 00:00 - 01:00: Introduction to equals() Methods The chapter titled 'Introduction to equals() Methods' begins with an engaging opening where the speaker humorously asks if the audience is interested in a coffee break. This sets a casual and relatable tone for the discussion that follows. The chapter is focused on the design aspects of equals() methods in programming. It emphasizes their pervasive use in software development, pointing out that these methods are often utilized even without developers being fully aware of it. This introduction sets the stage for a deeper exploration into best practices and considerations when implementing equals() methods.
            • 01:00 - 03:00: Generating equals() and hashCode() in IDEs The chapter discusses the practice of using IDEs (Integrated Development Environments) to generate the equals() and hashCode() methods in programming. It argues against manually writing these methods due to the tediousness and suggests that IDEs can automate this task effectively. The chapter further explores whether IDEs produce optimal code patterns or if there are better alternatives. It promises to address these questions and more, including concepts like performance optimization, instance checking, pattern matching with instanceof, and predictive branching, using JMH benchmarks.
            • 03:00 - 05:00: Understanding HashSet and HashCode The chapter titled 'Understanding HashSet and HashCode' focuses on the significance and implementation of the equals() and hashCode() methods in programming. It emphasizes the importance of understanding these methods for designing simple and efficient equals() methods. The chapter also touches upon the common practice of generating these methods using an Integrated Development Environment (IDE), alongside the toString() method. While automatic generation is beneficial, especially for the hashCode() method, having a deep understanding of their workings can greatly enhance code performance.
            • 05:00 - 09:00: Performance Issues with equals() Methods The chapter discusses the challenges of writing effective hashCode() methods. It emphasizes the complexity involved in creating a good hashCode() and warns about the unexpected negative impacts a poorly implemented hashCode() can have on application performance. The chapter advises using generated hashCode() methods unless the user is experienced enough to handle custom implementations and begins with a practical example to illustrate these points.
            • 09:00 - 14:30: Impact of Predictive Branching This chapter discusses the potential pitfalls of implementing a poorly designed hashCode() function in programming. The example given illustrates a point record with a default hashCode() provided by JDK and contrasts it with a 'dumb' implementation that always returns 0. This highlights the importance of designing efficient and sensible hashing methods.
            • 14:30 - 29:00: Testing equals() Methods with Various Data Sets In this chapter, the focus is on testing the equals() method across various data sets. It highlights the often overlooked but essential rule in Java that if two objects are considered equal by the equals() method, they must have the same hash code. This ensures consistent behavior when these objects are stored in hash-based collections like HashSet. The chapter further explores the practical implementation of this rule by using Java 19's newHashSet() factory method, which simplifies the creation of HashSet by determining the necessary initial capacity to store all incoming objects efficiently. This discussion serves as a reminder that while certain implementation tricks might appear unconventional or redundant, they are crucial for aligning with Java's specifications and ensuring robust application performance.
            • 29:00 - 32:00: Conclusion: Importance of Measuring Performance In the concluding chapter titled 'Conclusion: Importance of Measuring Performance', the importance of evaluating performance in programming is highlighted. The discussion involves measuring the execution time of a piece of code involving a `Point` class with a default `hashCode()` implementation compared to a poorly implemented one. The results demonstrate a significant performance deterioration, epitomized by a 20-fold performance decline when using a poorly crafted hash code, underscoring the necessity of efficient coding practices.
            • 32:00 - 35:00: Evaluating IDE-Generated Code The chapter discusses the inner workings of a HashSet, specifically how it wraps around a HashMap. It explains that a HashMap begins by computing the hash code for each key to determine the storage bucket. The chapter also covers the issue of hash code collisions, which can result in a linked list that may evolve into a red-black tree if it becomes excessively long.
            • 35:00 - 33:00: Final Thoughts on Pattern Matching in Java 21 The chapter emphasizes the complexity and performance considerations involved in managing code, particularly when using records and classes in Java 21. It advises on the best practices such as avoiding manual alteration of records and relying on integrated development environments (IDEs) to automatically generate hashCode() methods for plain classes to ensure safety and efficiency.

            Optimizing your equals() methods with Pattern Matching - JEP Cafe #21 Transcription

            • 00:00 - 00:30 Do you fell like a coffee break? So do I! What about we talk about the design of   your equals() methods. Methods that you use  all the time, even when you don't know it,
            • 00:30 - 01:00 and that you never write yourself. First because  it's boring, and second because your IDE can do   it for you. Is your IDE good at generating this  method? Are there better patterns of code than   the one it gives you? These are the questions  I would like to answer in this JEP Café.   So, you will learn about performance, instance  checking, pattern matching for instanceof,   predictive branching, all these  good things with JMH benchmarks,
            • 01:00 - 01:30 that, once again, will help you design  simple and performant equals() methods.   The equals() and the hashCode() methods  are probably the first methods you learn   to generate in your IDE, along with  the toString() method, of course.   And, dont get me wrong, generating them is a  good thing, especially for the hashCode() method,
            • 01:30 - 02:00 because writing a good hashCode()  method is actually not that easy.   And because it is used in places  where you may not expect it,   a bad hashCode() implementation can lead to  major performance hits in your application.   So yes, you should definitely use  this generated hashCode() method,   unless you know what you are doing. I just would like to start with a first example
            • 02:00 - 02:30 to show you what could go wrong with a dumb  hashCode() implementation. And when I say dumb...   Suppose you have a point record, plain and simple.  You do not touch this class, so the hashCode()   implementation is provided by the JDK. Good. And then, let us write this completely dumb   implementation of a hashCode()  method, that always return 0.
            • 02:30 - 03:00 I know, it's completely stupid, never do that  in your application, but this implementation is   actually OK with the spec: two points that  are equal, do have the same hash code.   Let us try to put these points in a HashSet,  along with the following code. And bytheway,   we are using this factory method newHashSet(),  that was added in Java 19, so, that you can   use it in 21. It creates a large enough hash  set to hold all your values, without having to
            • 03:00 - 03:30 resize it on the fly. Neat, I love this one. So let us measure the time it takes to run   this code in both cases: the Point with  the default hashCode() implementation,   and the dumb Point that has this stupid one. And as you can see, the performance hit is   quite hard! That's a factor of 20 between  a good hash code and a stupid hash code.
            • 03:30 - 04:00 Why is that? Well, as you probably know, a  HashSet is actually wrapping a HashMap. And   a HashMap first computes the hash code of your  key to decide in which bucket it will store your   entry. If all the hash codes are the same,  then you will get collisions, leading to the   construction of linked list, that will become  a red black tree, if this list is too long.
            • 04:00 - 04:30 This is a code that is much more complex and  costly to run than the simple storing of an entry   in an array. Thus the performance hit. So I hope this example convinced you:   if you are using records, do not touch  anything, and for your plain classes,   let your IDE generate your hashCode() method  for you. It's much safer, most of the time.
            • 04:30 - 05:00 Let us go back to our initial subject,  and talk about the equals() method.   There is actually a number of  generators that you can use,   and I would like to first take a look at the  code they give you, to compare these patterns.   In all the rest, we are going to use  our good friend the record Point,   with two components x and y, of type int. This is a very simple example, and comparing   two instances of this record is quite fast:  it's only about comparing two int values,
            • 05:00 - 05:30 which is, yes, fast. You may have objects in  your application, with more complex states,   that will be more costly to compare. First, we have the default generators   of the Eclipse IDE and the IntelliJ  IDE, which are slightly different.   Here is the generated code by both of them.  And, as you can see, it's mostly the same,
            • 05:30 - 06:00 organized in a different way. It starts with the comparison of this   and other with ==, to quickly exit if the objects  are actually the same. This is an instance check,   and is seen as an optimization that almost all  the generators are using. Then it compares the   classes, also with ==, because you know that there  is only one instance of a given class in your   application. And then the cast and the comparison  of the internal state of your objects.
            • 06:00 - 06:30 Then you can also use the Guava  generator, which is also very similar,   just organized in a different way. And you can see that all these generated   codes compare the instances with ==,  then the classes, then they do a cast,   and compare the internal state of your objects. And then you can check the Apache Commons Lang 3   generator. The code is organized in the  same way: first compare the instances,
            • 06:30 - 07:00 then the class, and then the internal state of  your object, but this time using a builder.   On the one hand, I find this code nice and  readable, but on the other hand, I suspect   that it will not be great performance wise. Creating this builder is costly, much more   costly than comparing primitive types with ==.  If performance is not an issue for you, and you   want to favor the readability of your code, this  is definitely a solution that you can consider.
            • 07:00 - 07:30 Other than that, I'm not sure that  this solution is very interesting.   And because we also need to check your basic  101 equals() method, we are going to a plain   instanceof to the lot, and the same, with  this instance check optimization first.   And because this is Java 21 we  are going to add two more.
            • 07:30 - 08:00 First the plain equals() method that is  present in your records. You do not see   this implementation because it is added for  you by the compiler, but it's still there.   And then the implementation advertised  in the JEP about Pattern Matching for   instanceof. This is the JEP 394, delivered  in the JDK 16, and written by Gavin Bierman.   We can take a look at this implementation  now, because it's really interesting.
            • 08:00 - 08:30 As you can see, there is one major difference  between this implementation and all the others,   including bytheway the default implementation  you currently get when you create a record,   which is: it does not check if the instances  are the same or not with ==. And it does   not check if the classes are the same or not  neither. It directly checks for the pattern.
            • 08:30 - 09:00 Could this be a mistake? A missing  optimization, overlooked by Gavin   Bierman that wrote the JEP? Or is it actually  the right way to write your equals() method?   So let us add this method from the JEP, and also  modify it by adding this instance check before,   to see if this is an optimization or not. And, bytheway, we are going to use the Record   Pattern, because this is what we have in 21,  and because our Point class is a record.
            • 09:00 - 09:30 The most important question is: is this  instance check with == an optimization or not?   And if it is really an optimization,  why does the code in the JEP doesn't   use it? Why would it give up on what  looks like an obvious optimization?   Well, you know the drill, right? When it  comes to performance, there is only one   principle to follow. Measure, don't guess! Before we move on, and examine all this, I
            • 09:30 - 10:00 would like to talk about a very important point in  computing, which is called predictive branching.   You may be wondering why we are taking about that  now. In fact, it turns out that it actually plays   a key role in all this, so we are going to  spend a few minutes to explain what it is.   Predictive branching is a nifty feature  that may make the execution of your tests
            • 10:00 - 10:30 much faster. It's not that it doesn't  execute your test, because it does,   it needs to execute it. It's more that, it makes a  bet on the outcome of this test, starts to execute   one of the branch of your if statement, and if  the bet is a win, carries on with your code.   Which is great, because if it works, then  it can execute your code much faster.
            • 10:30 - 11:00 But if it doesn't, then it becomes a  performance hit, because it needs to   rollback what it did and take the other branch. So let us see some benches on this topic.   First, let us run this code. We are just  summing all the int values of an array   of size 400. There is no test in it, and  it runs in about 72ns "on my machine".   Then we are going to suppose that all the  elements of this array are even integers,
            • 11:00 - 11:30 and we just add a test on that. Note that  the result of this test is always true.   This gives us an idea of the  cost of testing each element.   On the overall, running this code  takes 110ns "on my machine", which is   about 50% more than not running the test. Now we are going to run three more benches,   to evaluate the cost of a failing test. Let us make the last element of this array
            • 11:30 - 12:00 an odd integer. So the last test of the series is  failing, when all the other tests are a success.   And you can see that having just one test  that is failing, when all the others are   successful, is actually quite costly. You could expect that the performance should   be the same, because it is the same test, just a  different outcome. And we are executing 400 tests
            • 12:00 - 12:30 in 110ns, so the throughput of this code is about  0,27ns per operation. But because the result is   not what the optimizer expected, the performance  hit for this single failing test is about 7ns.   Which is quite a big performance hit. This  single failing test is about 30 times slower
            • 12:30 - 13:00 than all the others. This is the cost  of a failing predictive branching.   Now, let us move this failing test from the end of  the array to the middle of the array. And you can   see that the performance hit this time is almost  doubled. It is now about 13ns instead of 7.   It looks like we are paying for the  transition from one state to the other.
            • 13:00 - 13:30 The first state being: the expected outcome of  the next test is true, and the second state being:   the expected outcome is false. We can see that by running the same   bench with two consecutive odd integers at  the middle of the array instead of one.   If we measure this performance, we can see  that it is the same as the one where we   have only one odd integer, about 123ns. That  confirms it: what is coslty is to fail the
            • 13:30 - 14:00 predictive branching optimization, that is,  to transition from one state to the other.   Now we can try one last test: having  two odd integers spread apart in our   array. One at the first third, and  another one at the second third.   And you can see that the performance hit this  time is even higher. Not as high as expected,
            • 14:00 - 14:30 if we were paying for transitions the  performance hit would have been 138ns,   and it's only 128ns, but still higher  than having two odd consecutive integers.   So this gives you an idea of the cost of  having a test with an outcome that is not   what this "predictive branching"  feature would have expected.
            • 14:30 - 15:00 Actually testing elements and having an  outcome that is completely random is costly,   you should try to avoid being in  such a situation, when you can.   Now the question is: how is this related to  the design of your equals() methods? Well,   bear with me, this is what you  are going to see, right now.   How can we see if predictive branching has an  impact on the performance of an equals() method.
            • 15:00 - 15:30 Well, there is only one way to find  out, create the right set of data,   and run all the equals() methods we have on it. All the use cases we are going to see are the   same: we have a list of 400 Point instances.  Point being the record we created earlier. We   just loop over this list, and check if a given  object is equal to the objects of this list.
            • 15:30 - 16:00 This is the code we are running,  on different sets of data.   Bytheway this blackhole object is given to you  by JMH, and you can use it to make sure that all   the steps of your computation are actually  executed, that HotSpot will not "optimize"   things and remove some of your code because it  finds that it's actually useless, which is always   the case when you are running benchmarks. You absolutely want to avoid that situation,
            • 16:00 - 16:30 this blackhole trick is very useful for that. The first use case runs on a list of points   that are all the same instance of the Point  record. So yes, they are all the same object.   I'm not sure that you will see that a lot in  applications, but... we need to start somewhere.   And you can see that the result is what we  expected: all the methods that first checked
            • 16:30 - 17:00 for the instance equality perform better, and  the pattern matching one is lagging behind. So   in that case, performing this instance check is  a good idea: you will get better performance.   Let us move on to our next use case: we now have  a list of objects, still 400, and this time they   are all different instances, but they are all  carrying the same state. So they are all equal.
            • 17:00 - 17:30 And the object used to for the comparison is also  equal to all of them, but a different instance.   All the calls to this == are now false, the  class comparisons are true, so all the methods   are executing the code that is comparing the  internal state of the objects. And because the   state is the same, they have to execute all  the instructions of this state comparison.
            • 17:30 - 18:00 And this time the result is different. The  Commons Lang pattern is lagging behind,   as expected, obviously the goal of the design  proposed by Commons Lang is not performance   but readability, which is perfectly OK. All the others are doing roughly the same.   But what's interesting is two things. First, using  a simple pattern matching, without this instance   check before, actually performs better than  the rest, by about 40%, which is significant.
            • 18:00 - 18:30 Even if you compare the plain instanceof with  pattern matching, you can see that it is slower   by about 10%, which is also significant. So pattern matching is actually doing   very well in that situation. And because neither instanceof nor   the pattern matching solutions are executing  the instance check, they save on this test,   which is executed by all the others. So in the case of a list of objects that are
            • 18:30 - 19:00 different but that carry the same state, calling  this instance check does not optimize anything,   it is actually a performance hit. For the next use case we are going to   use a list of objects of the same type, once  again, but this time they are all different,   and the object we compare is also different. We expect a result that is similar to the
            • 19:00 - 19:30 previous one: the instance check is always  false, the class check is always true,   so the optimization there is in fact an  overhead. And indeed, the results are similar.   There is one result that is interesting in this  table, which the instanceof versus the pattern   matching one. Instanceof is better, with 142ns,  and Pattern Matching is behind with 178ns.
            • 19:30 - 20:00 You may be wondering why the result is different.  Well, we are using a record pattern in this bench,   that creates the two pattern variables  x and y, by calling the accessors of   this record. These accessors need to be  called to create the pattern variables,   because there may be some code in it that needs to  be executed. Like a defensive copy for instance.   So not calling the accessors would  actually be a backdoor to access
            • 20:00 - 20:30 the internal state carried by your records,  without any security checks or validations.   Then we use x for the comparison,  that is failing, so y is not used.   When you are just checking  for the type with instanceof,   no pattern variable is created. You create them  lazily. In that case, you create x, and not y.
            • 20:30 - 21:00 So in the case where you have  accessors that are costly to execute,   using a plain type pattern instead of a record  pattern may actually be more efficient.   For the last use case of this first series,  we are going to use a list of other objects,   of a different type. So the instance  check is always false, the class check   is also always false, and the rest of the  code is not executed. So, on the one hand,
            • 21:00 - 21:30 we are executing this code, and on the other  hand, the instanceof is also always false,   and the pattern variables are not created.  So which one will perform better?   Well, the benches give us a clear win for  pattern matching, with a factor of almost 2:   180ns for the instance check solutions, and  95ns for the pattern matching and instanceof
            • 21:30 - 22:00 ones. Checking for the instance equality is always  false, so it's an overhead, adding the check for   the class equality makes it more expensive than  calling instanceof or using pattern matching.   And as we can expect, adding this instance  check before executing an instanceof or a   pattern matching, actually degrades your  performance, instead of improving them.
            • 22:00 - 22:30 At this point, you can see that there is only one  use case where conducting an instance check is an   optimization, which is, you are comparing  objects that are all the same instance.   I'm not sure that this is the most common use  case you'll come across in your applications. But   so far, we have'nt seen any perdictive branching  hit, because all the tests had the same results.   Now, what we are going to do, it take these  data sets again, and add some glitches in them:
            • 22:30 - 23:00 objects that are different, to see the results. So let us take a look at the variations. We are   going to check more use cases, still with  a list of 400 elements, but we are going to   add different objects in it, glitches, to trigger  failures in the predictive branching algorithm.   What is a glitch? Well, in a list where all the  elements are the same as the one you compare
            • 23:00 - 23:30 them to, a glitch is a different object. In  a list where all the elements are different,   a glitch is an element that is the same.  Is it the same instance, or does it just   carry the same state? Well, we need to check  both, and this is what we are going to do.   How are we going to distribute  these glitches in a list?   We are going to distribute them evenly,  in the middle of the list. No glitch
            • 23:30 - 24:00 at the beginning, nor at the end. So one glitch makes it in the middle,   two glitches are a one third and two third,  and 10 glitches, well, it looks like that.   Now the use cases we are going  to consider are the following.   First use case: a list with objects that  are all the same instance, and the glitch   is a different object of the same type. Second use case: a list with objects that
            • 24:00 - 24:30 are all different instances, but carrying  the same state, so they are equal.   And the glitch is also an object that  is different, and of the same type.   Third case, we are going to put different  objects in your list, of the same type.   And we are going to add two  different kind of glitches.   First, an object that is the same  instance, and second, an object   that is equal, but a different instance. And the last case is the same, but with a list
            • 24:30 - 25:00 that contains objects of different types. That makes four use cases to look at,   with two variations for two of them. When you have a list with instances that are all   the same, adding glitches just makes the situation  even worse for pattern matching. Checking for the   instance gives you the result immediately. And  even if the glitches degrade the performance,
            • 25:00 - 25:30 it still much better than not doing it. So that situation is a clear win for all the   solutions that are doing the instance  check. Now the real question is:   do you see this use case that often in  your applications? Well, I'm not so sure.   In the next use case, the instances are  all different, but equal. And the glitch   is a different object, of the same type.  Pattern matching was performing better with
            • 25:30 - 26:00 no glitch, and it is still the case, even if  processing this data takes a little longer.   This use case is a win for pattern  matching and plain instanceof.   What I find interesting in this bench, is that you  can very clearly see that adding an instance check   on the pattern matching or the plain instanceof  solutions actually degrades your performance.   You may be thinking that this simple instance  check is almost free. Well, clearly it's not.
            • 26:00 - 26:30 In the two next series, we have a list with  objects of the same type, but different. And   in this one the glitch is an object that  is the always the same instance, and the   same instance as the object we check it against. That should favor the solutions that are doing the   instance check, right? Well, it does not. And the  reason if that predictive branching is messing up
            • 26:30 - 27:00 with this. On the one hand, you have an instance  check that is mostly failing, and sometimes it's   a success. And you saw on the predictive  branching examples, that it is a situation   that you do not want to find yourself into. And on the other hand, you have a pattern   matching or an instanceof, with a type  check somewhere, that is never failing.   A situation that is much better. So in this series, where you may be
            • 27:00 - 27:30 thinking that doing the instance check would be  an optimization, it is actually something that,   once again degrades your performance. And if you check the pattern matching and   the instanceof lines, and compare them with  the same solution with an instance check, you   can see that doing this instance check is again  a performance hit, by a factor of about 50%.
            • 27:30 - 28:00 The following series is the same as the previous  one, the only difference is that the glitch is a   different instance that carries the same state.  That is an object that is equal. The most   interesting thing is that if you compare these  numbers with the previous series, you can see   that the instance check solutions are actually  performing better. Which may seems unexpected,   because this instance check is always failing in  this series. Well, it's better, because predictive
            • 28:00 - 28:30 branching is playing with you in this case, and  it was playing against you in the previous one.   The performance of pattern matching and plain  instanceof is the same as the previous series,   which is also expected, and much better than  all the other solutions, by more that 50%.   And the degradation due to the instance check  is also roughly the same on the pattern matching   and the plain instanceof solutions. The last two series are about comparing
            • 28:30 - 29:00 objects of different types, with  a glitch that is the same object,   and then a different object that is equal. So for this first case, the instance check   is mostly failing, and sometimes  a success, which is not good.   Pattern matching is mostly performing  better, and adding an instance check is   always a degradation of your performance. When the amount of glitches becomes higher,   some solutions are doing better than pattern  matching, that is slowly degrading over
            • 29:00 - 29:30 time. But, actually up to a certain point. Be careful, because if you continue to increase   the amount of glitches, pattern matching and plain  instanceof are actually performing better again.   So just be aware of that, and carefully  bench your application with the actual   data your are working on. And the last series is the
            • 29:30 - 30:00 same as the previous one, but instead of  having an identical instance, the instances   are different, still carrying the same state. It does not make much difference, the results are   roughly the same as the previous series. The biggest difference is actually   the degradation of the record case. And, once again, you can see that adding   the instance check degrades the performance  for both pattern matching and instanceof,   that becomes better again as the  number of glitches increases.
            • 30:00 - 30:30 So, what can you make of all this? How all  this can help you write a better code?   If there is one conclusion you should draw,  it is the following: measure, don't guess.   You saw that there are many cases where checking  for the instance of an object actually degrades   your performances. It's actually what is  happenning in most of the cases I showed you.
            • 30:30 - 31:00 But as usual, choosing the right solution is  really about understanding what are your most   common use cases. What kind of objects are you  comparing? Are they all of the same type? Are they   all different, or sometimes you are comparing  identical instances? What it is the amount of   failing tests you are expecting? Are they mostly  failing, mostly successful, or evenly balanced?
            • 31:00 - 31:30 In short: what is your application doing, how is  it working? What kind of data are you processing?   As a rule of thumb, in most of the cases  I just showed you, doing this instance   check is actually a performance hit. The only use case we saw where it is   performing better is when all your objects  are the same instance. In all the others,
            • 31:30 - 32:00 not doing it is probably your best bet. Should you rely on the code your IDE can   generate for you? Well, to be honest I   have mixed feelings about it. These patterns  haven't changed much in years. Like 15 years,   maybe even more. And, what was true perfomance  wise 15 or 20 years ago might have changed now.
            • 32:00 - 32:30 Maybe thinking about these patterns  of code again could be a good idea.   But if you are lucky enough to have  an application running on Java 21,   pattern matching is definitely your best choice. If you have complex accessors, type pattern is   probably your best choice, and if not, record  pattern will give you a simpler solution,   with a more readable code, and  very good performance wise.
            • 32:30 - 33:00 And with this I'm out of coffee, so,  that's it for today, talk to you soon!