Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Java Coding Problems

You're reading from   Java Coding Problems Become an expert Java programmer by solving over 250 brand-new, modern, real-world problems

Arrow left icon
Product type Paperback
Published in Mar 2024
Publisher Packt
ISBN-13 9781837633944
Length 798 pages
Edition 2nd Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Anghel Leonard Anghel Leonard
Author Profile Icon Anghel Leonard
Anghel Leonard
Arrow right icon
View More author details
Toc

Table of Contents (16) Chapters Close

Preface 1. Text Blocks, Locales, Numbers, and Math 2. Objects, Immutability, Switch Expressions, and Pattern Matching FREE CHAPTER 3. Working with Date and Time 4. Records and Record Patterns 5. Arrays, Collections, and Data Structures 6. Java I/O: Context-Specific Deserialization Filters 7. Foreign (Function) Memory API 8. Sealed and Hidden Classes 9. Functional Style Programming – Extending APIs 10. Concurrency – Virtual Threads and Structured Concurrency 11. Concurrency ‒ Virtual Threads and Structured Concurrency: Diving Deeper 12. Garbage Collectors and Dynamic CDS Archives 13. Socket API and Simple Web Server 14. Other Books You May Enjoy
15. Index

65. Dealing with pattern label dominance in switch

The compiler matches the selector expression against the available pattern labels by testing the selector expression against each label starting from top to bottom (or, from the first to the last) in the exact order in which we wrote them in the switch block. This means that the first match wins. Let’s assume that we have the following base class (Pill) and some pills (Nurofen, Ibuprofen, and Piafen):

abstract class Pill {}
class Nurofen extends Pill {}
class Ibuprofen extends Pill {}
class Piafen extends Pill {}

Hierarchically speaking, Nurofen, Ibuprofen, and Piafen are three classes placed at the same hierarchical level since all of them have the Pill class as the base class. In an IS-A inheritance relationship, we say that Nurofen is a Pill, Ibuprofen is a Pill, and Piafen is also a Pill. Next, let’s use a switch to serve our clients the proper headache pill:

private static String headache(Pill o) {
  return switch(o) {
    case Nurofen nurofen -> "Get Nurofen ...";
    case Ibuprofen ibuprofen -> "Get Ibuprofen ...";
    case Piafen piafen -> "Get Piafen ...";
    default -> "Sorry, we cannot solve your headache!";
  };
}

Calling headache(new Nurofen()) will match the first pattern label, Nurofen nurofen. In the same manner, headache(new Ibuprofen()) matches the second pattern label, and headache(new Piafen()) matches the third one. No matter how we mix the order of these label cases, they will work as expected because they are on the same level and none of them dominate the others.

For instance, since people don’t want headaches, they order a lot of Nurofen, so we don’t have any anymore. We represent this by removing/comment the corresponding case:

return switch(o) { 
  // case Nurofen nurofen -> "Get Nurofen ...";
  case Ibuprofen ibuprofen -> "Get Ibuprofen ...";
  case Piafen piafen -> "Get Piafen ...";
  default -> "Sorry, we cannot solve your headache!";
}; 

So, what happens when a client wants Nurofen? You’re right … the default branch will take action since Ibuprofen and Piafen don’t match the selector expression.

But, what will happen if we modify the switch as follows?

return switch(o) { 
  case Pill pill -> "Get a headache pill ...";
  case Nurofen nurofen -> "Get Nurofen ...";
  case Ibuprofen ibuprofen -> "Get Ibuprofen ...";
  case Piafen piafen -> "Get Piafen ...";
};

Adding the Pill base class as a pattern label case allows us to remove the default branch since we cover all possible values (this is covered in detail in Problem 66). This time, the compiler will raise an error to inform us that the Pill label case dominates the rest of the label cases. Practically, the first label case Pill pill dominates all other label cases because every value that matches any of the Nurofen nurofen, Ibuprofen ibuprofen, Piafen piafen patterns also matches the pattern Pill pill. So, Pill pill always wins while the rest of the label cases are useless. Switching Pill pill with Nurofen nurofen will give a chance to Nurofen nurofen, but Pill pill will still dominate the remaining two. So, we can eliminate the dominance of the base class Pill by moving its label case to the last position:

return switch(o) { 
  case Nurofen nurofen -> "Get Nurofen ...";
  case Ibuprofen ibuprofen -> "Get Ibuprofen ...";
  case Piafenpiafen -> "Get Piafen ...";
  case Pill pill -> "Get a headache pill ...";
};

Now, every pattern label has a chance to win.

Let’s have another example that starts from this hierarchy:

abstract class Drink {}
class Small extends Drink {}
class Medium extends Small {}
class Large extends Medium {}
class Extra extends Medium {}
class Huge extends Large {}
class Jumbo extends Extra {}

This time, we have seven classes disposed of in a multi-level hierarchy. If we exclude the base class Drink, we can represent the rest of them in a switch as follows:

private static String buyDrink(Drink o) {
  return switch(o) { 
    case Jumbo j: yield "We can give a Jumbo ...";
    case Huge h: yield "We can give a Huge ..."; 
    case Extra e: yield "We can give a Extra ...";
    case Large l: yield "We can give a Large ...";
    case Medium m: yield "We can give a Medium ...";
    case Small s: yield "We can give a Small ...";
    default: yield "Sorry, we don't have this drink!";
  };
}

The order of pattern labels is imposed by the class hierarchy and is quite strict, but we can make some changes without creating any dominance issues. For instance, since Extra and Large are subclasses of Medium, we can switch their positions. Some things apply to Jumbo and Huge since they are both subclasses of Medium via Extra, respectively Large.

In this context, the compiler evaluates the selection expression by trying to match it against this hierarchy via an IS-A inheritance relationship. For instance, let’s order a Jumbo drink while there are no more Jumbo and Extra drinks:

return switch(o) { 
  case Huge h: yield "We can give a Huge ...";
  case Large l: yield "We can give a Large ...";
  case Medium m: yield "We can give a Medium ...";
  case Small s: yield "We can give a Small ...";
  default: yield "Sorry, we don't have this drink!";
};

If we order Jumbo (o is Jumbo), then we will get Medium. Why? The compiler matches Jumbo against Huge without success. The same result is obtained while matching Jumbo against Large. However, when it matches Jumbo against Medium, it sees that Jumbo is a Medium subclass via the Extra class. So, since Jumbo is Medium, the compiler chooses the Medium m pattern label. At this point, Medium matches Jumbo, Extra, and Medium. So, soon we will be out of Medium as well:

return switch(o) {
  case Huge h: yield "We can give a Huge ...";
  case Large l: yield "We can give a Large ...";
  case Small s: yield "We can give a Small ...";
  default: yield "Sorry, we don't have this drink!";
};

This time, any request for Jumbo, Extra, Medium, or Small will give us a Small. I think you get the idea.

Let’s take a step further, and analyze this code:

private static int oneHundredDividedBy(Integer value) {
  return switch(value) { 
    case Integer i -> 100/i;
    case 0 -> 0;
  };
}

Have you spotted the problem? A pattern label case dominates a constant label case, so the compiler will complain about the fact that the second case (case 0) is dominated by the first case. This is normal, since 0 is an Integer as well, so it will match the pattern label. The solution requires switching the cases:

  return switch(value) { 
    case 0 -> 0;
    case Integer i -> 100/i;
  };

Here is another case to enforce this type of dominance:

enum Hero { CAPTAIN_AMERICA, IRON_MAN, HULK }
private static String callMyMarvelHero(Hero hero) {
  return switch(hero) {
    case Hero h -> "Calling " + h;
    case HULK -> "Sorry, we cannot call this guy!";
  };
}

In this case, the constant is HULK and it is dominated by the Hero h pattern label case. This is normal, since HULK is also a Marvel hero, so Hero h will match all Marvel heroes including HULK. Again, the fix relies on switching the cases:

return switch(hero) { 
    case HULK -> "Sorry, we cannot call this guy!";
    case Hero h -> "Calling " + h;
  };

Okay, finally, let’s tackle this snippet of code:

private static int oneHundredDividedByPositive(Integer value){
  return switch(value) { 
    case Integer i when i > 0 -> 100/i;
    case 0 -> 0;
    case Integer i -> (-1) * 100/i;
  };
}

You may think that if we enforce the Integer i pattern label with a condition that forces i to be strictly positive, then the constant label will not be dominated. But, this is not true; a guarded pattern label still dominates a constant label. The proper order places the constant labels first, followed by guarded pattern labels, and finally, by non-guarded pattern labels. The next code fixes the previous one:

return switch(value) { 
  case 0 -> 0;
  case Integer i when i > 0 -> 100/i;
  case Integer i -> (-1) * 100/i;
};

Okay, I think you get the idea. Feel free to practice all these examples in the bundled code.

You have been reading a chapter from
Java Coding Problems - Second Edition
Published in: Mar 2024
Publisher: Packt
ISBN-13: 9781837633944
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image