Java Challengers Master The Java Fundamentals With Fun Java Code Challenges Become A Java Challenger (2023), 2023 - ISBN - English
Java Challengers Master The Java Fundamentals With Fun Java Code Challenges Become A Java Challenger (2023), 2023 - ISBN - English
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
Foreword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Jeanne Boyarsky . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Why Java? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 What kinds of concepts are in this book? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 Who is this book for? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.5 What are the Java Challenges? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.6 Disclaimer about the Java Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.7 Java versioning and tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.8 Will this book prepare you for the software development market? . . . . . . . . . . . . . 7
5 Overloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
5.1 Overloading basics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
5.2 Wrappers and autoboxing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
5.3 Varargs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
5.4 Overloading Real-World Usage Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
5.5 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
7 Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
7.1 Interfaces and Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
7.2 Covariant return types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
7.3 Default methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
7.4 Abstract classes vs. interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
7.5 Static methods in interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
7.6 Simulating multiple inheritance with default methods . . . . . . . . . . . . . . . . . . . . 173
7.7 Private methods in interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178
7.8 Command Design Pattern Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
7.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
CONTENTS
8 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
8.1 Checked and unchecked exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
8.2 Stack Trace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
8.3 Handling or Declaring Checked Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
8.4 try, catch, finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
8.6 try with resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
8.7 Multi catch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
8.8 Creating a customized exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
8.9 Throw early, catch late . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
8.10 Real World Exception Creation Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
8.11 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
10 Optional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
10.1 Wrapping a value into Optional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
10.2 isPresent and isEmpty . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
10.3 Optional Antipatterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
10.4 ifPresent and ifPresentOrElse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
10.6 Handling Exceptions with orElseThrow . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
10.7 Transforming Optional Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
10.8 Final Yoshi Food Optional Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
10.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
12 Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
12.1 Collections API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
12.2 List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
12.3 ArrayList . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
12.4 Vector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
12.5 Deque & Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301
12.6 ConcurrentModificationException . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
12.7 Using Comparable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
12.8 Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
CONTENTS
13 Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
13.1 Streams Basic Principles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
13.2 Creating a Stream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351
13.3 Intermediate VS Terminal Operation Methods . . . . . . . . . . . . . . . . . . . . . . . . 355
13.4 Intermediate Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356
13.5 Terminal Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375
13.6 Parallelizing Data Processing with parallel() . . . . . . . . . . . . . . . . . . . . . . . . . 383
13.7 Collecting Data with collect() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
13.8 Collectors.groupingBy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
13.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401
Jeanne Boyarsky
Back in 2017, I attended Rafael’s JavaOne session: “Learn the Concepts between these 10 Java
Challenges and Eliminate Stressful Bugs.” My reaction at the time:
This was fun! Being a cert book author gave me an advantage but some were still tricky and tough!
And using The Simpsons as examples was fun. He did show of hands for each option; most people
didn’t raise their hand for any of the choices.
It’s now five years later. I’m still a certification book author, but the session has grown into a full
book! There are more challenges with the same fun and style. I’m pleased that the Simpsons remain!
Whether you are learning Java or an experienced developer, there is something in this book for you.
Don’t let bugs hit you. Learn here so you can avoid surprises in your code!
And have fun. I’m sure you will find it, um, challenging.
1 Introduction
Java is one of the most used programming languages in the world, and it’s also very powerful.
Developers who know Java in depth are highly valued by the market and ablfe to create high-quality,
maintainable code. Java is not a simple language to master, but this book will provide valuable
guidance for developers who want to harness its full power and be a reference as a software engineer.
Even senior developers may not be familiar with some key concepts of the Java language, and that
will impact the quality of the code they write. But reading a book is not enough to really acquire
a skill. Developers have to put their knowledge into practice by applying what they’ve learned in
some way.
This book provides more than 70 Java Code Challenges in total to test your understanding of the
and trully absorb the concepts it introduces. You’ll get up-to-date with the most recent Java versions
and will learn Java from real project situations so that when you come across similar situations you
will know how to respond effectively.
As the name suggests, Java Challengers is not an easy book, but it will guide you through the
concepts and won’t leave gaps that will make learning difficult. Although the subjects the book
presents are complex, the concepts are explained in a practical way, aiming to facilitate the learning
process of the reader.
After you read, consider, and try to solve each challenge, you can test the code provided and run
your own tests by changing it and analyzing the output. This will help you connect the dots with
the content you consume in the book.
You’ll then be able to use the concepts you’ve learned in your day-to-day work, which is the whole
point of going deep into the concepts of a programming language.
To produce highly maintainable Java code you have to use the most suitable features of the language.
Then bugs have a really hard time hiding. This increases the satisfaction of developers, managers,
and customers with the delivered product, bringing more profit to the company. Everyone wins.
It’s definitely worth it to invest time in a deep dive into the Java language, and that’s exactly what
this book will provide. So, what are you waiting for? Are you ready for all the challenges? How
many of them can you get right? Are you prepared to take your Java skills to a whole new level?
Reliability, maturity, and robustness are the essence of the language, and companies of all sizes
choose Java because of those qualities.
Java also has a large ecosystem of open source projects that help programmers with their day-to-day
development, many of which are free.
With Java it’s possible to create organized, structured code with reliable unit tests that help
developers build robust software that’s resistant against bugs.
Java concepts is to give developers the power to use the full capabilities of Java by applying the most
suitable features of the language to the problem at hand.
Finally, developers who just want to write "code that works" without going any deeper shouldn’t
read this book.
The better you are able to analyze code, the faster and sharper programmer you will become. These
exercises will enable and empower you to produce flexible and meaningful code using the most
appropriate Java concepts. The more features and concepts from Java you master, the better able
you will be to track down bugs and fix them efficiently. In addition, you will be able to produce
highly optimized solutions for many different situations.
Then configure the environment variable JAVA_HOME and add JAVA_HOME\bin to your operating
system environment variable PATH.
Just a brief review about the PATH environment variable, this path will enable you to run commands
globally on your command terminal.
We have two options when running the java command lines, either we point our current directory in
the command terminal to JAVA_HOME\bin or we inform our OS that we want to use the commands
located in JAVA_HOME\bin globally without having to change your current directory to the JAVA_-
HOME\bin every time we want to use java commands.
Once you’ve done that, you can start JShell using the following command:
jshell
Then you can write Java code without the obligation of declaring a class and a main method, and
execute it immediately from the command line. It’s a great option to practice the examples in the
book, but any way you choose to write your code is fine. What really matters is you doing your own
tests with as many examples as you can from this book.
Another option is Eclipse, which has the best set of programming keyboard shortcuts but in my
opinion is not so good at integrating and building complex systems. A third popular IDE is NetBeans;
I haven’t used it much but it’s generally regarded as a good IDE for Java and it will do the job.
IDE choice is very personal, so pick the IDE you like the most—after all, what matters most is the
Java code, not where the code was written!
You will have to import the project as a Maven project, and then you will be able to run the code
from the book. There are also some unit tests included so you can learn a bit about how tests should
be applied.
In working through the challenges, you will be able to use and learn about a variety of popular tools.
Therefore frameworks such as Angular, React, and Vue are some of the big ones; this might change
with time. All these technologies are based on Javascript, so it’s a good idea to have a basic
knowledge of the language as well.
The good thing is that by mastering Java concepts, you will be able to learn any other language far
more easily because in the end you will simply use the same language concepts and paradigms you
have from Java to learn another language and this process will be incredibly faster.
It’s a lot to learn, isn’t it? But don’t worry, you don’t need to be a specialist in all those technologies.
You need a basic understanding of how to use them, and then you can choose one specific technology
that you would like to explore in depth. If you’re reading this book it means you’ve selected Java,
which I think is a powerful choice.
Let’s start going deep into Java!
2 Variable types and flow control
(Review)
This chapter covers:
The core purpose of every programming language is to control the flow of logic, making computa-
tions with different values and organizing information in a way that solves a problem for the final
user of the software. This chapter introduces several concepts that you can use to accomplish this
objective, including variables (primitives and special types), conditions, loops, and arrays.
Using the information in this chapter, you will be able to develop your own algorithms to solve
specific problems. For example, you could build a mathematical formula to make a calculation,
construct logic to check if a password is correct with a condition, or create a program that will
receive two numbers, multiply them, and print the result. Your imagination is the limit.
Your code might not have the robustness required by a real project, but you will be able to design
the logic to solve specific problems. We’ll start with an in-depth exploration of variables.
why we have programming languages that have powerful features to solve real-world problems
about financial businesses, entertainment, modern machines, mobile devices and the list goes on
and on.
Let’s then review the universal code concepts so that we can apply a logic algorithm effectively with
Java or any other language.
We have to always follow the code syntax to work with any programming language. Java for example
has a very similar syntax from the C language.
• Type: The variable type, defined by the size of the allocated space in memory and the data
structure (for example, if it’s text or numbers).
• Range: The range of values that can be used for the variable type.
Default instance value—The default value of the variable when no value is assigned to it upon
creation of a new instance. We will explore instances in the next chapter.
• Size: The size that will be allocated in memory for the variable type.
Note that the primitive types in Java can never have the value null; if you don’t assign them a value
when you create them the JVM will assign them the default value listed in this table. This will only
happen with an instance variable, a concept we will explore more deeply in future chapters. As
you’ll see later in this chapter, local variables are not assigned default values if one is not provided;
you must assign them values when you create them, or your code will not compile.
2 Variable types and flow control (Review) 12
1 int luckNumber = 7;
2 double weight = 70.0;
3 boolean isJavaRocker = true;
In Java 10 we also have the possibility to define a non-typed variable that can have any value type
within it.
For example:
var luckNumber = 7;
The luckNumber variable will be inferred as an int type with the value of 7.
We can’t try to declare a variable without giving a type like Javascript, the following code, for
example, won’t be compiled:
luckNumber = 7;
If we try to use the variable homerGrade as the following code, the code won’t compile:
System.out.println(homergrade);
To use variables in Java, the naming case has to exactly match, if we use the homerGrade variable
as the following, the code will compile successfully:
System.out.println(homerGrade);
Even though those are the rules to name Java variables, it’s not a good practice to start a variable
with a capital letter, with underscore (_), or a currency symbol ($).
Instead, we should follow the JavaBeans conventions which we will explore in the next session.
1 simpsonName
2 beerFlavor
3 misterBurnsAge
Every Java developer should follow the JavaBeans convention for naming different elements of code.
Following a defined and agreed-upon standard ensures your code is readable and understandable
by a wide audience.
1 homerGrade = homerGrade + 2;
2 System.out.println(homerGrade);
1 int homerGrade = 2;
2 homerGrade += 2; // #1
3 System.out.println(homerGrade); // #2
Other versions of the assignment operator exist for decreasing the value of a variable (-=),
multiplying or dividing it by another value (*= and /=), and more.
1 int homerGrade = 0;
2 homerGrade = homerGrade + 1;
But there’s a more concise option: using the increment operator, ++. You can add one to a variable’s
value by including ++ before or after the variable’s name in an expression. These are known as the
pre-increment and post-increment operators, respectively.
Let’s see how the post-increment operator works:
2 Variable types and flow control (Review) 15
1 int homerGrade = 0;
2 System.out.println(homerGrade++);
3 System.out.println(homerGrade);
1 0
2 1
As you can see, the value of the homerGrade variable is only incremented in the line following the
increment operation.
The pre-increment operator acts in a different way—the variable’s value is incremented by one on
the same line where the operator appears. For example:
1 int homerGrade = 0;
2 System.out.println(++homerGrade);
With the pre-increment operator, the value is incremented first and then the result is computed.
With the post-increment operator, the result is computed and then the value is incremented.
1 int homerGrade = 2;
2 System.out.println(homerGrade--);
3 System.out.println(homerGrade);
1 2
2 1
When using the pre-decrement operator the value of the homerGrade variable will be decremented
right away, before the result is computed:
2 Variable types and flow control (Review) 16
1 int homerGrade = 2;
2 System.out.println(--homerGrade);
3 System.out.println(homerGrade);
1 1
2 1
Operator Description
* Multiplication operator
/ Division operator
% Remainder operator
+ Additive operator (also concatenates String)
- Subtraction operator
Let’s explore then the operators precedence. Like in math, multiplication and division have
precedence over addition and subtraction. Let’s see some examples:
System.out.println(5 + 5 * 5);
The result of the code above will be 30 because the multiplication operation is the priority and will
be executed first. Considering that 5 * 5 will be 25 and then will be added to 5.
Then let’s analyze a similar example with division that is also a priority:
System.out.println(5 + 5 / 5);
In the code above, the priority will be decided by the order the operators were introduced. Therefore
5 / 5 will be executed first with the result of 1 and then will be multiplied by 1 which will give us
the output of 1.
Let’s see now what happens when we use multiplication first:
System.out.println(5 * 5 / 1);
• Multiplication, division and the remainder operators have priority over addition and subtrac-
tion.
• If we are using only the multiplication, division and the remainder operators, the priority will
be decided by what operator was used first.
• Parenthesis will always be the highest priority over any operator.
1 // The following code will calculate the sum from number1 and number2
2 int number1, number2 = 5;
3 System.out.println(number1 + number2); // The JVM will print 10
In real-world projects it’s not recommended to pollute code with several comments between code
instructions. According to Uncle Bob (Robert Martin), author of the popular handbook Clean Code
(Prentice Hall, 2008), commenting code is a way to say that we’ve failed at making our code simple
to understand.
Comments like these are used in the examples in this book for the purpose of clarity and to ensure
that the reader can follow along, but keep in mind that I would avoid using such comments in a
real-world project.
Another popular use for comments is to provide descriptions of variables, classes, and methods, as
a formal way to document your code. (Don’t worry, we’ll study all those concepts in depth in the
next chapter.)
You’ve already seen the main Java method, so let’s use that as an example:
2 Variable types and flow control (Review) 19
1 /* This is the main Java method that will bootstart the program */
2 public static void main (String[] args) { }
The compiler ignores everything between the /* and the */, so these comments can span multiple
lines. Documentation comments achieve the same effect:
1 /**
2 * This is the main Java method that will bootstrap the program
3 */
4 public static void main (String[] args) { }
You can also use commenting features known as tags in your comments, like these:
To specify additional information such as the author and what software version the code became
available in.
You can use HTML (Hypertext Markup Language) to format your code comments too, applying tags
such as <p> for a new paragraph, <b> for bold text, <table> to add a table, and so on. You can explore
classes from the Java Development Kit (JDK) to see some examples, the String class for example is
a good one to take a look at.
For the purposes of this book you don’t need to know all the possible tags and options for
commenting code; you can learn the main tags and apply them where appropriate.
When this command is executed, "Homer can drink beer" will be printed.
Notice that in this example braces ({}) are not used around the condition for the if statement. That’s
perfectly allowed in Java, but only when there’s a single instruction for the if statement. If we do
this, for example, the second message will always be printed, even if (as in this case) the condition
homerWeight > 340 is not true:
The point //#1 in the code will not be considered part of the if statement and will always be executed,
because there are no braces.
Not using braces might be fine for some cases, but you might forget to add the braces when adding
a new instruction to the if condition, so you must be careful if you decide to leave them out. The
safest practice is to always include curly braces.
In this case, if a person is at least 12 AND has money for a ticket, then they will be able to watch a
horror movie in the cinema. Note that both conditions must be true for the person to be able to go
to the cinema.
There is another important rule with the && operator that makes sense in terms of performance.
Because both conditions must be true, we know that if the first condition is false, the second
condition doesn’t need to be evaluated. This is exactly what happens: the JVM won’t bother to
evaluate the second condition if the first is false because it already knows that the && operator will
return false. This is known as short-circuit evaluation.
Let’s look at an example to illustrate how this works. In this example the first condition, bartAge >=
12, won’t be fulfilled, so the second condition, bartMoney >= 10, won’t even be evaluated:
You can prove this by checking the value of the bartMoney variable, which will be incremented by
one when the second condition is executed. When you print this variable after the if statement it
prints 20, which shows that the JVM did not execute this condition.
In this case, only one condition has to be true: if Bart has a ticket or money to buy a ticket, he will
be able to watch a movie at the cinema. Even though only the isBartWithMoney variable is true, the
|| operator will return true.
Again for performance reasons, the || operator works in a similar way to the && operator. If the first
condition is true, the second condition won’t even be checked because the JVM already knows the
operator will return true.
You can see this in code using a similar example to the one in the previous section:
Because the first condition is true, the second condition won’t be executed and the bartMoney
variable won’t be incremented. Also note that the if condition is met because we are using the ||
operator and at least one condition is true. Therefore, Bart can go to the cinema.
The NOT logical operator
The final logical operator is NOT, represented in Java with !. This operator takes a Boolean value as
its operand and reverses it—if the condition is true the ! operator will transform it to false, and vice
versa. For example:
This technique is useful for making code more readable, especially when there are multiple
conditions to compare inside a single if statement. A very powerful book to read if you’d like to
dig deeper into code quality and code refactoring is Martin Fowler’s Refactoring: Improving the
Design of Existing Code (Addison-Wesley).
Another option for improving the readability of code where there are several conditions in a single
if or other Java statement is to use nesting, placing one if statement inside another. It’s best to avoid
excessive nesting because it adds complexity and degrades the quality of the code, but considering
that you are highly likely to see code nesting in real projects, it’s good to know how it works. Here’s
an example:
Even better, we could improve the code in this case by using &&, and the result would be exactly
the same:
Can you see how much simpler your code can be if you know the different features of Java? This
is just one simple example of how an in-depth knowledge of what the language offers can help you
write much clearer, better, and more meaningful code.
Condition ? Return a value when condition is true : Return a value when condition is false
The following code checks whether the value of the beerQuantity variable is greater than or equal
to 50. If so, the value “Homer” will be returned. If the condition is false, the value “Moe” will be
returned instead:
The value “Homer” will be printed because the condition of the ternary operator is true.
Note: String
String is a class that represents characters. You don’t need to worry about the details for
now; this will be explored further in a later chapter.
In the same way it is possible to nest a normal if statement inside another one, it is also possible to
nest ternary operators—but this is far worse. Nesting ternary operators will make code maintenance
a nightmare.
Here’s an example:
1 String beerOwner = beerQuantity >= 100 ? beerQuantity >= 200 ? beerQuantity >= 300 ?\
2 "Barney" : "Homer" : "Lenny" : "Carl";
It’s very confusing and difficult to understand, and you should avoid doing this at all costs.
10
11 batchOfBeer1 = (batchOfBeer1++ > batchOfBeer2 &&
12 ++batchOfBeer2 < batchOfBeer1++ ? ++batchOfBeer1 : ++batchOfBeer2);
13
14 System.out.println("Homer will have: " + batchOfBeer1-- +
15 " and " + batchOfBeer2++ + " beers.");
16 }
17
18 }
Explanation:
The first key for this challenge is to know that the pre-increment operator increases a variable’s
value at the time the variable is used, and the post-increment operator increases the value after the
variable is used. The other important thing to know is that the short-circuit logical operator ||
will only check what is necessary to determine whether the condition will be fulfilled or not. That
means in the first if:
Only the first condition will be checked. Because it’s true, the second condition doesn’t need to be
checked. Therefore, only batchOfBeer2 will be incremented. Then, the batchOfBeer1 variable will
be incremented inside the if statement.
So, these will be the values of the two variables:
1 batchOfBeer1 = 6;
2 batchOfBeer2 = 6;
The condition batchOfBeer1++ > batchOfBeer2 will be false because the batchOfBeer1 variable
will be incremented only after this condition is checked. batchOfBeer1 and batchOfBeer2 will have
the same value, 6, so the “else” condition will be fulfilled and the third operand will be evaluated.
Because we are using the pre-increment operator the batchOfBeer2 variable will be incremented to
7, and this value will be assigned to the batchOfBeer1 variable.
1 batchOfBeer1 = 7
2 batchOfBeer2 = 7
Finally, in the last System.out.println the variables will each be incremented, but only after this
statement has executed.
In conclusion, the right alternative is A, with the final output:
Homer will have: 7 and 7 beers.
1 int variableToCompare = 3;
2
3 switch (variableToCompare) {
4 case 1: System.out.println("First condition");
5 case 2: System.out.println("Second condition");
6 default: System.out.println("Default condition");
7 }
This prints:
Default condition
Let’s look at an example:
2 Variable types and flow control (Review) 27
As you can see, the case "Duff" will fulfill the condition, so "Barney prefers Duff" will be printed.
But then "Some other beer" will also be printed, and that’s not what we want, right? We want only
the first message to be printed, not the default.
To only fulfill the "Duff" condition, we need to add the break keyword after each case statement so
that we break the switch case flow after doing what we want.
It’s important to know the nuances of the switch case statement, because at times you may find
yourself maintaining code that is not so clear. If you don’t know how the core Java features work,
you will have a hard time understanding or fixing bugs.
There are more optimized and less verbose ways to use the switch case statement in Java; we will
explore those in future chapters because they involve more advanced concepts.
A. Ne
B. NeZnMnHe
C. NeZnMn
D. NeHe
Explanation:
The formulaNumber with the value of 10 will fulfill the condition, so Ne will be printed. But note that
we are not using the break keyword after each case. The switch statement will continue executing
until it finds this keyword. This means Zn and Mn will be concatenated to Ne before the switch
statement is interrupted with the break keyword, giving us the final output of NeZnMn.
In conclusion, the correct alternative is:
C - NeZnMn
2 Variable types and flow control (Review) 29
2.4 Loops
Looping in programming is a way to repeat certain logic until a condition is fulfilled. You can
probably think of several examples in the real world: when we are brushing our teeth, we repeat
the brushing action until they are clean; when we study for a test, we keep studying until we’re
confident we know enough to pass it; when we need to drive somewhere, we keep driving until we
get where we want to go.
As in real life, in programming loops are used all the time. But not all developers know the full
range of possibilities with loops in Java. We are challenged every day with different problems, and
knowing the nuances of loops will help developers to produce the best code possible.
1 for (/* Initialize variable */; /* Condition to continue the loop */; /* Incrementer\
2 */) { ... }
You can create an infinite for loop by specifying a condition that will never be false, or omitting the
condition:
for ( ; ; ) { System.out.println("This will print forever"); }
This code will print only 2. Because the break keyword is inside the for loop, the loop will be broken
and the following numbers won’t be printed.
There is a code instruction at the end of the for loop that prints the text "Goku hits Majin Buu" to
the console. But we only want this text to be printed when the value of the variable i is less than 3.
If the i value is 3 or greater, we don’t want instruction outside of the if statement to be executed;
instead, we want to print the message “Goku beats one Majin Buu with " + i + " hits”. A perfect
solution for that is to use the continue keyword to go to the next loop iteration.
When the if condition is fulfilled, the continue statement will send control back to the top of the loop
body, skipping any statements below it; this means the second print statement will not be executed.
Keep in mind, though, that you should generally avoid using the continue keyword because having
multiple flows in code makes it harder to read and maintain. Use this feature only if it is particularly
suited to the situation you are coding for.
possible because most developers don’t know about it and it will make the code harder to maintain—
but still it’s useful to at least know that the feature exists with Java.
Here’s a code example that uses labels:
1 outer:
2 for (int i = 0; i < 5; i++) {
3 inner:
4 for (int j = 0; j < 2; j++) {
5 if (j == 2)
6 continue inner;
7 if (i >= 3)
8 break outer;
9
10 System.out.println(i + ":" + j);
11 }
12 }
In this code, we’re manipulating the outer loop (labeled with outer:) and an inner loop (labeled with
inner: ).
When the value of the i variable declared in the inner loop is 2, the inner loop will force the
continuation of the inner loop avoiding the execution of the following instructions within the inner
loop.
In the second if statement we are checking if the value of the i variable is greater or equals to 3, if this
condition is fulfilled we will break the outer loop which will break the inner loop as well. Therefore,
the final output of this program will be:
1 0:0
2 0:1
3 1:0
4 1:1
5 2:0
6 2:1
1 int i = 0;
2 while (i <= 10) { System.out.println(i++); }
It’s also important to mention that the while loop might not be executed at all. This is the case when
the while condition is false the first time through the loop, as in this example:
1 int i = 20;
2 while (i <= 10) {
3 System.out.println("This while loop instruction won't be executed");
4 }
1 int i = 0;
2 do {
3 System.out.println(i++);
4 } while (i <= 10);
In this example, even though the condition is not fulfilled, the loop will still be executed once,
printing 99:
1 int i= 99;
2 do {
3 System.out.println(i++);
4 } while (i <= 10);
1 int [] homerBeers = { 0, 1, 2, 3 };
2 for (int homerBeer : homerBeers) {
3 System.out.println(homerBeer);
4 }
2.5 Arrays
When you need to use multiple values in one variable, the most efficient way to do so is by creating
an array.
An alternative is to do the following:
But as you can see, the variable names are repetitive and that makes the code confusing. If you use
the array type instead, you can use a single variable name with multiple values:
String [] simpsonCharacterNames = { "Moe", "Burns", "Bart" };
This representation is much more compact, taking up just one one line. You can then access each
element of the array by index. Array indexes in Java start with 0, so if to print “Burns” you would
do the following:
System.out.println(simpsonCharacterNames[1]);
You can print all the information in the the array using any kind of loop, such as a standard for loop:
A while loop:
1 int i = 0;
2 while (i < simpsonCharacterNames.length) {
3 System.out.println(simpsonCharacterNames[i++]);
4 }
Or a for-each loop:
2 Variable types and flow control (Review) 34
You could even use streams and method references (these concepts will be explained in future
chapters, but here’s an idea of what this would look like):
Arrays.stream(simpsonCharacterNames).forEach(System.out::println);
1 Moe
2 Burns
3 Bart
If we want only to declare an array and create an instance of this array, we can use the following
syntax:
int [] simpsonCharacterAges = new int[3];
Note that the keyword new here is related to the array instance and not the primitive type int.
Now that we have an array instance with a size of three elements, we can populate it with values,
as follows. Note also that in Java the initial index of an array is 0, not 1:
1 simpsonCharacterAges[0] = 35;
2 simpsonCharacterAges[1] = 102;
3 simpsonCharacterAges[2] = 10;
1 35
2 102
3 10
It will print 0.
D. nullMario Luigi
The key to this challenge is the array index. As mentioned earlier, array indexes start from 0. Here
we are creating an array with three elements. Because every array in Java starts with the index of
0, not 1, this means it’s impossible to access the index 3, and if we try that the JVM will throw an
ArrayIndexOutOfBoundsException.
This exception name is very clear: it tells us that we can’t access an element whose array index is
out of bounds. For now, don’t worry too much about understanding exceptions in depth; we will
explore this concept in detail in future chapters.
In conclusion, the correct alternative is:
C - java.lang.ArrayIndexOutOfBoundsException will be thrown
1 0, 0 | 0, 1 | 0, 2
2 1, 0 | 1, 1 | 1, 2
3 2, 0 | 2, 1 | 2, 2
In each pairing you can think of the first index as the row number and the second as the column
number.
To create a tic-tac-toe board, you could use the following code:
1 0,0|0,1|0,2|
2 1,0|1,1|1,2|
3 2,0|2,1|2,2|
22
23 System.out.println(simpsonsFoods[0][0] + "|" + simpsonsFoods[0][1] + \
24 "|" + simpsonsFoods[1][0] + "|" + simpsonsFoods[1][1]);
25 }
26 }
A. Junk|null|Healthy|Junk
B. Junk|Junk|Junk|Healthy
C. Junk|Junk|Healthy
D. Junk|Healthy|Junk|
Variables that are declared inside a method can only be used within that method block. These are
called local or method variables. Local variables effectively come into existence when a method starts
its execution and cease to exist when the method ends its execution. They’re accessible anywhere
within the method block, including in inner/nested blocks, but there is no way to access a local
variable outside of the method in which it was declared.
In the following example, the simpsonName variable is declared in the main method and used inside
the enclosed for loop:
It’s possible to declare a variable in any Java statement. If you declare the variable in a for loop, if
statement, or any other non-method block, the local variable will only be accessible there. This is
known as block scope.
In the following example, you won’t be able to access the allanHangoverAge variable outside the if
block. Because the variable was declared inside the if block, it can only be used there:
Attempting to run this code with the print statement uncommented would result in a compile-time
error, and most IDEs would highlight this line and variable as being problematic.
We could even create a block without a statement (an “anonymous block”), and the same rule would
be applied:
2 Variable types and flow control (Review) 40
Note that in practice, these blocks are rarely used outside of switch case statements.
As these examples show, a local variable is accessible only within the block (set of braces) in which
it was declared, and any nested blocks.
It’s also possible for a variable to be local to a loop. In a real project, you may come across code like
this:
Here, the outerLooping variable is declared and accessible in the outer for loop and in the inner for
loop, but not outside both loops.
The innerLooping variable is declared and accessible only inside the inner for loop.
Another corner case in most Java statements is when we don’t use braces. If we attempt to include
multiple instructions without braces, only the first one will be executed and any variables we
declared within the loop will not be accessible beyond that first instruction. For example:
2 Variable types and flow control (Review) 41
1 012
2
3 This statement is not part of the for loop
A. System.out.println(beersQuantity);
System.out.println(homerFavoriteFood);
System.out.println(isHomerFat);
B. System.out.println(homerFavoriteBeer);
2 Variable types and flow control (Review) 42
Explanation:
Let’s first analyze the alternatives that won’t compile.
Alternative B can’t be right because homerFavoriteBeer is only accessible inside the anonymous
block.
Alternative C can’t be right because it begins by initializing a variable that can’t be used in just
one line. Because the if statement does not contain braces, it will be interpreted by the compiler
as containing only one instruction (the variable initialization). It will be impossible to do anything
with this variable afterwards because its scope will be local to that statement, so this code does not
make sense and doesn’t compile.
Alternative D is incorrect for a similar reason. Because the for loop does not contain braces,
it will be interpreted as containing only one instruction: in this case, if (isHomerFat)
System.out.println(homerFavoriteFood);. The beersQuantity variable is initialized in the
for statement, but we are trying to use it after the if statement. This code won’t compile because
that variable will not be accessible in the second line of code.
Finally, alternative A is correct. In this case the variable beersQuantity that was declared in the for
statement is accessible in the if statement. The homerFavoriteFood variable is also accessible: it was
declared in the outer if statement that encloses the for loop and inner if statement. The isHomerFat
variable was declared at the beginning of the main method and therefore is accessible throughout
the whole main block.
1 int moeAge;
2 System.out.println(moeAge);
1 int beerCount;
2
3 if (true) {
4 beerCount = 0;
5 }
6
7 System.out.println(beerCount);
1 int beerCount;
2
3 if (1 == 1) {
4 beerCount = 0;
5 }
6
7 System.out.println(beerCount);
If you try to use a boolean variable in the if condition, you’ll get a compilation error because the
JVM can’t predict if the variable will be true or false, and therefore the beerCount variable might or
might not be initialized:
1 int beerCount;
2 boolean test = true;
3
4 if (test) { // The JVM can't predict the beerCount variable will be initialized
5 beerCount = 0;
6 }
7 System.out.println(beerCount); // Compilation error here
If we try to run the program above we will get the following compilation error message:
variable beerCount might not have been initialized
The solution for this code is to use the else clause to ensure that the variable is initialized in every
case. The following code will compile fine:
2 Variable types and flow control (Review) 44
1 int beerCount;
2 boolean test = true;
3
4 if (test) {
5 beerCount = 0;
6 } else {
7 beerCount = 1;
8 }
9
10 System.out.println(beerCount); // This will print 0
Points to remember
totally used. If we count the number of candies that can be purchased, there will be 4 items for this
test case (1 + 2 + 3 + 4 = 10).
Array sorting wasn’t addressed in this chapter, but for now you can use the bubble sorting strategy,
which is one of the simplest ways to sort an array.
You create the candyPrices variable with the prices you wish, and then the following code should
work to sort your array:
Once you have the array sorted, it’s up to you to print the information on the largest number of
candies that can be bought with the money the boss has available.
Try the following test case scenario: you receive the array of candy prices [8, 4, 1, 3, 10, 11],
and the company boss has 20 dollars to spend on candies. The count of maximum possible candies to
be bought should be 4. You can use any kind of loop or flow control statement that you find suitable
for your logic.
2.8 Summary
• You learned about the different primitive types available in Java, what kinds of values they can
store, and how to name them.
• How to assign values to variables and how to increment and decrement the values of numerical
variables.
• Different flow control options available in Java were introduced, such as the if statement,
ternary operator, and switch case statement.
• How to use logical operators for comparing conditions and boolean variables to simplify code
dealing with complex conditions.
2 Variable types and flow control (Review) 46
• The core types of loops are: for, while, do while, and for-each.
• Multiple-valued variables, also called arrays, and you learned that you don’t need to declare
several different variables to handle multiple values.
• Declare, iterate, and manipulate bidimensional arrays.
• The restrictions of local variables were discussed, including variable scope, where to declare
them, and when to initialize them.
By applying the concepts in this chapter, you were able to solve an algorithm of reasonable difficulty.
Congratulations, you’ve finished the first chapter of Java Challengers! You are on your way to
becoming a better programmer and learning how to analyze and improve complex code.
3 Basic object-oriented programming
(Review)
This chapter covers:
Before the advent of object-oriented programming there was the procedural paradigm, which made
it very difficult to reuse code. With the procedural paradigm code easily gets spread throughout the
whole system, and as a result it’s hard to maintain. For example, every time it was necessary to
validate a certain type of ID, developers would have to invoke the same function to do this. Any
change to that function then required changes to numerous parts of the codebase. Developers also
had to write extensive documentation to ensure functions were used correctly.
To solve these problems, the object-oriented paradigm was created. The main intention of this
paradigm is to minimize differences between software code and the real world, making it easier
to solve business problems in a more concise way. With object orientation we can better express
real entities and actions from real-world problems, making it easier to reuse code.
Object orientation also makes it easier to organize the code structure, write less code, and centralize
code features, which makes applications more flexible and with encapsulated logic.
When you’ve mastered object-oriented concepts, it’s possible to create easy-to-maintain code whose
features are well organized and simple to track down.
To make the difference between both paradigms, procedural and object-oriented programming
clearer, let’s analyze diagrams describing them.
Let’s first analyze how difficult it is to organize code when working with the procedural paradigm:
3 Basic object-oriented programming (Review) 48
Now let’s analyze how easier it is to understand the communication between different objects
instead of only functions:
3 Basic object-oriented programming (Review) 49
It’s also important to emphasize that even when working in a language that uses this paradigm, like
Java, it’s possible to write code that does not fully take advantage of the power of object orientation.
It’s crucial to fully understand object-oriented principles so that we can develop flexible code that
uses the full power of the key concepts and tools.
In this chapter, we will explore the most fundamental concepts and why they are important in real-
world applications.
3.1 Classes
A class in the object-oriented programming paradigm is a way to abstract and organize some element
from the real world: a computer, game, person, animal, character, discount, payment, house, ship,
city, and so on. Can you spot the similarity between all these diverse items? That’s right, they’re
all nouns—people, places, or things. To make sense in a project (for simple systems, at least), a class
should be abstracted from something that exists in the real world.
An easy way to check for this is to verify that the class’s name is a noun. A class with an action
name (a verb), like PerformAction, should never be used.
Abstracting elements from the real world to create classes that make sense in the project you’re
working on is not an easy task. You need to analyze what the nouns are in the problem you want to
solve, and condense that information into classes that will be meaningful in the solution you want
to develop.
3 Basic object-oriented programming (Review) 50
You can also think of a class like a blueprint or a factory for creating particular objects. For example,
if you create a class with the name Car, you will be able to create a specific car with its own
characteristics.
Here are some examples of class declaration in Java:
1 class Car { }
2 class Animal { }
3 class Discount { }
As you can see in these examples, we can create a car, an animal, or even a discount.
A class contains state with variables and behavior, or actions. For example, a Car class might define
variables such as maximumSpeed, color, brandName and actions such as accelerate and brake. You
might also create a class named Calculator with actions for performing different operations with
numbers (add, subtract, multiply, and so on) and printing the result.
Now that we know that a class is a blueprint, in the remainder of this chapter, we’ll explore how to
make things happen using this blueprint.
Figure 3.1 illustrates this concept of creating living copies of the class from the blueprint it provides.
The jamesGosling and robertCecil instances are individual copies of the Person class with distinct
names and behaviors.
Constructor
Notice that when we are instantiating our class we use () at the end of the class name. This is
actually the invocation of a constructor, which we will explore in detail later in this chapter.
For now keep in mind that the constructor is responsible for initializing a class or instance
constructing the object with the essential information.
Similarly, if we have a Car class we can create an instance representing a Porsche 911 with a
maximum speed of 227 miles per hour, and another instance representing a Ferrari SF90 Stradale
with a maximum speed of 212 miles per hour. Once we’ve created these instances, we can manipulate
each of them in an isolated way. The Porsche and Ferrari can perform different actions without
affecting each other.
To create these instances of a Car, the Java syntax is the following:
3 Basic object-oriented programming (Review) 52
Figure 3.2 illustrates this concept of the Car class as a factory for creating instances with different
attribute values.
Figure 3.2 porsche and ferrari are instances of the Car class.
The following is a small but complete code example where we are instantiating a Java object:
emittedSound. We can instantiate two instances of this class, a cat and a dog, and specify the
attributes for each appropriately: :
1 class Animal {
2 String breed;
3 String emittedSound;
4
5 public static void main(String [] args) {
6 Animal dog = new Animal();
7 dog.breed = "Husky";
8 dog.emittedSound = "woof";
9
10 System.out.println(dog.breed);
11 System.out.println(dog.emittedSound);
12
13 Animal cat = new Animal();
14 cat.breed = "Persian";
15 cat.emittedSound = "meow";
16
17 System.out.println(cat.breed);
18 System.out.println(cat.emittedSound);
19 }
20 }
1 Husky
2 woof
3 Persian
4 meow
Table 3.1 lists the default values for Java primitive and object types.
3 Basic object-oriented programming (Review) 54
It’s important to emphasize that these default values are assigned only to variables declared at the
class or instance level (inside a class, but outside a method). As you’ll recall from chapter 2, local
variables that are not declared at the class level, such as those declared inside a method—must be
initialized manually upon creation.
Let’s see in practice how this works in code:
• #1 Prints false
• #2 Prints 0
• #3 Prints 0.0
• #4 Prints \u0000
• #5 Prints null
It’s important to know how default values in Java work because we deal with variables all the time.
Data that is being passed in the wrong way is a common cause of bugs. Knowing that default
values will be assigned when you don’t do this manually will make a huge difference when you’re
developing code, fixing bugs, or adding new features, enabling you to optimize your code design.
3 Basic object-oriented programming (Review) 55
3.2 Methods
In the object-oriented programming paradigm, a method is an action or behavior of a class or
instance. In Java a method has to declare the data type of the value it returns (we’ll explore this
concept further later in this section). If it doesn’t return a value—for example, if the method just
performs an action—it must be declared void.
Let’s see how a dog emits a sound in Java code. The emitSound method doesn’t return a value (it
simply prints “woof”), so we declare it with the void keyword:
1 class AnimalMethodInvocation {
2
3 String emittedSound; //#1
4
5 void emitSound() { //#2
6 System.out.println(emittedSound);
7 }
8
9 public static void main(String[] args) {
10 AnimalMethodInvocation dog = new AnimalMethodInvocation();
11 dog.emittedSound = "woof"; //#3
12 dog.emitSound(); //#4
13 }
14
15 }
1 class MessageParameter {
2
3 void print(String message) { //#1
4 System.out.println(message); //#2
5 }
6
7 public static void main(String[] args) {
8 MessageParameter messageParameter = new MessageParameter(); //#4
9 messageParameter.print("You are a Java Challenger"); //#5
10 }
11
12 }
When the print method is executed, "You are a Java Challenger" will be printed to the console.
Parameter VS Argument
Notice that I’ve used the terms "parameter" and "argument". Parameters are used when
we are declaring a method. For example, we declare the print method with the message
parameter.
Arguments are used when we are invoking a method. For example, we pass the argument
"You are a Java Challenger" to the print method.
1 class CalculatorSum {
2 int sum(int number1, int number2) { //#1
3 return number1 + number2; //#2
4 }
5
6 public static void main(String[] args) {
7 CalculatorSum calculator = new CalculatorSum(); //#3
8 int sumResult = calculator.sum(2, 2); //#4
9 System.out.println(sumResult); //#5
10 }
11 }
• #1 Declare the sum method with two parameters, number and number2, and specify that it will
return an int primitive type.
• #2 Return the sum of the number1 and number2 variables.
• #3 Instantiate a CalculatorSum instance and assign it to the calculator variable.
• #4 Invoke the sum method from the calculator instance, passing the argument value 2 for
both number1 and number2, and return the result to the sumResult variable.
• #5 Print the value of the sumResult variable, which is 4.
You can return any type of variable from a method; you just need to choose the most appropriate one
for your needs. Let’s see, for example, how we would return a float variable from a multiplication
operation with fractional numbers:
1 class CalculatorMultiplication {
2
3 float multiply(float number1, float number2) { // #1
4 return number1 * number2; // #2
5 }
6
7 public static void main(String[] args) {
8 CalculatorMultiplication calculator = new CalculatorMultiplication(); // #3
9 float multiplicationResult = calculator.multiply(2.50, 2.50); // #4
10 System.out.println(multiplicationResult); // #5
11 }
12
13 }
• #1 Declare the multiply method with the number1 and number2 parameters and specify that it
will return a double.
• #2 Return the result of multiplying the values of the number1 and number2 variables.
• #3 Create a Calculator instance and assign it to the calculator variable.
3 Basic object-oriented programming (Review) 58
• #4 Invoke the multiply method from the calculator instance, passing the argument values of
2.50 and 2.50; return the result of the multiplication to the multiplicationResult variable.
• #5 Print the value of the multiplicationResult variable, which is 6.25.
The value you return from a method can be any of the primitive types: byte, char, short, int, long,
float, double, or boolean.
1 class TextHandler {
2 String concatenate(String firstText, String secondText) { // #1
3 return firstText + secondText; // #2
4 }
5
6 public static void main(String []args) {
7 TextHandler textHandler = new TextHandler(); // #3
8 String concatenatedText = textHandler.concatenate("Java", "Challengers"); //\
9 #4
10 System.out.println(concatenatedText); // #5
11 }
12 }
• #1 Declare the concatenate method with the firstText and secondText parameters and specify
that it will return a String object.
• #2 Concatenate the firstText and secondText values and return them as a String.
• #3 Instantiate Concatenator and assign it to the concatenator variable.
• #4 Invoke the concatenate method and pass the argument values “Java” to firstText and
“Challengers” to secondText; then assign the result to the concatenatedText variable.
• #5 Print the value of the concatenatedText variable.
"Lamborghini" to the brand instance variable and "black" to the color instance variable by invoking
the setupCar method. The this keyword tells the JVM that we are accessing an instance variable,
although because there are no local variables declared with the same names (we’ll consider that
scenario in section 3.3.1) in this case using this is optional:
1 class Car {
2
3 String brand; // #1
4 String color; // #1
5
6 void setupCar() {
7 this.brand = "Lamborghini"; // #2
8 color = "black"; // #3
9 }
10
11 public static void main(String[] args) {
12 Car car = new Car();
13 car.setupCar();
14 System.out.println(car.brand); // #4
15 System.out.println(car.color); // #5
16 }
17
18 }
It’s also possible to invoke an instance method using the keyword this:
10 this.turnLeft(); // #2
11 }
12
13 void turnLeft() {
14 System.out.println("Turn left"); // #3
15 }
16 }
As this example demonstrates, you can invoke instance methods from another method.
Note that you always need to instantiate a class to invoke an instance method. Also note that the
use of the keyword this is optional when invoking an instance method: you can use it or not.
By using the keyword this, we can indicate that we want to access the instance variable color instead
of the local variable from inside the changeColor method, avoiding any confusion. Invoking the
following method changes the value of the color variable of the Car instance to "black":
To sum up, we use the keyword this when we want to access an instance member instead of a local
variable or parameter. When a local variable and an instance variable with the same name exist, the
local variable will take precedence over the instance variable if we don’t use the keyword this. We
need to explicitly indicate that we want to access the instance variable by using the keyword this.
3 Basic object-oriented programming (Review) 62
3.4 Constructors
A constructor in Java is a special type of method whose main responsibility is to construct (create)
an object of a class with certain attribute values. You can also invoke methods in the constructor,
but its primary use is to define the object’s state by providing initial values for its attributes .
In the following example we instantiate a Car object and pass the values "Porsche" and "black" to
the constructor, initializing the attributes of this class:
If you don’t declare a constructor, the JVM will create one automatically for you. When you
instantiate a new object, you are always invoking a constructor. For example, in this case:
1 public SimpsonCharacter() {
2 }
If you do declare a customized constructor, however, the default constructor won’t be created
automatically by the JVM. In this case, because we create a specific constructor, there will be no
default constructor:
This means that you can’t instantiate the SimpsonCharacter class with the following code:
new SimpsonCharacter();
The only way to instantiate this class is by invoking the customized constructor by passing a name:
new SimpsonCharacter("Homer");
If you want to make the class flexible, so it can be instantiated with either a custom or a default
constructor, you need to declare both explicitly:
This concept demonstrated here is called overloading. Simply put, this is a way of making different
versions of constructors or methods with the same name that receive different types or numbers of
parameters. It’s a core concept that will be explored in depth in future chapters.
• #1 This will print “Version name: Batman Forever Model year: 1995”
• #2 This will print “Version name: Arkham Knight Model year: 2014”
As you can see, instead of passing the values "Batman Forever" and 1995 outside our BatMobile class,
we’ve done this inside the class. This default instantiation doesn’t need to be exposed outside of this
class, and that’s what we want to accomplish when we are using the concepts of object-oriented
programming.
Another great advantage of using this approach to invoke a constructor inside the class is the
flexibility we gain; it makes our code much easier to maintain.
A. Leon
Claire
B. Nemesis
Claire
C. Nemesis
Nemesis
D. Leon
Nemesis
Important: Try out the Java Challenge before seeing the answer.
Explanation:
As mentioned previously, the key to this challenge is to understand how the keyword this works. By
using that keyword, we are pointing to the current instance.
The first action we take in this code is to instantiate ResidentEvilNameChange. Then we pass the
String "Leon" to its constructor—but notice that we are using the local variable here because we
are not explicitly using the keyword this. You must remember that when you have a local variable
that has the same name as an instance variable, the local variable will have higher priority and will
be used instead of the instance variable if you don’t use this.
The constructor assigns the value "Leon" to the local variable name, which ceases to exist right after
the constructor is invoked (recall that a local variable is alive only as long as the method in which
it is declared is executing). The instance variable name will keep the value of "Nemesis".
Next we invoke the changeName method, which assigns the instance variable name to itself because
we are using the keyword this twice. We are basically passing “Nemesis” to the name instance
variable again, so its value is not changed.
In conclusion, the final output of this challenge is:
C. Nemesis
Nemesis
duke variable we create when instantiating an object with new Duke("Java rocks!"); will act as a
controller, allowing us to manipulate the real Duke object by changing its properties and passing it
to other methods as an argument.
Figure 3.1 The duke variable we create when we instantiate the Duke object is able to access that
object in memory.
Let’s see how this works in code. In the following example, we first create an instance of the Duke
class, then assign it to the variable alternativeDuke, which will point to the same Duke object
in the memory heap. When we change any of this object’s instance variables (in this case, the
preferredJavaVersion variable), the object itself will actually be changed:
• #1 Create the Duke object in the memory heap and assign the object to the local duke reference
variable.
• #2 Assign the value 11 to duke’s preferredJavaVersion instance variable.
• #3 Pass the object’s reference to alternativeDuke.
• #4 Assign the value 15 to alternativeDuke’s preferredJavaVersion instance variable.
• #5 This prints 15.
• #6 This prints 15.
Both println statements print 15, because it’s the preferredJavaVersion instance variable
of the actual object stored in memory that is changed with each assignment. The duke and
alternativeDuke‘s reference variables both point to the same object, so the second change
overwrites the first one.
3 Basic object-oriented programming (Review) 68
A. 1212
B. 77
C. 912
D. 99
Important: Try out the Java Challenge before seeing the answer.
Explanation:
Your first action in the main method is to instantiate the Challenger object. Then you assign the
value 7 to the level variable.
3 Basic object-oriented programming (Review) 69
The second important action is to invoke the changeLevel method from the challenge instance and
then change the value of the level variable to 9. In doing this, you change the real object in the
memory heap.
The key part of this challenge is when you assign the challenge instance to the alternativeChallenger
variable. Remember that a variable is just a controller that points to the real object in the memory
heap. Therefore, both variables will be pointing to the same object and will be changing properties
of the same object.
When you change the value of alternativeChallenger’s level variable to 12, you are changing the
object you created. challenger’s level variable will then have a value of 12 as well.
In conclusion, the correct answer for this challenge is:
A - 1212
If you got this challenge right, it means you have a good understanding of the concept of object
references.
20 metalGearReference = null;
21 }
22
23 }
Important: Try out the Java Challenge before seeing the answer.
Explanation:
The first point to make about this challenge is that passing the solidSnake instance to the
instance method changeSoldierName is redundant. Because you’re using the keyword this inside
the changeSoldierName method, you are already accessing the instance that invoked this method.
In the changeSoldierName method, you are first changing the solidSnake instance’s name to “Solid
Snake”, then referencing the invoking instance, which in this context is solidSnake, with the
keyword this. Therefore, the solidSnake instance will have the name "Big Boss" at this point.
Finally, you assign the metalGearReference variable the value null—but note that
metalGearReference is only a reference variable, not the real object. Note also that you are
not changing the object itself; you are assigning null directly to this local variable. Only the local
variable will change, and nothing will happen to the real object in the memory heap.
Because the solidSnake instance is still accessible in the main method, the object is still alive. The
last value assigned to the solidSnake object was "Big Boss", so solidSnake.name will have this value
stored into it.
The same process will happen with the instance liquidSnake.
In conclusion, the final answer to this challenge is:
** B - 1 soldier=Big Boss 2 soldier=Big Boss**
You can imagine an object in Java as a controller that has access to the real object. When you declare
an array in Java the array variable is not an object itself; it’s a pointer to an object. Every variable
that you instantiate as an object in Java is just a reference pointing to the real object in memory.
In the following example, we declare the array homerSweets and pass it to the
changeFirstElementToPudding method. We then change the value of the first element of the
array from "Chocolate" to "Pudding". Note that because we are working with an object, the change
will be reflected even outside the changeFirstElementToPudding method:
In this example, because the homerSweets array is pointing to an object in the memory heap, the
value "Pudding" will be assigned to the real homerSweets object from the main method. Because
the homerSweets object is still accessible in the main method and the real object was changed, the
println instruction will print "Pudding".
A. 2 4 6 8 10
B. 02468
C. 00000
D. 4 6 8 10 12
Important: Try out the Java Challenge before seeing the answer.
Explanation:
The key to solving this challenge is to know that an array in Java is actually an object. When you
instantiate an object and assign it to a variable, the object itself will be stored in the memory heap
of the JVM; the variable will simply store a reference that the JVM will use to locate the object and
retrieve it for the Java application.
As long as the object is still accessible in the application, it will remain alive in the memory heap.
How do we know whether an object is still accessible? When there is a variable that is still able to
access the object.
3 Basic object-oriented programming (Review) 73
2 - The following line of code is another big key to this challenge. We are assigning the reference
to the array object, linkOcarinaOfTimeLevels, to linkMajorasMaskLevels. This means that both
variables will be pointing to the same object—they just have different names.
int[] linkMajorasMaskLevels = linkOcarinaOfTimeLevels;
3 - Then we invoke the slayMonsters method and pass the linkOcarinaOfTimeLevels array to it.
During the method invocation, we basically add 2 to each element in the array. Therefore, in this
iteration the array values will be 2 4 6 8 10.
linkAdventure.slayMonsters(linkOcarinaOfTimeLevels);
4 - Finally, we invoke the slayMonsters method with the other array variable reference, linkMajoras-
MaskLevels, which points to the same object in the memory heap. Because both array variables are
pointing to the same object, the effect of invoking the slayMonsters method twice will be to have 2
added to each element’s value twice. Therefore, we will have the values 4 6 8 10 12.
linkAdventure.slayMonsters(linkMajorasMaskLevels);
In conclusion, the correct alternative is:
D - 4 6 8 10 12
Invoke the transferAmount method again from the origin Account, passing 1000 and the recipient
Account as parameters.
Make sure your validation message is printed and check that the balance of the origin Account is
still 900 and the balance of the recipient Account is still 1100.
Disclaimer
This chapter’s real-world challenge was purposefully designed in a simple way to test how well
you’ve understood the concepts introduced here. In a real project you would likely have to add
more validation checks, externalize these into different classes, and so on, but for now that’s not
relevant.
You would also want to create unit tests to test the code and make sure nothing gets broken when,
for example, you refactor it (make it cleaner and better). You could then simply run the automated
test after making any changes to make sure everything still works. Another great benefit of using
unit tests is that it encourages you to create better and more organized code from the outset, because
you know you’ll be testing it later.
As a bonus, you can try to create your own unit test—it’s out of the scope of this chapter, but there
is a unit test in the source code that you can check out. You’re basically doing the same as you did
in the main methods; if you take a look at the test code you’ll understand.
Congratulations! You’ve completed a very important part of your Java education. Java relies strongly
on object-oriented concepts, and by absorbing the concepts of classes, instances, attributes, and
object references you’ve taken a crucial step in your software engineering career.
3.8 Summary
In this chapter you learned how to abstract the real world and transform it into code by using the
object-oriented programming paradigm.
If a class has too many responsibilities and does too many things, we would be also breaking the
concept of encapsulation.
The interface of a car is very intuitive, we know for example that if we access the accelerate method
the car will move and if we access the turnRight method we will turn right as well.
By applying the same principles of hiding internal functionalities from a class and externalizing
methods that are meaningful to other parts of the system, we will be using encapsulation.
Let’s see the example of the Car class in code then:
As you could see in the code above, we hid the attributes and only exposed the interface methods
that we should interact with. And that should be enough, we shouldn’t need to know a class works
behind the scenes but only know what it does.
Even better would be to make the Customer class more restrictive and not allow the use of setter
methods and transform every attribute in final. For doing that, we have to pass the values on the
constructor:
4 Encapsulation access modifiers, and package structure 79
Note that the approach of passing values into the constructor is only a good one if we have at the
maximum three parameters, more than that, it gets confusing.
Let’s see what approach we can adopt to create complex objects in the next section.
The Builder creational pattern is very famous and useful, in real-projects it’s vastly used. A powerful
way to use the Builder pattern is by making the Object immutable, this means that once the object
is created, the state (attributes) can’t be changed.
The advantages of using the Builder pattern are:
• Immutability, there is no way to change the object values (To have setters in a data object may
be a root-cause of bugs since it’s easy to change the value)
• Readability, it’s far clearer to build a data object by using the builder pattern
• Flexibility, we can easily change the builder structure since it’s encapsulated only in the data
object we are using it.
10
11 private String stealthCloth;
12 private String primaryWeapon;
13 private String secondaryWeapon;
14 private String meleeWeapon;
15 private String[] medicineItems;
16
17 public Builder(String stealthCloth, String primaryWeapon) {
18 this.stealthCloth = stealthCloth;
19 this.primaryWeapon = primaryWeapon;
20 }
21
22 Builder withExtraWeapons(String secondaryWeapon, String meleeWeapon) {
23 this.secondaryWeapon = secondaryWeapon;
24 this.meleeWeapon = meleeWeapon;
25 return this;
26 }
27
28 Builder withMedicineItems(String[] medicineItems) {
29 this.medicineItems = medicineItems;
30 return this;
31 }
32
33 public BigBoss build() {
34 return new BigBoss(this);
35 }
36 }
37
38 private BigBoss(Builder builder) {
39 this.stealthCloth = builder.stealthCloth;
40 this.primaryWeapon = builder.primaryWeapon;
41 this.secondaryWeapon = builder.secondaryWeapon;
42 this.meleeWeapon = builder.meleeWeapon;
43 this.medicineItems = builder.medicineItems;
44 }
45
46 // Getters
47 }
In the following code, we will basically print the information we constructed by using the Builder
pattern. Then, note that we are also using the printf method. If you are not familiar with this method
yet, it will print a String where we use the placeholder %s and at the end of this method, you can
see that the parameters have to be declared there.
4 Encapsulation access modifiers, and package structure 82
To exercise your knowledge about the Builder pattern, create a builder for the IronMan class with
the following attributes, name, age, car and specialWeapon. Then create a static inner class Builder
within it and follow the same process from the Builder above.
Then create another class named as IronManBuilderExecutor and make use of your builder. Try it
out and if you want to check, this exercise will be in the source-code named as IronManBuilder, you
can check it out also but it’s important that you practice it before.
Note: Lombok
Alternatively, to create getters, setters, builder and other boiler-plate code for data
objects you can use Lombok. With Lombok, you use annotations, e.g. @Getter, @Setter,
@Builder, and the code you need will be generated during compilation time. For more about
Lombok, check the following link: https://projectlombok.org/¹
You must be wondering why do we have to use this extensive name? In Java, it’s very common
to reutilize third-party libraries (a group of useful classes to solve specific necessities) and for this
reason, it’s very likely that we will find classes with the same names. But if we use a domain name
and the project name we should never find a duplicate class name, therefore there won’t be any class
conflicts.
Let’s suppose we have to handle a library to handle special operations with dates and the operation
with an external library such as Yoda Time and for coincidence there the classes names are the same
from Java and the Yoda Time library, then what would happen? The code wouldn’t compile, there
would be a conflict and the application wouldn’t work.
Those three main packages that will have the core code in a Java application usually would be named
as:
4 Encapsulation access modifiers, and package structure 84
1 com.company-name.domain-name.system-package
2
3 com.javachallengers.payment.resources
4 com.javachallengers.payment.services
5 com.javachallengers.payment.dao or
6 com.javachallengers.payment.repository
Then we would have other packages to assist our three main packages. Some of them are the config
package which will configure a web application with initial values we need, it might load resources
classes from our application, it will basically initialize the whole application.
Then we also need an exceptions package where we will declare our specific Exceptions for business
requirements. We will go deep into exceptions in future chapters:
com.javachallengers.payment.exceptions
The dtos (Data Transfer Object) package that are the objects used to pass data through the network.
We send a dto to the front-end of our application:
com.javachallengers.payment.dtos
The model layer that are the objects from requirements that represent how is the domain of our
application or how the core business data is abstracted.
com.javachallengers.payment.model
Another package that is common and useful in real-world projects is the util. Usually, in the util
package we can create generic methods for services and avoid code repetition.
“com.javachallengers.payment.util‘
The mentioned packages won’t be exactly the same for all projects. If we are working with micro-
services for example sometimes the feature-name would be removed from the package name since
the domain is the micro-service already, in other applications we would have the feature name after
the system package. Let’s see how those variations would be:
1 com.company-name.brief-project-name.system-package.domain-name
2 com.company-name.brief-project-name.system-package
I personally like the following package structure when we are using a micro-service that is
responsible for only one domain:
com.company-name.brief-project-name.system-package
The other option I like for projects with more than one domain is the following:
com.company-name.brief-project-name.feature-name.system-package
If you’ve been working with Java, you have probably seen many similarities with your project, if
you never seen a real-world Java application, now you know how it’s usually organized and that
will help you to understand a real-world project.
4 Encapsulation access modifiers, and package structure 85
As you can see in the diagram above, we are using the MVC pattern that basically separates the
application by different layers.
4 Encapsulation access modifiers, and package structure 86
This is a great example of encapsulation, each layer is responsible for dealing with one respon-
sibility, the view layer will interact with the user, the controller layer will orchestrate method
invocations between the view and the model layer. The model layer will deal with the business
logic and database interaction.
Then, let’s suppose we have a screen in the system that we need to only show the customer’s basic
information but can you notice the amount of data we have in the Customer domain Object? We have
a product, address and the paymentAgreement objects which will have a lot of unused information,
we will also use a lot more network resources to traffic this object.
That’s why the DTO pattern is very handy to send information to the screen. On this case we just
want to send the Customer’s basic information we can create the following object:
4 Encapsulation access modifiers, and package structure 87
As you can notice in the code above, we removed the extra information we don’t want and just kept
the Customer basic information into the DTO.
Object Serialization:
It’s also important to realize that when we send an object in the network, we need to
implement Serializable. We won’t cover serialization in this book but keep in mind that
you need to serialize a DTO. Notice also that we need to declare a serialVersionUID to avoid
data inconsistency when changing the class structure.
Then we can use the class/method/attribute from another package since all of the members from
ThePublicModier are public:
protected = The class/method/attribute is restricted to the package they are in and when there is
inheritance, they can be accessed in other packages.
Let’s create a class that has a protected attribute and method to see in practice how that works in
code:
1 package com.javachallengers.encapsulation.accessmodifiers.accesstoprotected;
2
3 public class ProtectedModifier {
4
5 protected String protectedAttribute;
6
7 protected void print() {
8 System.out.println("Protected method! This method is accessible in this pa\
9 ckage or outside with inheritance");
10 }
11 }
In the following code as we are working in the same package of ProtectedModifier class, we can
access everything.
4 Encapsulation access modifiers, and package structure 89
1 package com.javachallengers.encapsulation.accessmodifiers.accesstoprotected;
2
3 public class ProtectedModifierTest {
4
5 public static void main(String[] args) {
6 ProtectedModifier protectedModifier = new ProtectedModifier();
7 System.out.println(protectedModifier.protectedAttribute);
8 protectedModifier.print();
9 }
10 }
11
12 package com.javachallengers.encapsulation.accessmodifiers; // #1
13
14 import encapsulation.accessmodifiers.accesstoprotected.ProtectedModifier;
15
16 public class ProtectedModifierOutsidePackage extends ProtectedModifier { // #2
17
18 public static void main(String[] args) {
19 ProtectedModifier protectedModifier = new ProtectedModifier();
20 // protectedModifier.print(); // #3
21
22 // We are using inheritance here
23 ProtectedModifierOutsidePackage outsidePackage = new ProtectedModifierOutsi\
24 dePackage(); // #4
25 outsidePackage.print();
26 System.out.println(outsidePackage.protectedAttribute); // #5
27 }
28 }
default or when we don’t specify one = The class/method/attribute will be only accessible in the
package they are created.
4 Encapsulation access modifiers, and package structure 90
1 package com.javachallengers.encapsulation.accessmodifiers.accesstodefault;
2
3 public class DefaultModifier {
4
5 String defaultAttribute; // #1
6
7 void print() { // #2
8 System.out.println("This method can only be accessed inside the accesstodefa\
9 ult package");
10 }
11
12 }
• #1: This attribute can only be accessed inside the accesstodefault package
• #2: We don’t declare explicitly that we are using the default access modifier.If we are not using
public, protected or private we are using the default access modifier.
1 package com.javachallengers.encapsulation.accessmodifiers.accesstodefault;
2
3 public class DefaultModifierTest {
4
5 public static void main(String[] args) {
6 DefaultModifier defaultModifier = new DefaultModifier();
7 System.out.println(defaultModifier.defaultAttribute);
8 defaultModifier.print();
9 }
10 }
11
12 private = The class/method/attribute can be only accessed in its own class:
13
14 package com.javachallengers.encapsulation.accessmodifiers.accesstoprivate;
15
16 public class PrivateModifier {
17
18 private String privateAttribute; // #1
19
20 private void print() { // #2
21 System.out.println("This method can only be accessed inside the PrivateModif\
22 ier class");
23 }
4 Encapsulation access modifiers, and package structure 91
24
25 }
• #1: This attribute can only be used within the PrivateModifier class
• #2: This method can only be accessed inside the PrivateModifier class
1 package com.javachallengers.encapsulation.challengers.access;
2
3 public class LessRestrictiveModifiers {
4
5 public String publicVariable = "public";
6 protected String protectedVariable = "protected";
7
8 }
9
10 package com.javachallengers.encapsulation.challengers;
11
12 import encapsulation.challengers.access.LessRestrictiveModifiers;
13
14 public class LessRestrictiveModifiersExecutor {
15
16 public static void main(String[] args) {
17 LessRestrictiveModifiers challenger = new LessRestrictiveModifiers();
18 // Insert code here
19 }
20 }
A. System.out.println(challenger.publicVariable);
System.out.println(challenger.protectedVariable);
B. Extend the LessRestrictiveModifiers in the LessRestrictiveModifiersExecutor class and
insert the following code on the main method:
4 Encapsulation access modifiers, and package structure 92
1 System.out.println(challenger.publicVariable);
2 System.out.println(new LessRestrictiveModifiersExecutor().protectedVariable);
1 System.out.println(challenger.publicVariable);
2 System.out.println(challenger.protectedVariable);
1 System.out.println(challenger.publicVariable);
2 System.out.println(challenger.protectedVariable);
Let’s analyze first the incorrect alternatives, the alternative A is incorrect because we are trying to
access our protectedVariable from another package without inheritance, therefore the code won’t
compile. Also note that we are using the publicVariable which can be accessed everywhere in the
project so we won’t have a compilation error with the public access modifier.
Then there is the alternative D that is tricky, the publicVariable will be accessed normally.
The problem is the protectedVariable. Even though we are using inheritance, we are accessing
the LessRestrictiveModifiers class directly so we are not even using inheritance in our code
implementation. We are just declaring it in the LessRestrictiveModifiersExecutor but we are not
using it at all.
Let’s now analyze the correct alternatives, the alternative B is correct. Again, when accessing the
publicVariable will be all fine and the interesting point of this alternative is that we are making
use of inheritance in the code implementation. Even though we are in a different package, we can
use the protectedVariable because we are creating an instance from the class that extends the
LessRestrictiveModifiers class, therefore we can access the protectedVariable with no problem.
Finally, the alternative C is also correct. The publicVariable will be accessed normally. The key
point of this alternative is that we are moving the LessRestrictiveModifiersExecutor class to
the same package of the LessRestrictiveModifiers class, therefore we will be able to access the
protectedVariable normally.
MoreRestrictiveModifiersExecutor and your goal is to figure out what code will compile
by replacing it in the main method from the MoreRestrictiveModifiersExecutor class.
What of the code alternatives you can insert in the main method from the MoreRestrictiveModifiersExecutor
class that will compile and run with no errors? (More than one alternative might be correct)
1 package com.javachallengers.encapsulation.challengers.access;
2
3 public class MoreRestrictiveModifiers {
4
5 private String privateVariable = "private";
6 String defaultVariable = "default";
7
8 public static void main(String[] args) {
9 // Insert code here
10 }
11
12 }
13
14 package com.javachallengers.encapsulation.challengers;
15
16 import encapsulation.challengers.access.MoreRestrictiveModifiers;
17
18 public class MoreRestrictiveModifiersExecutor {
19
20 public static void main(String[] args) {
21 MoreRestrictiveModifiers challenger = new MoreRestrictiveModifiers();
22 // Insert code here
23 }
24 }
Explanation:
Let’s start with the wrong alternatives, in alternative A we are trying to access the defaultVariable
from a different package from where defaultVariable was created. The default modifier only
allows access in the same package.
The alternative B is also wrong because we are trying to access the most restricted access modifier in
Java, the private one that can be only accessed within the class the variable was created. Therefore,
when trying to access a private attribute from another class we will have a compilation error.
The alternative C is correct because first moving the MoreRestrictiveModifiersExecutor class to
the same package as MoreRestrictiveModifiers and then we are accessing the defaultVariable
which will be fine.
The alternative D is also correct because the privateVariable is being accessed within the class it
was created, therefore it will compile and run successfully.
When we want to use the concept of encapsulation effectively, inner classes are crucial to be
understood. One example where inner classes are vastly used is with the builder pattern.
Sometimes we want to create fake data for our tests, and inner classes come in handy to accomplish
this goal.
Let’s check the main types of inner classes:
When we want the enclosing class to be also instantiated when we want to utilize the instance inner
class, then we should create an instance inner class. Let’s see an example of how to work with it:
• #1: We can’t access our CustomPrinter inner class since it’s only accessible within the print
method.
• #2: We can’t create an instance from our enclosing LocalInnerClass to access
• #3: It’s only possible to access the inner class in the print method
A situation that can be useful to create an inner class in a method is when we want to rewrite or
create a specific functionality, maybe only one method to accomplish one task and we don’t want
to make it external in the class for example because this inner class should only be visible within
this method.
• #1: As you can see, we don’t need to create an instance from the EnclosingStaticInnerClass,
we just need to reference the enclosing class to access the StaticInnerClass.
• #2: Reinforcing that we can access private methods since it’s an inner class
• #1: We are instantiating the AnonymousInnerClass object and then opening brackets to specify
we are creating an anonymous inner class
• #2: Then we are creating the printSomethingElse method in this anonymous inner class
• #3: Finally, we are invoking the printSomethingElse() method and then it will print: “Printing
from the anonymous Inner class”.
The biggest advantage of an anonymous inner class is that we don’t need to specify an object name
nor externalize the class. Anonymous inner classes are handy when we want to use a few methods
but don’t want to create a whole structure of a class.
We will explore the subject of anonymous inner class further in future chapters when we see
inheritance and interfaces.
A static method behaves in a very similar way from a static variable. We also don’t need to create
an instance from a class to use a static method, we simply use it right away.
Let’s see then a code example where we can use a method instantly without the need of an instance
by using a static method:
• #1: We are invoking the static method right away, without the necessity of having an instance,
that’s why a static method is also called as a class method
• #2: We only need to specify the static keyword in the print method to make it static
• #1: There will be a compilation error at this line since we are trying to access the current
instance in a static context. In a static context there is not any instance.
4 Encapsulation access modifiers, and package structure 100
4.8.3 Static attribute values are all the same for instances
Since a static attribute is actually a class attribute, when we create a new instance, there won’t be a
specific value for this instance, instead, the static value will be the same for all instances.
As mentioned before a static variable or method are not connected to instances, therefore it doesn’t
matter if we create a new instance or not.
Let’s see a code example where we create different instances and change a static value to see what
will happen:
10 staticValues2.name = "value2"; // #2
11
12 System.out.println(staticValues1.name); // #3
13 System.out.println(staticValues2.name); // #4
14 }
15 }
• #1: This sintax works but it’s not appropriate since we are not manipulating the instance values
but the static value. The variable name will have the "value1" assigned
• #2: The same case from above, since we are accessing a static variable, the name variable is
actually being shared between instances.
• #3: It will print "value2" since we are using a class attribute
• #4: It will print "value2" too
It’s important to emphasize that we should never access a static member by using an instance, it’s
confusing to read a code like this. We should only know that it’s possible to do that in Java but
should never use this kind of syntax.
We should always reference the class name instead of the instance name when manipulating a static
member like the following:
1 ClassName.staticVariable;
2 ClassName.staticMethod();
By following the mentioned pattern, it’s very clear that we are dealing with static members. The
code get more readable and easier to understand.
• #1: We used the Java Beans convention in the USER_NAME variable name and we are using the
final keyword to specify it’s a constant value, it can’t be changed. Finally, it’s a static variable,
it means there is no need to create an instance to access it.
• #2: We used the static variable without an instance
One other very important point to consider about the use of static methods is that implementing
Unit Tests gets harder. It’s harder to test methods in an isolated way when we are using static
methods. Unit Tests are not in the scope of this book but it’s important to keep this principle in mind
too so we can make the best decision.
There are many good JDK examples we can rely on. The Math class is a perfect example. Let’s see
some of the methods in action:
As you could see, in the examples of the Math class, we have simple and clear methods that don’t
need any kind of object state, we simply pass the variable to each method and each method returns
what we want. If we have a similar case such as the Math class, it’s a good idea to use static methods.
Keep in mind that we will usually use static methods for creating utilitary methods.
This technique of looking at the JDK code is a great habit we can apply for the usage of anything we
want in our application. We can learn a lot only by looking at the JDK code.
A. BartBart
45
B. HomerBart
45
C. HomerHomer
22
D. HomerBart
22
Explanation:
The important points of this challenge is that an instance value will belong to an instance and a
static value will belong to the class.
Note that the name variable is an instance variable which means that the instance of homer will
have the value of "Homer" and bart will have the value of "Bart" in the name.
In the age variable, we are dealing with a class variable, therefore when we change the age variable
in homer and bart, it doesn’t matter, the static variable will be the same for all instances since it’s a
class variable. Therefore, when we change the class variable age to 10, it will be 10 for every instance.
Just keep in mind that accessing a static variable with an instance variable is a bad practice, never do
that when developing your code, this challenge was created like this to help you to understand the
instance and static concepts. You should instead use a static variable like this, ‘StaticSimpson.age’.
4 Encapsulation access modifiers, and package structure 105
The last detail of this challenge is that we are also adding one to the age variable and then it will be
11 for homer and bart.
In conclusion the correct alternative will be:
1 D) HomerBart
2 22
1 class InstanceBlockPrint {
2
3 String name;
4 { // #2
5 this.print(); // #3
6 } // #4
7
8 InstanceBlockPrint() { // #5
9 name = "Challenger"; // #6
10 }
11
12 public static void main(String[] args) {
13 InstanceBlockPrint instanceBlock = new InstanceBlockPrint(); // #1
14 instanceBlock.print(); // #7 \
15
4 Encapsulation access modifiers, and package structure 106
16 }
17
18 void print() {
19 System.out.println(name);
20 }
21
22 }
• #1: The very first code execution is when we instantiate the InstanceBlockPrint class.
• #2: Then what is going to be initialized is the instance block, the { means that we are initializing
the instance block.
• #3: We are invoking the print method that will print null here.
• #4: We are closing the instance block with }.
• #5: We are declaring our constructor, only now the constructor will be invoked.
• #6: Only now we will initialize the name variable with the value of “Challenger”.
• #7: Finally we invoke the print method and "Challenger" is printed.
Explanation:
When we are working with a static block remember that it will be executed only for the class and
not for each instance. Note also that the static block will be always the first code to be executed
because the class has to be the first to be loaded, without a class it’s impossible to create an instance
or even to invoke the main method. Therefore the first text to be executed will be static.
Then, after we get the MarioBrosBlockChallenge class loaded along with the static block, the first
text from the main method is printed, then we get Jump into.
In the following line we are instantiating the MarioBrosBlockChallenge class which will invoke then
our instance block printing instance.
After the instance block is executed, the constructor will be invoked therefore printing constructor.
Then we will create a MarioBrosBlockChallenge instance again and remember that the static block
won’t be executed again since the class was already loaded, a class is loaded only once. Therefore
only the instance block and the constructor will be executed and the following will be printed,
instance and constructor.
In conclusion, the correct alternative for this challenge is:
A - static Jump into instance constructor instance constructor blocks.
Then we will implement the findById method in the CustomerService layer returning a Customer
and receiving again the customerId which will have a very simple logic. Keep in mind that the service
layer is the one that should deal with programming logic and business requirements. Therefore
create a logic where if the customerId greater than zero you will invoke our customerDao.findById
method passing the customerId otherwise you will return an empty Customer which would be an
instance of a Customer with no values assigned to the attributes.
Finally, you will implement the findById method in the CustomerDao class which will return a new
Customer.
You don’t really need to implement anything sophisticated here, basically create a new Customer by
using the Builder pattern, assign the customerId to the id field, then populate the other fields with
the information you wish, remember that all fields must be populated.
4.11 Summary
• In this chapter you learned the core principles of encapsulation
• You learned how to use getters and setters
• You learned that we use encapsulation when we know what an Object does and not how
• You learned the MVC (Model View Controller) pattern that helps to encapsulate and organize
layers of the system
• You learned the DTO (Data Transfer Object) pattern that encapsulates the data that should
be used in the User Interface
• You know how and how and when to use access modifiers from the more restrictive to the less
restrictive, private, default, protected and public.
• You learned how to use instance and static blocks
4 Encapsulation access modifiers, and package structure 111
Overloading refers to the ability to use the same method name with different parameters. It’s a
way to make your methods more flexible in Java. Overloading is a widely used technique in object-
oriented programming languages, and you can see it in action in many classes of the JDK. It enables
us to extend the possibilities of methods without breaking existing code. In this chapter you’ll see
several code examples that clarify the main concept and the details of this incredibly useful language
feature.
15
16 void learnAbility(String ability, int binaryCode, String weapon) {
17 System.out.printf("Neo has learned %s with a %s and the code %s \n",
18 ability, weapon, binaryCode);
19 }
20
21 }
We can thus invoke the method with either two or three parameters, depending on the situation.
Overloading is widely used in real systems, and the knowledge of how to apply this powerful concept
improves code quality by simplifying the API that developers use. Method overloading prevents
developers from having to remember multiple method names, because we can simply create different
versions of a method with the same name for different purposes.
Similarly, we can’t use overloading just to change variable names. This code won’t compile either:
To overload a method, their argument lists must differ in one of three ways. We can change the
number of parameters, their types, their order or even mix all those three ways altogether will also
work.
5 Overloading 114
1 // Number of parameters:
2 public class MatrixOverloading {
3 void performAction(int number1) { }
4 void performAction(int number1, int number2) { }
5 }
6
7 // Type of parameters:
8 public class MatrixOverloading {
9 void performAction(int number1, int number2) { }
10 void performAction(float number1, float number2) { }
11 }
12
13 // Order of parameter types (Only valid if the variable types are different):
14 public class MatrixOverloading {
15 void performAction(double number1, int number2) { }
16 void performAction(int number2, double number1) { }
17 }
A type can be widened to any other type that can accommodate its value without information loss
(a “wider” type). For example, a byte can be widened to a short, int, long, float, or double, because
5 Overloading 115
all of those types can accommodate larger values. This means if we declare a variable of type double
and we want to assign a byte into it, it will work.
But if we want to assign a double into a byte, we will instead need to use Java’s casting feature.
This is a way of acknowledging to the compiler that you know you might be losing data on the
conversion, and that you take full responsibility if that happens. It’s an explicit type assignment,
made by specifying the target type in parentheses before the value.
Let’s take a look at some examples of widening and casting with primitives:
As you can see, it’s possible to assign a char to a double and a short to a float, but it’s not possible
to directly assign a double to an int, a double to a byte, or a float to a long.
It’s important to understand type promotion in the context of method overloading because Java will
promote types automatically when possible if the provided arguments don’t match the parameters
lists of any of the defined methods. For example, suppose you define two performAction methods,
as follows:
If this method is called with a byte argument and an int argument, the method call will succeed
because the byte value will automatically be promoted to an int. Similarly, if it’s called with two
float values, both will be promoted to double.
There is also the option of making numerical types explicit in hardcoded values to the JVM, to
accomplish that we can use the correspondent type letters at the end of the numbers:
The suffix L or l is used to designate a long. The lowercase 'l' is not recommended to use since it’s
similar to the number '1', therefore, difficult to read. If we use F or f the number will be interpreted
as a float, and D or d tells the compiler it’s a double. As a general rule it’s better to use capital letters
if you need to do this, because lowercase letters are harder to read in the code.
An important detail about the char type is that when a char is converted to int or long, this is done
by converting it to the ASCII table value of the provided character. For example, suppose we convert
the char ‘1’ to an int, like this:
The output will be 49 because the number 1 in the ASCII table is represented by 49. This same rule
will be applied with any other character. For example, if we convert the char value 'a' to an int, the
5 Overloading 117
number that corresponds to a in the ASCII table will be assigned to the int variable and the output
will be 97.
Like type promotion, it’s important to understand this feature of the Java language in relation to
method overloading. How the JVM interprets a method’s arguments has an effect on whether the
code compiles, and understanding these concepts will help you analyze your own and other people’s
code.
A. short:1
float:1.0
short:49
double:1.0
B. long:1
float:1.0
short:1
float:1.0
C. short:1
float:1.0
long:49
float:1.0
D. long:1
float:1.0
long:49
double:1.0
Explanation:
Let’s analyze the code. In the first invocation we pass the hardcoded value 1:
makeKey(1);
int is the default type interpreted to non-decimal numbers that fit within this type’s range, so the
JVM will convert this hardcoded number to int. But none of the overloaded methods takes an int,
so the type will be promoted to a wider one. The first method the JVM finds that can accommodate
this type is this one:
static void makeKey(long longCode) { ... }
In the second method invocation, we are passing an explicit float because we are using the letter F
after the number:
makeKey(1F);
As we already have a corresponding method for the float type, the output for this invocation will
be:
float:1.0
There’s no method that takes a char, so type promotion will again be used here. As the diagram in
figure 5.1 shows, a char can be promoted to an int, which as we’ve already seen will be promoted
5 Overloading 119
further to a long in this example. The char value will be converted to the corresponding number in
the ASCII table, which is 49. Therefore, the output will be:
long: 49
Finally, the number 1.0 will be automatically converted by the JVM to the type double, and then it
will invoke the method that receives a double:
double:1.0
1 D) long:1
2 float:1.0
3 long:49
4 double:1.0
1 int primitiveInt = 7;
2 Integer matrixInt = new Integer(primitiveInt);
Fortunately, since Java 1.5 we don’t need to do that anymore. We can just assign the primitive type
directly into the wrapper type:
1 int primitiveInt = 7;
2 Integer matrixInt = primitiveInt;
This feature in Java is called autoboxing. The JVM does the work for us, which is great because it
means our code is cleaner and more concise. This is what happens behind the scenes:
Integer matrixInt = Integer.valueOf(primitiveInt);
Auto-unboxing—converting in the other direction—works in the same way. We can write code like
this:
5 Overloading 120
1 Integer matrixInt = 7;
2 int newMatrixInt = matrixInt;
And the JVM will create this code behind the scenes:
1 Integer matrixInt = 7;
2 int newMatrixInt = matrixInt.intValue();
As you can see, the wrapper classes are very similar. But even though they both extend Number
(as do the other numeric types), we can’t assign a Double to an Integer or any other wrapper type.
That’s because of the widening rule—a type can be widened only to another type (class) that it
inherits from. If we try to do this, the second line won’t compile because Integer is not related to
Double with inheritance:
On the other hand, we can assign a Double, Integer, or any other numeric wrapper type to a Number:
5 Overloading 121
It’s also not possible to assign a primitive type directly to a non matching wrapper type. For example,
you can’t assign an int type directly to a Double wrapper. The following code won’t compile:
Double doubleMatrixNumber = 7;
The only way to accomplish this is by casting the value to the corresponding primitive type. Then
the JVM will be able to auto-unbox it:
Double doubleMatrixNumber = (double) 7;
Finally, you should be aware that autoboxing has a cost for the JVM—wrappers use more memory
resources than primitive types. The more you can use primitive types the better, because your logic
operations will be executed faster. But if you need your value to be null or you need to work with
Java collections, for example, which can only store objects, you will have to use wrappers.
1 import java.math.BigDecimal;
2
3 public class MatrixWrappers {
4
5 static String result = "";
6
7 public static void main(String... doYourBest) {
8 wrapBullets(5, 5D);
9 wrapBullets(1L, 5);
10 wrapBullets(new BigDecimal(4), 5D);
11 wrapBullets(5, 4);
12 wrapBullets((short) 5.0, 5.);
13
14 System.out.println(result);
15 }
16
17 static void wrapBullets(Integer integerWrapper, Double doubleWrapper) { \
18 result += "A";
19 }
20 static void wrapBullets(Short shortWrapper, Double doubleWrapper) { \
21 result += "B";
22 }
5 Overloading 122
A. ADCDB
B. BCBAA
C. BCDAC
D. ACCAB
Explanation:
Let’s analyze the method invocations. In the first one we are passing an int and a double value
(indicated by the D):
wrapBullets(5, 5D);
Those two primitive types can’t be widened to a broader type in another method, so the JVM will
auto-box them for us. The invoked method will be:
static void wrapBullets(Integer integerWrapper, Double doubleWrapper)
In the second method invocation we are passing a long primitive type and an int:
wrapBullets(1L, 5);
The long can be widened to a double, so the JVM will do that rather than converting it to a wrapper
type because widening consumes less memory resources. So in this case, it will invoke the following
method:
static void wrapBullets(double doublePrimitive, int intPrimitive) { ... }
In the third method invocation we are using the wrapper type BigDecimal and a double primitive
type:
wrapBullets(new BigDecimal(4), 5D);
BigDecimal is a Number, so this type can be widened to a Number, and the double will be autoboxed.
The method invoked by the JVM is therefore:
static void wrapBullets(Number numberWrapper, Double doubleWrapper) { ... }
The strategy the JVM will choose is widening, so the following method will be invoked:
static void wrapBullets(double doublePrimitive, int intPrimitive)
5 Overloading 123
Finally, in the fifth method invocation we are passing a double that is being cast to a short type,
and another double (note that it’s possible to use a double value like this, without the 0 after the
decimal point; it’s the same as 5.0):
wrapBullets((short) 5.0, 5.);
The only strategy the JVM can choose here is autoboxing. The short primitive type will be autoboxed
to Short and the double primitive type will be autoboxed to Double. Therefore, the invoked method
will be:
static void wrapBullets(Short shortWrapper, Double doubleWrapper) { ... }
In conclusion, the final answer will be:
A) ADCDB
5.3 Varargs
Some methods need to take a variable number of arguments. In Java, the varargs (variable
arguments) feature makes it possible to pass as many parameters as we want in a very flexible
manner. Let’s see what this looks like in code. We define varargs using a standard type declaration
followed by an ellipsis (…):
What methods do you think are going to be invoked? If you followed the rules described previously,
you probably got the answer right: it’s ABC.
In the first method invocation, where we are passing two int primitive types, the JVM will prefer
autoboxing to varargs because it consumes less memory resources.
In the second method invocation we are passing a double and two int primitives, so the only
available option is the method receiving a double and an int varargs.
In the third invocation we pass nothing. Remember that when we use varargs we can pass as many
parameters as we want, including no parameters at all. In this case, the third method will be invoked
because it’s the only method receiving one varargs type and nothing else.
Varargs is the most costly option in terms of resource usage, so the JVM will always choose it last.
It’s lazy and optimized to invoke the method that will consume the least memory. If multiple options
are available, it will prefer (in this order):
5 Overloading 125
1 Widening
2 Autoboxing
3 Varargs
Now that you’ve learned the main principles of varargs, let’s try solving a challenge!
A. 322451
B. 231551
C. 231555
D. 332455
Let’s analyze each of the method invocations in turn. First we pass an int and a boolean type:
jump(1, true);
5 Overloading 126
The only method capable of receiving these types and autoboxing is:
static void jump(Object... o) { finalResult += "2";}
When we pass nothing, the varargs method with the most specific type will be invoked:
static void jump(Exception... i) { finalResult += "3";}
Remember that an array in Java is an object, so in this case it’s easier for the JVM to widen the array
to an Object instead of invoking the varargs method. The invoked method will be:
static void jump(Object o) { finalResult += "1"; }
**Varargs is an Array **
Varargs is an array behind the scenes. If there was a method receiving a varargs of int,
it would be easier for the JVM to simply invoke it when passing an array of int. In this
exceptional case using varargs would take priority over widening.
The easiest strategy for the JVM is to widen a long to a double primitive type. Therefore, the invoked
method will be:
static void jump(double d) { finalResult += "5";}
The quickest solution for the JVM is again to widen the int type to double, so the same method will
be invoked:
static void jump(double d) { finalResult += "5";}
The best strategy for the JVM is to widen the Double wrapper type to an Object, so the last method
invoked by the JVM will be:
static void jump(Object o) { finalResult += "1"; }
You can use this simple DiscountService class for your implementation:
You need to implement a new business requirement where you must pass an extra String containing
the userName to log information about which the user is executing this action. The solution is to
implement a new method with the extra userName parameter, log the customerId and the userName,
and finally invoke the existing method, reusing its functionality.
5 Overloading 128
Instead, you want to overload this method and pass in only a String with the customer name.
To solve this part of the challenge you can use this pre created CustomerDao class:
And you can use this simple POJO (Plain Old Java Object) to pass the String into it:
5 Overloading 129
How could you redesign the CustomerService class to meet your needs using the power of method
overloading?
Your goal is to create a method, processCustomerProduct, that can be invoked with only a
customerId or with a customerId and a discountId, or with a customerId, productId, and
discountId.
The method that receives one or two parameters should invoke the method that receives three
parameters, passing null for the missing values. This is a more efficient design than creating three
methods with different names because the logic that must be used is very similar in each case, and
instead of having this logic externalized, it’s better to have this intelligence encapsulated in the
service. How can you do that?
Insert your overloaded methods in the following code:
Don’t worry about the logic of the method that will receive three parameters; simply print the three
attributes with a default message. The important point of this challenge is to use overloading.
The knowledge you’ve gained in this chapter will enable you to create flexible and extensible method
APIs that are easy to maintain and understand. The more Java you learn the better your code gets,
and the more capable you will be to solve complex code problems like these.
5.5 Summary
• In this chapter you learned how to make effective use of method overloading to create flexible,
reusable, and easy-to-read methods.
• You learned the widening order for primitive and object types.
• You learned how to use casting to convert a wider to lower types
• You learned how the JVM interprets hardcoded numbers and how to explicitly declare them
with a specific type.
• You know how to make good use of wrappers with autoboxing, and you understand the
drawbacks and tradeoffs of working with wrapper types.
• You learned how to pass multiple parameters to a method in a clean way by using varargs.
• You are now capable of using advanced overloading, and you know that the JVM is lazy and
will choose the least memory-consuming method to invoke when different options are available
(from widening to, autoboxing, and finally varargs).
6 Inheritance and Polymorphism
This chapter covers:
• Developing code using inheritance and polymorphism, and overriding methods from a
superclass
• Understanding that every class in Java inherits from Object
• Understanding access modifiers
• Choosing between inheritance and composition
• Inheriting from interfaces (an alternative option for multiple inheritance)
• Developing flexible code by using the super keyword effectively, and invoking constructors
with super
• Casting objects with the use of inheritance
• Preventing inheritance of classes and methods with the final keyword and the private modifier
• The differences between normal and abstract classes, and how to create an abstract class design
that makes sense in a system
• Total of 5 Java code Challenges
Composition is another valuable code reuse mechanism, with lower coupling. With composition,
one class can have an instance of another class as one of its fields—for example, a House can be
composed of Bedrooms, a Kitchen, and so forth, and each of those component parts can be changed
independently. This is an important concept in object-oriented programming, where our goal as
developers is to abstract and translate real-world problems into code to develop solutions that will
make people’s lives easier.
6 Inheritance and Polymorphism 132
Conversely, an inappropriate use of inheritance would be for Dog to inherit from Cat: it wouldn’t
make sense to say “Dog is a Cat.”
Inheritance in Java is declared by using the extends keyword in the subclass’s definition. For
example:
It works the same way with both concrete classes and abstract classes, which are classes that cannot
be instantiated. The Java API uses these extensively to encapsulate functionality and state. We’ll
look at abstract classes in depth later in this chapter.
1 class Hero {
2 void usePower() {
3 System.out.println("The hero is going to use his power!");
4 }
5 }
6
7 class SpiderMan extends Hero {
8 @Override
9 void usePower() {
10 System.out.println("Hyper Web Throw");
11 }
12 }
13
14 class Wolverine extends Hero {
15 @Override
16 void usePower() {
17 System.out.println("Fatal Claw");
18 }
19 }
20
21 public class HeroExecutor {
22
23 public static void main(String... methodOverridingExample) {
24 Hero hero = new Hero();
25 hero.usePower(); // #1
26
27
28 Hero spiderMan = new SpiderMan();
29 spiderMan.usePower(); // #2
30
31
32 Hero wolverine = new Wolverine();
33 wolverine.usePower(); // #3
34 }
35 }
When we instantiate only Hero, we get the behavior specified for that class. Then we instantiate
Hero again with SpiderMan. This class overrides the usePower method, so a different message is
6 Inheritance and Polymorphism 134
printed ("Hyper Web Throw").Finally, we instantiate Hero with Wolverine. This class also overrides
usePower, so the output from Wolverine is printed ("Fatal Claw").
@Override
You may be wondering about the use of the @Override annotation in this example. This
annotation indicates that the method is overriding a base class method. If there is no
matching method in the base class (same name, parameter types, and so on), there will
be a compilation error.
It’s not required to use the @Override annotation, but it provides a useful check that your
method overriding efforts will work correctly. Otherwise, in case of a mismatch the subclass
method will simply behave as a new method, not an override of the existing method, possibly
resulting in unexpected behavior.
Explanation:
This challenge explores the concept of polymorphism, also known as virtual method invocation.
When we override a parent class method in a subclass, the JVM will execute the version of the
method defined in that subclass when instantiating it, enabling us to alter its behavior. This allows
us to invoke methods in a flexible way.
Let’s analyze the code step by step. In the main method, we begin by creating an instance of the
Bart class and assigning it to the variable bart, of type SimpsonCharacter. We then pass the instance
of Bart to the drinkBeer method:
Next, we instantiate Homer and pass this instance to the method directly:
drinkBeer(new Homer());
The Homer class’s version of the method will be executed in this case, so the output will be:
Homer drinks
Finally, we invoke the drinkBeer method passing a SimpsonCharacter instance:
drinkBeer(new SimpsonCharacter());
Note that we are not only using the power of inheritance here, but also polymorphism. Can you see
how much flexibility we gain by just declaring the parent class in the method? The main method is
responsible for providing the instance, which means that the drinkBeer method is flexible enough
to receive any kind of SimpsonCharacter. If this method needs to be expanded in any way, it’s easier
because we can manipulate its behavior in the main method.
Creating decoupled code is crucial for code quality: it makes it easier to make changes to the code,
improves testability, and makes the code more flexible and reusable.
You can also override any methods from the Object class in your classes, such as equals, hashCode,
and toString. We will explore those methods more deeply in future chapters. For now, keep in mind
that you can define the behavior you want for these Object methods.
A. true
false
B. false
false
C. java.lang.StackOverflowError
D. true
true
Explanation:
The key to this challenge is in the implementation of the equals method.
Notice that this method was overridden incorrectly—because we are pointing to the current instance
by using the keyword this, we are invoking the same equals method and passing the obj. This is an
infinite recursive method invocation (resulting when a method invokes itself), and to avoid the
infinite loop the JVM will throw an java.lang.StackOverflowError.
In conclusion, the correct alternative is:
C) java.lang.StackOverflowError
13 class Hero {}
14 class Venom {}
15 class Xavier {}
16 class Wolverine extends Hero {}
Can you see how powerful the instanceof keyword is? Let’s analyze each of the statements in turn.
First we instantiate Venom and check whether it is an Object:
System.out.println(new Venom() instanceof Object);
The Wolverine class extends Hero, so this will also print true.
Finally, we check if a Hero is a Wolverine:
System.out.println(new Hero() instanceof Wolverine);
The Hero class doesn’t know anything about the Wolverine class—only the opposite is true, because
Wolverine extends Hero. Therefore, the result of this comparison is false.
A. 1111
B. 1100
C. 0000
D. Compilation error at line 13
Explanation:
All Java classes extend Object with no exception, even the Class class is an object.
Note that we are using Barney.class which references a class literal. In other words, it means we
are getting the class information such as class name, attributes names, types and the class metadata
from Barney. Barney.class is the same as Barney.getClass().
Therefore the correct answer is:
A - 1111
The toString method can be handy for logging, for example. Instead of creating specific methods
for showing the attributes of the object, we can simply override the toString method with what we
want.
An important detail of the toString method is that we can simply print the whole object that the
information of the toString method will be simply printed, we don’t need to invoke the toString
method explicitly.
itself and any other classes in the same package (including subclasses, as long as they’re in the same
package). protected access is similar but allows access to all subclasses regardless of what package
they’re in.
When you’re overriding a method, you can’t use a more restrictive access modifier than was used
in the superclass. If you do, there will be compilation errors. You can use a less restrictive modifier,
though, such as public instead of protected.
Let’s see what’s possible and what isn’t with a code example:
1 class Hero {
2 void attack() {}
3 public void jump() {}
4 }
5
6 class Wolverine extends Hero {
7 public void attack() {} // It's possible to use a more open access modifier
8 public void jump() {} // It's possible to use the same access modifier
9 // void jump() {} Compilation error, the default access modifier is more restric\
10 tive
11 // private void jump() {} Compilation error, the private access modifier is more\
12 restrictive
13 }
When overriding methods, keep in mind that they must have the same access restrictions as in the
superclass or use a more open access modifier.
13
14 @Override
15 public boolean equals(Object obj) {
16 return this.equals(obj); // Line 18
17 }
18 }
A. theHiddenModifier true
B. theHiddenModifier false
C. Compilation error at line 12
D. Compilation error at line 18
Explanation:
The equals method works just fine since we are overriding it with the public access modifier.
However, let’s analyze the Object class’s toString method from the JDK code:
Notice that it’s public. This means it can’t be overridden with a weaker access modifier, like the
default modifier used at this code challenge; it has to be public.
Since at line 12 the default access modifier is more restrictive than public from the Object class,
we will get a compilation error.
Therefore, the final answer will be:
C) Compilation error at line 12
Note also that the relationship works both ways. A Heart can’t live outside of a Person (or similar
entity), an Engine is no use without a Car to run, and an Employee needs to have a Company to employ
them.
To better understand this type of relationship, consider the composition of a Person:
Composition enables code reuse by allowing you to define objects that are made up of other objects,
but without you having to inherit all the methods of those objects in your class.
1 class Library {
2 List<Book> books = new ArrayList<>();
3 }
4 class Book { }
Note that composition and aggregation are abstract concepts. They depend on your point of view in
the real world, and the rules you define for your system. In a real-world application you can simply
use the term “composition”—you don’t need to define specifically if an object is a composition or an
aggregation—but it’s still good to know the difference between them.
1 import java.util.ArrayList;
2
3 public class SimpsonCharacterInheritance extends ArrayList<String> {
4
5 public static void main(String... badExampleOfInheritance) {
6 SimpsonCharacterInheritance simpsonCharacters = new SimpsonCharacterInherita\
7 nce();
8 simpsonCharacters.add("Homer");
9 simpsonCharacters.forEach(System.out::println);
10 }
11
12 }
1 import java.util.ArrayList;
2 import java.util.List;
3
4 public class SimpsonCharacterComposition {
5
6 static List<String> characters = new ArrayList<>();
7
8 public static void main(String... goodExampleOfComposition) {
9 characters.add("Homer");
10 characters.forEach(System.out::println);
11 }
12 }
We get all the functionality we need, and none of the functionality we don’t.
1 class Hero { }
2 class MarvelCharacter { }
3 class SpiderMan extends Hero, MarvelCharacter { }
4 One solution would be to inherit the classes one by one:
5 class MarvelCharacter { }
6 class Hero extends MarvelCharacter { }
7 class SpiderMan extends Hero { }
1 interface Hero { }
2 interface MarvelCharacter { }
3 class SpiderMan implements Hero, MarvelCharacter { }
In Java an interface can inherit from another interface just as a class can inherit from another class,
so we can reuse the talk and rescue methods from the Human and Hero interfaces within the Batman
interface. This is useful when we want to separate responsibilities into more generic interfaces and
reuse them in a more effective way—we don’t need to declare those methods again, which makes
our code easier to maintain.
The output of the main method in this example will be "Moe was created".
This is a particularly useful feature to be aware of because unlike other methods, constructors are
not inherited by subclasses. There are circumstances where you might want to call a parent class’s
constructor from a subclass, however; for example, if you want to reuse the initialization code with
some customization to reduce code duplication.
We’ll consider other aspects of the relationship between constructors and inheritance in the next
section.
automatically whenever a subclass is instantiated. This is required because the superclass might
have private fields that are initialized in its constructor.
Because every Java class extends Object, this means the Object class’s default constructor is
implicitly invoked every time an object is instantiated. Imagine if we had to invoke the super
constructor for all Java classes—remembering to do that would be a nightmare. That’s one of the
reasons that the JVM invokes the super constructor automatically when no parameters are being
passed. In effect, it includes the line super(); for you.
In the following example, the Magneto class will be invoking the super constructor of the Villain
class with the default constructor created automatically by the JVM:
This means when we instantiate the Magneto class, the JVM will invoke automatically the super
constructor and print the message "A villain was created!".
Keep in mind that when using inheritance, the first constructor to be executed will be the one from
the topmost parent class. In our case, the constructor execution order will be Object, Villain, then
Magneto.
In the following code, the Moe class explicitly invokes the super constructor from the Character
class. This is required, because otherwise the code won’t compile:
Be aware that if there is another constructor defined in the parent class that does not receive any
parameters, you don’t need to invoke the parent class constructor explicitly, as in this example. In the
following code, we will see an example of overloaded constructors (constructors receiving different
types of parameters). When there is a nontyped constructor in the parent class, we don’t need to
add the invocation of the super constructor explicitly.
In this example the Character class has one constructor without any parameters, and another one
that takes a String. In this case the Moe subclass will invoke the superclass’s nonparameterized
constructor automatically, because the JVM will be able to create a default constructor invoking the
super constructor with no parameters:
6 Inheritance and Polymorphism 151
Keep in mind that the super keyword will be added by the JVM automatically only for parent class
constructors that don’t take any parameters.
If you think this code will print "Homer drinks a beer", think again. We’re going to get a compilation
error (a ClassCastException) because it’s impossible for the JVM to know at compilation time what
the method from the instance is. The JVM will only know about the methods from the Character
class, and because the Character class doesn’t have a drinkBeer method, it won’t be possible to
invoke this method directly from that class.
The only way to make this happen is through class casting. This is like saying, “Hey JVM, I know
what I’m doing, so please cast this class with this type.” When we do that explicitly, the
JVM allows us to use the specific method from Homer, drinkBeer.
Let’s see how that works in code:
13
14 static class Homer extends Character {
15 void drinkBeer() {
16 System.out.println("Homer drinks a beer");
17 }
18 }
19 }
This code will compile and produce the expected result—by using the power of casting, we can invoke
the drinkBeer method from the Homer class. Bear in mind that you’ll have to use this technique in
your code if a specific method exists only in the subclass. Casting from a parent class to a subclass
like this is known as downcasting.
This example works because the Barney class extends the Character class, and therefore the Barney
class knows everything about Character. With that in mind, the JVM will not complain about casting
the Barney object to Character: Barney is a Character.
Note that the String class is declared final. Can you guess why that is? String is an immutable class,
which means that its main attributes will never be changed. The final keyword is used to ensure that
this is the case—but that isn’t the whole point of this keyword. The main goal of using final is to not
allow any other class to extend the String class.
Have you ever tried to extend this class? If you do this:
class AnyClass extends String { }
the code won’t compile, because the String class is declared as final.
Another important point to mention about a final class is that it’s not possible to override any of
its methods. This is because you can’t inherit from such a class. But what if you want to allow
inheritance in your classes, but prevent just certain methods from being overridden? You can use
the final keyword for that too.
This code won’t compile because it’s not possible to override a final method.
The only way to make it work is by removing the final keyword from the method declaration, like
in the following code:
1 class Villain {
2 private int age;
3 private void attack() {}
4 }
5
6 class Joker extends Villain {
7 void doTrick() {
8 System.out.println(super.age);
9 super.attack();
10 }
11 }
This code won’t compile, because the super.age variable and the super.attack method won’t be
accessible in the subclass. The only way to make it work is by removing the private access modifier
and using a less restrictive option, such as the default modifier:
1 class Villain {
2 int age;
3 void attack() {}
4 }
5
6 class Joker extends Villain {
7 void doTrick() {
8 System.out.println(super.age);
9 super.attack();
10 }
11 }
The code will now work because we are no longer restricting access with the private modifier.
Declaring these members protected (or public) would also work.
JDK classes use this concept, because it wouldn’t make sense to instantiate them. For example:
Let’s explore the abstract class Number. It doesn’t make sense to instantiate that class, because it’s too
generic. Java is a strongly typed programming language, so instantiating the Number class won’t help
developers to work with numbers. What’s this class good for, then? When we declare the Number
class as abstract, it all makes sense!
We can reuse lots of code from Number in the Java wrapper classes. Here are some examples of JDK
classes that extend this one:
In fact, all the numeric wrapper classes extend Number. Note that they all pass the is a test: an Integer
is a Number, as are a Double, Byte, and so on. All of these classes can reuse the methods declared in the
Number class, providing their own implementations for the abstract ones—this is a perfect example
of a good use of inheritance.
A good example to illustrate the use of an abstract method is the abstract class OutputStream because
this class doesn’t make sense to be instantiated by itself.
Some of the concrete classes that extend OutputStream are FilterOutputStream,
ByteArrayOutputStream, DataOutputStream, and others. Those classes can be instantiated
and they also inherit the generic behaviours from OutputStream.
6 Inheritance and Polymorphism 157
Let’s take a look at some code with a customized example of an abstract method. Here, we’re
exploring the wrong way to instantiate an abstract class:
Abstract classes can’t be instantiated directly, but we can instantiate the Homer class, which will
instantiate the Simpson abstract class and then print “D’oh”.
Note that in the Marvel abstract class, it’s not necessary to declare the usePower method. We only
need to do so in the first concrete class in the inheritance chain, which in this case is the SpiderMan
class. If we don’t declare this method in the SpiderMan class the code won’t compile.
19 System.out.println("Boom!");
20 }
21 void attack() { armor.standingLight(); }
22 }
23 static class Armor {
24 void standingLight() { System.out.println("--->>>"); }
25 }
26 static class SpiderMan extends Character {
27 void attack() {
28 new SpiderMan();
29 System.out.println("Spider-Web!");
30 }
31 }
32 }
A. —>>>
Boom!
Spider-Web!
3
The ClassCastException will be thrown at line 10
B. —>>>
The ClassCastException will be thrown at line 6
C. 2
The ClassCastException will be thrown at line 10
D. Boom!
Spider-Web!
3
Boom!
Explanation:
Let’s analyze what’s happening in the main method of this challenge. First, we create an instance
of the IronMan class and assign it to the variable ironMan, of type Character. Then we invoke the
attack method from IronMan (demonstrating the concept of polymorphism):
This method invokes the standingLight method from the Armor class. The resulting output will
therefore be:
6 Inheritance and Polymorphism 160
1 --->>>
Next we invoke the protonCannon method, which is specific to the IronMan class. Note that this is
only possible if we use casting, because the declared type we used is Character. The Character class
doesn’t include this method, so there is no way for the JVM to know it exists at compilation time.
We have to take responsibility for this and basically say to the JVM, “I know what I’m doing here,
if something bad happens it’s my fault.” The JVM will then allow us to invoke the method:
((IronMan)ironMan).protonCannon();
This prints:
Spider-Web!
The damage variable count will be 3 at this point, because with each instantiation we invoke the
default super constructor. Remember that the parent class’s constructor will always be invoked
first when the child class is instantiated. (If you’re wondering why 3, note that another instance of
SpiderMan is created inside the attack method.)
Finally, we invoke the protonCannon method again—but the SpiderMan instance doesn’t have this
method, so a ClassCastException will be thrown:
((IronMan)character).protonCannon();
A. —>>>
Boom!
Spider-Web!
3
The ClassCastException will be thrown at line 10
You have to create a method to calculate the prices and expose it as a REST service to the other
system. You will receive a String called command in your method that indicates which of two types
of discounts to apply ("NORMAL_DISCOUNT" or "VIP_DISCOUNT"), and a product ID that you will be
able to use to get the original price of the produce from your system. That method has the goal of
applying the appropriate discount to the product the customer is buying. The method will return
the final price of the product with the discount applied.
If "NORMAL_DISCOUNT" is passed, you need to apply a 20% discount to the price of the received product.
If "VIP_DISCOUNT" is passed you must apply a 50% discount.
Note that you can use polymorphism in this situation: you can encapsulate the logic for the "NORMAL_-
DISCOUNT" in a specialized class and the logic for the "VIP_DISCOUNT" in another class. To do this
you will need a generic Discount class that can’t be instantiated (an abstract class) and that has a
common method to give a 10% discount for every product on which you need to invoke it for each
input you are receiving on your method.
As a bonus, create a unit test method that passes the following input data and make sure the output
will be as it was defined in this situation.
This is the input data you will be receiving:
1 "NORMAL_DISCOUNT", 1
2 "VIP_DISCOUNT", 2
3 "NORMAL_DISCOUNT", 3
4 "NORMAL_DISCOUNT", 4
5 "VIP_DISCOUNT", 5
6 "NORMAL_DISCOUNT", 6
Create a List populated with the following information in your Product POJO:
In your logic implementation, you should log the name of the product, the price without any
discount, and the price with the discount, and then return the price with the appropriate discount
applied.
6.14 Summary
In this chapter you learned how to make good use of inheritance, and when to choose composition
over inheritance.
6 Inheritance and Polymorphism 162
• You also learned about overriding inherited methods and how to use the great power of
polymorphism.
• You now know that all Java classes use inheritance because they all extend Object, and that
if you’ve ever overridden the toString, equals, or hashCode methods you already made use of
polymorphism.
• You understand how access modifiers work in relation to inheritance, and how to prevent
inheritance of classes or methods with the final keyword or the private modifier.
• Although Java doesn’t allow inheriting from multiple classes, you’ve seen how to simulate it
by inheriting from interfaces.
• You learned how to work with the super reserved word to access a parent class’s methods and
constructors.
• You also learned how to use abstract classes to design systems that are well organized and easy
to maintain.
7 Interfaces
This chapter covers:
• Developing flexible code that depends on interfaces, not instances, and designing interfaces
that are reusable
• Creating method implementations in interfaces by using default methods
• Deciding whether to use interfaces or abstract classes
• Using static methods in interfaces
• Simulating multiple inheritance with default methods
• Using private methods in interfaces
• Total of 5 Java code Challenges
A great way to make Java code more flexible is by using interfaces. This reduces coupling in our
code, making it easier to change and extend it when necessary.
An interface works like a contract. Any class that implements a given interface contract will have to
implement all public abstract methods from that interface. With interfaces, we can apply the concept
of polymorphism, which means that we can pass different implementations to the same interface.
That’s a crucial concept for building code that’s easy to extend, because chances are that changes
will be only necessary in the implementation of the interface parameter.
Many core Java classes use the concept of interfaces. For example, ArrayList implements List,
HashSet implements Set, HashMap implements Map, ArrayDeque implements Deque, and so on.
This is a powerful concept, because it means if you need to work with a different List you can
simply send the type you need. Now imagine if you needed to implement this same logic without
polymorphism—you would have to replicate this code with a specific method for each List type:
1 import java.util.ArrayList;
2 import java.util.LinkedList;
3 import java.util.Vector;
4 import java.util.concurrent.CopyOnWriteArrayList;
5
6 public class AddListElementWithoutPolymorphism {
7
8 public static void main(String... addingElementsWithoutPolymorphism) {
9 addElement(new ArrayList());
10 addElement(new LinkedList());
11 addElement(new Vector());
12 addElement(new CopyOnWriteArrayList());
13 }
14
15 static void addElement(ArrayList list) {
16 list.add("Adding elements with different types of lists.");
17 System.out.println(list + ":" + list.getClass().getSimpleName());
18 }
19
20 static void addElement(LinkedList list) {
21 list.add("Adding elements with different types of lists.");
22 System.out.println(list + ":" + list.getClass().getSimpleName());
23 }
7 Interfaces 165
24
25 static void addElement(Vector list) {
26 list.add("Adding elements with different types of lists.");
27 System.out.println(list + ":" + list.getClass().getSimpleName());
28 }
29
30 static void addElement(CopyOnWriteArrayList list) {
31 list.add("Adding elements with different types of lists.");
32 System.out.println(list + ":" + list.getClass().getSimpleName());
33 }
34
35 }
Note the repetition—this code is far harder to read and will be much more difficult to maintain. It
accomplishes the same thing as the previous version, but requires much more code. This should give
you an idea of the power of polymorphism with interfaces.
10 class Dinosaur {
11 Object move() {
12 return "The Dinosaur is moving";
13 }
14 }
15
16 class Pterodactyl extends Dinosaur {
17 @Override
18 String move() {
19 return "The Pterodactyl is moving in the air";
20 }
21 }
The return type from the child class has to be a child of the parent class’s return type. This technique
won’t work with primitive types because there is no inheritance between them.
21 long attack() {
22 return 99999;
23 }
24 }
25 }
A. useSaber
99999
3
B. Compilation error at line 6
C. useSaber
99999
1
D. RuntimeException
This code will compile and run fine. The concepts demonstrated here are mostly about polymor-
phism. In the first method invocation, we are simply invoking the overridden useSaber method:
System.out.println(new Yoda().useSaber());
Note that we are using a covariant return type for the overridden method: the Yoda subclass’s
useSaber method returns a String instead of an Object. This method prints “useSaber”.
In the second method invocation we are invoking another overridden method, attack:
System.out.println(new Yoda().attack());
We can’t use a covariant type here because there is no inheritance between primitive types; both
methods return a long. The output of this method call is 99999.
Finally, we print the lightForce variable that is incremented each time the LightForce class is
instantiated:
System.out.println(new Yoda().lightForce); // Line 6
Even though we instantiate the LightForce class three times, note that the lightForce variable is
an instance variable. Therefore, the variable will be reset every time a new instance is created. We
have three instantiations, but the result of this variable will be 1 for each of the three instances.
In conclusion, the correct alternative is:
C. useSaber
99999
1
7 Interfaces 168
We can also provide customized default methods in our own classes. Let’s see how that works:
As you can see, we don’t need to implement the default method; we simply instantiate the Yoda
class and use the useForce method. The output will be:
"Using the force!"
21
22 class LukeSkywalker extends LightForce {
23 LukeSkywalker(int force) {
24 super(force);
25 }
26 }
27 class DarthVader implements DarkForce {}
19 };
20
21 darthMaul.useSaber();
22 }
23
24 }
25
26 abstract class ObiWan {
27 Object attack(int attackPower) {
28 return "ObiWan attacks DarthMaul with the power of " + attackPower;
29 }
30 }
31
32 interface DarthMaul {
33 default void useSaber() {
34 System.out.println("DarthMaul uses the saber");
35 }
36 }
Important: Remember, practice makes it perfect. Therefore, try out the Java Challenge
before seeing the answer.
Explanation:
Let’s analyze the code to understand what is happening. In the first lines of the main method we
are using the concept of anonymous inner classes. As explained before at chapter 4, an anonymous
inner class is a class without a name intended to be used just once. It can define static members and
class methods, extend another class, or implement an interface.
In our case, the anonymous inner class is extending ObiWan and then declaring the attackPower
variable. It then overrides the Object class’s jump method, changing the value of attackPower
to 999999. The last instruction in the method is the attack method invocation; we pass this the
attackPower variable and it prints the String as normal.
Next, we use the same concept of an anonymous inner class to implement the DarthMaul interface.
Notice that we are not instantiating an interface or an abstract class; we are using a class with no
7 Interfaces 172
name that extends an abstract class or implements an interface. At the beginning of this anonymous
inner class we declare a constant called defencePower. Then we override the default useSaber
method, passing the defencePower variable into it. Finally, we invoke the useSaber method from
DarthMaul.
In conclusion, the final result will be:
The output in this case will be "Roar!". Just remember that when you use an interface’s static or
default methods you can’t use the access modifier protected; if you try this you’ll get a compilation
error.
Another important thing to understand about static methods is that they can’t be overridden.
Considering that a static method is a class method, not an instance method, that makes sense.
As the following example demonstrates, the only way to invoke a static method defined in an
interface is by referencing the interface name and invoking the method. If you try to invoke the
static method by the interface implementation instance, the code won’t compile:
7 Interfaces 173
The above code will print the following: “Anakin uses his saber”
This example also demonstrates how it’s possible to use the super keyword to reference a specific
interface method. It’s important to mention that we can only do this if we’re implementing the
interface. In conclusion, as mentioned in the previous chapter, although Java doesn’t actually support
multiple inheritance there are some ways to simulate this behavior.
16
17 interface Warrior extends Brave {
18 int attackForce = 99999;
19
20 default void attack() {
21 System.out.println("Warrior attack:" + attackForce);
22 run(); // Line 27
23 Brave.super.attack();
24 }
25
26 static void pilotShip() {
27 System.out.println("Warrior pilots the ship");
28 }
29
30 void run();
31 }
32
33 interface Brave {
34 default void attack() {
35 System.out.println("Brave attack");
36 }
37 }
Important: Remember, to improve your programming skills you need to try out the Java
Challenge before seeing the answer.
Explanation:
If you guessed that it will compile fine, you got this challenge right—congratulations! If not, don’t
worry; you’ll learn why as we walk through the code.
The first method we invoke in the main method is the static method pilotShip:
pilotShip();
7 Interfaces 177
The pilotShip method from the ChewbaccaWarrior class will be invoked in this case because we are
not expliciting saying that we want to invoke the pilotShip method from another class or interface.
The JVM will automatically add the class name of the enclosing class, like this:
ChewbaccaWarrior.pilotShip();
Remember that it’s not possible to use polymorphism with static methods, so they always refer to
classes, not instances.
Now we’re going to explore polymorphism. In the second line we invoke the attack method:
new ChewbaccaWarrior().attack();
This method is declared in both the Warrior and Brave interfaces. The Warrior interface overrides
the attack method from the Brave interface, so the Chewbacca class will use the Warrior attack
method and not the Brave attack method.
Let’s take a closer look at the default attack method in the Warrior interface:
The first line simply prints the String "Warrior attack: 99999". Remember that the attackForce
constant can’t be changed—interfaces don’t have state, so they can’t have instance variables that are
mutable.
In the second line of the attack method, we invoke the run method. At first glance this seems strange,
because it looks like we’re invoking a method without a body. Just remember that it’s impossible
to invoke a default method without an instance, and to have an instance it’s necessary to have an
implementation of the interface; thus, we can conclude that whenever we invoke this attack method
we will always have an implementation for the run method. That’s exactly what is happening in
our challenge. The ChewbaccaWarrior class is implementing the Warrior interface, and it’s also
implementing the abstract run method. Consequently, the code implemented in the run method
of the ChewbaccaWarrior class will be executed. So, this method invocation will print:
"Chewbacca is running"
The last line of the Warrior interface’s attack method invokes the default attack method from the
Brave interface. This will print:
Brave attack
In conclusion, the right alternative is B.
7 Interfaces 178
There’s a lot of repetition here! Now let’s see what happens when we refactor that with the use of a
private method. This example demonstrates why private methods are important for interfaces, and
how to take advantage of this feature:
A. 13
B. 12
C. Compilation error at line 14
D. Compilation error at line 16
Important: By analyzing and running the code in your mind, you will absorb the concepts
of the Java Challenge.
7 Interfaces 180
Explanation:
The first point to analize is that we are instantiating an annonymous inner class from the Jedi
interface, this means, an instance without a name.
Then we are trying to override the jump() method in the annonymous inner class. However, note
that the jump() method declared in the Jedi interface is private. Therefore, it’s impossible to
override this method, it’s not reacheable outside of this interface. The jump() method declared in
the annonymous inner class will never be used since it wasn’t overridden.
Now, we are going to invoke the attack method which invokes the jump(), useSaber and useForce()
methods. Let’s see what happens in the following invocations:
7.9 Summary
With the knowledge you’ve gained in this chapter, you will be able to design your systems in a
more loosely coupled and easily maintainable way by using interfaces together with the concepts
7 Interfaces 181
of polymorphism and overriding methods. Polymorphism is one of the most important and difficult
concepts to master in any object-oriented programming language, and understanding it will make
it much easier to grasp other concepts.
• You learned that it’s possible for an interface to extend another one, which will be helpful in
designing your classes. Many classes from the JDK use this concept.
• You know the differences between interfaces and abstract classes and when to use each one.
• You also know how to use default, static, and private methods in interfaces and how to simulate
multiple inheritance with default methods.
8 Exceptions
This chapter covers:
• Understanding the difference between checked and unchecked exceptions and when to use
each type
• Using the stack trace to identify the root cause of an error
• Handling or declaring checked exceptions in your code
• Using the try, catch, finally flow effectively
• Closing Java resources effectively by using the try-with-resources statement
• Catching multiple exceptions in a single catch block
• Creating customized exceptions that make sense for the application
• Using the “throw early, catch later” principle for code quality
• Total of 4 Java Code Challenges
Just as Object is the superclass of all object types in Java, Throwable is the superclass of all exception
types (both checked and unchecked). When an exception occurs in a method the method creates an
object of type Throwable, hence the term “throwing” an exception.
With that basic introduction in mind, let’s take a closer look at the details of the different types of
exception.
There are several different strategies you could use to recover from this error. For example, you could
return a Connection from another database, or you could wrap the sqlException in a more generic
8 Exceptions 184
type. You could also use a generic exception to avoid throwing many different exceptions in a single
method, like the InfrastructureException used here:
Note that we simply throw the NullPointerException without handling it. The Locale class is crucial
for the logic of this method; if there is no Locale (an Object representing a specific geographical,
political, or cultural region), the intention of the programmer that implemented the toUpperCase
method is to make the application crash.
Another well-known example is IllegalArgumentException, which should be thrown
when an unexpected argument is passed to a method. The File class from the JDK uses
IllegalArgumentException when the uniform resource identifier (URI) it’s passed is invalid,
because there is no way to handle this condition programmatically:
8 Exceptions 185
When the main method is executed, a RuntimeException will be thrown and the stack trace will be
printed to the console (this is the default behavior whenever an unhandled exception is thrown). In
this case, the stack trace of the exception will be the following:
Note that the stack trace is a list of methods leading from the root method that threw the exception,
which is applyDiscount, to the last method that received it, which is main.
More important exception methods
Other than the printStackTrace method there is the getMessage method that will inform what error
has happened in the application. We can pass this message in the constructor to an Exception class
or any customized exception.
8 Exceptions 188
Notice that we are passing the exception message into the constructor. That brings us a lot of power
to make the error clear and easily fixable.
There is also the getCause method that will help you to find out what was the root cause of the
exception.
Here we have two options. Either we handle it with try and catch blocks:
8 Exceptions 189
Remember that this is only necessary with checked exceptions. When we are working with
RuntimeExceptions or unchecked exceptions we don’t need to do anything, because unchecked
exceptions in theory shouldn’t be handled and instead should be avoided programmatically.
In the example where we handle the exception, note the use of the printStackTrace method:
e.printStackTrace();
This method prints the exception and its stack trace to the standard error stream, which is very
useful for debugging.
Then we’ll create the more specific exception that will extend the generic exception:
class NoAccountBalanceException extends BusinessException { }
If we have a BankAccountService class that implements the logic of a money withdrawal, we can
then throw a NoAccountBalanceException when the account balance is insufficient to fulfill the
withdrawal request:
1 class BankAccountService {
2
3 void withdrawMoney(String accountNumber, double moneyAmount) throws NoAccountBal\
4 anceException {
5 // Perform logic
6 throw new NoAccountBalanceException();
7 }
8 }
Now consider the controller class that is using the service we created. In this class, we only want
to throw the generic BusinessException. To make that happen, we need to wrap the NoAccountBal-
anceException in a BusinessException:
15 }
16
17 }
Knowing how to use the keywords throw and throws make a great difference in code. Remember
that throws will be used in a method signature and throw will be used directly in code logic.
11 } finally {
12 System.out.println("Batman is late for playing poker with the Joker");
13 }
14 }
15
16 }
Earlier I mentioned that there is a way to avoid the finally block being executed, by calling the
System.exit method. Let’s see that in action:
This causes the program execution to stop, no matter what, so the code in the finally block will not
run. This example also demonstrates the use of try and finally without a catch. This can be useful, for
example, if you want to close a specific resource in all cases, even if some unexpected error happens.
1 import java.io.IOException;
2
3 public class CatchThemAll {
4
5 public static void main(String... catchThemAll) {
6 try {
7 throw new NullPointerException();
8 } catch (RuntimeException runtimeException) {
9 runtimeException.printStackTrace();
10 }
11
12 try {
13 throw new IOException();
14 } catch (Exception exception) {
15 exception.printStackTrace();
16 }
17
18 try {
19 throw new StackOverflowError();
20 } catch (Error error) {
21 error.printStackTrace();
22 }
23 }
24
25 }
Note that the specialized types NullPointerException, IOException, and StackOverflowError are
being caught by the more generalized types RuntimeException, Exception, and Error, respectively.
This is because the is-a relationship holds in each case:
• A NullPointerException is a RuntimeException.
• An IOException is an Exception.
• A StackOverflowError is an Error.
Be careful not to catch a more specific type of exception than you throw: if you throw a
RuntimeException but catch an IllegalArgumentException, the more general exception will not be
caught. In this case, the is-a relationship does not hold.
A. Shinkuu Hadouken
Isshun Sengiku
B. java.lang.NullPointerException will be thrown
Shinkuu Hadouken
C. java.lang.NullPointerException will be thrown
Akuma Secret Super Art
Shinkuu Hadouken
D. java.lang.NullPointerException will be thrown
Akuma Secret Super Art
Shinkuu Hadouken
Isshun Sengiku
Important: Play aroung with the Java Challenges code, make code changes, see what
happens. More importantly, try out the Java Challenge and only then see the answer.
Explanation:
Let’s analyze the try block first. Note that we are trying to access a method of the null ryu variable,
which causes the well-known NullPointerException to be thrown:
8 Exceptions 195
1 try {
2 String ryu = null;
3 ryu.contains("Shoryuken");
4 }
In the next code block we have the catch statement, but remember that NullPointerException is
not a subclass of Error. A NullPointerException is a RuntimeException, not an Error, so this catch
block won’t be executed:
1 catch(Error error) {
2 System.out.println("Akuma Secret Super Art");
3 }
What about the finally block? As you’ve learned, the finally block will almost always be executed,
unless the System.exit method is invoked before it is reached. That’s not the case here, so even
though a NullPointerException is thrown in the try block, the finally block will be executed
normally:
1 finally {
2 System.out.println("Shinkuu Hadouken");
3 }
1 import java.sql.Connection;
2 import java.sql.DriverManager;
3 import java.sql.SQLException;
4
5 public class TryWithFinally {
6
7 public static void main(String... tryWithFinally) throws SQLException {
8 Connection connection = null;
9 try {
10 connection = DriverManager.getConnection("dbURL");
11 } catch (SQLException e) {
12 e.printStackTrace();
13 } finally {
14 connection.close();
15 }
16 }
17
18 }
Note that this code is fairly verbose. In addition to the finally block being required to close the
connection, we had to declare the connection variable outside the try block for it to be accessible
in the finally block. A less verbose option is to use the try-with-resources statement to solve this
problem. Here’s the same example, making use of this feature:
1 import java.sql.Connection;
2 import java.sql.DriverManager;
3 import java.sql.SQLException;
4
5 public class TryWithResources {
6
7 public static void main(String... tryWithResources) {
8 try (Connection connection = DriverManager.getConnection("dbURL")) {
9 // Do the database logic
10 } catch (SQLException e) {
11 e.printStackTrace();
12 }
13 }
14 }
In this code we are ensuring that the database resource will be closed in the same way, but this
version is much more compact—we can declare the resource in the try statement, and it will be
closed automatically when we exit that block. This works with any resource that implements the
AutoCloseable interface, as you’ll see shortly.
8 Exceptions 197
As you can see, it’s easy to extend this method because we’re passing an interface, and we can simply
transfer the responsibility for instantiating the class to the code that is invoking this method.
Also note that there is a close method in both interfaces that must be implemented in the class that
will have its resource closed.
You can also create your own AutoCloseable class. Let’s look at an example:
8 Exceptions 198
Here we create one AutoCloseable class, Moe, and one Closeable class, Apu. Even though these
classes implement different interfaces, the behavior will be the same; we can use both of them inside
the try-with-resources statement.
One other important point to keep in mind about the try-with-resources statement is that the
resource declared last will be closed first. It works like the Stack data structure, following the “last
in, first out” principle. The objects are created in order from left to the right, and the close method
is invoked from right to left.
1 import java.io.Closeable;
2 import java.io.IOException;
3
4 public class ClosingTheBar {
5 static String whoClosedTheBar = "";
6
7 public static void main(String... marvel) {
8 Moe moe = new Moe();
9 executeAction(moe, new Barney());
10 System.out.println(whoClosedTheBar + moe.moeClosedBarCount);
11 }
12
13 private static void executeAction(Closeable moe, AutoCloseable barney) {
14 try (moe; barney) {
15 new Moe().close(); // Line 16
16 } catch (Throwable ignore) {
17 whoClosedTheBar += "?";
18 }
19 }
20
21 static class Barney implements AutoCloseable {
22 public void close() {
23 throw new StackOverflowError();
24 }
25 }
26
27 static class Moe implements Closeable {
28 int moeClosedBarCount = 0;
29 public void close() throws IOException {
30 moeClosedBarCount++;
31 if (moeClosedBarCount == 2) {
32 whoClosedTheBar += "moe";
33 throw new IOException();
34 }
35 }
36 }
37
38 }
A. moe?2
B. StackOverflowError will be thrown and the program will stop
C. ?1
D. RuntimeException at line 16
8 Exceptions 200
Important: Try out the Java Challenge code, make your code changes, see what happens.
More importantly, try out the Java Challenge and only then see the answer.
Explanation:
Let’s examine the executeAction method, which is where the action is happening. Notice that we
are passing moe as a Closeable instance and barney as AutoCloseable. The first close method to be
executed will be the one at line 16—that’s because the close method is executed after the try block
execution.
One important observation is that the try-with-resources statement will always close the resources
from right to left, so barney will close first, then moe. This means barney’s close method will execute
first, throwing a StackOverflowError, and then moe’s close method will be executed. Note that there
is logic in this method:
1 if (moeClosedBarCount == 2) {
2 whoClosedTheBar += "moe";
3 throw new IOException();
4 }
But note also that we are using different instances to invoke the close method. The value of the
instance variable moeClosedBarCount will never reach 2, because each time an instance is created
it will be reset.
The only value that will be concatenated will be “?”, and the value of moeClosedBarCount will be 1.
In conclusion, the final answer to this challenge will be:
C) ?1
1 import java.sql.Connection;
2 import java.sql.DriverManager;
3 import java.sql.SQLException;
4
5 public class DatabaseConnection {
6
7 public static void main(String... multiCatchExample) {
8 try {
9 Connection connection = DriverManager.getConnection("DBURL");
10 executeComplexLogic();
11 } catch (SQLException e) {
12 e.printStackTrace();
13 } catch (Exception e) {
14 e.printStackTrace();
15 } catch (Throwable e) {
16 e.printStackTrace();
17 }
18 }
19
20 private static void executeComplexLogic() { }
21 }
Note that when we use multiple catch blocks, the classes we catch must have an inheritance
relationship and we must start with the most specific class and proceed to the most generic one. For
example, in the preceding code, note that SQLException extends Exception and Exception extends
Throwable.
This code won’t compile, because Throwable does not extend Exception:
1 catch (Throwable e) {
2 e.printStackTrace();
3 } catch (Exception e) {
4 e.printStackTrace();
5 }
Also keep in mind that it’s not necessary to extend a class directly, as long as the next class caught
is at a higher inheritance level. For example, even though SQLException doesn’t directly extend the
Exception class, this code will compile:
8 Exceptions 202
1 catch (SQLException e) {
2 e.printStackTrace();
3 } catch (Exception e) {
4 e.printStackTrace();
5 }
There’s another way to capture exceptions that was introduced in Java 7. Like the
try-with-resources statement, its goal is to reduce the boilerplate code. Instead of creating
several catch blocks in the event that you might need to catch many different exceptions, it’s
possible to create only one catch block that catches more than one type of exception.
In the following example, we will be catching the ArrayIndexOutOfBoundsException,
NullPointerException, and StackOverflowError in the same catch block. Note that those
exception classes don’t have any inheritance relationship, and that’s why this code compiles:
If we use a class that is a parent class in a multi-catch statement, like in the following example, the
code won’t compile:
1 try {
2 String array[] = null;
3 System.out.println(array[0].contains("shotgun"));
4 } catch (ArrayIndexOutOfBoundsException | Exception | StackOverflowError e) {
5 e.printStackTrace();
6 }
Important: Try out the Java Challenge before seeing the answer. That’s how you will get
the best benefit of the Java Challenges.
Explanation:
Let’s examine the key points of this challenge. The first exception thrown is an IllegalArgumentEx-
ception:
throw new IllegalArgumentException();
1 catch (RuntimeException e) {
2 throw new StackOverflowError();
3 }
But the Error catch block will capture the StackOverflowError, because this class extends Error:
The first catch block won’t be able to capture this exception because there is no inheritance
relationship:
catch (Exception | StackOverflowError error) { ... }
But the second catch block will, because Throwable catches all exceptions:
Notice that we are creating a constructor in the BillingException and CalculationException classes.
That’s important, because it enables us to pass a clear message and cause relating to the exception,
making it easier to track down and understand the error that occurred. For example:
• BillingException
1 try {
2 // do some logic
3 } catch (SomeException someException) {
4 throw new BillingException("The customer can't be billed.", someException);
5 }
• CalculationException:
1 try {
2 // do some logic
3 } catch (SomeException someException) {
4 throw new CalculationException("The sum operation has failed", someExcepti\
5 on);
6 }
We also could encapsulate the message into our exception, if we wanted to. Also we can even
overload the CalculationException constructor if we want to pass a different message. For example:
8 Exceptions 207
When working with exceptions, we should aim to create clear and meaningful log messages that
will make sense to developers.
As suggested earlier, we can structure our business exceptions. It’s a good idea to create a generic
exception class that will be extended by all the other custom exception classes you need. You can
then declare just the generic type in the throws clause instead of listing all the specific exceptions
the method throws.
To show how this works, let’s suppose we have two more business exceptions:
Now imagine that this scenario is reproduced in various classes throughout the system. Handling all
those different exceptions would be a pain, and the code would be complicated to read and difficult
to maintain. It would be far better to create a generic exception type that aggregates all those business
requirements—but how can this be done?
Fortunately, there is a solution for this problem: we can simply create a BusinessException class and
make all the other business exceptions extend it, like this:
8 Exceptions 208
Then we can remove all the specific exceptions from the throws clause in the previous example and
throw only the BusinessException. This approach is far more concise and better organized:
void calculateCustomerBilling() throws BusinessException { ... }
You can then write code to catch the various exceptions that might be thrown, ensuring that you’ll
have a detailed and clear log of any exceptions that occur in the system.
A. GetBeerBarClosingNoWineNoAlcohol
B. BarClosingGetBeerNoWhiskeyNoBar
C. GetBeerNoWineNoAlcohol
D. GetBeerNoWhiskeyNoAlcohol
Important: Our brain is like a muscle, the more you practice, the stronger you get. Therefore,
try out the Java Challenge before seeing the answer.
Explanation:
The first part of the code demonstrates use of the try-with-resources statement. We have three
custom exception classes: NoBeerException that extends Exception and implements AutoCloseable,
NoBarException extending RuntimeException, and NoAlcoholError extending Error.
Remember that the try block will be executed first when using the try-with-resources statement.
The first String to be concatenated will therefore be:
GetBeer
Then the close method from NoBeerException will be invoked and the second String will be
concatenated:
BarClosing
Notice that a NoBarException is being thrown in the close method, but a NoAlcoholError was thrown
as well. What happens here? When an exception is thrown and then the close method from an
AutoCloseable, for example, throws another exception, the first one will be suppressed. In our case,
the NoBarException will be suppressed by the NoAlcoholError.
If we print the getSuppressed method referencing the first position of the array, we will get the
following result:
...NoBeerExceptionChallenge$NoBarException
The catch block that will be executed will therefore be:
8 Exceptions 210
The finally block will always be executed, so next the calculation 1 / 0 will be performed. But when
we try to divide a number by zero in Java, we get the following exception:
java.lang.ArithmeticException: / by zero
We can conclude that this is a RuntimeException because this error could be avoided with an if
statement.
NoBarException extends RuntimeException but has nothing to do with the ArithmeticException.
Therefore, the RuntimeException catch block will catch this error and the final String to be
concatenated will be:
NoAlcohol
12 }
13
14 import java.sql.SQLException;
15 import java.util.List;
16
17 public class SimpsonService {
18
19 SimpsonRepository simpsonRepository = new SimpsonRepository();
20
21 public List<Simpson> findAllSimpsons() {
22 try {
23 return simpsonRepository.findAll();
24 } catch (SQLException sqlException) {
25 sqlException.printStackTrace();
26 }
27 }
28
29 }
Instead, it’s much better to follow the “throw early, catch late” principle. This will centralize the
reporting on the errors that happened, making it easier to find out what went wrong and where.
Let’s see what the same logic looks like when we apply this principle:
1 import java.sql.SQLException;
2 import java.util.List;
3
4 public class SimpsonRepository {
5
6 List<Simpson> findAll() throws SQLException {
7 // Perform action to find all Simpsons
8 }
9
10 }
11
12 public class SimpsonService {
13
14 SimpsonRepository simpsonRepository = new SimpsonRepository();
15
16 public List<Simpson> findAllSimpsons() {
17 try {
18 return simpsonRepository.findAll();
19 } catch (SQLException sqlException) {
20 sqlException.printStackTrace();
8 Exceptions 212
21 }
22 }
23
24 }
This code is less verbose and easier to understand. We should try to centralize our exception-handling
code as much as possible to facilitate debugging. .
Finally, you’ll need to create a simple service class named AccountService that implements a method
named withdrawAccountMoney receiving a customerId and moneyAmount. You don’t need to
implement the logic in the withdrawAccountMoney method; you just need to throw the exceptions
you created, catch them as BusinessExceptions, and then print the stack trace. That will be enough
for this challenger. Over to you!
8.11 Summary
• Exceptions are a crucial concept to master in Java. In this chapter you learned how to use
the most suitable exception types for multiple situations, and how to structure your exception-
handling code in try, catch, and finally blocks.
• You learned the difference between checked and unchecked exceptions, and that all exception
types in Java inherit from Throwable.
• You know how to handle and declare exceptions, and how to wrap specific exceptions inside
generic ones.
• You learned that it’s possible to close Java resources by using the try-with-resources statement
instead of using a finally block.
8 Exceptions 213
• You understand the options Java provides for catching multiple types of exceptions, and the
effects of inheritance on exception handling.
• You learned how to create meaningful customized exceptions with clear log messages when
appropriate.
• You also learned how to apply good programming practices when working with exceptions,
such as the “throw early, catch late” principle.
9 Lambdas and Functional Interfaces
This chapter covers:
1 int effectiveFinalVariable = 5;
2 System.out.println(effectiveFinalVariable);
We never changed the code above, therefore, effectiveFinalVariable is an effectively final variable.
Let’s see how is a non effective final variable now:
1 int nonEffectiveFinalVariable = 5;
2 System.out.println(++nonEffectiveFinalVariable);
As you can see we changed the nonEffectiveFinalVariable, therefore it’s not an effective variable.
Of course, if we pass a local final variable to a lambda expression, it’s totally fine since we can’t
change it. Ideally, we should prefer to use only explicitly final variables in a lambda as a good
practice.
Now that you have a good understanding of how local variables work with lambdas, try out the
next Java Challenge!
1 import java.util.Arrays;
2 import java.util.List;
3
4 public class MysteriousDoorLambdaChallenge {
5
6 public static void main(String... theDoors) {
7 int doorNumber = 0;
8 doorNumber++;
9 List<String> doors = Arrays.asList("A", "B", "C");
10 doors.forEach(e -> {
11 System.out.println(e + doorNumber); // Line 11
12 });
13 }
14
15 }
9 Lambdas and Functional Interfaces 217
A. A0
B1
C2
B. A1
B2
C3
C. Compilation Error at line 11
D. Unpredictable
Important: Try out the Java Challenge before seeing the answer. The more Java Challenges
you solve, the better you get.
Explanation:
Lambdas can only access variables that are final or effectively final. As mentioned above in its
essence should access only immutable data from outside.
Note that the doorNumber variable is being changed when we increment it. Therefore, the doorNumber
variable is not effectively final anymore from that moment. Simply put, effectively final variables
are variables that when given a value, this value will be never changed.
In conclusion, in this challenge, there will be a compilation error at line 11 and the final answer will
be:
C) Compilation Error at line 11
The only method we can invoke is get. Therefore, to get the “Moe” value we can do the following:
System.out.println(supplier.get());
To make it clear for you what is a functional interface, you can take a look at how the JDK code
defines it:
9 Lambdas and Functional Interfaces 218
1 @FunctionalInterface
2 public interface Supplier<T> {
3 T get();
4 }
If we want to use a Supplier that is specific for the int type, we can use the interface IntSupplier.
Note that we don’t have to use the generic type here:
1 import java.util.function.Consumer;
2 import java.util.function.IntSupplier;
3
4 public class SupplierConsumerChallenge {
5
6 static int value;
7 public static void main(String... doYourBest) {
8 IntSupplier valueS = () -> value++; // Line 8
9 Consumer<Object> oneMoreValue = (Object test) -> value++; // Line 9
10
11 oneMoreValue.accept(2);
12 System.out.println(value + " " + valueS.getAsInt());
13 }
14
15 }
9 Lambdas and Functional Interfaces 219
Explanation:
Important: Try out the Java Challenge before seeing the answer. Run the Java code in your
mind and get sharp with Java!
Explanation:
Although a lambda is not supposed to mutate any external state, we still can change a static variable.
Therefore, there won’t be any compilation error. Lambdas are also lazy, which means that the value
won’t be incremented unless we invoke the lambda methods accept or getAsInt in this case.
Then the only moment that the value variable will be incremented is when we invoke the following
method. Note that even though we are passing ‘2’ to the accept method, we are not doing anything
with this number. Therefore after invoking the following method, the value variable will be 1:
oneMoreValue.accept(2);
Finally, when we print the variable value, it will print 1. Then when we invoke the
valueS.getAsInt() method it will print 1 as well since the value variable is incremented
with the post-increment operator. The value variable will be incremented only after we invoke the
valueS.getAsInt(). After the valueS.getAsInt() invocation, the value variable will have the value of
2:
System.out.println(value + " " + valueS.getAsInt());
Therefore the final output and the right alternative will be then:
D. 1 1
Let’s now see the and method. This method joins two conditions and both of them have to be true
to be fulfilled.
Output:
false
Explanation:
Output:
true
Explanation:
We can also compose functions. Let’s suppose we want to first multiply a number by 2 and then add
2. We have two ways to do that in Java. We can either use the compose or the andThen function.
Let’s see the example below:
Then when we invoke the andThen method, it’s the opposite. The times2 function will be executed
giving us the result of 4 first. Then the add2 function will be invoked adding 2 and giving us the
final result of 6.
Considering the above explanation, what do you think will happen in the following code?
When we try to compose a BiFunction that returns a Double as the result with a Function that
receives an Integer as a parameter, the code won’t compile. That happens because a Double type
can’t be transformed into Integer. Therefore, the times2 function can’t be composed with the
addNumbers BiFunction.
This would be different if the times2 Function was receiving a Double as a parameter as the
following code:
Function<Double, Number> times2 = (number) -> number * 2.0;
1 0```
2
3 ### 9.2.5 BiFunction Consumer Challenge
4
5 Now that you learned more about the `Function`, `BiFunction`, and `BiConsumer` inter\
6 faces, it's time to try out the Java Challenge! The following Java Challenge involve\
7 s the operations add, subtraction, and multiplication executed with the above-mentio\
8 ned interfaces!
9
10 Can you guess what will be the output of the following Java Challenge?
11
12 ```java
13 import java.util.function.BiConsumer;
14 import java.util.function.BiFunction;
15 import java.util.function.Function;
16
17 public class BiFunctionConsumerChallenge {
18
19 public static void main(String... doYourBest) {
20 BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
21 Function<Integer, Integer> sub = (a) -> a - 2;
22 Function<Integer, Integer> mult = (a) -> a * 3;
23
24 BiFunction<Integer, Integer, Integer> func =
25 add.andThen(sub).andThen(sub);
26
27 BiConsumer<Integer, Integer> consumer = (a, b)
28 -> System.out.println(a + 2 + b + 2);
29
30 int firstResult = func.apply(2, 2);
31 add.andThen(sub).andThen(mult);
32 int secondResult = add.apply(2, 2);
33
34 consumer.accept(firstResult, secondResult);
35 }
9 Lambdas and Functional Interfaces 224
36
37 }
A. 6
B. 8
C. 12
D. 4
Important: Try out the Java Challenge before seeing the answer. Stay consistent, stay
solving the Java Challenges, and improving your Java skills.
Explanation:
Let’s analyze the part of the code where the action happens. Remember that the functions will only
be invoked when we use the apply method.
The firstResult variable will have the value of 0 at this moment because we invoked the subtraction
functions:
int firstResult = func.apply(2, 2);
The tricky part of this challenge is the following code because note that we are assigning or doing
anything with those functions. Therefore, nothing will happen when the following code is executed:
add.andThen(sub).andThen(mult);
Then when we invoke the following code, we will add 2 to 2 which will give us the result of 4:
int secondResult = add.apply(2, 2);
Finally, the last piece of code is the consumer, which will receive the firstResult and secondResult
variables and will sum them. In the consumer, we are also adding 2 to each of the variables, giving
us the result of 8.
consumer.accept(firstResult, secondResult)
B. 8
As you can see in the code above, we can only set one generic type, which in this case is Integer.
Therefore, we can only receive an Integer parameter and only return an Integer value.
Now when we invoke the above function:
System.out.println(unaryOperator.apply(2));
1 @FunctionalInterface
2 interface Duke {
3 void dance(String song);
4 }
Note that it’s also possible to add a private, default, or static method in a functional interface. That’s
because the compiler will assume we just have to override the only public abstract method. The
other methods are there to support the interface. Take a look at the following code:
9 Lambdas and Functional Interfaces 226
1 @FunctionalInterface
2 interface Duke {
3 void dance(String song);
4
5 private void jump() {
6 System.out.println("Duke is jumping!");
7 }
8
9 default void fly() {
10 System.out.println("Duke is flying!");
11 }
12
13 static void playGame() {
14 System.out.println("Duke is playing Metal Gear!");
15 }
16
17 }
Remember that in a functional interface, the limitation we have is that we can’t have more than
ONE public abstract method. Therefore the following code won’t compile:
1 @FunctionalInterface
2 interface Duke {
3 void dance(String song);
4 void fly();
5 }
Note also that what makes the above code not compile is the @FunctionalInterface. It’s a good
practice to use the @FunctionalInterface annotation so we can make it very clear that the interface
is functional.
Other important points are that public default and public static were only available in Java 8. Also,
private and private static were first introduced in Java 9.
1 @FunctionalInterface
2 public interface Runnable {
3 public abstract void run();
4 }
1 @FunctionalInterface
2 public interface Comparator<T> {
3 int compare(T o1, T o2);
4 // Other methods
5 }
We can do even better, we can use a method reference making the code cleaner and more concise.
The lambda above and the following method reference produce the same result:
9 Lambdas and Functional Interfaces 228
Now, we will use the isEmpty String instance method to match this predicate interface. However,
note that the isEmpty method doesn’t receive any parameter, but how is it possible to match the
Predicate interface then?
public boolean isEmpty() { … }
Remember one important detail, an instance method can only be invoked with an instance.
Therefore, we have to pass an instance of a String to the Predicate method reference anyway.
Let’s see the following example:
To make this concept even more clear, let’s see how instance method reference works with a
BiFunction. It’s the same fundament, we have to match an instance, how many parameters are
received, and the return value.
This is the compareTo method from the JDK, and notice this method only receives one parameter
and return an int value:
public int compareTo(Integer anotherInteger) { … }
Since 7 is greater than 1 the output from the code above will be:
1
Note in the code above that we are matching the compareTo method that receives only one parameter.
Therefore, we have to pass this Integer instance when we invoke the apply method.
Also, notice that the number 7 is the Integer instance that will invoke the compareTo method. The
key to understanding this concept is that every time we are using instance method reference, we
have to pass the instance as the first argument:
compareToBiFunction.apply(7, 1);
9 Lambdas and Functional Interfaces 230
1 class Simpson {
2 String name;
3 int age;
4
5 public Simpson(String name, int age) {
6 this.name = name;
7 this.age = age;
8 }
9 }
Then, we are using the method reference to a constructor matching a BiFunction. This works because
in the BiFunction we can receive also two parameters and return a type that in our case is Simpson:
Important: Try out the Java Challenge before seeing the answer.
9 Lambdas and Functional Interfaces 232
Explanation:
Let’s analyze the code:
Let’s first analyze the equals method in the MedicineSupplier interface. In a functional interface, we
have the rule of having only one public abstract method. However, when we declare the equals
method, we are only overriding from the Object class. Therefore, we can have those methods
declared in the MedicineSupplier interface.
Now let’s go to the main part of the code. The following method reference only works because the
provideMedicine method matches exactly with one of the constructors from the Smithers class. Let’s
see it:
MedicineSupplier medicine = Smithers::new;
As mentioned, in the following code you will notice that both methods receive an int and return
Smithers:
As you can see in the code above, method references are intelligent enough to match the right
constructor to the provideMedicine method. Since the method reference will match only one
constructor, only the following constructor will be invoked:
1 Smithers(int any) {
2 System.out.printf("Smithers gives:%s to Mister Burns with int \n", any);
3 }
Therefore all invocations from provideMedicine will execute the Smithers(int any) { … }
constructor. The result is the following:
1 class Morpheus {
2 public Morpheus() {
3 System.out.println("Free your mind");
4 }
5
6 public String learnKungFu() {
7 return "Downloaded Kung Fu";
8 }
9 }
10
11 Supplier methodReferenceSupplier = new Morpheus()::learnKungFu;
There will be no output. This happens because lambdas are lazy by essence. Therefore, the Morpheus
constructor will be only invoked if we execute the following code:
lambdaSupplier.get();
• If we invoke the get method with lambdas more times, the constructor will be invoked again
• If we invoke the get method with method reference more times the constructor will not be
invoked again
• If we use a constructor method reference, the constructor won’t be invoked
Now that we explored the differences between lambdas and method references, it’s time for action!
9 Lambdas and Functional Interfaces 234
Important: Try out the Java Challenge before seeing the answer.
Explanation:
Let’s analyze the code:
9 Lambdas and Functional Interfaces 235
There is a crucial difference between lambda and method reference. Lambdas are lazy and they will
invoke the class constructor only when the method is invoked. On the other hand, with method
reference, the constructor will be invoked right away only where the method reference is assigned,
not on the method invocation.
So, at this line the constructor is not invoked:
Runnable universeImpactRunnable = () -> new ChuckNorris().roundHouseKick();
1 universeImpactRunnable.run();
2 universeImpactRunnable.run();
With lambdas, every time the run method is invoked, the constructor will be invoked, which means
that the numberOfKicks variable will be increased. So the value from galaxyDamage will be:
12
Then when we invoke those methods:
1 galaxyImpactRunnable.run();
2 galaxyImpactRunnable.run();
The value will be 0 because remember that the constructor will be only invoked one and it was
already invoked at the moment of the method reference declaration.
By knowing that, and also remembering that the post ++ operator increases a value after the line
being processed. Then the values will be:
00
The final result will be then:
D) The galaxy is finished = 1200
Let’s complete each of the functional interfaces with lambdas and method references then:
1 - Supplier<String> vaderSupplier = // Complete with A or B
A) () -> "DarthVader";
B) () -> System.out.println("Darth Vader");
2 - Supplier<String> methodReferenceSupplier = // Complete with A or B
A. String::toString;
B. String::new;
A. String::isEmpty;
B. String::contains;
A. System.out::printf;
B. System.out::println;
A. new String()::valueOf
B. String::toString;
A. Integer::compareTo;
B. new Integer()::equals;
A. Integer::valueOf;
B. String::isBlank;
A. (moe, homer, beer) -> moe + "gives the beer:" + beer + "to:" + homer;
B. (moe, beer) -> moe + "gives the beer:" + beer;
A. Double::max;
B. new Double()::max;
9.4.1 Explanation
1 - The answer is A, Supplier<String> vaderSupplier = () -> “DarthVader”; because when we are
using a Supplier, we need to return the value we defined in the generic type. In this case, we need
to return a String value. Note that we don’t need to use the keyword return in a lambda when we
have only one line of code.
2 - The answer is B, String::new because we are supplying a String value, which means we are
returning a String. Therefore the String constructor fits in this situation. The toString method won’t
work because we need an instance to invoke the String::toString method. If we used the following
new String()::toString(); then it would work.
9 Lambdas and Functional Interfaces 238
3 - The answer is A. Since we are using a Predicate that receives a String and returns a boolean,
(jedi) -> jedi.equals("jedi"); will work just fine. The following (jedi) -> { return new
Object(); } won’t work because we are returning an Object instead of a boolean.
4 - The answer is A. That’s because we need an instance to be able to invoke the isEmpty method.
Also, since the Predicate is expecting a String as a parameter, the isEmpty method will receive an
object instance and then return a boolean value.
The String::contains; method would only work if it was static or if we had an instance. The following
code would work, "anyString"::contains because then we just have to pass a parameter and return
a boolean just like the contains method is expecting.
5 - The answer is B. The reason is that the following code (saber) -> System.out.println("Moe
uses the " + saber); is only consuming saber and not returning any value. Which matches the
Consumer contract. However, the following code (saber) -> "Moe uses the " + saber; is returning
a value, therefore, it doesn’t match the Consumer contract.
6 - The answer is B. Because the println method only consumes a parameter and returns void. The
printf method won’t work because there are ambiguous overloaded methods within it. Therefore,
the method reference can’t guess what method to use.
7 - The answer is A. The following code (jedi) -> jedi.concat(jedi); matches the contract of a
Function because it receives a value and returns a value too. The following code (jedi, simpson)
-> jedi.concat(simpson); doesn’t match the Function contract because it’s receiving two parameters.
8 - The answer is B. Even though the toString method doesn’t receive a parameter, it needs an
instance to be invoked. Therefore the following code String::toString; will work because we need to
pass the String instance and then the toString method will return a String. The following code new
String()::valueOf won’t work because we are passing an instance to the String. If we were doing the
following, String::valueOf, then this would work just like String::toString.
9 - The answer is B. Since in a BiFunction we receive two parameters and return one value, the
following code is valid, (jedi, sith) -> jedi + sith. The following code (jedi, sith, naboo) -> jedi + sith
+ naboo; is not valid because there are three parameters in this lambda.
10 - The answer is A. The compareTo method receives only one parameter but needs an instance
to be invoked. Therefore, when we invoke the method reference, we need to pass two parameters,
which makes the following code Integer::compareTo; match the BiFunction. The equals method also
receives only one parameter but we are already instanting a String. Therefore, in the following
code new Integer()::equals; we need to pass only one value when invoking the method reference. To
conclude, the new Integer()::equals; doesn’t match a BiFunction.
11 - The answer is A. This is because (homer) -> homer.concat(“Marge”); receives a String and returns
a String, which matches a UnaryOperator. The following code (marge) -> homer.hashCode() + 7; is
returning an int instead of a String, which breaks the following contract UnaryOperator<String>
simpsonUnaryOperator.
12 - The answer is A. The following method reference Integer::valueOf; receives an Integer and
returns an Integer, therefore, it matches the UnaryOperator contract. The other method reference
9 Lambdas and Functional Interfaces 239
String::isBlank; doesn’t match the contract because it receives a String and returns a boolean value.
13 - The answer is B. The following lambda (moe, beer) -> moe + “gives the beer:” + beer; receives
two String parameters and returns one String, therefore, it matches the BinaryOperator<String>
moeBiOperator contract. The following code (moe, homer, beer) -> moe + “gives the beer:” + beer +
“to:” + homer; doesn’t match the contract because it’s receiving three parameters.
14 - The answer is A. The max method receives two parameters, also notice that this is a static
method, therefore, there is no need for an instance. For this reason it matches the BinaryOpera-
tor<Double> methodReferenceBiOperator contract. The following code new Double()::max; won’t
work because we can’t use a static method reference with an instance.
9.5 Summary
• You learned why lambdas and method references are important.
• You know when to use lambdas and method references for their appropriate reasons.
• You learned the lambda and method references syntax.
• You know how to use lambdas with the correspondent functional interfaces.
• You know how to match lambdas and method references to functional interfaces.
• You learned what is an effective final variable.
• You know how to create customized functional interfaces.
• You know how to use older Java classes as functional interfaces.
• You solve many Java Challenges to fix and absorb the concepts of this chapter.
Congratulations, you’ve finished the Lambdas and Functional Interfaces chapter! Now you un-
derstand better how to use the benefits of functional programming in Java! Those concepts will
massively improve your code quality!
10 Optional
This chapter covers
The Optional class helps Java developers to avoid the annoying NullPointerException when used
correctly. It also helps developers to create more readable code with less code. Instead of using null
checks, we can create elegant code by using Optional and achieve more.
According to the [Optional class documentation]:(https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java
Optional is primarily intended for use as a method return type where there is a clear need to
represent “no result,” and where using null is likely to cause errors.
Since there is value in the snakeEaterOps variable, the isPresent method returns:
true
Note that the Optional.of method can throw a NullPointerException if a null value is passed to
it:
Therefore, whenever we pass a null value to the Optional.of method, the requireNonNull check
will throw a NullPointerException.
It prints:
Optional.empty
There is also the option of using the Optional.ofNullable method which avoids
NullPointerException in case a null value is wrapped to it:
It prints:
true
Let’s see what happens when we wrap null in an Optional.ofNullable now:
When we wrap a null value in an Optional.ofNullable method, the optional variable will wrap
the value of Optional.empty. Therefore the output will be:
false
Since Java 11, we can also use the isEmpty method which as the name suggests, it checks if the value
is Optional.empty:
It prints:
false
Let’s see what happens when we wrap null in an Optional.ofNullable now:
It prints:
true
Note
You might be wondering why the isEmpty method exists since we could just negate the
optional.isPresent(). However, in terms of code design, it’s more readable to have an
explicit method name instead of negating the isPresent method. By reading the isEmpty
method you know right away that you are checking if the value is empty.
1 import java.util.Optional;
2
3 public class OfNullableSpartanWarriorChallenge {
4
5 public static void main(String ... doYourBest) {
6 boolean isWarriorPresent = getOfWarrior("Kratos").isPresent();
7
8 Optional<Object> spartanGhost = Optional.empty();
9 Optional<String> ofNullableWarrior = getOptWarrior();
10 Optional<Object> emptyWarrior = Optional.of("");
11
12 System.out.printf("%s %s %s %s", isWarriorPresent,
13 spartanGhost, ofNullableWarrior, emptyWarrior.isEmpty());
14 System.out.println(getOfWarrior(null));
15 }
16
17 static Optional<String> getOfWarrior(String name) {
18 return Optional.of(name);
19 }
20 static Optional<String> getOptWarrior() {
21 return Optional.ofNullable(null);
22 }
23 }
A) true
Exception in thread “main” java.lang.NullPointerException will be thrown
B) false null Optional.empty true
Optional.empty
Explanation:
At the first line of the main method, we will pass “Kratos” to the getOfWarrior method. Then, the
Optional.of method will return an Optional with the "Kratos" value. As a result, the isPresent
method will return true.
Then, we assign Optional.empty to spartanGhost.
Invoke the getOptWarrior that returns an Optional.empty since we are using the ofNullable
method.
10 Optional 244
Finally, we pass an empty String to the Optional.of method. Remember that an empty String is
different than an empty Optional. Therefore, the emptyWarrior variable will have an empty String
in it but will not be an Optional.empty.
Therefore, when printing the values:
Notice that there are two main responsibilities in the method above. We will have to create a test flow
for each case, when there is a value present and when there isn’t. This makes the code confusing.
Instead, it’s better to remove the Optional type from the method parameter and handle this edge-
case scenario wherever this processValue method is being invoked:
10 Optional 245
1 Optional.ofNullable("value").ifPresentOrElse(this::processValue,
2 () -> System.out.println("Process logic when there is no value..."));
Observe in the method above that we are making clear there are two method flows when there is a
value and when there isn’t. By doing that, we don’t hide features in a method, we make it explicit.
Therefore, other programmers will be able to understand the code more easily.
Now, let’s suppose a developer invokes the above method passing null in the Optional<Child> child
parameter:
1 saveSimpson("Homer", null);
You probably guessed it, a NullPointerException will be thrown! Therefore, it completely defeats
the purpose of the Optional class.
To manage this situation better, we can remove the Optional parameter and handle the null value:
By following the above approach, we make our API very clear. Therefore, whoever is using the API
will immediately notice the method that only receives the parentName. It will be also much easier to
test both of the methods.
In real projects, we have data objects everywhere and if we have the attributes of those classes
declared as Optional, the code would easily become a mess.
For more information, you can check the notes from Brian Goetz at the following link:
(http://mail.openjdk.java.net/pipermail/lambda-libs-spec-experts/2013-May/001814.html)[http://mail.openjdk.java.
libs-spec-experts/2013-May/001814.html]
As you can see in the code above, when we have a null value in the Optional and we use the get
method, we will get a NullPointerException.
To avoid it, we can use the isPresent method to check the value first:
Still, the code above is not optimal, it doesn’t add the real benefit of an Optional.
Much better would be to use either the ifPresent method because we can do the same as above
with less and more readable code:
If you need to handle the case of having an absent value, you can use the Java 9 method
ifPresentOrElse:
10.3.5 Takeaways
To summarize the most important points of the Optional anti-patterns, let’s check the main points:
1 if (javaMascot != null) {
2 System.out.println(javaMascot.concat(" Rocks"));
3 }
To avoid NullPointerException, we are doing a null check. With Optional we can make it easier.
We can use a lambda to do the same as above:
As you can see in the method above, we are receiving a Consumer which means that we consume the
Optional value and perform an action. Otherwise, we run a code action in case the Optional value
is null:
As mentioned above, we are consuming the value of javaMascotOpt in the Consumer and printing it.
In the Runnable we print that there is "No Java Mascot".
When the Optional value is empty the orElse Runnable will be executed:
10 Optional 249
Let’s see the case scenario when we have a value in the Optional:
Therefore, instead of only returning a value as the orElse method, we can use some logic with a
lambda expression:
Prefer using Method Reference when a lambda code has more than one line:
It is a good practice to use a method reference in a functional interface when the code
has more than one line. If we used a in place lambda, the code would get confusing. We need
to use good judgement whenever we want to use more than one line of code in a lambda
expression.
1 import java.util.Optional;
2
3 class JavaMascotOrElseOrElseGet {
4 public static void main(String[] args) {
5 Optional<String> optJavaMascot = Optional.ofNullable("Duke");
6 optJavaMascot.orElseGet(JavaMascotOrElseOrElseGet::printJavaMascot);
7 }
8
9 static String printJavaMascot() {
10 System.out.println("Duke gets prepared...");
11 return "Duke";
12 }
13 }
The orElse method will be always executed no matter if the value is present or absent:
1 import java.util.Optional;
2
3 class JavaMascotOrElse {
4 public static void main(String[] args) {
5 Optional<String> optJavaMascot = Optional.ofNullable("Duke");
6 optJavaMascot.orElse(printJavaMascot());
7 }
8
9 static String printJavaMascot() {
10 System.out.println("Duke gets prepared...");
11 return "Duke";
12 }
13 }
1 import java.util.Optional;
2
3 public class VisionFromOracleChallenge {
4
5 static String finalZionValue = null;
6 static int matrixCount = 0;
7
8 public static void main(String... matrix) {
9 Optional<String> optFromMatrix = Optional.ofNullable(finalZionValue);
10 var agentSmith = "Virus";
11
12 finalZionValue = finalZionValue + Optional.ofNullable(agentSmith).orElse(getVisi\
13 onFromOracle());
14 finalZionValue += optFromMatrix.orElseGet(VisionFromOracleChallenge::getVisionFr\
15 omOracle);
16
17 finalZionValue += matrixCount;
18
19 System.out.println(finalZionValue);
20 }
21
22 static String getVisionFromOracle() {
23 matrixCount++;
24 finalZionValue += "KeyMaker";
25 return "Architect";
26 }
27 }
A. nullVirusArchitect1
B. nullVirusKeyMakerArchitect1
C. nullVirusArchitect2
D. nullKeyMakerVirusKeyMakerArchitect2
Explanation:
The first action we take is to wrap the finalZionValue variable in the optFromMatrix. Then we
assign the "Virus" String to the agentSmith variable.
10 Optional 253
Then, we concatenate the finalZionValue that has the value of null and encapsulate the agentSmith
variable in an Optional. However, remember that the orElse method will be invoked anyway even if
the Optional value is not null. Therefore, the getVisionFromOracle method will be invoked and will
increment the matrixCount variable, will concatenate with "KeyMaker" and will return “Architect”
to nothing.
Notice that the finalZionValue is receiving a new value because we are using =, therefore, even
though we concatenated "KeyMaker" in the finalZionValue, this value will be replaced with the
value of agentSmith. This occurs because finalZionValue + Optional.ofNullable(agentSmith)
will be concatenated first!
Therefore, value of the finalZionValue at this moment is nullVirus.
In the second action, we are using the optFromMatrix that is actually empty. Remember that String
in Java is immutable. Therefore, even though we changed the finalZionValue it doesn’t matter
for the wrapper optFromMatrix. Since the optFromMatrix is empty, the orElseGet method will be
invoked. It’s important to keep in mind that if optFromMatrix wasn’t empty, the orElseGet method
would not be invoked.
Then the matrixCount variable will be incremented to 2. The finalZionValue will be concatenated
again, and this time "Architect" will be returned.
Note that the following code:
finalZionValue += optFromMatrix.orElseGet(VisionFromOracleChallenge::getVisionFromOracle);
We can also use a constructor method reference for the IllegalArgumentException since it matches
the contract of the Supplier interface. The IllegalArgumentException constructor returns a value
and this is exactly what a Supplier needs:
As you see in the code above the trade-off of using a method reference is that we can’t pass a
parameter like we did when we used a lambda. However, if you really want to have a specific
Exception message you could create a specific Exception as the following:
Since we are passing "D'oh!" to the RuntimeException constructor, now we will see our specific
message "D'oh!".
1 import java.util.Optional;
2
3 public class OrElseThrowLolChallenge {
4
5 public static void main(String... doYourBest) throws Throwable {
6 String finalOpt = "finalLol";
7 Optional<String> opt = null;
8
9 try {
10 opt = Optional.of(null);
11 } catch (RuntimeException exception) {
12 System.out.println(opt.orElseThrow(() -> new Throwable())); // Line 12
13 } catch (Throwable error) {
14 finalOpt = opt.orElse("lol");
15 }
16
17 System.out.println(finalOpt);
18 }
19
20 }
A. lol
B. null
C. NullPointerException will be throw at line 12
D. finalLol
Explanation:
In the try block, we start trying to encapsulate null into an Optional. When we use the Optional.of
method, a NullPointerException will be thrown.
Considering that NullPointerException is a RuntimeException, this catch statement will catch the
NullPointerException.
Then, notice that we are trying to access the orElseThrow method from the opt variable but
the opt variable never got initialized. Therefore, another NullPointerException will be thrown.
However, pay attention that we are don’t have another try for this code line. As a result, the
NullPointerException will be break the program execution at line 12.
10 Optional 256
It will print:
1 true
2 false
We can also do more sophisticated filtering with Optional. Let’s see the following code example
without Optional:
As you can see, the code above is verbose. With the map and filter methods from Optional we can
do the same as the code above with less code.
In the following code, we will first encapsulate the value of simpson into an Optional. Then we
will filter the Optional to have only "Homer". We map Simpson to return the age with the method
reference Simpson::getAge. Then in the filter method, we will be manipulating the age, therefore,
we check if the age is between 34 and 39.
Finally, we check if the value is present.
As you can see in the code above, the code looks verbose. Now, let’s see how to concatenate a String
from an Optional with the map method:
10 Optional 258
Let’s try to change the value of the getEatenApples with the map method. Notice that the map method
is meant to manipulate the first Optional layer, not the second one:
As you can see in the code above, the else statement is encapsulating the Optional value inside
an Optional.ofNullable. Therefore, if we try to handle a value that is an Optional, we will have
something weird like Optional<Optional<Integer>>. Let’s see that happening in the following code:
10 Optional 260
The code above is confusing and messy. That’s because the map method shouldn’t manipulate a value
from an Optional inside an Optional.
Notice the else statement of the code above. We are not encapsulating the value again into an
Optional as the map method. Instead, we are transforming the data and then returning the value of
the Optional:
Optional<U> r = (Optional<U>) mapper.apply(value);
The following code is a good example of the flatMap purpose. We have the Yoshi class with the
Optional getEatenApples method:
10 Optional 261
• #A: Notice we are encapsulating yoshi into an Optional, then there is the getEatenApples that
also returns an Optional. Therefore, we have what we need, an Optional inside an Optional.
Then we can manipulate the value of getEatenApples directly avoiding NullPointerException
in case yoshi is null.
In the code above, we could return the value of the getEatenApples directly as an Optional.
Therefore, to get the value of getEatenApples we just need to use a getter method for our Optional:
System.out.println(eatenApples.orElse(0));
1 import java.util.Optional;
2
3 public class SopranoNullableGunChallenge {
4 public static void main(String... args) {
5 Soprano soprano = new Soprano();
6
7 Optional.of(soprano)
8 .map(Soprano::getName)
9 .map(String::toUpperCase)
10 .filter(n -> n.contains("Tony"))
11 .ifPresentOrElse(System.out::println,
12 () -> System.out.println("No value!"));
13
14 String sopranoGun = Optional.of(soprano)
15 .flatMap(Soprano::getGun)
16 .map(String::toLowerCase)
10 Optional 262
A. Tony
Beretta
B. No value!
Colt
C. No value!
D. Tony
Colt
Explanation:
The first detail to pay attention is that we have the static inner class Soprano that has a getter to a
String name and a another one to an Optional<String> for gun‘.
We map the soprano object to the getName method which means that we will manipulate the getName
value instead of the sopranos value:
.map(Soprano::getName)
Then, we filter the value of name of "TONY" to "Tony" which considering that Java is case-sensitive,
the contains method will return false. Therefore, the filter method returns Optional.empty:
.filter(n -> n.contains("Tony"))
Now, we invoke the ifPresentOrElse method that returns "Tony" in case the value is present, or
"No value!" in case the Optional is empty. Considering that the previous filter method returned
Optional.empty, "No value!" will be printed:
10 Optional 263
1 .ifPresentOrElse(System.out::println,
2 () -> System.out.println("No value!"));
In the second Optional statement, we also encapsulate the soprano object. Then we use the flatMap
method. Notice that we are getting only the getGun Optional layer. This is only possible to
accomplish with the flatMap method:
.flatMap(Soprano::getGun)
Now we map the value of the Optional<String> getGun() from "Beretta" to lowercase, as a result,
it will transform it to "beretta":
.map(String::toLowerCase)
We use the or method, it returns the Optional value if the value is present. Otherwise, it returns
the logic we implemented in the Supplier we are passing, in this case, Optional::empty:
.or(Optional::empty)
Considering that the previous or statement received an Optional.empty, the orElse statement will
be invoked:
.orElse("Colt");
Therefore, the value of sopranoGun will be Colt and the right answer is:
B. No value!
Colt
1 import java.util.Optional;
2
3 public class YoshiFoodOptionalChallenge {
4
5 public static void main(String... yoshiFood) {
6 Integer yoshiFoodCount;
7 Object isYoshiFoodEmpty;
8 Optional<Integer> optYoshi = Optional.ofNullable(0);
9
10 try {
10 Optional 264
11 yoshiFoodCount = optYoshi.orElseThrow();
12 } finally {
13 isYoshiFoodEmpty = Optional.ofNullable(null)
14 .filter(y -> y == null).orElse(Boolean.FALSE);
15 }
16
17 boolean isApple = Optional.of("Apple").flatMap(a -> Optional.of("APPLE"))
18 .equals(Optional.of("APPLE"));
19
20 optYoshi.ifPresentOrElse(System.out::print,
21 Optional.ofNullable(null)::get);
22 System.out.printf("%s %s %s", yoshiFoodCount, isYoshiFoodEmpty, isApple);
23 }
24
25 }
Explanation:
First, we create an Optional encapsulating the value of 0:
Optional<Integer> optYoshi = Optional.ofNullable(0);
Then, we get the value from optYoshi since there is a value there of 0:
yoshiFoodCount = optYoshi.orElseThrow();
Now in the finally statement, we will encapsulate the value of null into the Optional. Notice that
we are encapsulating the value of null into the Optional. The filter method has a check for a null
value. If the value is null, the condition won’t be even checked.
Therefore, the logic we passed into the filter method will be ignored and the orElse method will
return false instead:
1 isYoshiFoodEmpty = Optional.ofNullable(null)
2 .filter(y -> y == null).orElse(Boolean.FALSE);
In the following code, we encapsulate "Apple" in the Optional. Notice that when we use the flatMap
method we will make the Optional flat, this means, instead of having an Optional of an Optional,
we will return only one Optional with the value of "APPLE". Therefore, it will be equals to the
Optional.of("APPLE") and will return true.
10 Optional 265
10.9 Summary
By reading this chapter, now you can to the following:
Congratulations! By knowing the Optional concepts you are now far more prepared to create high-
quality code and deal better with NullPointerException. Now it’s your time to apply this knowledge
in a real-world project!
11 Generics and Object Comparison
This chapter covers:
Generics and Collections are one of the most used concepts in Java. Very commonly we will have
to deal with arrays of data. List, Set and Map are interfaces that are present in our day-to-day work
very often. Therefore, understanding how we can use those Collections effectively makes all the
difference to create high-quality code.
native modifier:
Notice that the Object class uses the native modifier in the hashCode method. This modifier
indicates that the method implementation will use native code and JNI (Java Native
Interface). This modifier is used for lower-level interaction (machine, memory level).
When using the native keyword, we can implement Java methods using C or C++. One
of the main motivations to use the native operator is to obtain more performance.
Considering the raw implementation of the equals and hashCode methods, when we create a custom
class, we will have to override those methods. Otherwise, we will be comparing the object instance
instead of the object values.
Keep in mind the golden rule that whenever we override the equals method, always override the
hashCode method too. Remember, the equals and hashCode methods must be always together.
In the following code example, we will override the equals method comparing the current instance
attribute with the object that is being passed in the equals method. In the hashCode method we are
generating a unique hashCode based on the fields of the SpiderMan class:
1 import java.util.List;
2 import java.util.Objects;
3
4 public class SpiderMan {
5
6 String armorName;
7 String webGadget;
8 List<String> skills;
9
10 @Override
11 public boolean equals(Object o) {
12 if (this == o) { // #A
13 return true;
14 }
15 if (o == null || getClass() != o.getClass()) { // #B
16 return false;
17 }
18 SpiderMan spiderMan = (SpiderMan) o; // #C
19 return Objects.equals(armorName, spiderMan.armorName) && Objects
20 .equals(webGadget, spiderMan.webGadget) && Objects.equals(skills, spiderMan.\
21 skills); // #D
22 }
23
24 @Override
25 public int hashCode() {
26 return Objects.hash(armorName, webGadget, skills); // #E
11 Generics and Object Comparison 268
27 }
28
29 }
• #A: The first check we make in the equals method is if both objects are pointing to the same
object reference. If this is true, we will be sure the attributes will be also the same considering
it’s the same object.
• #B: Then we will check if the passed object is null, if this is true, for sure the objects won’t
be equals. This is because to invoke the equals method it’s necessary to create an instance,
otherwise, it’s impossible to invoke the equals method. The second condition checks if the
objects have different class type. If one of those conditions are true then the objects are
different.
• #C: We cast the passed object to SpiderMan.
• #D: Notice we are using the Objects.equals method from the java.util package that basically
avoids NullPointerException in case an attribute is null:
If we were not using the Objects.equals method, we would have to do the null checks for each
attribute, making the code verbose and confusing. Notice other important point that the equals
method is meant to check the total equality of objects, therefore, we are supposed to check the
equality of every attribute.
• #E: Whenever we override the equals method, we must also override the hashCode method.
This is a good practice because it’s far more expensive in terms of performance to check the
equality of every single attribute, rather than checking the hashCode of attributes.
1 import java.util.List;
2 import java.util.Objects;
3
4 public class SpiderMan {
5
6 private String suit;
7 private List<String> skills;
8 private int age;
9
10 // equals method...
11
12 @Override
13 public int hashCode() { // #A
14 return Objects.hash(suit, skills, age); // #B
15 }
16
17 }
• #A: Notice that the hashCode method simply returns an int number. We don’t receive another
object to be compared like the equals method.
• #B: We are using the handy Objects.hash method from the java.util.Objects package. It’s
better to use this method because then we don’t need to replicate the code below, we just reuse
it:
In a real-world project, this is what we will usually do. We will generate the hashCode based on the
state of the object.
This principle is mainly used in Set or Hash collections for performance reasons.
When the equals() method returns true, it means that the objects are equal in all values and
attributes. In this case, the hashcode comparison must be true as well.
We can also return a constant int number that will be always the same. However, by doing that, we
won’t get the benefit of the hashCode method.
11 Generics and Object Comparison 271
1 @EqualsAndHashCode
2 public class SpiderMan { ... }
As you can see in the code above, it’s very easy and simple to implement the equals and hashCode
methods with Lombok. Lombok is also very flexible, that means that you can customize the way
you use the equals and hashCode methods the way you prefer.
23 return name.equals(spiderMan.name);
24 }
25 public int hashCode() {
26 return (43 + 777);
27 }
28 }
29 }
A. false
true
2
B. true
false
1
C. false
false
0
D. true
true
2
Explanation:
Notice that the areEquals method is doing what it’s supposed to be done when two objects are being
compared with Java. First, there is a comparison with hashCode followed by the equals check of two
objects.
The data class SpiderMan that has the equals and hashCode methods overridden.
Another important point to pay attention is the static equalsCount variable that counts how many
times the equals method was invoked.
Then, in the main method, we are invoking the areEquals method passing two SpiderMan objects
with the name of "Peter". Since the value of both objects are the same, the areEquals method will
return true. Notice also that the equalsCount variable is being incremented by 1.
In the following statement, we are instantiating another SpiderMan with the "Symbiote" name.
However, we are also overriding the hashCode method passing a different value. Therefore, when we
invoke the areEquals method, the hashCode will be different between the other "Symbiote" object.
When the hashCode is different from another object, there is no point to execute the equals method.
That’s because if the hashCode method is different from the other object’s hashCode, the equals
method has to return false as well. Therefore, the equalsCount variable won’t be incremented.
Considering that the hashCode from both objects are different, the result from the areEquals method
will be false.
11 Generics and Object Comparison 273
B. true
false
1
11.2 Generics
I know that explaining Generics before Collections is not the standard of most Java books. Most
books will explain Collections first and then Generics but when I was learning Collections I was
confused by the symbol <>. I was using it with Collections but I didn’t know what was that exactly.
That’s why I decided to not follow what most Java books do and before going through Collections,
you can first understand the Generics concept. Then the <> symbol will make sense when using
Collections.
As mentioned when we use Collections, we are most of the time also using Generics. The generic
type of a Collection is the type between the <>. The generics concept was introduced in Java 1.5.
There is a great definition of Generics in the Java documentation:
In a nutshell, generics enable types (classes and interfaces) to be parameters when defining
classes, interfaces and methods.
This means we can define a type of a parameter in a class or interface in a dynamic way. By using
generics, we get the benefit of strongly type a parameter dynamicaly so that we can have more
control over the code.
It’s far easier for bugs to come up in an application when we don’t strongly type our code. Generics
make code strongly-typed and secure during compile-time since we don’t need to make use of casting
for example.
To make the concept of generics clear, let’s see some code examples.
11 Generics and Object Comparison 274
Output:
777
Let’s analyze the code:
• #A: We instantiate our List without a generic type, this means that we can add any element
type into our List.
• #B: We add a String.
• #C: We add an int number that will be auto-boxed and wrapped into an Integer type.
• #D: When we need to get an array element, we need to cast the type to what we are expecting.
That happens because there is no way for the compiler to know what type we inserted in the
List. In this case, we are getting the second element that contains an Integer number 777 and
adding it to the list.
Adding any type to the List is not good because it’s very easy to have the wrong data type in the
List and cause bugs will be difficult to be tracked.
Basically, the <E> is the type of the List element. Therefore, when using the List we need to pass
the element’s type when instantiating it:
11 Generics and Object Comparison 275
Output:
Duke
• #A: Notice that now we are passing the generic type of <String>, therefore, we can only add
objects that are typed as String into the List.
• #B: We are adding a String type to the List. Notice that if we try to add the following
list.add(777); the code wouldn’t compile because the List is expecting only String.
• #C: Finally, we retrieve the the first element of the List by the index. Note that we are not
using casting because the compiler knows this List will only accept String elements.
<> operator:
Before Java 7 we would have to repeat the generic type on the instantiation like the following
: List<String> list = new ArrayList<String>(); which is verbose and unnecessary.
Fortunately, since Java 7 we can ommit the generic type of the instantiation as the following:
List<String> list = new ArrayList<>(); having the same effect.
The code won’t compile, so be aware that we can only use a class type. Another important detail is
that if we try the following, the code will work:
1 int number = 7;
2 List<Integer> list = new ArrayList<>();
3
4 list.add(number); // This works because of the auto-boxing concept
As we saw in previous chapters, Java has a concept of auto-boxing. Therefore, when we add an
int type to a List<Integer>, actually, the int type will auto-boxed to Integer implicitly by the
compiler.
The same will happen with the other primitive types.
11 Generics and Object Comparison 276
1 import java.util.ArrayList;
2 import java.util.List;
3
4 public class CollectionWithoutGenericsChallenge {
5
6 public static void main(String[] args) {
7 List list = new ArrayList();
8 list.add("Venom");
9
10 // ##REPLACE##
11
12 System.out.println(superVillain);
13 }
14
15 }
16 ````
17
18 A) String superVillain = list.get(0);
19
20 B) Object superVillain = list.get(0);
21
22 C) var superVillain = list.get(0);
23
24 D) String superVillain = (String) list.get(0);
25
26 **Explanation**:
27
28 In alternative `A`, we can't use the code `String superVillain = list.get(0);` becau\
29 se we are not using casting. Note that there is no way for the compiler to guess wha\
30 t is the variable type since we can put anything into the `list`.
31
32 In alternative `B`, we can use `Object superVillain = list.get(0);` because we are p\
33 utting the `list` value into an `Object` since it can receive any type.
34
35 In alternatice `C` we can use `var superVillain = list.get(0);` also because a `var`\
11 Generics and Object Comparison 277
Output:
class java.lang.StringBuilder
class java.lang.String
class java.lang.Integer
7
Code Analysis:
11 Generics and Object Comparison 278
• #A: This is the syntax to use a generic type at a class level. We use the <T> after the class name.
Notice that the generic type name can be anything. T is commonly used because it’s a T for
Type.
• #B: Once we have the <T> generic type declared in the class declaration, we can use the generic
type in this class. Notice that we are using the generic type T as a return type. We are also using
the T as the parameter type.
• #C: We print the class type of the generic type.
• #D: We instantiate the GenericClass with the generic type of StringBuilder.
• #E: Notice that we already passed the StringBuilder type to GenericClass, this means that
the generic type is already set for StringBuilder. Therefore, when invoking the doSomething
method, we can only use the StringBuilder type. If we try to pass something else as argument,
there will be a compilation error.
• #F: We are instanting the GenericClass again, but now passing a String as a parameter. Notice
that we are not passing anything in the <>, therefore, the generic type applied by default will
be Object, not String.
• #G: We are passing the generic type of Integer when we instantiate the GenericClass.
Therefore, T will be Integer and we won’t have to cast this type. Therefore, we can return
the value of the doSomething method and print it with no complications.
Output:
class java.lang.String
11 Generics and Object Comparison 279
• #A: Note that instead of declaring the generic type at class level, we are declaring it after the
public modifier. Therefore, we will be able to use a generic type as a parameter in the method.
• #B: We are passing the type of String to the T generic type. Notice that since we are using
generics at a method level, when we pass a String as a parameter, T will be inferred as String
automatically.
• #C: Notice that if we want, we can also pass the method generic type when invoking the method.
It’s not necessary though since the type we pass as parameter will be already inferred as the
generic type. It works but it’s redundant.
Output:
T type
Code Analysis:
11 Generics and Object Comparison 280
• #A: Notice that we need to declare the generic type at class level to be able to use it as an
instance variable. Another important point is that the T type can be used as a constructor or
method parameter.
• #B: Since we declared the T generic type in the class, we can use T as an instance type.
• #C: We can pass the value of our generic type here and then assign to the instance variable.
• #D: It’s important to notice here that we are passing the generic type when we use the following
<String>. If we don’t provide the type here, the T type can’t be inferred when we only pass a
value. That’s because when we declare the generic type in a class level, the type is supposed to
be the same for the whole class.
• #E: We can return the t value as a String because we defined that in the generic type. That’s
one of the greatest advantages of using generics, to define a return type during runtime.
Explanation:
In this Java Challenge we are exploring generics at class, constructor, and method level.
When we instantiate GenericMystiqueChallenge we are passing the <String> generic type. When
we do that, we will pass the String type to T.
Also note that we are passing "Raven" in the constructor. If we tried to pass anything else there
would be a compilation error since we already defined the type of T to be String.
Therefore, it will be fine to retrieve the t variable as String since the T generic type is String.
Then we invoke the mystiqueTransform method passing "Wolverine". Notice that the
mystiqueTransform method receives the generic type of K. Since the K generic type is being
declared at a method level, the type will be inferred without the need of the <> operator. Therefore,
the variable transformed will have the value of "Wolverine".
Finally, notice that we are using a generic type shadowing. We already have the T type declared at
class level but we are declaring it again in the mystiqueAttackPower method signature since we are
using <T>.
In conclusion, the correct answer is:
C) Wolverine Raven 7
Notice the BinaryOperator that it’s possible to extend BiConsumer and override the generic types
with another generic type:
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
As you can see in the above examples, there are plenty of interfaces and classes in the JDK that use
multiple generics.
Let’s create our own interface that has many generic types:
11 Generics and Object Comparison 282
Output:
3.0
Code Analysis:
• #A: We are declaring the generic types into the interface, K, V and R.
• #B: Then we are using the generic types in the put method.
• #C: We are passing the type Integer to K, Double to V, and String to R. Then we are passing a
lambda expression, basically definining the body of the put method.
• #D: Finally, we invoke the put method passing an Integer 1, Double 2.0 and returns a String
which matches exactly the generic types we passed to the MultiMap interface.
Output:
upperBoundGenerics
Code Analysis:
• #A: Notice the syntax of the upper bound generic type. We are declaring T as the generic type
and we are also extending String. This means that we can receive any type that is or extends
a String.
• #B: Another important point is that we are using the specific concat method from the String
class. This is only possible because we are making explicit to the compiler that T extends String,
therefore, the concat method will be on t.
Therefore, if you want to create powerful and robust APIs, unbounded generics will be a great help
for you.
When we use ?, we can use the upper bound or lower bound generic type.
Let’s see how unbounded type, lower and upper bounds generics work in the following code:
Unbounded type: we can pass a list with any type. ? will be erased at runtime and will be substituted
by the type we are passing:
Lower bounds generics: we can pass a list that is Double or any super type of Double:
Upper bounds generics: we can pass a list that is Number or any sub type of Number:
11 Generics and Object Comparison 284
1 import java.util.ArrayList;
2 import java.util.List;
3
4 public class LowerUnboundedGenerics {
5 public static void main(String[] args) {
6 List<Number> numbers = new ArrayList<>();
7 numbers.add(7.0);
8 numbers.add(3.0);
9 List<? super Double> result = addNumbers(numbers);
10 System.out.println(result);
11 }
12
13 static List<? super Double> addNumbers(List<? super Double> numbers) { // #A
14 numbers.add(1.0); // #B
15 return numbers;
16 }
17 }
Output:
[7.0, 3.0, 1.0]
Code Analysis:
• #A: Notice that it’s only possible to use lower bound generic type with the unbounded wildcard.
Therefore, we can’t declare an unbounded generic type, we can only pass it as a type to an
existent generic type.
• #B: Another important point is that we can add a Double as a type to the list. We can’t add an
Integer, Float or any other type to this generic type.
Your objective is to replace ##REPLACE## by one of the code alternatives so that the code will compile
and print "Everything went fine!".
Which alternatives are correct? Note that more than one alternative may be correct.
A. fight(kratos, zeus);
B. fight(atreus, new Warrior<>());
C. fight(kratos, atreus);
D. fight(zeus, kratos);
Explanation:
The code in alternative A will compile fine since kratos extends Zeus and zeus is super of kratos.
The code in alternative B will also compile fine since atreus extends zeus and new Warrior<>() will
infer the generic type of Kratos.
The code in alternatice C won’t compile because the second atreus argument is not super of Kratos.
The code in alternative D works fine since the type we see in the wildcard either for extends or super
will always work. For example, in the first warrior we use <? extends Zeus>, therefore, zeus will
work. For Warrior<? super Kratos> kratos also works fine.
In conclusion, the correct alternatives are:
11 Generics and Object Comparison 286
A) fight(kratos, zeus);
B) fight(atreus, new Warrior<>());
D) fight(zeus, kratos);
1 import java.math.BigDecimal;
2 import java.util.ArrayList;
3 import java.util.List;
4
5 public class UpperUnboundedGenerics {
6 public static void main(String[] args) {
7 List<Integer> intNumbers = new ArrayList<>();
8 intNumbers.add(7);
9 printNumbers(intNumbers); // #A
10
11 List<BigDecimal> decimalNumbers = new ArrayList<>();
12 decimalNumbers.add(BigDecimal.TEN);
13 printNumbers(decimalNumbers); // #B
14 }
15 static void printNumbers(List<? extends Number> numbers) { // #C
16 numbers.forEach(System.out::println);
17 }
18 }
• #A: We pass a List of Integer to the printNumbers method. This is fine since we are receiving
any List that extends Number in the gneric type.
• #B: Here we pass a List of BigDecimal and that’s one of the benefits of using the upperbound
generic type, we can pass a whole List to a method with some flexibility in the type. We can
pass a List with any generic type that is a Number.
• #C: We are receiving here List<? extends Number> numbers which enables us to pass a List
with a flexible Number type. Therefore, we can print any Number in this method.
The code won’t compile, this is because we can’t use polymorphism with generics. To fix the code
above we have to use the following:
List<Number> list = new ArrayList<>();
We can simulate the effect of polymorphism by using bounded generics though. We could also create
a method that receives a List<? extends Number>:
1 import java.util.ArrayList;
2 import java.util.List;
3
4 public class NoPolymorphismGenerics {
5 public static void main(String[] args) {
6 List<Integer> intNumbers = new ArrayList<>();
7 intNumbers.add(7);
8 printNumbers(intNumbers);
9 }
10 public static void printNumbers(List<? extends Number> numbers) { // #A
11 numbers.forEach(System.out::println); // #B
12 }
13 }
• #A: We are using a wildcard ? here which gives us the flexibility to pass a List that has the
generic type of any Number.
• #B: We iterate over the List of Number and print the elements.
Notice that we still can use polymorphism in the element level. That’s why we can add any Number to
the List. However, if we want to use polymorphism at the Collection level as the previous example,
then we have to use bounded generics.
1 import java.math.BigDecimal;
2 import java.util.ArrayList;
3 import java.util.List;
4
5 public class MiniMogWildcardChallenge {
6 public static void main(String... doYourBest) {
7 List<Integer> miniMogHitDamage;
8
9 List<Integer> intDamage = new ArrayList<>();
10 intDamage.add(10);
11 miniMogHitDamage = addInto(intDamage); // #A
12
13 List<Long> longDamage = new ArrayList<>();
14 miniMogHitDamage.addAll(addInto(longDamage)); // #B
15
16 List<BigDecimal> bigDecimalDamage = new ArrayList<>();
17 bigDecimalDamage.add(BigDecimal.ZERO);
18 miniMogHitDamage.addAll(addInto(bigDecimalDamage));
19
20 miniMogHitDamage.forEach(System.out::println); // #C
21 }
22 static List addInto(List<? extends Number> list) {
23 List<Number> resultList = new ArrayList<>();
24 resultList.addAll(list);
25 return resultList;
26 }
27 }
Explanation:
We first invoke the addInto method with the intDamage Integer List. Then we add all the Integer
elements into the resultList. This works because Integer extends Number.
11 Generics and Object Comparison 289
Then we invoke the addInto method again with the longDamage list. It works fine again because
Long also extends Number.
We invoke the addInto method for the last time passing the bigDecimalDamage list. It also works fine
because BigDecimal extends Number.
Finally, we try to iterate in the List<Integer> but there is a problem, we added an Integer and
BigDecimal elements into it. Therefore, when we try to iterate the elements of this array, we will
get a ClassCastException because we can’t use a BigDecimal type in an Integer List.
In conclusion, the final answer will be:
D. 10
Exception in thread “main” java.lang.ClassCastException: class java.math.BigDecimal cannot
be cast to class java.lang.Integer at line // #C
11.5 Summary
Generics will help you understand better the Java API and will help you design better APIs when
needed.
In this chapter, you learned that:
Now that you have a brief understanding of the Collections api, let’s explore their concepts more
deeply and of course do some Java code challenges!
12.2 List
The List interface is the most used Collection interface in the day-to-day work of a Java developer.
As the name suggests, we can add many elements in the list, we can access elements quickly by
index and elements are ordered in the way they are inserted.
Note that we will also use ArrayList as an implementation of List here but we will explore it further
after List.
Let’s analyze the following code to see a List in practice:
1 import java.util.ArrayList;
2 import java.util.List;
3
4 List<String> heroes = new ArrayList<>(); // #A
5 heroes.add("Batman"); // #B
6 heroes.add("Spider-Man");
7 heroes.add("Iron Man");
8
9 System.out.println(heroes); // #C
Output:
Code Analysis:
• #A: Note that we are instantiating the List with ArrayList. By doing that we are using
polymorphism. ArrayList is the most used Collection in real Java projects. We are also
passing the generic type of String to the list, which means we can only add String to it.
• #B: We are adding String elements in the list here.
• #C: We are printing the toString method from the List. Notice that it’s nicely formatted.
We can easily see all the list elements. Out of curiosity, the toString is implemented in the
AbstractCollection class:
12 Collections 292
1 // You don't need to fully understand the following code, don't worry!
2
3 public abstract class AbstractCollection<E> implements Collection<E> {
4 // Ommitted other methods...
5 public String toString() {
6 Iterator<E> it = iterator();
7 if (! it.hasNext())
8 return "[]";
9
10 StringBuilder sb = new StringBuilder();
11 sb.append('[');
12 for (;;) {
13 E e = it.next();
14 sb.append(e == this ? "(this Collection)" : e);
15 if (! it.hasNext())
16 return sb.append(']').toString();
17 sb.append(',').append(' ');
18 }
19 }
20 }
1 // Omitted imports and list adding of Batman, Spider-Man, and Iron Man
2 for (int i = 0; i < heroes.size(); i++) // #A
3 System.out.println(heroes.get(i)); // #B
Output:
Batman
Spider-Man
Iron Man
Code Analysis:
• #A: We create a for looping iterating up until the list size and keep incrementing the i
variable.
• #B: Then we get each element by index printing then all the characters.
12 Collections 293
1 // Omitted imports and list adding of Batman, Spider-Man, and Iron Man
2
3 // Iterator to traverse the list
4 Iterator iterator = heroes.iterator(); // #A
5
6 // Verifies if there is a next element in the iterator
7 while (iterator.hasNext()). // #B
8 System.out.print(iterator.next() + " "); // #C
Output:
Batman Spider-Man Iron Man
Code analysis:
• #A: Notice that the List has the iterator method which returns an iterator object capable
of iterating over a list.
• #B: Then we put the iterator in a while looping and invoke hasNext() to check if there is a
next element.
• #C: Finally, we use the next method that will effectively get the list element and will pass the
pointer to the next element.
Therefore, if you don’t need any specific feature from the Iterator class, you can use the forEach
statement instead.
Let’s see how that works:
12 Collections 294
1 // Omitted imports and list adding of Batman, Spider-Man, and Iron Man
2 for (String hero : heroes) System.out.print(hero + " "); // #A
Output:
Batman Spider-Man Iron Man
Code analysis:
• #A: It retrieves String from heroes and puts each of them into a String variable.
Note that we receive a Consumer in the forEach method. So, if we want to print all elements using
a lambda, we can do the following:
1 // Omitted imports and list adding of Batman, Spider-Man, and Iron Man
2 list.forEach(e -> System.out.println(e));
Output:
Batman
Spider-Man
Iron Man
To make the code above even better, we can use a method reference to print the elements from the
list:
1 heroes.remove(0); // #A By index
2 heroes.remove("Iron Man"); // #B By object
3 System.out.println(heroes);
Output:
[Spider-Man]
Code analysis:
• #A: We remove the element by the index. Therefore, we remove the first element that is Batman
in that case.
• #B: We remove the element by object value. This means we will remove the "Iron Man", the
last element.
1 heroes.set(0, "Wolverine"); // #A
2 System.out.println(heroes);
Output:
Wolverine
Code analysis:
• #A: It changes the element located at the index we are passing. In that case, the index 0 is
changed from Spider-Man to Wolverine.
• void add(int index, E element): Adds an element based on the index we pass
• E get(int index): Retrieves an element by the index
12.3 ArrayList
As mentioned before, the most used implementation of List in real projects is certainly ArrayList.
ArrayList is has good performance to accessing data with the get and set methods because elements
are indexed.
Also, behind the scenes an ArrayList actually stores an array of objects as you can see in the variable
elementData in the JDK code:
An array in any programming language has to have pre-allocated size in memory. Since behind
the scenes in the ArrayList we use the type Object[] elementData, we also have to have an initial
capacity. In the case of ArrayList, the DEFAULT_CAPACITY is used. Therefore, whenever you add more
elements than the array’s capacity, a new array with the double of the capacity will be created since
it’s not possible to change an existing array’s capacity.
To add elements in an array while the defined capacity is not reached is quick but when the capacity
is reached then it’s necessary to copy the existing array elements into a new array which makes it
a bit slow.
ArrayList is not synchronized, which means that it’s not safe to use in a multi-thread environment
because you might have data colision. However, because ArrayList is not synchronized, it’s also
more performant.
12.3.3 List.of
In Java 9, there is the handy List.of method that makes our lives easier when creating an array.
However, note that it creates an immutable list as you will see in the following example:
When we try to add "Robin" to the heroes list, we will get the folowing Exception:
Exception in thread "main" java.lang.UnsupportedOperationException
Therefore, whenever we have an immutable Collection, we can’t add, delete or change the list
object in any way. If we really need to change the data we would need to create another ArrayList
object:
12.3.4 Arrays.asList
The Arrays.asList method is similar to the List.of method and has the same behaviour. It will add
elements to it and will return an immutable List.
Let’s see the following code example:
12.3.5 Collections.unmodifiableList
It’s usual to see lists in real Java projects being returned as unmodifiable lists in data objects. This
is useful because that prevents programmers to change data when it’s not suitable.
Let’s see a code example:
12 Collections 299
1 import java.util.ArrayList;
2 import java.util.Collections;
3 import java.util.List;
4
5 public class UnmodifiableList {
6 private static List<String> villains = new ArrayList<>();
7 public static void main(String[] args) {
8 villains.add("Magneto");
9 villains.add("Venom");
10 villains.add("Dr. Octopus");
11
12 villains = getUnmodifiableVillains();
13 villains.add("Carnage"); // #B
14 }
15
16 public static List<String> getUnmodifiableVillains() {
17 return Collections.unmodifiableList(villains); // #A
18 }
19 }
• #A: We use the Collections.unmodifiableList method that will return an immutable list.
Note that we pass the list as a parameter.
• #B: When we try to add an element to our list, we will get the following Exception:
1 import java.util.List;
2
3 public class ListChallenge {
4
5 public static void main(final String... doYourBest) {
6 final List<String> soldiers = List.of("Tyrion", "Arya", "John");
7
8 if (soldiers.size() > 3) {
9 soldiers.add("Cersei");
10 } else {
11 soldiers.add("Bran");
12 }
13
14 System.out.println(soldiers);
15 }
16
17 }
There is a crucial concept in the List.of method which is immutability. This means that we can’t
add, change or remove any element from the List when we use the List.of() method. Immutable
objects make code more reliable and less susceptible to bugs.
Therefore, in the if statement we are checking if the list has more than three elements which is
false.Then the else statement will be executed:
soldiers.add(“Bran”);
However, since we are trying to add an element to an immutable list, the following Exception will
be thrown:
java.lang.UnsupportedOperationException
12.4 Vector
The Vector class is very similar to ArrayList with the difference that it is thread-safe. If you
are working in a multi-thread environment and you don’t want data collision with other thread
processes then Vector can be a better option.
12 Collections 301
Since Vector is synchronized, it’s also less performant than ArrayList. Vector also implements List
which means that we can use polymorphism at the same way as an ArrayList.
Let’s see a code example with Vector:
1 import java.util.List;
2 import java.util.Vector;
3
4 public class VectorExample {
5 public static void main(String[] args) {
6 List<String> vector = new Vector<>(); // #A
7 vector.add("Batman");
8 vector.add("Joker");
9 vector.add("Robin");
10 vector.add("Joker");
11
12 int indexOfJoker = vector.indexOf("Joker"); // #B
13 int lastIndexOfJoker = vector.lastIndexOf("Joker"); // #C
14
15 System.out.println(lastIndexOfJoker);
16 System.out.println(indexOfJoker);
17 }
18 }
Output:
1
3
• #A: We are instantiating the List with a Vector making use of polymorphism.
• #B: Then we are using the indexOf method that will return the first found element’s index
number. In our case, the first "Joker" is found at the index 1.
• #C: The lastIndexOf method is passing "Joker" and will return 3 because the last position
"Joker" is found.
Just imagine how you would pile up your plates in your kitchen one on the top of the other, getting
the easiest accessible plate from the pile (Last-in First-out):
_/ <- Put the last plate on the top (Last-in)
_/
__/
_/ _/ <- Remove the first plate (First-out)
__/
As the name suggests, a Deque has the behaviours of a double ended queue, which means that we
can use it as a stack or a queue. In other words, we can add or delete the first or the last element.
Let’s see first how to use a Deque with stack capabilities (Last-in First-out):
1 import java.util.Deque;
2 import java.util.LinkedList;
3
4 public class DequeLinkedList {
5
6 public static void main(String[] args) {
7 Deque<String> deque = new LinkedList<>(); // #A
8 deque.push("Wolverine");
9 deque.push("Storm");
10 deque.push("Xavier");
11 deque.push("Juggernaut"); // #B (Last-in)
12 deque.pop(); // #C (First-out)
13 System.out.println(deque);
14 }
15 }
Output:
[Xavier, Storm, Wolverine]
• #A: Notice we are using the Deque interface as the type of the LinkedList implementation. By
doing that, the LinkedList will have capabilities to work as a double-linked stack or queue.
• #B: The push method will insert the element at the top of the stack and also follows the Last-in
principle. The push method does the same as the addFirst method. At this point of the code,
this is how the deque will be:
• #C: The pop method follows the First-out behaviour which means that it will remove the first
element of the stack. The pop method does the same as the removeFirst() method.
12 Collections 303
You can use all of the above-mentioned methods in the Deque interface to keep retro-compatibility
with Stack but it’s recommended to use the newer methods from Deque instead.
______
� � | Bank |
/|\ /|\ | |
/\/\||
�
/|\
/\
↑
First-out
Now that you understand the principles of a queue let’s see the code example with the ArrayDeque
implementation. ArrayDeque is not thread-safe and it’s faster than LinkedList to use queue
capabilities:
12 Collections 304
1 import java.util.ArrayDeque;
2 import java.util.Queue;
3
4 public class QueueLinkedList {
5
6 public static void main(String[] args) {
7 Queue<String> heroesQueue = new ArrayDeque<>(); // #A
8 heroesQueue.offer("Wolverine"); // First-in // #B
9 heroesQueue.add("Storm"); // #C
10 heroesQueue.offer("Xavier");
11 System.out.println(heroesQueue);
12 heroesQueue.remove(); // First-out // #D
13 heroesQueue.poll(); // #E
14 System.out.println(heroesQueue);
15 }
16 }
Output:
• #A: We can use LinkedList as the implementation if we wish to but for the case of a Queue,
ArrayDeque is faster.
• #B: We are using the offer method that will simply add an element to the tail, in other words,
to the end of the queue. It returns true in case the element was inserted in the queue and false
otherwise depending in the queue elements’ capacity.
• #C: We are also using the add that will add an element to the tail of the queue similarly to the
offer method. The difference of the add method is that it will throw IllegalStateException
in case there is no available elements’ capacity in the queue.
• #D: The remove method will retrieve and remove the head element of the queue, in other words,
it removes the first element. It throws NoSuchElementException in case the queue is empty. In
this case, it will remove "Wolverine".
• #E: The poll method will retrieve and remove the head element of the queue, in other words, the
first element. The poll method doesn’t throw IllegalStateException as the remove method.
Instead, it returns null in case the queue is empty. In this method invocation, "Storm" will be
removed.
1 import java.util.Deque;
2 import java.util.LinkedList;
3
4 public class StarTrekDequeChallenge {
5
6 public static void main(String... confusedCrew) {
7 Deque<String> crew = new LinkedList<>();
8 crew.addFirst("Worf");
9 crew.push("Odo");
12 Collections 306
10 crew.addLast("Scott");
11
12 System.out.println(crew.remove());
13 System.out.println(crew.peek());
14 System.out.println(crew.pop());
15 System.out.println(crew.poll());
16 }
17
18 }
A. Scott
Worf
Worf
Odo
B )Scott
Odo
Odo
Worf
C. Worf
Odo
Odo
Scott
D. Odo
Worf
Worf
Scott
Explanation:
The addFirst method will add the element "Worf" as the first element of the Deque.
The push method does the same as the addFirst method, it will add the "Odo" element to the first
position and will push the other element to the second position. Therefore, we have "Odo" and
"Worf".
The addLast method will add the element to the last position. Therefore, we have no "Odo", "Worf",
and "Scott".
Then we will invoke the remove method that will remove the first element, we will also print "Odo".
The peek method will retrieve the first element but it won’t remove it. We will then show the value
of "Worf" without removing it.
12 Collections 307
Then we will invoke the pop method showing and removing the first element. Therefore, we will
show and remove "Worf".
Finally, we will invoke the pop method that removes and return the last element of the deque. Then,
the element "Scott" will be printed.
In conclusion the correct answer is:
D. Odo
Worf
Worf
Scott
12.5.5 LinkedList
The LinkedList class is doubly-linked list that is faster to insert and delete elements and slower to
iterate than ArrayList. The LinkedList data structure is also a graph because it stores data in the
first and last Node pointers:
LinkedList is a Deque (double ended queue) and is a Queue, therefore, we can also use the same
capabilities of a Queue. A Deque can be used at the same way of a Stack, it’s even recommended to
use Deque instead of the legacy Stack class. Which means that we can use the LinkedList class as
a Stack that follows the LIFO (Last-in first-out) principle. We also can use a LinkedList as a Queue
that follows the FIFO (First-in first-out) principle.
12.6 ConcurrentModificationException
If we use the ArrayList implemention and try to add or remove elements while iterating over it, we
will have the ConcurrentModificationException.
12 Collections 308
That happens because the ArrayList, Vector, or LinkedList have a fail-fast mechanism
that will prevent data colision when multiple threads are accessing the same list Iterator.
Instead of taking the risk of causing a bug very difficult to identify, the JDK code will throw
ConcurrentModificationException to be deffensive.
Therefore, whenever we try to add or remove elements with one of the above mentioned list
implementations when iterating elements we will get this ConcurrentModificationException.
Let’s see what happens if we try to remove an element from an ArrayList first:
1 import java.util.ArrayList;
2 import java.util.List;
3
4 public class RemoveListElement {
5
6 public static void main(String[] args) {
7 List<String> villains = new ArrayList<>();
8 villains.add("Joker");
9 villains.add("Magneto");
10 villains.add("Venom");
11
12 var villainToRemove = "Venom";
13
14 for (String eachVillain: villains) {
15 if (eachVillain.equals(villainToRemove)) {
16 villains.remove(villainToRemove);
17 }
18 }
19
20 System.out.println(villains);
21 }
22 }
Output:
Exception in thread "main" java.util.ConcurrentModificationException
The same would happen if we instantiate our List with a Vector, LinkedList. Therefore, we should
avoid the above actions if we are using any of those List implementations.
The CopyOnWriteArrayList completely avoids thread collisions because on every mutative operation
such as (add, set, and others) it will make a fresh copy of the underlying array. Therefore it’s
impossible to get a ConcurrentModificationException when using CopyOnWriteArrayList:
12 Collections 309
CopyOnWriteArrayList is too costly for common operations, however, it might be better when we
want to use many operations such as add or delete while traversing the array than only mutating
the array’s element.
Let’s see now a code example with CopyOnWriteArrayList:
1 // Omitted imports
2 public class CopyOnWriteArrayListHeroes {
3 public static void main(String[] args) {
4 List<String> heroes = new CopyOnWriteArrayList<>();
5 heroes.add("Iron Man");
6 heroes.add("Spider Man");
7
8 var heroToRemove = "Iron Man";
9 for (String eachHero: heroes) {
10 if (eachHero.equals(heroToRemove)) {
11 heroes.remove(heroToRemove);
12 heroes.add("Batman");
13 }
14 }
15 System.out.println(heroes);
16 }
17 }
Output:
[Spider Man, Batman]
As you can see in the example above, we can add and delete elements when iterating the list and
nothing will happen. As explained before, this happens because in the add or delete methods a new
array copy will be created preventing data colision.
12 Collections 310
1 // Omitted imports
2 public class RemoveIfListElement {
3
4 public static void main(String[] args) {
5 List<String> villains = new ArrayList<>();
6 villains.add("Joker");
7 villains.add("Magneto");
8 villains.add("Venom");
9
10 villains.removeIf(e -> e.equals("Venom"));
11 System.out.println(villains);
12 }
13 }
Output:
[Joker, Magneto]
As you can see the code above is more succint and does the same as the previous examples. Therefore,
if you only want to remove an element given a condition, use the removeIf method.
Output:
[Doctor Strange, Iron Man, Spider Man]
Notice in the code above that we are using the java.util.Collections class to sort the list’s
elements. Another important point is that we could sort the elements from many other JDK classes
and we would get the elements sorted.
Let’s now analize the method signature from the sort method:
public static <T extends Comparable<? super T>> void sort(List<T> list) {}
As you can see, the sort method can only receive objects that implements Comparable. Therefore, if
we want to do the following, the code would not compile when we try to invoke the sort method:
1 import java.util.ArrayList;
2 import java.util.Collections;
3 import java.util.List;
4
5 public class HeroComparable implements Comparable<HeroComparable> { // #A
6
7 private String name;
8 private String power;
9
10 public HeroComparable(String name, String power) {
11 this.name = name;
12 this.power = power;
13 }
14
15 public static void main(String[] args) {
16 List<HeroComparable> heroes = new ArrayList<>();
17 heroes.add(new HeroComparable("Iron Man", "Repulsor Ray"));
18 heroes.add(new HeroComparable("Spider Man", "Webball"));
19 heroes.add(new HeroComparable("Iron Man", "Laser Beam"));
20 heroes.add(new HeroComparable("Hulk", "Monstrous Swipe"));
21 Collections.sort(heroes);
22 System.out.println(heroes);
23 }
24
25 @Override
26 public int compareTo(HeroComparable o) { // #B
27 int i = name.compareTo(o.name); // #C
28 if (i != 0) return i; // #D
29
30 return power.compareTo(o.power); // #E
31 }
32
33 @Override
34 public String toString() {
35 return "{name='" + name + "' , power='" + power + "'}";
36 }
37 }
Output:
HeroComparables:[{name='Hulk' , power='Monstrous Swipe'}, {name='Iron Man' ,
power='Laser Beam'}, {name='Iron Man' , power='Repulsor Ray'}, {name='Spider Man' ,
power='Webball'}]
12 Collections 313
• #A: The first important point to notice is that we are implementing the Comparable interface.
This is required to be able to sort elements by using the Collections.sort method.
• #B: The compareTo method implementation from the Comparable interface.
• #C: We are using the compareTo method from the String name. It returns 1 if name is greater
than o.name, 0 if they are equal or -1 if name is lower than o.name.
• #D: We just want to return a value here if i is different than 0 because then we won’t need
to compare the next field. If the comparison returns 0 though we will move to the next field
comparison.
• #E: We return the comparison from power and o.power only if the name comparison returns 0.
1 @Override
2 public int compareTo(HeroComparableGuava o) {
3 return ComparisonChain.start()
4 .compare(name, o.name, Ordering.natural(). nullsLast())
5 .compare(power, o.power, Ordering.natural().nullsLast()).result();
6 }
Notice the important detail that we can also handle the edge-case of having a null field value with
Guava. We have to make it explicit though, we have to add the third parameter to the compare method
Ordering.natural(). nullsLast(), then a null field won’t cause a NullPointerException.
The ApacheCommons library handles null fields values automatically, we don’t need to make it explicit.
By default, the CompareToBuilder will sort a null value first.
Now it’s up to you to decide what is the best way for you to sort your data.
12 Collections 314
1 import java.util.ArrayList;
2 import java.util.Comparator;
3 import java.util.List;
4
5 public class HeroComparator {
6 private String name;
7 private String power;
8
9 public HeroComparator(String name, String power) {
10 this.name = name;
11 this.power = power;
12 }
13
14 public static void main(String[] args) {
15 List<HeroComparator> heroes = new ArrayList<>();
16 heroes.add(new HeroComparator("SpiderMan", "Web ball"));
17 heroes.add(new HeroComparator("Iron Man", "Laser Beam"));
18 heroes.sort(new Comparator<HeroComparator>() { // #A
19 @Override
20 public int compare(HeroComparator o1, HeroComparator o2) { // #B
21 return o1.name.compareTo(o2.name);
22 }
23 });
24 for (var hero: heroes) { System.out.println(hero.name); }
25 }
26 }
Output:
Iron Man
SpiderMan
• #A: It’s important to notice that we are using the sort method from the list interface. Also,
we are using the Comparator interface as an annonymous inner class, this means that we
have an instance without a name implementing the compare method. We are also passing the
HeroComparator as a generic type so we can use this type in the compare method.
12 Collections 315
• #B: The compare method has two parameters instead of one of the compareTo method from
Comparable. In essence the compare method is very similar to the compareTo method, it also
returns an int number.
1 @FunctionalInterface
2 public interface Comparator<T> { ... }
That means we can use lambdas! We can make the above code much simpler with the same effect:
heroes.sort((o1, o2) -> o1.name.compareTo(o2.name));
We reduced the code of six to one line! That’s the power of lambdas in Java.
But we can make it even better, we can use the helper comparing method from Comparator:
An important detail to notice when using a lambda is that if you have a null field value, you will
get a NullPointerException. Therefore, you will have to handle this edge-case scenario.
1 Comparator<HeroComparatorNullValue> employeeNameComparator
2 = Comparator.comparing(HeroComparatorNullValue::getName,
3 Comparator.nullsFirst(String::compareTo));
By using the above code, we can pass a null value to the field we are comparing and avoid a
NullPointerException.
12 Collections 316
1 import java.util.ArrayList;
2 import java.util.Comparator;
3 import java.util.List;
4
5 public class JediComparatorChallenge {
6 public static void main(String... doYourBest) {
7 List<Jedi> jediList = new ArrayList<>();
8 jediList.add(new Jedi("Anakin", 10));
9 jediList.add(new Jedi("Luke", 5));
10 jediList.add(new Jedi("Luke", 6));
11 jediList.add(new Jedi("Obi Wan", 7));
12
13 Comparator<Jedi> comparator = Comparator
14 .comparing(Jedi::getName)
15 .thenComparing((a1,a2) -> a2.age.compareTo(a1.getAge()));
16
17 jediList.sort(comparator);
18 jediList.forEach(j -> System.out.println(j.name + ":" + j.age));
19 }
20 static class Jedi {
21 String name;
22 Integer age;
23 public Jedi(String name, Integer age) { this.name = name; this.age = age; }
24 public String getName() { return name; }
25 public Integer getAge() { return age; }
26 }
27 }
A. Anakin:10
Luke:5
Luke:6
Obi Wan:7
B. Anakin:10
Luke:6
12 Collections 317
Luke:5
Obi Wan:7
C. Obi Wan:7
Luke:6
Luke:5
Anakin:10
D. Luke:6
Luke:5
Obi Wan:7
Anakin:10
Explanation:
Let’s analyze the following code:
Note in the first comparison that we are passing a method reference for the getName method that
will a Jedi sort by name.
Then we use the thenComparing method that will also sort the age of a Jedi. However, notice that
we are using a lambda that is sorting the age in the reverse:
a2.age .compareTo(a1.getAge())
Since we are positioning a2 first, we will also revert the sorting order.
Therefore, the final answer will be:
B. Anakin:10
Luke:6
Luke:5
Obi Wan:7
12.8 Set
The main purpose of the Set interface is to have unique elements in a Collection. Another crucial
point of a Set is that the elements are not ordered, this means that the elements won’t be in the same
ordered as they are added.
The Set interface will heavily rely on the equals and hashcode methods to decide if elements are
the same.
12 Collections 318
12.9 HashSet
The main implementation of the Set interface is the HashSet class. Behind the scenes a HashSet
actually contains a HashMap as you can see in the following JDK code:
The main purpose of a Set is to add unique elements. We can’t also forget that to make it work we
need to remember to override the equals and hashCode methods from the element we are working
on.
A HashSet offers a constant time performance for the add method (it uses a HashMap behind the
scenes). Assuming the size of some arrray is m - the overall time complexity is Big O(m) (Algorithm
time complexity).
Another important point of a HashSet is that it stores elements in a non-ordered way, which means
that the order of insertion will be always random.
Let’s see HashSet in action:
12 Collections 319
1 import java.util.HashSet;
2 import java.util.Set;
3
4 public class HashSetInAction {
5 public static void main(String[] args) {
6 Set<String> heroes = new HashSet<>(); // #A
7 heroes.add("Batman"); // #B
8 heroes.add("Batman"); // #C
9 heroes.add("Doctor Strange");
10 heroes.add("Wolverine");
11
12 System.out.println(heroes); // #D
13 }
14 }
Output:
[Wolverine, Batman, Doctor Strange]
Let’s explore the code above:
• #A: Note that we are instantiating our Set with HashSet. Remember that this is a preferable
approach since it decouples the HashSet class from the Set declaration. Because then we are able
to use any Set implementation more easily which means the code will be easier to maintain.
• #B: We are adding "Batman" to the HashSet. This works fine since the String class implements
the equals and hashCode methods. Remember that the HashSet class will check if the object is
the same by using the equals and hashCode methods as you can see in the add implementation
of the HashSet.
• #C: Notice that we are trying to add the same String "Batman" but this value will replace the
old "Batman". Therefore, we won’t have a duplication of "Batman".
• #D: Another crucial point is that a HashSet is not ordered, therefore, the order of insertion is
not guaranted. The result order will be random, it can be any of the following:
1 import java.util.HashSet;
2 import java.util.Set;
3
4 public class BoratHashSetChallenge {
5 public static void main(String... doYourBest) {
6 Set<Borat> borats = new HashSet<>();
7 borats.add(new Borat(1, "Kazakhstan"));
8 borats.add(new Borat(2, "USAndA"));
9 borats.add(new Borat(2, "England"));
10 borats.forEach(b -> System.out.print(b.id + " " + b.country + " "));
11 }
12 static class Borat {
13 int id;
14 String country;
15 Borat(int id, String country) { this.id = id; this.country = country; }
16
17 public int hashCode() {
18 System.out.println("hashCode:" + this.id + this.country);
19 return this.id;
20 }
21 public boolean equals(Object obj) {
22 System.out.println("equals:" + this.id + this.country);
23 return ((Borat) obj).country.equals(this.country);
24 }
25 }
26 }
A. hashCode:1Kazakhstan
equals:1Kazakhstan
hashCode:2USAndA
equals:2USAndA
hashCode:2England
equals:2England
1 Kazakhstan 2 USAndA 2 England
B. hashCode:1Kazakhstan
hashCode:2USAndA
1 Kazakhstan 2 USAndA
C. hashCode:1Kazakhstan
hashCode:2USAndA
hashCode:2England
equals:2England
1 Kazakhstan 2 USAndA 2 England
12 Collections 321
D. hashCode:1Kazakhstan
hashCode:2USAndA
hashCode:2England
1 Kazakhstan 2 USAndA 2 England
Explanation:
The key concept of this challenge is that the HashSet class relies on the equals and hashCode methods
to decide if an element is the same or not.
Remember that the hashCode method will use less memory resources, therefore, it will be compared
first. If the hashCode of objects are different, there is no necessity to check the equals method as well.
Then when we insert "Kazakhstan" the hashCode method will be invoked but not the equals method.
This will happen because the hashCode from the first and second element insertion are different.
At the third element insertion, note that the id is the same. This means that the equals method will
have to be invoked to make sure if the objects are the same or not. So when we insert the third
element, the hashCode and equals method will be invoked and since the equals method will return
false, the third element will be inserted.
In the end we will print all the inserted elements. In conclusion, the right alternative is:
C. hashCode:1Kazakhstan
hashCode:2USAndA
hashCode:2England
equals:2England
1 Kazakhstan 2 USAndA 2 England
12.10 LinkedHashSet
What if we want to work with unique and ordered elements? Then we can use the LinkedHashSet
implementation.
To talk details, the LinkedHashSet documentation says that the performance is likely to be a bit below
than HashSet because the added expense of a LinkedList. There is one exception though, iteration
over a LinkedHashSet requires time proportional to the size of a set regardless of its capacity.
Iteration over a HashSet is likely to be more expensive, requiring time proportional to its capacity.
A linked hash set has two parameters that affect its performance: initial capacity and load factor.
They are defined precisely as for HashSet.
LinkedHashSet is also not synchronized, therefore thread synchronization must be done mannualy.
It’s also possible to wrap the LinkedHashSet with Collections.synchronizedSet to make it thread-
safe.
Note that LinkedHashSet extends HashSet:
12 Collections 322
1 import java.util.LinkedHashSet;
2 import java.util.Set;
3
4 public class LinkedHashSetInAction {
5 public static void main(String[] args) {
6 Set<String> heroes = new LinkedHashSet<>(); // #A
7 heroes.add("Batman");
8 heroes.add("Batman"); // #B
9 heroes.add("Doctor Strange");
10 heroes.add("Wolverine");
11 heroes.add("Spider Man");
12
13 System.out.println(heroes); // #C
14 }
15 }
Output:
[Batman, Doctor Strange, Wolverine, Spider Man]
• #A: We are instanting our Set with LinkedHashSet which gives us the ability of adding elements
in the order of insertion.
• #B: The LinkedHashSet will replace the value of "Batman" to the new one since it’s the same.
• #C: The order is guaranteed to be always the insertion order.
To summarize, HashSet and LinkedHashSet are fast to insert, remove, and search for elements. But
12 Collections 323
12.11 TreeSet
To use unique elements that will be automatically sorted we can use TreeSet.
The TreeSet will also use the hashCode and equals methods to verify if objects are the same to
insert unique elements. The great difference between HashSet and TreeSet is that the TreeSet will
rely on the compareTo method to sort the elements. Also, the TreeSet only accepts Comparable
objects, therefore whenever we try to add an element that isn’t Comparable we will get a
java.lang.ClassCastException.
The TreeSet class is also not thread-safe and it has the fail-fast check if we try to modify the set
while iterating over. It also throws ConcurrentModificationException.
Now let’s see what happens when we don’t implement Comparable in the data class and try to add
elements into a TreeSet:
1 import java.util.Set;
2 import java.util.TreeSet;
3
4 public class TreeSetNonComparable {
5 public static void main(String... args) {
6 Set<Simpson> set = new TreeSet<>(); // #A
7 set.add(new Simpson("Homer ")); // #B
8 }
9 static class Simpson {
10 String name;
11 public Simpson(String name) { this.name = name; }
12 public String toString() { return this.name; }
13 }
14 }
Output:
java.lang.ClassCastException
• #A: There is a warning at this line of code because the Simpson class doesn’t implement
Comparable. The restriction to not use an object that is not Comparable is not in the instantiation
though, so this piece of code will work.
• #B: When we try to add "Homer" we will get a java.lang.ClassCastException. This happens
because the add method implementation will verify if the element implements Comparable, and
when it doesn’t it throws the Exception.
12 Collections 324
1 import java.util.Set;
2 import java.util.TreeSet;
3
4 public class TreeSetComparable {
5 public static void main(String... doYourBest) {
6 Set<Simpson> set = new TreeSet<>(); // #A
7 set.add(new Simpson("Homer ")); // #B
8 set.add(new Simpson("Bart "));
9 set.add(new Simpson("Maggie "));
10
11 System.out.println(set);
12 }
13 static class Simpson implements Comparable<Simpson> { // #C
14 String name;
15 public Simpson(String name) { this.name = name; }
16 public String toString() { return this.name; }
17
18 @Override
19 public int compareTo(Simpson o) { // #D
20 return this.name.compareTo(o.name);
21 }
22 }
23 }
Output:
[Bart , Homer , Maggie ]
12 Collections 325
• #A: We are instanting the TreeSet object with a Comparable Simpson this time, therefore, there
won’t be any warning informing otherwise.
• #B: The add method will work fine now that we are implementing Comparable in the Simpson
class.
• #C: Notice that we are implementing Comparable in the Simpson data class which enables us
to sort and compare this data object.
• #D: We are overriding the compareTo method comparing the Simpson's name.
To summarize, you want to use TreeSet when you want a Collection with:
• Unique elements
• Sorted elements
1 import java.util.*;
2
3 public class SortComparableChallenge {
4 public static void main(String... doYourBest) {
5 Set<Simpson> set = new TreeSet<>();
6 set.add(new Simpson("Homer "));
7 set.add(new Simpson("Marge "));
8 set.add(new Simpson("Lisa "));
9 set.add(new Simpson("Bart "));
10 set.add(new Simpson("Maggie "));
11
12 List<Simpson> list = new ArrayList<>(set);
13 Collections.reverse(list);
14 for (Simpson simpson : list)
15 System.out.print(simpson);
16 }
17
18 static class Simpson implements Comparable<Simpson> {
19 String name;
20 public Simpson(String name) { this.name = name; }
21
12 Collections 326
Firstly in this Java Challenge we are adding elements into an instance of a TreeSet. When we do
that, we will sort the elements according to what was implemented in the compareTo method.
At the moment we added all the elements in the TreeSet, the elements will be already inverted as
the following:
Marge Maggie Lisa Homer Bart
You might be wondering why the elements were inverted after adding elements in the TreeSet.
However, note that in the compareTo method from the Simpson class, we are inverting the
comparison. We are using first the simpson parameter, invoking the compareTo method and then
passing the current instance name. This means that we are actually inverting the sorting of the
TreeSet elements.
In the following code, we are encapsulating our set into a list because we want to invoke the reverse
method:
List<Simpson> list = new ArrayList<>(set);
Then, when we invoke the reverse method from the List, we will reverse what is already reversed:
Collections.reverse(list);
Which means that the order of the elements after we print will be the ascending order as the
following:
Bart Homer Lisa Maggie Marge
12.12 Map
Very often in the day-to-day work of a Java developer, it will be necessary to work with a key-value
Collection. To accomplish that there is the Map interface.
12 Collections 327
The main implementations of the Map interface are HashMap, LinkedHashMap, and TreeMap.
To have a macro-view of the Map hierarchy, let’s explore the following diagram:
12.13 HashMap
The most used Map in Java is the HashMap. It has the capability of adding key value elements and it’s
also not ordered.
Keep in mind also that a map is a graph under the hood. A HashMap will store data into a Node as you
can see in the JDK code:
Output:
Tifa
null
[1, 2, 3]
Key:1 Value:Cloud Key:2 Value:Vincent Key:3 Value:Barret
Code Analysis:
• #A: Instantiate our Map with a HashMap. We could instantiate this Map with any other Map
implementation.
• #B: Puts into the map the key of 1 and value of "Cloud".
• #C: Remove the second key value of the map. In this case, 2 and “Tifa”.
• #D: Put an element if the key is not existent in the map. Since we removed the element 2, this
method will put the key of 2 and the value of "Vincent". It also returns null because there
isn’t any element with the key of 2. Therefore, it will also print null.
• #F: The map has the entrySet method that will return the key and value to the Map.Entry.
Therefore, we can use the for looping to get each entrySet of the map. This way to iterate a map
is a bit verbose though but in the next session we will see a concise way to do so with lambdas.
• #G: We can access the key and value from the current entrySet.
12 Collections 329
Output:
Key: 1 Value: Cloud Key: 2 Value: Sephiroth Key: 3 Value: Zack Key: 4 Value: Aerith
Code Analysis:
- #A: Adding "Sephiroth" to the key 2. When we put the same key into a map the value will be
replaced. In this case, "Tifa" will be replaced with "Sephiroth".
• #B: Then we use the putAll method that will put all the elements into the ffCharacters map.
Therefore, we will have all the elements from ffCharacters and moreFFCharacters in the
ffCharacters map.
• #C: As the name suggests, the clear method will remove all the elements from a map. Therefore,
when we try to print the values, nothing will be printed.
Output:
Cloud
Vincent
[Cloud, Tifa, Barret]
[1, 2, 3]
Code Analysis:
Output:
true
true
Code Analysis:
• #A: Check if the map has the key of 3. In this case, the key of 3 is there. Therefore it returns
true.
• #B: Check if the value of "Cloud" is present in the map object. In this case, "Cloud" is there.
Therefore it returns true.
Since we are using a Set to store the keys of the HashMap, we have access to the iterator method
since a Set implements Iterator. Take a look at the hierarchy from Set up until Iterator:
Let’s see now how we use the iterator with map elements:
12 Collections 332
Output:
Key: 1 Value: Cloud
Key: 2 Value: Barret
Key: 3 Value: Tifa
• #A: We access the entrySet from the map and then the iterator. Note that we receive this
iterator typed as <Entry<Integer, String>>.
• #B: Then once we have the iterator elements, now we can iterate. To accomplish that we can
use the hasNext method in a while loop. This is what the compiler does behind the scenes when
we use the enhanced for statement as well.
• #C: We get the next element with the next method from the iterator.
• #D: Finally, we print the key and value from the entry object.
• #A: By using the enhanced for looping we use the iterator implicitly. When compiling this
code though, the iterator will be used behind the scenes. To make it clear, this is the code that
will be actually used when compiling this code:
12 Collections 333
• #A: We get the set of keys of the map by using the keySet method.
• #B: We print the key directly and then retrieve the value of each key by using the
avalanche.get(key) method.
Keep in mind to use the keySet iteration when you are using only the key, otherwise the entrySet
method is more efficient in terms of performance.
Note that we receive a BiConsumer that enables us to pass two parameters of any type and do some
logic without returning any value. That’s why in our lambda we pass two parameters, we consume
two values on each iteration of the looping.
Output:
Cloud with Ultima Weapon
• #A: Notice that we are computing the key and value of ffCharacters from of the key 1‘.
• #B: We encapsulate the possibly null value we are receiving in the compute method. Then we
filter the map value to "Cloud" only.
• #C: If the value from the map is "Cloud" we concatenate " with Ultima Weapon", otherwise,
"No weapon".
1 import java.util.HashMap;
2 import java.util.Map;
3
4 // Omitted imports
5 public class HashMapMerge {
6
7 public static void main(String[] args) {
8 Map<Integer, String> ffSummons = new HashMap<>();
9 ffSummons.put(1, "Ifrit");
10 ffSummons.put(2, "Bahamut");
11 ffSummons.put(3, "Knights of Round");
12
13 Map<Integer, String> ffParty = new HashMap<>();
14 ffParty.put(1, "Cid");
15 ffParty.put(2, "Yuffie");
16 ffParty.put(4, "Zack");
17
18 ffSummons.forEach((k, v) -> // #A
19 ffParty.merge(k, v, (fighter, summon) // #B
20 -> fighter +" invokes " + summon)); // #C
21
22 System.out.println(ffParty);
23 }
24 }
Output:
• #A: We iterate over the elements of ffSummons, giving us access to the key and value from
them.
• #B: On each iteration we invoke the merge method in the ffParty map. We pass the key and
value from ffSummons and the last parameter is a BiConsumer where we can pass the value
from ffParty and ffSummons. If the key we are passing is existent, then the merge method is
invoked. Otherwise, the key and value of ffSummons will be put into the ffParty map.
• #C: We perform the logic of our BiConsumer. In this case, we are basically concatenating the
value of ffParty with ffSummons.
1 import java.util.HashMap;
2 import java.util.Map;
3
4 public class LannisterMapChallenge {
5 static int hashCodeCount = 0;
6 static int equalsCount = 0;
7
8 public static void main(String[] doYourBest) {
9 Map<Lannister, String> m = new HashMap<>();
10 m.put(new Lannister("Tyrion"), "I understand the way this game is played");
11 m.put(new Lannister("Jaime"), "The things I do for love...");
12 m.put(new Lannister("Cersei"), "When you play the game of thrones, you win "
13 + "or you die. There is no middle ground.");
14 m.put(new Lannister("Tywin"), "Any man who must say, 'I am the king' is no t
15 ing.");
16
17 System.out.printf("Size: %s equalsCount: %s hashCodeCount: %s", m.size(), eq
18 ount, hashCodeCount);
19 }
20
21 static class Lannister {
22 String name;
23 public Lannister(String name) {this.name = name;}
24 public boolean equals(Object obj) {
25 equalsCount++;
26 return (name.equals(name));
27 }
28 public int hashCode() {
29 hashCodeCount++;
30 return 1 + 8 * 453453 / 77 + 40340403 - 3000000;
31 }
32 }
33 }
Explanation:
Notice that when we are inserting a key to a HashMap, it will rely on the hashCode and equals methods
to define whether or not a key will be inserted or replaced.
12 Collections 337
Also, notice that the hashCode method in the Lannister class is always returning the same value. This
means that every key in this HashMap will have the same hashCode. The equals method is comparing
the same name from Lannister, we are not using the obj passed by parameter.
The hashCode method returns the same number and the equals method will always return true,
which means that the elements in the HashMap will be replaced.
Also, when we put "Tyrion" in the HashMap, note that only the hashCode method will be invoked
because this value is used when inserting the key. However, the equals method will not be invoked
because there isn’t any other object to be compared at the moment.
Then, when we put "Jaime" the hashCode method will be invoked but also the equals method since
we have more than 1 element in the HashMap. The same will happen for "Cersei" and "Tywin".
In conclusion, the correct alternative is:
12.14 Hashtable
The Hashtable class behaves very similary from a HashMap with the main difference that is
synchronized, this means it can be used in a multi-thread environment safely.
Notice also that a Hashtable doesn’t allow null values as key. If you pass a null value like the
following:
Another important difference is that Hashtable extends an obsolete abstract class Dictionary. This
means we should declare the type of a Hashtable as a Map and not as a Dictionary.
12.15 LinkedHashMap
If you want to use an ordered map, the LinkedHashMap implementation will do this job for you. This
means that the elements will be ordered as they are inserted.
The LinkedHashMap maintains a doubly-linked list running through all of its entries. It’s also slightly
less performant than a HashMap.
12 Collections 338
Another important point is that the LinkedHashMap is not synchronized or thread-safe. This
means that data collision may happen when different thread executions are changing data from
a LinkedHashMap.
The LinkedHashMap also has a fail-fast mechanism in case we change modify values while iterating
in a loop. That’s a deffensive approach to prevent hard to track bugs due to data loss.
Another important detail from the LinkedHashMap class is that it extends the HashMap class and
implements a Map:
1 import java.util.LinkedHashMap;
2 import java.util.Map;
3
4 // Omitted imports
5 public class LinkedHashMapInAction {
6 public static void main(String[] args) {
7 Map<Integer, String> map = new LinkedHashMap<>(); // #A
8
9 for (var i = 0; i <= 100000; i++) { // #B
10 map.put(i, "Element:" + i);
11 }
12 out.println(map);
13 }
14 }
Output
• #A: We can declare a LinkedHashMap with the Map interface and make use of polymorphism.
The benefit of using polymorphism is the flexibility you have in your code to pass the
implementation you find more suitable.
• #B: Notice that we are iterating from 0 to 100000 and the output will be guaranteed in insertion
order. If you need your elements to be in insertion order, use LinkedHashMap.
12.16 TreeMap
To have the keys of your map sorted in natural, you can use the TreeMap.
A TreeMap uses the Red-black tree algorithm based on the NavigableMap implementation. The keys
of a TreeMap must implement Comparable, in case we don’t do that, we will get an Exception.
The TreeMap also has the following direct hierarchy:
1 import java.util.Map;
2 import java.util.TreeMap;
3
4 public class TreeMapInAction {
5 public static void main(String[] args) {
6 Map<Integer, String> ffSummons = new TreeMap<>();
7 ffSummons.put(3, "Shiva"); // #A
8 ffSummons.put(2, "Cactuar"); // #B
9 ffSummons.put(1, "Siren");
10
11 System.out.println(ffSummons);
12 }
13 }
Output:
12 Collections 340
• #A: The first important to notice is that the key has to implement Comparable. As we are passing
the key as a primitive int number, it will be autoboxed to Integer that implements Comparable
as you can see in the Integer class definition:
The value doesn’t need to implement Comparable since values are not sorted, only keys.
• #B: When we put the key of 2, the TreeMap will automatically sort the elements in the natural
order. Therefore, at that moment we have:.
Output:
• #A: If we use the Comparator.reverseOrder() instance, we will invert the order of the
Comparable objects comparison as you can see in the following JDK code:
12 Collections 341
Therefore, the results will be returned with the key order sorted in the reverse order from the Integer
natural order.
• #B: Then, whenever we add an element to the TreeMap they will sorted in the reverse order.
Output:
1 import java.util.Map;
2 import java.util.TreeMap;
3
4 public class TreeMapExceptionCase {
5 public static void main(String[] args) {
6 Map<Summon, Integer> ffSummons = new TreeMap<>(); // #A
7 ffSummons.put(new Summon("Carbuncle"), 1); // #B
8 ffSummons.put(new Summon("MiniMog"), 2);
9 ffSummons.put(new Summon("Tonberry"), 3);
10
11 System.out.println(ffSummons);
12 }
13 }
14
15 class Summon { // #A
16 private String name;
17 public Summon(String name) { this.name = name; }
18 }
Output:
12 Collections 342
• #A: We are passing the Summon class that doesn’t implement Comparable to the TreeMap key. If
you are using the Intellij IDE you will probably see a warning informing that the key is not
a Comparable object.
• #B: When we try to add the first element to the TreeMap we will get the
java.lang.ClassCastException since the put method will try to use a Comparable key
but it won’t succeed.
Output:
12 Collections 343
• #A: There won’t be any warning here since the Summon class now implements Comparable.
• #B: Notice that on each time we put elements into ffSummons, the comparaTo method will be
invoked to sort the map keys.
• #C: We are implementing Comparable now in the Summon class. Notice that we are also using
the generic type of Summon in the Comparable. This is because then we can use Summon in the
comparaTo method implementation.
• #D: Here we compare the current instance with another one that is received by parameter. This
method will be invoked by the TreeMap on every put invocation. Our logic is quite simple here,
we are comparing the name of the current instance with the other Summon object.
The underlying algorithm used in a TreeMap is the Red-Black Tree, the same as used in a TreeSet.
Let’s see the time-complexity table of a TreeMap:
A. 123
B. 12345
C. 245
D. 425
Explanation:
Note that in this Java Challenge we are overriding the equals and hashcode methods, therefore the
Map implementations will behave accordingly to what was implemented on those methods.
Another important point to note is the hashCode method that even though it’s a bit complex, it’s
returning always the same value:
public int hashCode() { return 4000 << 2 * 2000 / 10000; }
Pay attention that the equals method is actually comparing the length of the String. So, objects are
going to be the same if the String length is the same.
As Bran has 4 characters and it was the last one to be inserted, it will replace Arya. Therefore the
first value is 4.
As Ned is the only one with 3 characters, his value will be printed that is 2.
Then, Sansa has 5 characters, it will be inserted and then Jaime will replace Sansa’s value with 5.
Therefore, the correct alternative is:
D) 425
12 Collections 345
1 import java.util.Collections;
2 import java.util.List;
3
4 public class BinarySearchNotSorted {
5 public static void main(String[] args) {
6 List<String> ff8Fighters = List.of("Squall", "Zell", "Quistis");
7 int searchIndex = Collections.binarySearch(ff8Fighters, "Quistis");
8 System.out.println(ff8Fighters.get(searchIndex));
9 }
10 }
Output:
Exception in thread “main” java.lang.IndexOutOfBoundsException: Index -1 out of bounds for
length 3
As you can see in the code above, we got an Exception because we didn’t sort our array. Therefore,
the search result is inconsistent and it might not work properly.
1 // Omitted imports
2 public class BinarySearchSorted {
3 public static void main(String[] args) {
4 List<String> ff8Fighters =
5 new ArrayList<>(List.of("Squall", "Zell", "Quistis")); // #A
6 Collections.sort(ff8Fighters); // #B
7 int searchIndex = Collections.binarySearch(ff8Fighters, "Quistis"); // #C
8 System.out.println(ff8Fighters.get(searchIndex));
9 }
10 }
Output:
Quistis
• #A: Notice that the List.of method returns an immutable list which means that we can’t
change the list. That’s why we are wrapping the result of List.of into the constructor of an
ArrayList. Then we can change our array.
• #B: We pass our mutable array to the Collections.sort method. Notice that the elements will
be sorted in the object reference which means that we don’t need to return anything, the sort
method will change the ff8Fighters list and sort the elements.
• #C: We invoke the binarySearch now with the list already sorted and we retrieve the
searchIndex successfully.
1 import java.util.Arrays;
2
3 public class MarvelSearchChallenge {
4
5 static String[] marvel = {"Spider-man", "Venom", "Carnage", "Mysterio"};
6
7 public static void main(String[] args) {
8 Arrays.sort(marvel);
9
10 System.out.print(Arrays.binarySearch(marvel, "Xavier") + " ");
11 System.out.print(marvel[Arrays.binarySearch(marvel, "Carnage")] + " ");
12 System.out.print(Arrays.binarySearch(marvel, "Lizard") + " ");
13 System.out.print(Arrays.binarySearch(marvel, "Spider-man"));
12 Collections 347
14 }
15 }
A. -1 Carnage -1 0
B. -4 2 -1 0
C. -5 Carnage -2 2
D. -4 Carnage -1 2
Explanation:
The first important point of this challenge is that the array must be sorted. If it wasn’t sorted, we
would have the ArrayIndexOutOfBoundsException.
Therefore, the array is like this at this point:
[Carnage, Mysterio, Spider-man, Venom]
So, when we look for Xavier, it returns -5. That’s because Xavier is compared with the other
elements in natural order. Since Xavier comes after all elements alphabetically, it’s therefore the
fifth element. Notice also that when the element is not found, it doesn’t start counting from 0. It
starts from -1 instead.
Then, we find Carnage in the array and we get back the index that is 0. When we access this index
with the marvel array, Carnage will be printed.
When we try to look for Lizard, it returns -2. That’s because Lizard would come after Carnage,
therefore, the second element.
Finally, we retrieve the index from Spider-man, which in our sorted array is the third position.
Therefore, it translates to index 2.
In conclusion, the final answer of this Java Challenge is:
C) -5 Carnage -2 2
12.18 Summary
In this chapter we explored the most important concepts in the Collections api to enable you to
manipulate array, set, map, queue and stack.
In every real-world system you will have to deal with data all the time, therefore, by knowing the
main Collection objects in Java will massively help you to create high-quality code and not reinvent
the wheel.
In this chapter your learned to use:
- equals and hashCode methods to check if objects have the same values or not.
• ArrayList for most common use of arrays. Stores an array of objects behind the scenes and
it’s going to grow the array dinamically.
• Vector when you want your array to have synchronized data in a multi-thread environment.
• CopyOnwriteArrayList when you want to manipulate the array as you wish while traversing
it. You won’t get ConcurrentModificationException.
• Prevent ConcurrentModificationException with effective techniques.
• Comparing and sorting data from a List.
• Deque as a type to use functionalies of a Stack and Queue.
• LinkedList when you want to make use of Stack and Queue functionalities.
• Set to be used as a type for HashSet, and LinkedHashSet, and TreeSet. Also will make elements
unique.
• HashSet, it is the most basic implementation of a Set. It uses a map behind the scenes to store
data. Also inserts unique elements and the elements are not ordered as they are inserted.
• LinkedHashSet, it is very similar to HashSet but has the difference that elements will keep the
order as they are inserted.
• TreeSet, it inserts unique elements and also sorts them depending on what you configured on
your compareTo method.
Stream Pipeline
This is just a small example of the power of a stream! We can do much more such as transform
a collection to another one, transform a data type, get the sum, average, group data, parallelize
processes, and the list goes on!
Output:
[Homer, Marge, Bart, Maggie]
[]
Code analysis:
• #A: We invoke the stream method from our originalList to get a stream of data from this list.
This means, we can iterate over the elements "Homer", "Marge", "Bart", "Maggie".
• #B: We filter the elements from originalList to "Moe". The problem is that we don’t have any
"Moe" in the list. Therefore, the stream will be empty.
13 Streams 351
• #C: We collect a new list with the changes we’ve made in the stream. Now we have two lists,
newList and originalList.
• #D: Notice now that we show the originalList values that it will contain the same values
as before. That’s because when we use stream operations, we will use a copy from the
originalList, not the real one. Therefore, all the elements will be there.
• #E: Even though stream won’t mutate the originalList, we can collect the changes we made
in a newList. That’s why we have no elements in the newList.
Output
Nothing will be printed…
Notice in the code above that we didn’t use any terminal operation method. We used only filter
and the peek methods that are intermediate operation methods. Therefore, nothing will be processed
or printed for this stream operation.
1 import java.util.List;
2 import java.util.stream.Stream;
3
4 List<String> mavericks = List.of("X ", "Zero ", "Axl ", "Protoman ");
5 Stream<String> maverickStream = mavericks.stream();
6 maverickStream.forEach(System.out::print);
Output:
X Zero Axl Protoman
1 // Omitted imports
2 String[] mavericks = new String[]{"X ", "Zero ", "Axl ", "Protoman "};
3 Stream<String> maverickStream = Stream.of(mavericks);
4 maverickStream.forEach(System.out::print);
Output:
X Zero Axl Protoman
Alternatively, we can also use the Arrays.stream method to have the same output as above:
1 Stream<String> maverickStream =
2 Stream.<String>builder().add("X ").add("Zero ").add("Axl ").build();
3 maverickStream.forEach(System.out::print);
Output:
X Zero Axl
1 Stream<String> maverickStream =
2 Stream.generate(() -> "X ");
3 maverickStream.forEach(System.out::print);
Output:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX…
As you can see, the code above prints X forever and that doesn’t make much sense. To make the
generate method more useful, we need to limit the stream to not print elements forever:
1 Stream<String> maverickStream =
2 Stream.generate(() -> "X ").limit(5);
3 maverickStream.forEach(System.out::print);
Output:
XXXXX
Output:
0123456789
13 Streams 354
Output:
1234
If we want the second argument of the range method to be inclusive, we have the rangeClosed
method to do that:
Output:
12345
We can also use DoubleStream to generate double numbers as we want:
Output:
0.0 1.5 3.0
Output:
X Zero Axl Sigma
1 new Random().ints(0,10).limit(10).
2 forEach(n -> System.out.print(n + " "));
Output:
3 7 3 7 4 9 8 1 8 5 (Randomly)
Terminal Operation Method: methods that will effectively perform an action with what was
programmed with the intermediate operation methods. For example, we can collect a list, sum,
13 Streams 356
group elements, get the average, and so on. We can also only use a terminal operation once per
stream operation.
Another crucial point to remember when using a terminal method operation is that it can only be
used once per stream. If we use a terminal operation method more than once for the same stream
object, we will get an IllegalStateException.
Let’s check the list of the terminal operation methods:
Now that you have a macro view of the streams api, it will be easier to explore all of the intermediate
and terminal operation methods in detail in the next sections of this chapter.
1 class Maverick {
2 private String name;
3 private double power;
4
5 // Omitted constructor initializing name and power
6 // Omitted toString method
7 }
Now, let’s see an example where we will filter the mavericks who have the power greater than 8:
Output:
X
Zero
We can also filter elements of a stream with different conditions. We can filter by name and power,
let’s see how that works in code:
1 mavericks.stream()
2 .filter(m -> m.name.equals("X") && m.power > 8)
3 .forEach(m -> System.out.println(m.name));
Output:
X
Reinforcing the point that streams are imutable, notice that when we use the filter method, we
are not changing the real mavericks array. We are only filtering it within the stream but there will
be no changes in the real array. Therefore, if we print our mavericks array, it will have the same
information as it was not filtered:
1 System.out.println(mavericks);
Output:
[X, Zero, Axl]
Output:
Bojack and Mr. Peanutbutter
Diane and Mr. Peanutbutter
Todd and Mr. Peanutbutter
In the following code, we will create a stream from a list, then we will map it to the length of the
String, will filter to the Strings with 5 or more characters, finally we will print the data.
Notice that in the code above we used a lambda expression in the .map(b -> b.length()) method
instead of a method reference. To simplify the code above, we can use method reference:
Output:
6
5
We can also map a String to uppercase:
13 Streams 359
Output:
BOJACK DIANE TODD
As you can see we can transform data as we wish. However, it’s important to see how the most
common examples work, we can sum, multiply, divide, subtract, get the mod number… But to be
practical let’s see only the multiply operation in code because the other operations will be very
similar:
Output:
2468
You can get the code above and do your own tests to map a number in the way you like. Remember
that your practice is key for your learning.
1 class ItemDTO {
2 long id;
3 String description;
4 BigDecimal price;
5
6 // Omitted constructor and toString creation
7 }
1 class Item {
2 long id;
3 String description;
4 BigDecimal price;
5
6 // Omitted constructor creation
7 }
1 class ItemMapper {
2 static ItemDTO toDTO(Item item) {
3 return new ItemDTO(item.id, item.description, item.price);
4 }
5 }
Then let’s simulate we are getting a list of items from the “database”:
1 class ItemRepository {
2 List<Item> findAll() {
3 return List.of(new Item(1L, "PS5", new BigDecimal("650")),
4 new Item(2L, "Xbox Series X", new BigDecimal("600")));
5 }
6 }
We have everything now to use the map method and convert our list of Item to a a list of ItemDTO.
Let’s see how this orchestration works in the following code:
1 import java.math.BigDecimal;
2 import java.util.List;
3
4 public class MapRealExample {
5
6 public static void main(String[] args) {
7 ItemRepository itemRepository = new ItemRepository();
8 final List<ItemDTO> itemDtoList = itemRepository
9 .findAll() // #A
10 .stream() // #B
11 .map(ItemMapper::toDTO) // #C
12 .toList(); // #D
13
14 System.out.println(itemDtoList);
15 }
16 }
13 Streams 361
Output:
[ItemDTO, ItemDTO]
Code analysis:
1 import java.util.List;
2
3 public class JediFilterMapChallenge {
4 public static void main(String... doYourBest) {
5 List<Jedi> jediList = List.of(new Jedi("Luke", 20),
6 new Jedi("ObiWan", 30), new Jedi("QuiGon", 40));
7
8 jediList.stream()
9 .filter(jedi -> jedi.name.startsWith("Obi") || \
10 jedi.name.startsWith("Luke"))
11 .filter(jedi -> jedi.name.startsWith("QuiGon"))
12 .map(Jedi::getAge)
13 .filter(age -> age > 10)
14 .forEach(System.out::println);
15 }
16
17 static class Jedi {
18 private String name;
19 private int age;
20 public Jedi(String name, int age) {
21 this.name = name;
22 this.age = age;
23 }
24 public String getName() { return name; }
13 Streams 362
A. 40
B. 20
30
C. Nothing will be printed
D. 20
30
40
Explanation:
We firstly create the list of Jedi and then we create a stream from them.
Then we filter the elements names that start with "Obi" or "Luke". At that moment we have the
following elements:
[Jedi(“ObiWan”, 30), Jedi(“Luke”, 20)]
Now we try to filter only the elements names that start with "QuiGon", since we don’t have "QuiGon",
the list will be cleared.
When we use the map(Jedi::getAge) operation we will transform the stream element to return the
jedi age. Then we will filter again to get jedis who are older than 10. However, we have nothing in
the stream data already.
In conclusion, when we print the values of the stream we will have nothing. Therefore, the correct
alternative is:
C) Nothing will be printed
Output:
1234
Let’s see now how the distinct method will behave when we create our own object:
13 Streams 363
1 class BojackCharacter {
2 private String name;
3
4 public BojackCharacter(String name) {
5 this.name = name;
6 }
7 }
Let’s see what happens when we use the above object in the distinct function:
Output:
Bojack Bojack Diane Diane
Notice that the elements didn’t get distincted. That’s because the distinct function relies on the
equals and hashCode methods. Therefore, if we implement those methods in the BojackCharacter
class, then the elements will be distincted:
1 class BojackCharacter {
2 // Omitted field name and constructor
3
4 @Override
5 public boolean equals(Object o) {
6 if (this == o) return true;
7 if (o == null || getClass() != o.getClass()) return false;
8 BojackCharacter that = (BojackCharacter) o;
9 return Objects.equals(name, that.name);
10 }
11
12 @Override
13 public int hashCode() {
14 return Objects.hash(name);
15 }
16 }
Now, if we try to run the same code as above, then the elements will be distincted:
13 Streams 364
Output:
Bojack Diane
Output:
Nothing will be printed here…
Why is that? That’s because the peek method is an intermediate operation method, this means that
nothing will happen unless a terminal operation method is invoked.
It’s important to remember that streams are lazy, therefore, something will only happen when there
is a method that will effectively do something with the stream.
Output:
Lower Case Elements: bojack
Filtered to bojack: bojack
13 Streams 365
Then, the filter method will be invoked filtering a String that is equals to bojack. In that case, this
condition will be true, therefore, the following will be printed:
Filtered to bojack: bojack
The String DIANE will also be transformed to lowercase. Therefore, the peek method will print the
following:
Lower Case Elements: diane
However, notice that diane will not be equals to bojack, therefore, the next peek method will have
nothing to print. The same will happen to TODD and the following will be printed:
Lower Case Elements: todd
Output:
Bojack
Peanutbutter
As you can see in the code above, the limit method will limit the stream to two elements.
13 Streams 366
1 import java.util.List;
2
3 public class MatrixPeekOrder {
4 public static void main(String... crewOrder) {
5 List<String> crew = List.of("Neo ", "Trinity ", "Morpheus ");
6
7 crew.stream()
8 .peek(e -> System.out.print(e + "peek "))
9 .limit(1)
10 .forEach(e -> System.out.print(e + "forEach "));
11 }
12 }
A. Neo forEach Neo peek Trinity forEach Trinity peek Morpheus forEach Morpheus peek
B. Neo forEach Neo peek
C. Neo peek Neo forEach
D. Neo Neo Trinity Trinity Morpheus Morpheus
Explanation:
The key concept to focus on this code challenge is that streams are lazy in Java to optimize
performance. Which means that only the elements we instructed in our stream pipeline will be
processed.
For example, we used the intermediate method peek that only shows what element we are traversing.
But we also used the limit method that will limit(1) the elements to 1. Therefore, we don’t need
to traverse the three elements from the list, this would be a waste of computer resources.
Considering that only the elements we programmed with our intermediate operation methods will
be processed, the following will happen:
• The intermediate operation methods peek and limit will be processed but not executed.
• The terminal operation method forEach will be read and only then peek and limit will be
effectively invoked.
As a consequence, only Neo will be printed because the method execution starts from the order we
instructed our stream. Therefore, the right alternative is:
**C) Neo peek Neo forEach **
13 Streams 367
Output:
Princess Carolyn Diane
• #A: Filtering only the Strings that have more than 4 characters.
• #B: Skips 2 elements that fulfill the filter condition. In this case Bojack and Peanutbutter are
skipped.
Output:
Aldebaran Ikki Kanon Seya Shaka
1 import java.util.List;
2
3 public class SortedHeroesStreamChallenge {
4
5 public static void main(String... doYourBest) {
6 List<Hero> heroesList = List.of(new Hero("Spider Man"),
7 new Hero("Wolverine"), new Hero("Batman"),
8 new Hero("Iron Man"), new Hero("Beast"));
9
10 heroesList.stream()
11 .sorted() // #A
12 .forEach(h -> System.out.print(h.name + " ")); // #B
13 }
14
15 static class Hero {
16 private String name;
17 Hero(String name) {
18 this.name = name;
19 }
20 }
21
22 }
Explanation:
The key point of this Java Challenge is that whenever we are using a sorting method in Java, we
need to implement Comparable. Or at least inform what is the sorting strategy with Comparator. In
the code above we are doing neither of those. We are passing the Hero object that doesn’t implement
Comparable. Therefore, it’s impossible to the sorted() function to guess what is the sorting strategy.
For this reason, in the terminal operation method foreach the java.lang.ClassCastException will
be thrown.
In conclusion, the correct alternative is:
**D) java.lang.ClassCastException will be thrown at // #B **
Also, remember that the Comparator.reverseOrder() will only work if your class implements
Comparable:
Output:
Shaka Seya Kanon Ikki Aldebaran
Output:
[psg1, socom, nikita, rex, m60, fgm-148 javelin]
As you could see in the example above, the code gets quite verbose to do operations from a list of a
list. Fortunately, that’s the problem the flatMap function solves. We can do the same as the above
code using less and more understandable code. We can also more easily manipulate the list the
way we want.
In the following code, we will perform the same operations as the code above using flatMap. We will
stream on the eliteSoldiers list, filter the soldier name to “Snake”, we will stream on the weapons
list, map it to lowerCase and finally collect it as a list:
Output:
[psg1, socom, nikita, rex, m60, fgm-148 javelin]
Output:
1.0 1.5 2.0
Similarly from DoubleStream, we can also convert an array of int to IntStream, or an array of long
to LongStream.
13 Streams 371
1 class FootballClub {
2 String name;
3 List<Player> players;
4
5 // Omitted the constructor and getter methods
6 }
7
8 class Player {
9 String name;
10 int age;
11 int goalsCount;
12
13 // Omitted the constructor, getter and toString methods
14 }
Now we are able to implement our code example by using flatMap with customized objects:
Output:
Player
Player
13 Streams 372
Code Analysis:
• #A: The map function is a perfect candidate for a method reference. That’s because it receives a
Function. Therefore, it must receive a parameter that is our footballClub object and it returns
a list of Player. We could also use a lambda expression: f -> f.getPlayers() to have the same
effect of the method reference.
• #B: Now we have the List<Player> mapped in the stream. That enables us to invoke the stream
method from the List interface. The flatMap method will give us access to the stream of the
List<Player>.
• #C: Let’s now filter the players who scored more than 500 goals. Notice that with flatMap we
can make the code concise and easy to read.
A last thing to notice in the flatMap method is that it receives a Function that takes the current
element of the stream and has to return a Stream:
That’s why the flatMap method is perfect to flatten elements from multiple collections. It’s made to
return a stream of data given an object.
Now that you know the main concept of a flatMap, it’s very important for you to make some changes
in the example above and see what happens. By doing that you will absorb this concept!
Output:
1234
Let’s change the order of the numbers now to see what will be the result:
13 Streams 373
Output:
12
Notice that the takeWhile method will continue the iteration only if the condition is true. Since the
number 7 is not lower than 5, the takeWhile method will stop it’s iteration. Therefore, that’s why
we only get number 1 and 2 and number 3 and 4 are ignored.
Output:
567
As you can see in the code above, the elements that were lower than 5 were dropped.
Now let’s change the order of the numbers and see what happens:
Output:
76453
Similarly to the takeWhile method, the dropWhile method will only drop the elements if the
condition is true. Considering that 7 is greater than 5, the dropWhile method will stop comparing
the remainning elements. Therefore, even numbers lower than 5 will still be in the list.
13 Streams 374
1 import java.util.stream.IntStream;
2
3 public class TakeDropWhileChallenge {
4
5 public static void main(String... doYourBest) {
6 IntStream.iterate(10, i -> i - 2)
7 .limit(5)
8 .skip(1)
9 .dropWhile(i -> i < 6)
10 .sorted()
11 .takeWhile(i -> i > 2)
12 .forEach(System.out::print);
13 }
14 }
A. 864
B. 68
C. 468
D. None of the above alternatives. Nothing will be printed.
Explanation:
Let’s deconstruct the pipeline of this IntStream. Let’s see what happens on each line of the code:
IntStream.iterate(10, i -> i - 2): we are starting an iteration from 10 and subtracting 2 on
each iteration.
limit(5): we limit the iteration amount to 5 so we avoid infinite looping. At that moment we have
the following elements: 10, 8, 6, 4, and 2.
skip(1): we skip one element, therefore, we will have the following elements now: 8, 6, 4, and 2.
dropWhile(i -> i < 6): note that the first element of the list 8 is not lower than 6, therefore, no
elements will be dropped here.
sorted(): we sort the elements in the natural order, therefore, the elements will be sorted as the
following: 2, 4, 6, and 8.
takeWhile(i -> i > 2): finally, we take only elements that are greater than 2, in our case, the first
number of the list is not greater than 2. Therefore, no elements will be taken leaving the stream with
no values.
13 Streams 375
forEach(System.out::print): when showing the elements here, nothing will be printed since the
list is empty.
Output:
It’s not deterministic, it may vary since any element can be found. However, the output will be
mostly the first element since we are not working with parallel streams.
Output:
Snake
If we perform operations to filter data in the stream, then the elements will be filtered first and then
the first element will be found:
13 Streams 376
Output:
Ocelot
parallel and findFirst: Notice that if we are working with a parallel stream that are not
ordered, the findFirst method will still make sure the first element will be retrieved.
Output:
true
Since Shiryu starts with ‘S’, the anyMatch condition will be fulfilled, therefore, it will return true.
Output:
true
Because all names from the stream knights start with S, the code above returns true. However, if
we change one name of the stream the output will be false:
13 Streams 377
Output:
false
Output:
true
Since none of the above elements start with B, the noneMatch method will return true. If we pass S
instead, the output will be false:
Output:
false
1 import java.util.stream.IntStream;
2
3 public class IsPrimeNoneMatch {
4
5 public static void main(String... doYourBest) {
6 System.out.println(isPrime(6));
7 System.out.println(isPrime('1')); // Line #A
8 }
9
10 static boolean isPrime(final int number) {
11 return number > 1 && IntStream.range(2, number)
12 .noneMatch(index -> {
13 boolean lol = number % index == 0;
14
15 return false;
16 });
17 }
18
19 }
A. false
true
B. true
false
C. Compilation error at // Line #A
D. true
true
Explanation:
Let’s analize the code, let’s start from the first invocation isPrime(6):
In the second invocation isPrime('1'), notice that we are using a char with '1' which translates
to 49 since we are passing it to an int type. You can take a further look at the ASCII table here².
²https://en.wikipedia.org/wiki/ASCII
13 Streams 379
Therefore, the same as the above will happen with the difference that the looping will go until 48
but still will return true.
In conclusion, the correct alternative is:
D. true
true
Output:
4
Since the number 1, 3, 5, and 7 are odd, the count result will be 4.
It’s also possible to count String elements:
Output:
3
The output is 3 because we are filtering the elements that starts with Go.
Output:
1
To make the above code simpler we can use method reference:
Output:
1
Output:
99
1 import java.util.OptionalInt;
2 import java.util.stream.IntStream;
3 import java.util.stream.Stream;
4
5 public class MinMaxChallenge {
6
7 public static void main(String... doYourBest) {
8 IntStream intStream1 = Stream.of(1, 2, 3, 4, 5, 6).mapToInt(n -> n);
9 IntStream intStream2 = intStream1;
10
11 OptionalInt optIntMin = intStream1.min();
12 OptionalInt optIntMax = intStream2.max();
13
14 int sum = optIntMax.orElse(5) + optIntMin.orElse(5);
15 System.out.println(sum);
16 }
17
18 }
A. 11
B. 7
C. 10
D. java.lang.IllegalStateException will be thrown at line 12
Explanation:
Firstly in this code challenge we convert an array of number to an IntStream and then we assign it
to intStream1. Then we assign intStream1 to intStream2 which is a problem here.
Remember that a terminal method operation can be used only once per stream object. Notice
also that we are using the same IntStream object to use the min and max operation methods.
Therefore, we will have a problem once we try to use this same IntStream object to collect the
max number. When we try to use a terminal operation method twice with a stream, we get the
java.lang.IllegalStateException.
The sum operation won’t even happen because of the java.lang.IllegalStateException thrown
in the max method. If we were using two streams objects the result would be 7.
In conclusion, the correct alternative is:
• return type T: it’s the generic type that will be returned based on the identity and accumulator
type we are passing.
• T identity: it’s the initial value of the reduce method. If we pass 0 to the identity for example,
the reduce operation would start from 0.
• BinaryOperator<T> accumulator: It’s a function that receives two values of the same type and
perform a logic you want when combining the values.
Now that we know the reduce method signature, let’s see a simple and practical example of the
reduce method:
Output:
15
Notice in the code above that the numbers of numbersList were summed. You can see that it’s very
easy to combine values using the reduce method. It can get even easier by using a method reference:
Output:
15
13 Streams 383
Output:
java rocks
Notice in the code above that we passed "ja" to the identity parameter. Therefore, the reduce
operation started with "ja" and then concatenated with "va", "ro" and "cks".
Output:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Output:
a Thread: main b Thread: main c Thread: main d Thread: main e Thread: main
1 List<String> letters = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h");
2 letters.parallelStream()
3 .forEach(letter -> System.out.printf("%s Thread: %s ", \
4 letter, Thread.currentThread().getName()));
Let’s first see an example with 100 elements to check which performance will be better:
Let’s see what happens when we have 100_000_000 elements to iterate. In this case using parallel
will significantly improve performance:
Output:
500000000500000000
1311 milliseconds
To sum numbers from 1 to 1_000_000_000 without parallelization takes 1311 milliseconds! It’s a lot
of time!
Let’s improve this by using the parallel method and let’s see what is the result:
Output:
500000000500000000
78 milliseconds
13 Streams 387
The difference is astonishing! When we use the reduce method with the parallel method we get 16
times improve in performance. Rather than spending 1311 milliseconds, we used only 78 milliseconds
by parallelizing the reduce operations!
Keep in mind though that there will be only improvement in performance if you have more than
one processor core in your machine, otherwise the parallelization won’t help.
The machine I used for those operation is:
Also, remember that it’s only worth it to use the parallel reduce operation when we are dealing
with heavy processing and/or a large amount of elements. That happens because it takes time for
the compiler to create a new thread, allocate it in memory, manage it and so on.
For example, for the above operation I tried on my machine with a closed range from 1 to 10_000_000
without parallel and with parallel:
I got the following time processing:
Keep in mind though that the above operation is a light one, therefore, it’s only worth it to
use parallel for at approximately at least 100_000_000 elements. Then the results would be the
following:
1 import java.util.List;
2
3 public class ParallelSortedSimpsonsAgeChallenge {
4 public static void main(String... doYourBest) {
5 List<Integer> simpsonAges = List.of(38, 36, 10, 8, 1);
6
7 simpsonAges.stream().parallel()
8 .filter(s -> s > 1)
9 .map(s -> s + " ")
10 .forEachOrdered(System.out::print);
11
12 System.out.println();
13
14 simpsonAges.stream().parallel()
15 .filter(s -> s > 1)
16 .map(s -> s + " ")
17 .forEach(System.out::print);
18 }
19 }
A. 38 36 10 8
B. 8 10 36 38
C. 38 36 10 8
10 38 8 36
D. 38 36 10 8
38 36 10 8
Explanation:
The key point of this code challenge is that the parallel method won’t keep the order of the
elements.
In the first stream of this code challenge, we are using the forEachOrdered method which will
guarantee the order of the elements even using parallel. Therefore, the output here will be always:
38 36 10 8.
However, when we use parallel with only forEach, the elements will be in a random order. So
it’s impossible to predict or rely on the order of the elements when we use parallel without
forEachOrdered.
In conclusion, the correct answer is:
13 Streams 389
A. 38 36 10 8
Output:
[Atreus, Athena]
If we don’t want anyone to make a change in the collected list, we can use the
Collectors.toUnmodifiableList() method. If we try to add, change or remove an element
from newList we will get an UnsupportedOperationException:
Output:
java.lang.UnsupportedOperationException...
In Java 16, we can make the same as above in a simpler way obtaining the same result by using the
toList method. Just keep in mind that the toList method will also return an unmodifiable list.
13 Streams 390
Output:
[Atreus, Athena]
Output:
[Kratos]
As you can see in the example above, the String Kratos was deduplicated. Therefore, the output
from our set is only Kratos. Also, Atreus was filtered out of the list.
If we want to restrict usage of our set, we are also able to collect an unmodifiableSet as we can see
in the following code:
Output:
java.lang.UnsupportedOperationException
Since we can’t add, change or delete elements from an unmodifiable set, we will get the
UnsupportedOperationException.
Output:
[Atreus, Athena]
1 class MarvelHero {
2 private String name;
3 private int age;
4
5 // Omitted constructor, equals and hashCode, getters, and toString.
6 }
Now that we have our data class, let’s see code example example where we transform a stream of
a heroes’ list into a map. We will also filter only heroes that are older than 30 years old in the
following code.
Output:
The signature of this toMap method is:
13 Streams 392
Let’s explore the signature above, notice that it returns a Collector so that the stream can execute
its operations to transform our data into a map.
It receives a Function as a keyMapper which enables us to implement some logic to retrieve a key
value for our map. The valueMapper is similar, it also receives a Function but it will be the value of
our map.
Output:
As you can notice in the code above, we are showing the name of the hero and then its whole identity.
Keep in mind that we are showing the toString method information from the MarvelHero object.
Output:
java.lang.IllegalStateException: Duplicate key Spider-man…
Output:
Output:
As you can see in the example above, the age from the duplicate Spider-man was summed, therefore,
we have 42 as a result.
To make the example above a bit simpler, we can also use a method reference Integer::sum and the
result will be the same:
The method signature from the operation we did above is the following:
Notice that the only difference from the previous toMap examples is the BinaryOperator<U>
mergeFunction that will merge the duplicate elements values.
Output:
Output:
Output:
[1, 2, 3, 4]
[Homer, Marge, Bart, Lisa]
As you could see in the code above we easily transformed a map to a list by using streams.
13.8 Collectors.groupingBy
Very often we need to group data from a map, to accomplish that we can use the groupingBy method.
We can group data from the key and do specific operations with the values.
Output:
Output:
Code Analisis:
• #A: The Collectors.groupingBy method will group the elements by the String name using
the method reference String::valueOf. Notice that here we could also use: e -> e or
Function.identity which would return the String instance.
• #B: The Collectors.counting() will count how many elements there are for each hero.
Output:
Output:
Output:
,
Hero,
Hero],
DC=[Hero,
Hero,
Hero]}
Output:
Output:
Output:
Code Analisis:
Then after getting a Comparator object, we can use the reversed() method to reverse the natural
sorting of our map values.
• #D: With the sorted results of our stream pipeline, then we populate our
highestHeroesFightPower map with the key and value.
13 Streams 401
13.9 Summary
Congratulations! In this chapter you learned the main principles of streams that will help you build
much better code by using the power of streams such as:
You also learned the most important methods to use with a stream which will cover the vast majority
of use cases in a real-world Java project.
To recap, in this chapter you learned to use:
• Oracle (Oracle JDK), free for personal use but not free for comercial use
• AdoptOpenJDK, free for comercial use
• Amazon (Corretto)
• Red Hat
• Azul
• Alibaba
• BellSoft (Liberica)
• SAP (SapMachine)
• Short-Term Version: This version won’t be supported by Java providers such as Oracle, Azul,
Amazon and so on. That means that patches for security or updates won’t be created.
• Long-Term Version: The Java vendors will maintain and support this version for longer, usually
for example the latest LTS version is Java 17 released in 2021 and it will be supported until
September 2026 and possibly extended until September 2029.
14 Newest Features of Java 403
Another important concept regarding the new releases of Java is that now we have preview features
and standard features:
• preview: since a new Java versions are now released every 6 months it’s more difficult to add
new features as standard. Therefore, features added to Java will usually be considered preview.
Simply put, a preview feature is a feature that might be changed in next releases, this means
that, it’s not a good idea to use a preview feature in production for example.
• standard: when a feature is standard, this means there are no plans to change a keyword or
anything like that. Therefore, it can be safely used in production.
Finally, to make a good decision for which Java version to use it’s very important to choose a long-
term support one because of the security patches and support Java vendors will provide. That’s
because you won’t need to change the Java version every 6 months.
On Windows:
/jdk-17.0.2/lib
Those are the modules that are accessible when using java.base:
io, util, lang, math, net, nio, security, text, time, util.
Java SE Graph
Note that in the above graph all dependencies use java.base indirectly or in a transitive way.
The above graph was extracted from the Java docs in the following link:
https://docs.oracle.com/javase/9/docs/api/java.se-summary.html³
To see all the other modules from the JDK since Java 9 we can use the following command on your
terminal:
java --list-modules
Note that the Java version you are using will be shown after the module name, therefore the output
will be the following:
1 [email protected] [email protected]
2 [email protected] [email protected]
3 [email protected] [email protected]
4 [email protected] [email protected]
5 [email protected] [email protected]
6 [email protected] [email protected]
7 [email protected] [email protected]
8 [email protected] [email protected]
9 [email protected] [email protected]
10 [email protected] [email protected]
11 [email protected] [email protected]
12 [email protected] [email protected]
13 [email protected] [email protected]
14 [email protected] [email protected]
15 [email protected] [email protected]
16 [email protected] [email protected]
17 [email protected] [email protected]
18 [email protected] [email protected]
19 [email protected] [email protected]
20 [email protected] [email protected]
21 [email protected] [email protected]
³https://docs.oracle.com/javase/9/docs/api/java.se-summary.html
14 Newest Features of Java 406
22 [email protected] [email protected]
23 [email protected] [email protected]
24 [email protected] [email protected]
25 [email protected] [email protected]
26 [email protected] [email protected]
27 [email protected] [email protected]
28 [email protected] [email protected]
29 [email protected] [email protected]
30 [email protected] [email protected]
31 [email protected] [email protected]
32 [email protected] [email protected]
33 [email protected] [email protected]
34 [email protected] [email protected]
35 [email protected] [email protected]
Notice that in front of each module there is a number @17.0.1. However, this is not a version of the
module. It’s actually the version of the JDK.
It’s also possible to show details of a specific module by using the following command:
java describe-module java.sql
Output:
1 [email protected]
2 exports java.sql
3 exports javax.sql
4 requires java.xml transitive
5 requires java.logging transitive
6 requires java.transaction.xa transitive
7 requires java.base mandated
8 uses java.sql.Driver
1 -- javachallengersbook
2 -- hello-module
3 -- src
4 -- hellomodule
5 -- HelloModule.java
6 -- module-info.java
If you are using Maven, the following should be the place where you will use it:
1 -- javachallengersbook
2 -- hello-module
3 -- src
4 -- main
5 -- java
6 -- hellomodule
7 -- HelloModule.java
8 -- module-info.java
Notice that we have the HelloModule.java class above and after compiling the hello-module we
will see the output from it:
1 package hellomodule;
2
3 public class HelloModule {
4
5 public static void main(String[] args) {
6 System.out.println("Hello module!");
7 }
8
9 }
To use the command, go to the root of the hello-module and use the following:
javac -d out src/main/java/hellomodule/*.java src/main/java/module-info.java
Notice in the command above that we are passing all classes by using the wildcard *.java.
By running the above command sucessfully you will see the .class files in the out folder of the
hello-module.
Now we are able to run the compiled module class from out by running the following command
from the root of hello-module:
java -p out -m helloModule/hellomodule.HelloModule
Output:
Hello module!
Notice that we don’t need to use the classpath to run the class but instead we can use the fully
qualified module class name.
It’s possible to create a jar file based on the generated .class files by using the following command
from the root of hello-module:
jar --create --file=hello-module.jar -C out/ .
Once we have the hello-module.jar file created, now we can inspect it to see what modules it’s
using with the following command from the root of hello-module:
jar --describe-module --file=hello-module.jar
Output:
requires java.base mandated
contains hellomodule
To solve this issue the JVM will create a module automatically from a jar for retro-compatibility.
That will only happen when we don’t explicitly declare a module descriptor.
Automatic modules export, open all packages and read all other modules including unnamed modules.
It keeps the jar name as a module which might cause collisions. It’s also possible to set name to the
automatic module jar file.
The name of the module can be defined in a manifest file of the project.
1 -- automatic-module
2 -- src
3 -- main
4 -- java
5 -- util
6 -- Automatic.java
Notice in the above structure that we don’t have a module-info.java file. In the root from the
automatic-module, run the commands:
1 package util;
2
3 public class Automatic {
4 public void hello() {
5 System.out.println("Hello Automatic Module!");
6 }
7 }
Let’s run the command to compile the Automatic class from the root of the automatic-module project
and put it into the out folder:
javac -d out src/main/java/util/Automatic.java
1 -- use-automatic-module
2 -- src
3 -- main
4 -- java
5 -- use
6 AutomaticModuleUse.java
7 module-info.java
1 module useAutomaticModule {
2 requires automatic;
3 }
1 package use;
2
3 import util.Automatic;
4
5 public class AutomaticModuleUse {
6
7 public static void main(String[] args) {
8 Automatic automatic = new Automatic();
9 automatic.hello();
10 }
11 }
Notice in the above class that we are using the Automatic class from the automatic module.
To use the automatic module, the first step is to create a lib folder in the root of
use-automatic-module:
mkdir use-automatic-module\lib
Let’s now move the jar we created for the automatic module:
Mac:
mv automatic-module/automatic.jar use-automatic-module/lib
Windows:
move automatic-module/automatic.jar use-automatic-module/lib
Finally, let’s run the AutomaticModuleUse class with the following command:
Mac:
java --module-path ./out:./lib --module useAutomaticModule/use.AutomaticModuleUse
Windows:
java --module-path out;lib --module useAutomaticModule/use.AutomaticModuleUse
1 package use;
2
3 import util.Automatic;
4
5 public class ModuleMetadata {
6
7 public static void main(String[] args) {
8 Automatic automatic = new Automatic();
9 showModuleMetadata(automatic);
10 }
11
12 static void showModuleMetadata(Automatic automatic) {
13 Module module = automatic.getClass().getModule();
14 System.out.println("Module Name: " + module.getName());
15 System.out.println("Module Descriptor: " + module.getDescriptor());
16 System.out.println("Is Automatic: " + module.getDescriptor().isAutomatic());
17 }
18
19 }
Windows:
java --module-path out;lib --module useAutomaticModule/use.ModuleMetadata
Output:
Module Name: automatic
Module Descriptor: module
Is Automatic: true
As the name suggests, an unnamed module is a module that has no name, in other words, has no
module-info.java file.
1 package com.javachallengers.newfeatures;
2
3 public class ModuleExamples {
4
5 public static void main(String[] args) {
6 Module module = ModuleExamples.class.getModule();
7
8 System.out.println("Module Object: " + module);
9 System.out.println("Module Name: " + module.getName());
10 System.out.println("Module Descriptor: " + module.getDescriptor());
11 }
12
13 }
Output:
Module Object: unnamed module @6b884d57
Module Name: null
Module Descriptor: null
Since the code above has no module-info.java declared, we will have the output of an unnamed
module.
Notice that in the code above I am using the Maven package structure. Simply put, currently Maven is
the most popular build automation tool, roughly it helps to declare project dependencies, build and
test a project. We could also create a normal Java project in the example above though.
Before seeing the module-info.java file, let’s see what we have in the CustomerDAO class:
14 Newest Features of Java 414
1 package dao;
2
3 public class CustomerDAO {
4 public String getCustomer() {
5 return "customer_duke";
6 }
7 }
Now let’s see how we can export the dao package in the module-info.java file:
1 module daoModule {
2 exports dao;
3 }
If we want, we can restrict the package export to only one module as the following. For now pretend
we have the serviceModule created:
1 module daoModule {
2 exports dao to serviceModule;
3 }
In the module descriptor above, we can only use the code from the module dao in the serviceModule.
If we try to use the code by another module we won’t have access to it.
Another important point is that exporting the dao package to the serviceModule will create coupling
with serviceModule.
If we want to export the module to more specific modules we can add more modules using , after
serviceModule:
1 module daoModule {
2 exports dao to serviceModule, viewModule;
3 }
1 package dao.util;
2
3 public class JavaMascotUtils {
4
5 public String getMascotItem() {
6 return "Java wand";
7 }
8 }
The above class will not be accessible to other modules even if we are exporting the dao module!
That’s because, it’s necessary to export a submodule explicitly to have access to a submodule.
The class above will only be accessible to other modules if we make it explicit as the following:
1 module daoModule {
2 exports dao to serviceModule;
3 exports dao.util to serviceModule;
4 }
1 module serviceModule {
2 requires daoModule;
3 }
Ok, all set for the modules. Notice though that I am using Maven in this example, therefore, before
requiring the module from the other project, it’s necessary to declare the dependency first in the
Maven configuration file known as pom.xml file. If we were using a normal Java project we would be
able to use modules immediately.
Now that we are requiring the daoModule we will have access to the CustomerDAO and
JavaMascotUtils classes in the serviceModule since we exported the packages dao and dao.util.
Let’s see that in code:
1 package service.impl;
2
3 import dao.CustomerDAO;
4 import dao.util.JavaMascotUtils;
5 import service.CustomerService;
6
7 public class DukeCustomerServiceImpl implements CustomerService {
8
9 public void chargeCustomer() {
10 CustomerDAO customerDAO = new CustomerDAO();
11 JavaMascotUtils javaMascot = new JavaMascotUtils();
12 System.out.println("Charging customer:" + customerDAO.getCustomer() + " \
13 with " + javaMascot.getMascotItem());
14 }
15 }
As you can see in the above code, we are only able to access the CustomerDAO class because we used
the requires clause in our module descriptor. Otherwise, we would have a compilation error.
1 package cache;
2
3 public class SpecialCache {
4
5 private String cachedString;
6
7 public String getCachedString() {
8 return cachedString;
9 }
10
11 public void setCachedString(String anyString) {
12 cachedString = anyString;
13 }
14 }
1 module cacheModule {
2
3 exports cache;
4
5 }
In the CustomerDAO class from the dao-module, let’s add a method that returns the SpecialCache
from the cache-module:
1 package dao;
2
3 import cache.SpecialCache;
4
5 public class CustomerDAO {
6
7 // Omitted code...
8
9 public SpecialCache getCachedCustomer() {
10 if (specialCache.getCachedString() == null) {
11 specialCache.setCachedString(getCustomer());
12 }
13
14 return specialCache;
15 }
16 }
1 module daoModule {
2 requires transitive cacheModule;
3 // Omitted code...
4 }
Now we can use the SpecialCache class in the service-module since we require the dao-module.
Remember that this will only work if the dao-module requires the cache-module with the transitive
keyword.
Notice that we have to require only the dao-module in the service-module:
14 Newest Features of Java 419
1 module serviceModule {
2 requires daoModule;
3 // Omitted code...
4 }
We already have the cache-module required for the service-module transitively. Now we just need
to use it:
1 package service.impl;
2
3 import cache.SpecialCache;
4 import dao.CustomerDAO;
5 import service.CustomerService;
6
7 public class DukeCustomerServiceImpl implements CustomerService {
8
9 // Omitted code
10
11 public void logCustomer() {
12 CustomerDAO customerDAO = new CustomerDAO();
13 SpecialCache specialCache = customerDAO.getCachedCustomer();
14 System.out.println(specialCache.getCachedString());
15 }
16 }
The code above will compile sucessfully and if logCustomer was invoked, we would have the output
of: customer_duke
Before Java 9 we would have to declare the instance we want to use with the ServiceLoader in a
META-INF/services file which is not a nice approach. With modules we can simply use the provides
clause eliminating the necessity of declaring instances into META-INF/services.
Let’s see a practical example of how it works on code. Let’s create an interface CustomerService for
our services implementations:
1 package service;
2
3 public interface CustomerService {
4 void chargeCustomer();
5 }
Let’s create an implementation for the CustomerService in the serviceModule and package
service.impl:
1 package service.impl;
2
3 public class JuggyCustomerImpl implements CustomerService {
4 public void chargeCustomer() {
5 System.out.println("Charging customer: Juggy");
6 }
7 }
Let’s create another implementation for CustomerService in the serviceModule and package
service.impl:
1 package service.impl;
2
3 import dao.CustomerDAO;
4 import service.CustomerService;
5
6 public class DukeCustomerServiceImpl implements CustomerService {
7
8 public void chargeCustomer() {
9 CustomerDAO customerDAO = new CustomerDAO();
10 System.out.println("Charging customer:" + customerDAO.getCustomer());
11 }
12 }
14 Newest Features of Java 421
Now let’s provide the interface and implementations in the module-info.java file from the
serviceModule:
1 import service.CustomerService;
2 import service.DukeCustomerServiceImpl;
3 import service.JuggyCustomerImpl;
4
5 module serviceModule {
6
7 requires daoModule;
8 exports service;
9 provides CustomerService with DukeCustomerServiceImpl,
10 JuggyCustomerImpl;
11
12 }
One very important detail to notice is that the service.impl package is not being exported to other
modules. This means that DukeCustomerServiceImpl and JuggyCustomerImpl can only be accessed
through a ServiceLoader which means strong encapsulation!
Also note that the exports service clause will only export the service package classes, not the
subpackages, in this case, impl.
Notice that only the exports service clause wouldn’t work if we want to use the ServiceLoader.
We need to explicitly use the provides statement.
1 import service.CustomerService;
2
3 module viewModule {
4 requires serviceModule;
5 uses CustomerService;
6 }
Notice in the module-info.java file above that we need to require the serviceModule to be able to
use the CustomerService. Otherwise, we won’t have access to the CustomerService interface.
Now we can access both instances DukeCustomerServiceImpl and JuggyCustomerImpl through a
service loader in a view-module class:
14 Newest Features of Java 422
1 package view;
2
3 import service.CustomerService;
4 import java.util.ServiceLoader;
5
6 public class CustomerView {
7
8 public static void main(String[] args) {
9 ServiceLoader<CustomerService> serviceLoader =
10 ServiceLoader.load(CustomerService.class); // #A
11 serviceLoader.stream().forEach(e -> e.get().chargeCustomer()); // #B
12 }
13
14 }
Code analisis:
1 package service;
2
3 public class CurrencyUtil {
4
5 private String currency = "$";
6
7 }
1 // Omitted imports
2 public class CustomerView {
3
4 public static void main(String[] args) throws NoSuchFieldException,
5 IllegalAccessException {
6 // Omitted ServiceLoader code
7
8 Field field = CurrencyUtil.class.getDeclaredField("currency"); // #A
9 field.setAccessible(true); // #B
10 System.out.println("The currency is: " + field.get(new CurrencyUtil()));
11 }
12 }
• #A: We access the metadata from the CurrencyUtil class and get the declared field currency.
• #B: To be able to access a private member, it’s necessary to set the setAcessible method to
true. However, when this method is invoked an Exception will be thrown with the following
message:
As you can see, by using modules we have real encapsulation. If we don’t explicitly open the package
to be used via reflection we will have the same Exception as above. Fortunately, there is a way to
open the packages we want to access via reflection and this is what we will see in the next section.
The open clause is a pragmatic approach to open all module packages for reflection access. The cons
of using this approach is that you lose the possibility to give access just to specific packages.
Let’s see how that works in code, first let’s open the serviceModule:
1 // Omitted imports...
2 open module serviceModule { // Open the module for reflection access
3 exports service;
4 // Omitted other module statements
5 }
Notice in the code above that we are oppening everything from the serviceModule when we use the
open clause at the start of the module descriptor.
Now if we try to access the private currency member via reflection in the viewModule that will
work with no issues:
1 // Omitted imports
2 public class CustomerView {
3
4 public static void main(String[] args) throws NoSuchFieldException,
5 IllegalAccessException {
6 // Omitted ServiceLoader code
7
8 Field field = CurrencyUtil.class.getDeclaredField("currency");
9 field.setAccessible(true);
10 System.out.println("The currency is: " + field.get(new CurrencyUtil()));
11 }
12 }
Output:
The currency is: $
1 // Omitted imports
2 open module serviceModule {
3
4 exports service;
5 opens service to viewModule; // Compilation error
6 // Omitted other module statements
7 }
Notice in the above code that it’s not possible to open the whole module and a package since it’s
redundant. Instead, we have to remove the open clause from the module declarion and leave only
opens with the package we want:
1 // Omitted imports
2 module serviceModule {
3 exports service;
4 opens service to viewModule;
5 // Omitted other module statements
6 }
Now we will be able to access the service package from the viewModule via reflection to private
members. If we try to run the same code as above accessing the currency field, we will be able to
accomplish that. If we try to access currency from another module other than viewModule, it won’t
work.
1 // Omitted imports
2 module serviceModule {
3 exports service;
4 opens service;
5 // Omitted other module statements
6 }
Now we can access the currency field from the service package in any module using reflection.
1 package com.defiant;
2
3 public interface Captain {
4
5 void attack(String shipName);
6 }
We have the Spock class implementing Captain and notice also the private method doh:
1 package com.defiant;
2
3 public class Spock implements Captain {
4
5 public void attack(String shipName) {
6 if (shipName.equals("reliant"))
7 System.out.println("The ship is destroyed");
8 else {
9 throw new StackOverflowError("Non-existent ship");
10 }
11 }
12
13 private void doh() {
14 System.out.println("Hey, this is from the Simpsons!");
15 }
16
17 }
1 import com.defiant.Spock;
2 import com.defiant.Captain;
3
4 module vulcan {
5
6 exports com.defiant to risa;
7 provides Captain with Spock;
8 }
1 package com.reliant;
2
3 import com.defiant.Captain;
4 import com.defiant.Spock;
5 import java.lang.reflect.InvocationTargetException;
6 import java.lang.reflect.Method;
7 import java.util.ServiceLoader;
8
9 public class Executor {
10 public static void main(String... doYourBest) throws NoSuchMethodException,
11 InvocationTargetException, IllegalAccessException {
12 Captain captain = ServiceLoader.load(Captain.class).findFirst().get();
13 captain.attack("reliant");
14
15 Method method = Spock.class.getDeclaredMethod("doh");
16 method.setAccessible(true);
17 method.invoke(new Spock());
18 }
19 }
risa module-info.java:
1 import com.defiant.Captain;
2
3 module risa {
4
5 requires vulcan;
6 uses Captain;
7 }
When running the Executor main method from the risa module, what will happen?
Explanation:
14 Newest Features of Java 428
There are key points to notice in the vulcan module descriptor. The first one is that we are
providing the Captain interface with the implementation of Spock to be used in the risa module
via ServiceLoader.
The second one is that we are not oppening any package to be accessed via reflection.
Other than that we are also exporting the com.defiant package to the module risa.
Notice in the module descriptor from risa that we are using the requires statement to import the
vulcan module. We are also using the uses clause to use the Captain interface in a ServiceLoader.
Therefore, in the Executor class we will be able to access Captain with the implementation of Spock
with the ServiceLoader. However, we won’t be able to access the doh method via reflection since
the vulcan method didn’t use the open clause.
In conclusion, the correct alternative is:
Output:
PLAYSTATION 5
class java.lang.String
Notice in the code above that a var type is inferred in compilation time and that enables use to use
an auto-complete feature from an IDE gracefully.
As you can see in the code output above, we assigned the String “Playstation 5” to product. Then
there was a process of type-inference where the product var was transformed to String.
The var feature also infers types with generics. If we want to infer an ArrayList of String, this is
possible:
14 Newest Features of Java 429
Output:
[Playstation 5, Nintendo Switch]
We can infer a List.of array creation:
Output:
[Playstation 5, Nintendo Switch]
Output:
run!
Output:
java: illegal start of expression
To solve this problem, in Java 11 we can use the var keyword within a lambda expression parameter.
This will reduce the boiler-plate code because we won’t have to write the specific variable type for
each lambda parameter:
Output:
PLAYSTATION 5
Keep in mind that if we declare the type of one lambda parameter as var we have to do the same
with the other ones. If we try to do the following, the code won’t compile:
Output:
java: invalid lambda parameter declaration
(cannot mix ‘var’ and explicitly-typed parameters)
Another important point to notice is that the var keyword can still be used as a class/method/variable
name. This happens with all new keywords after Java 8 to avoid retro-compatibility problems with
previous Java versions. Therefore, the following is valid but not recommended to use since the var
name doesn’t describe much what is the variable:
1 void var() {
2 var var = "Var";
3 }
Output:
java: ‘var’ is not allowed here
It’s not possible to change the type of the variable after it’s created with a var. The following code
won’t compile:
Output:
java: incompatible types: java.lang.String cannot be converted to int
We can’t also use a var as a parameter, if we try so we will have a compilation error:
Output:
java: ‘var’ is not allowed here
A var type can’t be used as a return type, if we try the following the code won’t compile:
Output:
java: ‘var’ is not allowed here
A var type has to be initialized as it is declared, otherwise, we will have a compilation error as the
following code:
Output:
java: cannot infer type for local variable product
(cannot use ‘var’ on variable without initializer)
A var type can’t infer a lambda type:
Output:
Cannot infer type: lambda expression requires an explicit target type
The lambda code above will only work if we use a Supplier explicitly:
14 Newest Features of Java 432
Output:
Playstation 5
A null value can’t be assigned to a var. If we try the following, the code won’t compile:
Output:
Cannot infer type: variable initializer is ‘null’
Since var is a type inference, we can’t use [] or <> in the var type as you can see in the following
code:
Output:
java: ‘var’ is not allowed as an element type of an array
By removing the [] from cars, the code above works fine.
When we try to use generics in a var type the code won’t also compile:
Output:
java: illegal reference to restricted type ‘var’
If we remove <String> from var the code above will compile. Since var is a type inference, we don’t
need to specify that a var is an array or a generic type, we just need to assign the value to a var.
1 import javax.annotation.Nonnull;
2 import java.util.ArrayList;
3 import java.util.Arrays;
4
5 public class TurnAboutIntruderChallenge {
6 public static void main(String... awayTeam){
7 var var = new Var() { public String collide(String c1, String c2)
8 { return c1 + c2; }};
9 var drLester = (Var) null;
10 drLester = var;
11
12 Var reliantMistake = (@Nonnull var s1, final var s2) ->
13 String.join(";", s1, s2, "Darth Vader");
14
15 var numbers = new ArrayList<>(Arrays.asList(3, 2, 1));
16 numbers.remove(Integer.valueOf(2));
17
18 System.out.println(var.collide("Kirk", "Spock"));
19 System.out.println(reliantMistake.collide("Khan", "Borg"));
20 System.out.println(drLester.getClass().getName());
21 System.out.println(numbers);
22 }
23
24 private interface Var { String collide(String c1, String c2); }
25 }
Explanation:
14 Newest Features of Java 434
Notice that we have a private interface Var created in the bottom of this Java code challenge which
will be used in the main method implementation. The interface name Var can be used without a
problem also.
In the first statement of the main method, we assign an anonnymous inner class of Var implementing
the collide method by concatenating two Strings.
Another important point is that we are using var as a variable name which is possible. That happens
because since Java 9, the Java language won’t contain a keyword that can’t be used as a class, method
or variable. This aligns with the principle of retro-compatibility the Java language has.
Then notice we are assigning a null value to the var type which works when we use casting. This
will cast the type to the Var class type not to a null value.
We assign the value from var to drLester.
Then we assign a lambda expression to Var following the contract of String collide(String
c1, String c2);. Notice also that we are using var in the lambda parameter with @Nonnull and
final which is only possible since Java 11. The method implementation will concatenate the Strings
Khan;Borg;Darth Vader.
We initialize the var numbers with Arrays.asList(3, 2, 1).
Then we have a tricky part, we remove the value of 2 not the index. That happens because the
remove method from the ArrayList is overloaded with the following:
Since we are using the Integer.valueOf(2) we return a wrapper Integer, therefore, the public
boolean remove(Object o) will be invoked. Therefore, the element with value of 2 will be removed
from the array.
Therefore, the correct alternative is:
D. KirkSpock
Khan;Borg;Darth Vader
TurnAboutIntruderChallenge$1
[3, 1]
The new switch case statement was firstly introduced on Java 12 as a preview feature and became
standard on Java 14.
Let’s see how different the old way and the new way of using the switch case statement are:
1 int formulaNumber = 7;
2 String heisenbergElement = "";
3
4 switch (formulaNumber) {
5 case 5:
6 heisenbergElement += "H";
7 break;
8 case 7:
9 heisenbergElement += "N";
10 break;
11 default:
12 heisenbergElement += "He";
13 }
14
15 System.out.println(heisenbergElement);
Output:
N
As you can see above, the code is verbose and we need to add the break keyword on each case
statement. We can accomplish the same thing as above by using the new switch case statement:
1 int formulaNumber = 7;
2 String heisenbergElement = "";
3
4 switch (formulaNumber) {
5 case 5 -> heisenbergElement += "H";
6 case 7 -> heisenbergElement += "N";
7 default -> heisenbergElement += "He";
8 }
9
10 System.out.println(heisenbergElement);
Output:
N
By using lambdas on each case stament we reduce a lot of the boiler-plate code and make it more
readable.
Notice that the code above with a lambda expression can only be used like that if it’s a one liner. If
there is more than one line we must use the yield keyword:
14 Newest Features of Java 436
Output:
He
Notice in the code above that in the new switch case statement it’s possible to return a value also.
Note also that to return a value when there is more than one line in our lambda, we need to use the
keyword yield. The yield keyword will break the switch case process and return the value. In our
case, the value will be returned to the chemicalElement variable. Therefore, He will be printed.
10
11 static Object getBeers(String character) {
12 return switch (character) {
13 case "Homer" -> {
14 System.out.print("Every day is a beer day. ");
15 yield (Integer.valueOf(129) == 129 ? 10 : 7) + " beers for Homer. ";
16 }
17 case "Carl" -> "Lenny" == new String("Lenny") ? 2 : 4 +
18 " beers for Carl.";
19 default -> new NumberFormatException("9999 beers. Not enough beers for Barney!\
20 ");
21 };
22 }
23 }
A. Every day is a beer day. 10 beers for Homer. 4 beers for Carl.
java.lang.NumberFormatException: 9999 beers. Not enough beers for Barney!
B. Compilation error at line 12
C. Every day is a beer day. 7 beers for Homer. 2 beers for Carl.
java.lang.NumberFormatException is thrown
D. Compilation error at line 19
Explanation:
The purpose of this Java code challenge is to help you fixate the syntax from the new switch case.
The first case statement is for “Homer”, therefore “Every day is a beer day. “ will be printed followed
by the result of Integer.valueOf(129) == 129. The valueOf method returns a new Integer() object
but since we are comparing it with a primitive type 129 it will return true because of thre will
be a conversion from the wrapper to a primitive type. When we compare primitive types we are
comparing values and not an object, therefore it returns true. Next then we will print: “10 beers for
Homer.”.
In the “Carl” case statement we have the comparison a String “Lenny” with a new String(“Lenny”).
Remember that when we create a new String() we are forcing the creation of a new object in the
memory heap. Therefore, “Lenny” and new String(“Lenny”) are different objects. Then “4 beers for
Carl.” will be printed.
Lastly, notice that the new NumberFormatException("9999 beers. Not enough beers for
Barney!") is not being thrown, it’s being returned instead. Therefore, the toString method from
the NumberFormatException object will be printed: java.lang.NumberFormatException: 9999 beers.
Not enough beers for Barney!
In conclusion, the correct alternative is:
14 Newest Features of Java 438
A. Every day is a beer day. 10 beers for Homer. 4 beers for Carl.
java.lang.NumberFormatException: 9999 beers. Not enough beers for Barney!
Output:
class java.lang.String
Let’s see now how we can do the same as the above code using the pattern matching feature:
Output:
class java.lang.String
As you can see, the pattern matching feature is a syntax sugar that will do the type casting implicitly.
It also makes the code more legible.
The || operator, on the other hand, can execute the second condition even if the first condition is
false. Therefore, we have the possibility that the pattern matching variable won’t be created. For
that reason, we will have a compilation error in the following code:
Output:
java: cannot find symbol
symbol: variable s
If the compiler already knows that the if statement will be false we won’t be able to access the
pattern variable also as the following:
14 Newest Features of Java 440
Since the compiler knows simpsonCharacter is a String, we won’t be able to use the pattern variable
s in the if statement. Therefore, we will get a compilation error.
1 import java.io.Serializable;
2
3 public class SpaceWeaponsPatternMatchingChallenge {
4 public static void main(String... doYourBest) {
5 shoot(1l + "Type-1 Phaser");
6 shoot(true);
7 shoot(new StringBuffer("Plasma cannon").toString());
8 shoot(new RuntimeException("Disruptor"));
9 }
10
11 static void shoot(Object object) {
12 if (object instanceof StringBuffer) {
13 System.out.println(((StringBuffer) object).append(":buffer"));
14 } else if (object instanceof Comparable comparable) {
15 System.out.println(comparable.equals(11 + "Type-1 Phaser"));
16 } else if (object instanceof Serializable s && s instanceof Throwable) {
17 System.out.println(s + ":serializable");
18 }
19 }
20 }
A. true
true:serializable
Plasma cannon:buffer
false
B. false
Plasma cannon:serializable
java.lang.RuntimeException: Disruptor:serializable
14 Newest Features of Java 441
C. false
false
false
java.lang.RuntimeException: Disruptor:serializable
D. true
true:serializable
false
Explanation:
In the first shoot method invocation we are passing the number 1 and an l to indicate it’s a long
type, it’s not 11. The long number 1 will become a String though since we concatenate with "Type-1
Phaser". A String is instance of a Comparable as you can see:
However, 11 + "Type-1 Phaser" is different than 1l + "Type-1 Phaser", therefore, the output here
is false.
Then we pass the primitive value of true. Remember that when we pass a primitive value to an
Object this value will be autoboxed to a wrapper, in this case, Boolean so that it can fit into an
Object. A Boolean type is also Comparable:
Therefore, since true is different than 11 + "Type-1 Phaser", we will also get false as an output.
Now we pass a new StringBuffer("Plasma cannon").toString() in the shoot method. Notice that
we are converting the StringBuffer into a String. Therefore the Comparable if will be executed
again and "Plasma cannon" is not equals to 11 + "Type-1 Phaser", therefore, we will get false here
too as an output.
Finally, we pass a RuntimeException class that is actually Serializable as you can see in the
following class hierarchy:
Therefore, the toString method from the RuntimeException will be printed with ":serializable".
In conclusion, the correct alternative is:
14 Newest Features of Java 442
C. false
false
false
java.lang.RuntimeException: Disruptor:serializable
1 Object anyObject = 1;
2
3 String numberType = switch(anyObject) {
4 case Byte b -> b.getClass().toString();
5 case Integer i -> i.getClass().toString();
6 case BigDecimal b -> b.getClass().toString();
7 case Object o -> o.getClass().toString();
8 };
9
10 System.out.println(numberType);
Output:
class java.lang.Integer
We can also use pattern matching in a switch case statement without a lambda expression:
1 Object getObjectTypeSwitchStatement() {
2 Object anyObject = 1;
3
4 switch(anyObject) {
5 case Byte b: return b.getClass().toString();
6 case Integer i: return i.getClass().toString();
7 case BigDecimal b: return b.getClass().toString();
8 case Object o: return o.getClass().toString();
9 }
10 }
Output:
class java.lang.Integer
14 Newest Features of Java 443
The record feature was introduced on Java 14 and became standard on Java 16.
Let’s see that in practice creating a data object without a record and then using record:
1 class CarWithoutRecord {
2
3 private String brand;
4 private String model;
5 private int year;
6
7 public CarWithoutRecord(String brand, String model, int year) {
8 this.brand = brand;
9 this.model = model;
10 this.year = year;
11 }
12
13 public String getBrand() {
14 return brand;
15 }
16
17 public String getModel() {
18 return model;
19 }
20
21 public int getYear() {
22 return year;
23 }
24
25 @Override
26 public boolean equals(Object o) {
27 if (this == o) return true;
28 if (o == null || getClass() != o.getClass()) return false;
29 CarWithoutRecord car = (CarWithoutRecord) o;
30 return year == car.year && Objects.equals(brand, car.brand) && Objects.equals(mo\
14 Newest Features of Java 444
31 del, car.model);
32 }
33
34 @Override
35 public int hashCode() {
36 return Objects.hash(brand, model, year);
37 }
38
39 @Override
40 public String toString() {
41 return "CarWithoutRecord[" +
42 "brand='" + brand + '\'' +
43 ", model='" + model + '\'' +
44 ", year=" + year +
45 ']';
46 }
47 }
48
49 CarWithoutRecord carWithoutRecord = new CarWithoutRecord("Lamborghini", \
50 "Aventador", 2022);
51 System.out.println(carWithoutRecord.getBrand());
52 System.out.println(carWithoutRecord);
Output:
Lamborghini
CarWithoutRecord[brand=’Lamborghini’, model=’Aventador’, year=2022]
As you can see, there is a lot of code in the code above. Now let’s see the same as above using record:
Output:
Ferrari
CarRecord[brand=Ferrari, model=Portofino, year=2022]
As you could see, we reduced 46 lines of code to 1 which is a huge difference. Another important
difference is that the getters from a record doesn’t have the prefix get, we just use the variable
name like the following example: brand().
14 Newest Features of Java 445
Output:
Exception in thread “main” java.lang.NullPointerException: null values are not accepted…
Code analysis:
• #A: Note that we don’t have the parenthesis () in the compact constructor. This constructor
will be executed after the main record constructor. It’s suitable to validate data from a record.
• #B: We make a simple validation checking if the field brand or model are null. If one of them
are null a NullPointerException is thrown.
• Implement interfaces
• Use generics
• Use compact constructors
• Overload constructors
• Use static methods
• Use static variables
• Use instance and static blocks
• Override the getters, toString, equals and hashCode methods
• Use static or instance inner classes within it
• Use annotations within it
• Be used as an inner class
14 Newest Features of Java 446
Therefore, the restrictions of a record will be pretty much the same as an abstract class. Let’s see a
list of the restrictions to reinforce them, a record can not:
1 import java.io.Serializable;
2
3 public class CommanderRecordChallenge {
4
5 record Commander<T>(T ship, String name, String planet) {
6 public Commander {
7 if (!(ship instanceof Serializable))
8 throw new RuntimeException();
9 }
10 public String name() {
11 return "Worf";
12 }
13 public boolean equals(Object obj) {
14 return (obj instanceof Commander commander) &&
15 (commander.name.equals(this.name));
16 }
17 }
18
19 public static void main(String[] args) {
20 Commander<String> commander = new Commander<>("V-ger", "Spock", "Vulcan");
21
22 record NewString(String ship) implements Serializable { }
23
24 System.out.println(commander.name());
25 System.out.println(commander.equals(new Commander<>(new NewString("V-ger") \
26 , "Spock", "Kronos")));
27 }
28 }
A. Worf
true
B. Spock
java.lang.RuntimeException will be thrown
C. Line 5 won’t compile
D. Worf
false
Explanation:
There are many important points to be observed in this code challenge. The first one is that we
are using the Commander record as an inner class. We are also using a generic type <T> for the ship
variable.
14 Newest Features of Java 448
Then we use the compact constructor and we validate if the ship variable is instance of
Serializable.
Then we create another inner record NewString and note we are implementing Serializable.
Then we print the value from commander.name() and remember we overrode this value with “Worf”,
therefore, “Worf” will be printed.
Finally, we use the equals method we overrode with the commander and a new Commander. Notice
that we are passing the NewString record we created and that’s totally fine since the first
parameter from Commander receives a generic type. NewString is also Serializable which means
the RuntimeException won’t be thrown.
There is a trick in the equals method though, notice that we are not using the name() method in
the comparison. Instead, we are using the name variable. Therefore, since the name passed is also
“Spock”, the result from the equals method will be true. Another detail is that we are using the
concept of pattern matching for the instanceof operation.
In conclusion, the correct alternative is:
A. Worf
true
Output:
Sealed class must have subclasses
A sealed class can’t be extended by a normal class. The following code won’t compile:
Output:
All sealed class subclasses must either be final, sealed or non-sealed
We can’t use annonymous inner classes in a sealed class or interface. Let’s see what happens when
we try to use it with the above sealed class Car:
14 Newest Features of Java 450
Output:
Anonymous classes must not extend sealed classes
Notice that a record is implicitly a final class. Therefore, there is no need to declare it explictly.
Since record is implicitly final it can’t be also sealed or extend another class:
1 System.out.println(Car.class.isSealed());
2 System.out.println(Arrays.toString(Car.class.getPermittedSubclasses()));
Output:
true
[Ferrari, §Porsche]
Output:
Ferrari
14 }
15 }
16
17 public static void main(String... doYourBest) {
18 Captain captain = new Spock("Vulcan");
19 captain.giveOrder();
20 System.out.println(captain.getClass().getSuperclass().isSealed());
21 new Saru().giveOrder();
22 new Captain() { }.giveOrder(); // #C
23 }
24 }
Explanation:
There is a compilation error on #A and that’s because the Janeway class is not sealed, non-sealed or
final. Therefore, it’s not possible to permit Janeway.
Invoke the giveOrder method from the record and that should print Let's go to:Vulcan if there
wasn’t a compilation error:
captain.giveOrder();
Then we use the getClass and then getSuperclass methods in the Captain instance. However, notice
that we are not getting the interfaces, instead we are getting the class. In the following case the
getSuperclass method actually returns a record. Therefore, the following code would return false
if there wasn’t a compilation error:
System.out.println(captain.getClass().getSuperclass().isSealed());
Then we invoke the giverOrder method on Saru that will print the default implementation from
Captain:
14 Newest Features of Java 453
new Saru().giveOrder();
Finally, we try to use an anonymous inner class on Captain which will cause a compilation error
since a sealed class or interface can’t do that:
new Captain() { }.giveOrder(); // Compilation error
1 String customerProductSQL =
2 "select product.name, customer.name, address.city " +
3 " from product " +
4 " inner join customer on customer.product_id = product.id " +
5 " inner join address on customer.address_id = address.id";
As you can see in the above code, there are " and + for each String line which makes the code more
difficult to read and verbose. Now with the text block feature this is different. We can use now """
to store a big String. Let’s see how we can use text blocks:
It’s also possible to use " or ' without the necessity of escaping it as a literal String:
Keep in mind we don’t need to escape a " if we are not using three double quotes ("""). That’s
because the compiler will understand that as the end of the text block. To workaround that, we can
escape the last \":
14 Newest Features of Java 454
Output: “”” Some text here… Test… Test…Some text here… “””
1 String someText = """ Big text here... Test... // Compilation error here
2 Big text here... Test... Test... Test...
3 """;
1 String someText = """ Some text here... Test..."""; // Compilation error here
Output:
Some text here… Test… Test…Some text here…
Lorem Ipsum has been the industry’s standard dummy text
ever since the 1500s, when an unknown printer took a galley
of type and scrambled it to make a type specimen book.
If we want to see spaces in a String text block output we need to put the String ahead of the last
""" as you can see in the following example:
Output:
Some text here… Test… Test…Some text here…
Lorem Ipsum has been the industry’s standard dummy text
ever since the 1500s, when an unknown printer took a galley
of type and scrambled it to make a type specimen book.
If we use the """ at the end of the text, then the text that is at the most left will be the reference
for the rest of the text. Those “incidental” spaces” that comes before the text at the most left will be
ignored:
Output:
apiVersion: v3
kind: Service
metadata:
labels:
14 Newest Features of Java 456
run: customer-registration-service-dev
To use explicitly indent the code we can use the indent method as the following:
Output:
apiVersion: v3
kind: Service
metadata:
labels:
run: customer-registration-service-dev
As you can see in the example above, we lose a lot of space. It’s much better to use the following
indentation instead:
14 Newest Features of Java 457
In the above code we save plenty of space and the text gets easier to read.
One other thing that makes code reading difficult is line wrapping or horizontal scrolling. Therefore,
when we have nested code and we need to store big texts, the following is not recommended:
It’s not possible to see the horizontal scroll bar here but on your development IDE that would be
seen. To make the above code indentation better, we can take the text to the further left:
1 void chuckNorrisGoodIndentation() {
2 final String chuckNorrisGoodIndentation = """
3 Carlos Ray "Chuck" Norris (born March 10, 1940) is an American martial artist and ac\
4 tor. He is a black belt in
5 Tang Soo Do, Brazilian jiu jitsu and judo.[1] After serving in the United States Air\
6 Force, Norris won many martial arts championships and later founded his own discipl\
7 ine Chun Kuk Do. Shortly after,
8 in Hollywood, Norris trained celebrities in martial arts. Norris went on to appear i\
9 n a minor role in the spy film
10 The Wrecking Crew (1969). Friend and fellow martial artist Bruce Lee invited him to \
11 play one of the main villains
12 in Way of the Dragon (1972).
13 """;
14 }
14 Newest Features of Java 458
Also remember, when indenting text blocks with a big text within a code expression such as stream
or lambda, the recommendation in this case is to store the text block in a variable and then use it in
the code expression. Otherwise, the code will get difficult to read.
A. false
true
B. true
true
C. true
false
D. false
false
Explanation:
14 Newest Features of Java 459
The first jediWithoutTextBlock String is going to receive a literal String. A literal String behaves
differently than a text block when we have many lines. In that case, each line we create is a new
String object.
Notice that we are using the strip method to remove trailling spaces and then we invoke the
formatted method. When we invoke formatted the whole String won’t be concatenated. That
happens because the Java compiler will prioritize invoking a method first and then concatenate
the String. Therefore, the formatted method will only consider the last String: "</html>", and the
substitution won’t happen. The stored String from jediWithoutTextBlock will be:
1 <html>
2 <head>Master Yoda</head> %s
3 </html>
Then we use text blocks in the jediWithTextBlock variable. In a text block, there is only one String,
therefore, the substitution with formatted happens successfully.
Notice another detail that the spaces on the left are being ignored. That’s because of the way we
used text blocks. To close the text blocks we are using """ on the right of the last text. Which means
that the text that is at the most left will be the margin for all the rest. In that case, the left spaces
will be ignored. This will be what we will have stored in the String:
1 <html>
2 <head>Master Yoda</head> Luke
3 </html>
When printing the following, false is printed since the texts are different:
System.out.println(jediWithoutTextBlock.equals(jediWithTextBlock));
In the following condition we use the formatted method again that will now substitute the text
successfully. That’s because the String is already fully concatenated. Therefore, the output here is
true.
A. false
true
14.17 Summary
In this chapter we explored the newest Java features that will help us to create better reliable easier
to maintain code. Notice that many of the features were created to reduce the boiler-plate code.
Java is a very explicit language, therefore, it might be considered verbose but the new features are
certainly helping reducing that.
In this chapter you learned how to use:
14 Newest Features of Java 460
Congratulations! You learned how to use the main new Java features and got prepared to choose the
right Java version for your project!
15 Concurrency Fundamentals
This chapter covers:
• Fundamenstals of concurrency
• Concurrency vs parallelism
• States of a Thread
• Creating Threads
• Using Runnable in a Thread
• Using the sleep method to temporarily stop a thread
• Using the join method to join thread executions
• non-daemon and daemon threads to have control on when the thread dies
• Using the interrupt method to stop a thread execution
• Understanding race condition to avoid unexpected results from threads
• Mutual exclusion to synchronize threads (Mutex)
• Object synchronization
• Using the wait and notify methods in a thread
• Total of 4 Java code Challenges
The fact of two or more events or circumstances happening or existing at the same time.
As mentioned before, concurrency in computer science is not the same as in the dictionary. To absorb
this concept, think of interruptibility. This means that a task will be interrupted so the other can
continue using the same CPU.
Non-concurrent mode:
To get a visa in average you will wait at least 2 hours in the queue. So you wait the two hours get
your visa. Only then you start doing your coding task from work but that takes around 2 hours and
you are not able to complete it on time. Finally, your annoying final task is to explain to your boss
why you couldn’t complete your task.
Concurrent mode (the smart way):
You can use the powerful concept of concurrency here! You plan ahead of time that you will be
waiting in the queue for 2 hours and then you take your laptop with you! When you get in the queue
to get your visa, you interrupt the task to get the visa and start working on your coding task. After
waiting for 2 hours in the queue, you have your coding task done and get your visa! Finally, you can
tell your boss how good you are to finish the task and get the visa!
15.1.2 Parallelism
Parallelism actually runs tasks at the same time which makes it faster than concurrency. This means
that if there are multiple tasks that need to be run, they will run simultaneously. Parallelism uses
more than one CPU to process its tasks. When talking about parallelism remember of tasks running
independently.
Since you are going to the party of your best friend, you decide that you want to be present there
and not work on your presentation. However, you also need to complete your presentation you will
give a talk. Then you have the awesome idea of hiring a highly skilled software engineer to do the
presentation for you. Your friend accepts the idea and the beauty of parallelism happens.
While you are enjoying your time on your best friend’s party, your presentation will be done by the
highly-skilled software engineer at the same time!
To make an analogy in this situation, imagine you are one CPU and the highly-skilled sofware
engineer is another CPU who are working on two tasks simultaneously.
Thread states
that a thread might suddenly stop its execution for other threads. The CPU plays a role on that too,
the thread will need resources to run.
Blocked: When a thread is waiting for a monitor lock. This means, when another thread got a lock
and other threads want to access this lock but it’s blocked since the other thread has to finish its
execution first.
Simply put, a thread will be blocked when it’s waiting for indeterminate time, most times until other
threads finish their executions.
Timed Waiting: It’s a more predictable way to wait. Therefore, it’s when we specify how much time
the thread will wait to be executed. As per Java doc, those are the invoked methods:
Thread.sleep(time)
Object.wait(timeout)
Thread.join(timeout)
LockSupport.parkNanos(time)
LockSupport.parkUntil(time)
Terminated state: When a thread finishes its execution normally. There is also the case where there
is an error and the thread gets terminated.
15.2 Thread
The most fundamental class to work with concurrency in Java is the Thread class. A Thread in Java
is the smallest process that can be run in parallel. When creating even the simplest program in Java
we will be dealing with Threads, and that’s because the main method has a main Thread.
We can create many Threads and run them in parallel. Depending on the situation, we can have a
huge gain in performance. Since when working in a multi-thread environment code will be executed
at the same time or concurrently, maintaining it is more challenging.
There is a special method Thread.currentThread() where we can get the current thread and then
see its attributes:
1 package com.javachallengers.concurrency;
2
3 public class MainThread {
4
5 public static void main(String[] args) {
6 System.out.println(Thread.currentThread().getName());
7 }
8 }
Output:
main
15 Concurrency Fundamentals 465
1 package com.javachallengers.concurrency;
2
3 public class ThreadStart {
4
5 public static void main(String[] args) {
6 Thread thread = new Thread();
7 thread.start();
8 }
9 }
In the code above we started a new Thread but we didn’t override the run method. Therefore, there
was no output.
Now, let’s override the run method to see some action happening:
1 package com.javachallengers.concurrency;
2
3 public class ThreadStartImplemented {
4
5 public static void main(String[] args) {
6 System.out.println(Thread.currentThread().getName());
7 Thread thread = new ThreadRunner();
8 thread.start();
9 }
10 }
11
12 class ThreadRunner extends Thread {
13 @Override
14 public void run() {
15 System.out.println(Thread.currentThread().getName() + " is running...");
16 }
17 }
Output:
main
Thread-0 is running…
Notice that a thread will be only started by invoking the start method.
15 Concurrency Fundamentals 466
Output:
Run Method 1: main
Run Method 1: main
Run Method 2: main
Notice in the code above that even though we are using a thread object we are not actually starting
a new thread. We are only invoking the run method. That’s why when we print the thread name,
we see only main.
Explanation:
The key to get this Java code challenge right is to understand the difference between running an
actual thread or just a method.
We will only start a thread by running the start method. The run method is just the action a thread
will perform when started.
First, notice that we have the Heisenberg class that extends Thread and also overrides the run method.
Therefore, this class is ready to be started as a Thread.
15 Concurrency Fundamentals 468
In the main method, we effectively start a thread by invoking the start method with the heisenberg
variable. Therefore, we get the output from the run method here. However, notice that this is a new
thread getting started so there is no guarantee of order of execution.
Then, we invoke the run method that does not really start a new thread. The run method is just a
method. As a result, we have the output from the run method twice.
Now, when we try to invoke the start method with the same thread instance we get an
java.lang.IllegalThreadStateException. That happens because we can never start a thread twice
with the same thread instance.
In conclusion, the correct alternative is:
Output:
Running a Runnable!
Code analisis:
• #A: Using an anonymous inner class. An anonymous inner class is an unnamed class that
implements the Runnable interface and overrides the run method.
• #B: Overriding the run method to use it in the Thread.
• #C: We pass the runnable we created to the Thread.
1 package java.lang;
2
3 @FunctionalInterface
4 public interface Runnable {
5
6 public abstract void run();
7
8 }
Therefore the Runnable interface can be used with lambdas! This means we can save lots of code
and have the same output as you can see in the following code:
Output:
Running a Runnable!
A. Shoots G4
Shoots Super Tyrant
java.lang.IllegalThreadStateException is thrown
B. Shoots Super Tyrant
Shoots G4
java.lang.IllegalThreadStateException is thrown
C. Shoots Super Tyrant
java.lang.IllegalThreadStateException is thrown
D. leonThread and claireThread will be executed in a random order
Then java.lang.IllegalThreadStateException is thrown
Explanation
One important rule when working with Threads is that they are unpredictable. Therefore, there
is no way to know which thread is executed first between leonThread and claireThread. The
implementation of the JVM will decide that, but by rule remember that thread execution is always
unpredictable unless it’s manipulated with special methods that we will see in future sections.
This means that leonThread and claireThread will be executed in a random order.
Then notice that we pass the instance reference from claireThread to chrisThread. However,
remember that claireThread already gave the command to start a new thread. Therefore,
when we try to start another thread with the same instance with the chrisThread variable the
java.lang.IllegalThreadStateException will be thrown.
Output:
main thread slept for: 5000
Code Explanation:
As you can see in the code above, we get the currentTimeMillis and store it into the beforeSleep
variable.
Then we invoke the Thread.sleep method passing 5000 as milliseconds. Notice that the sleep
method throws the InterruptedException in case this thread execution is interrupted by other
threads (this will be further explored in further sections).
Now we subtract the currentTimeMillis with the beforeSleep variable
1 import java.util.stream.IntStream;
2
3 public class NoJoinExample {
4
5 public static void main(String[] args) {
6 Thread thread1 = new Thread(() -> IntStream.rangeClosed(1, 5)
7 .forEach(System.out::print));
8 Thread thread2 = new Thread(() -> IntStream.rangeClosed(6, 10)
9 .forEach(System.out::print));
10
11 thread1.start();
15 Concurrency Fundamentals 472
12 thread2.start();
13 }
14 }
Random Output:
67891012345
As you can see in the output above, the order of execution is randomic. There is no way to predict
how the output will be. The numbers from 1 to 10 can be printed in any order.
It’s possible to use the join method though to guarantee the order of execution of both threads. Let’s
see the same example as the code above using the ‘joing method:
1 import java.util.stream.IntStream;
2
3 public class JoinExample {
4
5 public static void main(String[] args) {
6 Thread thread1 = new Thread(() -> IntStream.rangeClosed(1, 5)
7 .forEach(System.out::print));
8 Thread thread2 = new Thread(() -> IntStream.rangeClosed(6, 10)
9 .forEach(System.out::print));
10
11 thread1.start();
12 thread1.join();
13 thread2.start();
14 thread2.join();
15 }
16 }
Output:
12345678910
Notice that the output above will be always the same since we use the join method right after we
start each thread.
Simply put, the join method will join the thread execution with the current running thread and will
wait until the end of the execution.
1 import java.util.stream.IntStream;
2
3 public class NonDaemonWithNonDaemon {
4
5 public static void main(String[] args) {
6 System.out.println(Thread.currentThread().getName() + " is running..." +
7 " isDaemon:" + Thread.currentThread().isDaemon()); // #A
8
9 Thread nonDaemonThread = new Thread(() -> IntStream.rangeClosed(1, 10)
10 .forEach(i -> System.out.print(Thread.currentThread().getName() + \
11 " count is: " + i + " | "))); // #B
12
13 System.out.println("Is daemon: " + nonDaemonThread.isDaemon()); // #C
14 nonDaemonThread.start(); // #D
15 }
16 }
Output:
main is running… isDaemon:false
Thread-0 count is: 1 | Thread-0 count is: 2 | Thread-0 count is: 3 | Thread-0 count is: 4 | Thread-0
count is: 5 | Thread-0 count is: 6 | Thread-0 count is: 7 | Thread-0 count is: 8 | Thread-0 count is: 9 |
Thread-0 count is: 10 |
Code analisis:
• #A: As mentioned before, notice that the main method is also a non-daemon thread. Therefore,
it’s guaranteed that whatever is programmed within it will be executed until the end.
• #B: We are creating a thread with a simple logic in the run method. We are iterating from 1 to
10, printing the current thread name and the current count from the iteration.
• #C: Notice that a new thread we create is by default non-daemon. Therefore, the isDaemon
method will return false.
• #D: Finally, we start the non-daemon thread we created and notice that this thread will be
executed until the end. Therefore, we will always get the count from 1 to 10 printed.
15 Concurrency Fundamentals 474
1 import java.util.stream.IntStream;
2
3 public class NonDaemonWithDaemon {
4
5 public static void main(String[] args) throws InterruptedException {
6 Thread daemonThread = new Thread(() -> IntStream.rangeClosed(1, 100) // #A
7 .forEach(i -> System.out.println(Thread.currentThread().getName() \
8 + " count is: " + i)));
9 daemonThread.setDaemon(true); // #B
10
11 daemonThread.start(); // #C
12 Thread.sleep(1); // #D
13 }
14 }
• #A: Then we create a Thread here with a simple logic in the run method. We basically iterate
from 1 to 100 and print the currentThread name with the current count number.
• #B: A thread is created by default as a non-daemon thread. If we want to make it daemon, we
have to do that explicitly by invoking the setDaemon method passing true to it.
• #C: We are starting our daemon thread here.
• #D: To have better chances that there will be some output, there is the sleep method with 1
millisecond so that at least the count until three is done. The important thing to realize here is
that the daemon thread will live whenever there is another non-daemon thread still running. If
there isn’t any the daemon thread will die.
Explanation:
The key to solve this challenge is to master the use of a daemon thread. Notice that we are using the
join method on each daemon thread. Therefore, each daemon thread will join its execution with the
main thread. That also means that both daemon‘ threads will execute until the end.
Then we start another non-daemon thread with the following command for (;;) that actually means
infinite looping. Therefore, since a non-daemon thread needs to be executed until the end, the for
looping will be executed forever.
In conclusion, the correct alternative is:
15 Concurrency Fundamentals 476
C. 2
Then the for looping will run forever
Output:
Interrupting thread…
As you can see in the code above, the Thread.interrupted will check the interrupted flag from
the current running thread. Since we invoked the interrupt method, this will change the current
running thread interrupted flag to true. Therefore, the if condition will be fulfilled and this thread
will be interrupted.
Notice that when we use the sleep method, we have to handle the InterruptedException. That
happens because if we use the interrupt method while a thread is sleeping, this Exception will be
thrown.
Let’s see how that works in code:
15 Concurrency Fundamentals 477
Output:
The Interrupted Exception was caught…
The wait method also throws the InterruptedException if the interrupt method is invoked
to the thread. In the following code example, we will invoke the wait method for the
interruptWaitExample instance and then we will interrupt this thread:
Output:
The Interrupted Exception was caught…
As you can see in the code above, by interrupting the thread in the waiting state it will also throws
the InterruptedException. That’s why the InterruptedException is a checked Exception.
15 Concurrency Fundamentals 478
1 import java.util.stream.IntStream;
2
3 public class RaceConditionMultipleThreads {
4
5 private int counter = 0;
6
7 public void incrementCounter() {
8 try {
9 Thread.sleep(10); // #E
10 counter++; // same as: counter = counter + 1;
11 } catch (InterruptedException e) {
12 e.printStackTrace();
13 }
14 }
15
16 public int getCounter() {
17 return counter;
18 }
19
20 public static void main(String... args) {
21 RaceConditionMultipleThreads raceCondition = new \
22 RaceConditionMultipleThreads(); // #A
23 IntStream.rangeClosed(1, 5).forEach(i -> { // #B
24 Thread thread = new Thread(() -> { // #C
25 raceCondition.incrementCounter(); // #D
26 System.out.println(Thread.currentThread().getName() + ":" + \
27 raceCondition.getCounter()); // #F
28 });
29
30 thread.start(); // #G
31 });
32 }
33
34 }
15 Concurrency Fundamentals 479
Output (Random):
Thread-0:1
Thread-3:4
Thread-4:4
Thread-1:2
Thread-2:2
Code Analisis:
An important point to pay attention is that a race condition will only happen if there is data being
shared.
• #A: Create only one instance from RaceConditionExample, therefore, the instance variable
counter is being shared.
• #B: Create a looping from 1 to 5 with the IntStream.
• #C & #D: Create 5 Threads invoking the incrementCounter method.
• #E: Invoke Thread.sleep with 10 milliseconds to cause a race condition more easily.
• #F: Print the name of the current thread and then the counter.
• #G: Start 5 threads. However, those threads will be invoked at the same time, therefore,
there will be conflict with the data processing. Remember that a thread execution time is
unpredictable, so the output will be random and also unpredictable.
1 import java.util.stream.IntStream;
2
3 public class RaceConditionForTwoThreads {
4
5 private int counter = 0;
6
7 public void incrementCounter() {
8 try {
9 Thread.sleep(10);
10 counter++;
11 } catch (InterruptedException e) {
12 e.printStackTrace();
13 }
14 }
15
16 public int getCounter() {
15 Concurrency Fundamentals 480
17 return counter;
18 }
19
20 public static void main(String... args) {
21 RaceConditionForTwoThreads raceCondition =
22 new RaceConditionForTwoThreads();
23 runThread(raceCondition);
24 runThread(raceCondition);
25 }
26
27 private static void runThread(RaceConditionForTwoThreads raceCondition) {
28 Thread thread = new Thread(() -> IntStream.rangeClosed(1, 2)
29 .forEach(i -> {
30 raceCondition.incrementCounter();
31 System.out.println(Thread.currentThread().getName() + ":"
32 + raceCondition.getCounter());
33 }));
34
35 thread.start();
36 }
37
38 }
Output (Random):
Thread-1:1
Thread-0:1
Thread-1:2
Thread-0:2
Code Analisis:
As you can see the code above is similar to the previous race condition example. The difference is
that there are only two threads and the looping is inside the Thread run method.
Notice in the output that we have only two threads, Thread-0 and Thread-1. Note also that the race
condition is very likely to happen, since we are not using any mechanism to synchronize values they
will repeat very often.
Thread synchronization is the technique used to make sure a critical section (code that might
cause data conflict between threads) of the code will always run in isolation from the other threads.
A simple analogy to a synchronized block with Threads is to imagine that the critical code
section is a bathroom and the lock is the bathroom’s door lock. Imagine the thread are the people
that will go to the bathroom. When a person goes to the bathroom and locks the door, other people
won’t be able to enter the bathroom. They will have to wait until the lock is opened. Once the door
is opened then the other person will be able to enter.
Let’s see how that works if we fix the code above to avoid data conflict and race condition:
1 import java.util.stream.IntStream;
2
3 public class RaceConditionMultipleThreadsSync {
4
5 private int counter = 0;
6
7 public synchronized int incrementAndGet() { // #A
8 try {
9 Thread.sleep(10);
10 counter++;
11 } catch (InterruptedException e) {
12 e.printStackTrace();
13 }
14 return counter;
15 }
16
17 public static void main(String... args) {
18 RaceConditionMultipleThreadsSync raceCondition = \
19 new RaceConditionMultipleThreadsSync();
20 IntStream.rangeClosed(1, 5).forEach(i -> {
21 Thread thread = new Thread(() -> {
22 System.out.println(Thread.currentThread().getName() + ":" +
23 raceCondition.incrementAndGet()); // #B
24 });
25
26 thread.start();
27 });
28 }
29
30 }
Thread-0:1
Thread-4:2
Thread-3:3
Thread-2:4
Thread-1:5
Code Analisis:
• #A: Notice that we are using the synchronized keyword which means that this method will be
executed in isolation with each Thread.
• #B: To be able to get the same value for each thread, we have to make the increment operation
atomic. An atomic operation means that the counter variable will be incremented and returned
at the same code block. It’s the concept of all or nothing.
Notice in the output of the code above that we don’t have any repated value and that’s because now
we are synchronizing the values.
1 import java.util.stream.IntStream;
2
3 public class RaceConditionForTwoThreadsSync {
4
5 private int counter = 0;
6
7 public synchronized int incrementAndGetCounter() {
8 try {
9 Thread.sleep(10);
10 counter++;
11 } catch (InterruptedException e) {
12 e.printStackTrace();
13 }
14 return counter;
15 }
16
17 public static void main(String... args) {
18 RaceConditionForTwoThreadsSync raceConditionSync =
19 new RaceConditionForTwoThreadsSync();
20 runThread(raceConditionSync);
21 runThread(raceConditionSync);
22 }
23
24 private static void runThread(RaceConditionForTwoThreadsSync \
25 raceConditionSync) {
26 Thread thread = new Thread(() -> IntStream.rangeClosed(1, 2)
27 .forEach(i -> {
28 System.out.println(Thread.currentThread().getName() + ":"
29 + raceConditionSync.incrementAndGetCounter());
30 }));
31
32 thread.start();
33 }
34
35 }
Code analisis:
• #A: Notice that we are synchronizing the instance of the SynchronizeBlock class which is
bounded to the thread. As seen before this means the current instance being executed. As
mentioned before, the synchronize block is useful to synchronize part of a method code.
15 Concurrency Fundamentals 485
Code analisis:
As per the Java docs, use the above technique very cautiously since this might cause a race condition
if not used properly.
To summarize, only use the technique above when methods don’t share the same data and there is
no need to synchronize those methods at the same time. This will allow one thread to execute the
increment1 method and the other to execute the increment2 method at the same time. Therefore,
performance will be enhanced.
15 Concurrency Fundamentals 486
Output:
Exception in thread “main” java.lang.IllegalMonitorStateException: current thread is not owner…
To fix that, we can add the synchronized keyword in the tryToUseWait method as the following:
15 Concurrency Fundamentals 487
Output:
main thread will be in the non-runnable state…
The program will run forever
In the code above, the wait() method from the NoMonitorWaitExample instance will put the main
thread in the non-runnable state. In other words, the main thread will stop its execution but will
remain alive.
Threfore, the program above will be running forever and the following code after the wait method
will never be reached.
Output:
main thread will be in the non-runnable state…
This text will be printed after 1 second…
As you can see in the above output, even though the main thread wasn’t notified by any other thread
it awakes after the timeout of 1 second.
15.9.3 notify
As we saw in the code example above, when a thread invokes the wait method it keeps on waiting
forever. To solve this issue, we can use the notify method from another thread and then change the
non-runnable thread to runnable.
Notice that the notify method is another one from the Object class.
Just like the wait method, the notify also has to have the lock. In other words, it has to be
synchronized. If that doesn’t happen an IllegalMonitorStateException will be thrown as you can
see in the following code:
Output:
Exception in thread “main” java.lang.IllegalMonitorStateException: current thread is not owner
Just like the wait method, if we add the synchronized keyword in the tryToUseNotify method, then
it will run ok:
15 Concurrency Fundamentals 489
Output:
Runs fine when there is lock…
The notify method will only notify other thread to change the state from non-runnable to runnable.
Therefore, the program above runs normally until the end. If there isn’t a thread to notify, that’s
also totally fine.
Output:
Thread-0 is waiting…
Thread-1 will notify other thread…
Thread-0 is notified!
Code Analisis:
Notice now that the program is being finalized normally. In the waitForNotification we will invoke
the wait method and in the notifyOtherThread method we will notify the other thread, therefore,
it will always change the state from non-runnable to runnable.
1 class MessageSender {
2 private boolean messageSent = false;
3 private String message;
4
5 public synchronized void receiveMessage() {
6 while(!messageSent){
7 try {
8 wait();
9 } catch (InterruptedException e) { e.printStackTrace(); }
10 }
11 System.out.println(message);
12 }
13
14 public synchronized void sendMessage() {
15 message = "Hello!";
16 this.messageSent = true;
15 Concurrency Fundamentals 491
17 notify();
18 }
19
20 public static void main(String[] args) {
21 MessageSender messageSender = new MessageSender();
22 new Thread(messageSender::receiveMessage).start();
23 new Thread(messageSender::sendMessage).start();
24 }
25 }
Output:
Hello!
Notice that the receiveMessage has a while looping checking if the message was not sent, it must
wait. This check will ensure that once the thread that runs the receiveMessage will wait for the
message even it it awakes suddenly.
15.9.6 notifyAll
The notifyAll method will awake all threads that are in the non-runnable state. It’s also another
method from the Object class.
In the following code example, we will see three threads invoking the wait method and changing
state from runnable to non-runnable. Then we will use the notifyAll method to awake all of those
threads:
18 try {
19 this.wait();
20 } catch (InterruptedException e) { e.printStackTrace(); }
21 System.out.println(Thread.currentThread().getName() + " notified");
22 }
23
24 private synchronized void notifyAllThreads() {
25 this.notifyAll();
26 }
27 }
Output:
Thread-0 starts
Thread-2 starts
Thread-1 starts
Thread-0 notified
Thread-1 notified
Thread-2 notified
Code analisis:
• #A: We use a method reference here because the method waitNotification matches the
contract from Runnable. It’s also a void method. Also, we wait using the notifyAllExample
instance. We do the same for three threads.
• #B: We start the three Threads we created.
• #C: Wait 1 second just to make sure all three threads already started and waiting for the
notifyAll method.
• #D: We invoke the notifyAllThreads using the instance of notifyAllExample to notify the
three threads.
Notice that it’s not that simple to use the wait, notify, and notifyAll methods. In real applications
it’s even more complicated since we will be dealing with more complex requirements.
However, it’s crucial for every Java developer to know how to use it. Once you know how to use it,
it’s recommended to use higher-level classes from the java.util.concurrent package.
To reinforce this point, Josh Bloch also emphasasis in his excellent book which I recommend for
every Java developer. It’s the Effective Java 2nd Edition book in the Item 69: Prefer concurrency
utilities to wait and notify.
Explanation:
15 Concurrency Fundamentals 494
The trick of this code challenge is that even though we are synchronizing the main method, we
are not actually using the wait and notify methods for the main method. Instead, we are trying to
invoke the wait method from the current thread which actually does not hold the lock.
That means, we are not synchronizing the current thread object at all!
Therefore, when trying to invoke the wait method from the currentThread we will get an
IllegalMonitorStateException. The same will happen when invoking the notify method from
the thread object since there is no lock for it.
As a rule of thumb, always synchronize the object you want to use the wait, notify, and notifyAll
methods. Also remember that every Object can invoke those methods.
15.10 Summary
This was an extensive chapter but a very important one. Knowing how to write multi-thread code
is a fundamental skill for every software engineer.
In this chapter you learned how to use:
When dealing with concurrency and parallelism in Java, we must give preference to the already
existing powerful APIs to not reinvent the wheel.
This is key to creating robust and reliable high-quality software. By knowing the available tools in
Java, there will be no need to create complex concurrent code.
Therefore, let’s then explore the most important and powerful classes from the amazing concurrent
API Java provides to us.
Notice in the code above that we used the lock method within a try block and a the unlock method in
a finally block. This is because in case any Exception happens the unlock method must be invoked.
Another great advantage from the ReentrantLock class is that it can be passed via parameter:
16 Advanced Concurrency 496
Simply put, when a thread is reading a shared resource, the others can’t write. Also, many threads
can acquire the read lock at the same time.
When a thread acquires the write lock, other threads can’t acquire both read and write locks.
Let’s see how that works in code by using ReadWriteLock:
Output:
Start writing: 0
Finish writing: 1
pool-1-thread-2 Start reading: 1
pool-1-thread-1 Start reading: 1
pool-1-thread-4 Start reading: 1
16 Advanced Concurrency 498
Those variable types will avoid race condition because they are obviously atomic. This means that
the write and read operations are done together, it’s all or nothing. The variable behind the scenes
of an atomic variable is also volatile which we will explore it further in next sections.
16 Advanced Concurrency 499
1 import java.util.concurrent.atomic.AtomicInteger;
2
3 class AtomicCounter {
4 private AtomicInteger atomicCounter = new AtomicInteger(0);
5
6 public void increment() {
7 atomicCounter.incrementAndGet();
8 }
9
10 public void decrement() {
11 atomicCounter.decrementAndGet();
12 }
13
14 public int getValue() {
15 return atomicCounter.get();
16 }
17
18 }
The above approach will make the code thread-safe. Also, this approach is recommended when the
code we have to make thread-safe is complex enough. By using an atomic variables we don’t need
to synchronize a method or a block.
For further details, check the Java docs:
(https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html)[https://docs.oracle
summary.html]
16.2.1 AtomicReference
To atomically change a value from any Object we want, we can use the AtomicReference class. This
class is useful in a multi-thread environment since it will avoid data collision between threads.
1 import java.util.concurrent.atomic.AtomicReference;
2
3 public class AtomicReferenceEx {
4 public static void main(String[] args) {
5 AtomicReference<Object> atomicReference = new AtomicReference<>();
6 String initialValue = "NoChallenger";
7 atomicReference.set(initialValue);
8
9 System.out.println(atomicReference.get());
10 Object newValue = "JavaChallenger";
11 boolean valueUpdated = atomicReference
16 Advanced Concurrency 500
12 .compareAndSet(initialValue, newValue);
13
14 System.out.println(atomicReference + ", " + valueUpdated);
15 }
16 }
Output:
NoChallenger
JavaChallenger, true
Therefore, if you need to change a value of an Object you created and want it to be thread-safe,
the AtomicReference class is a great choice.
JVM: the Java Virtual Machine at a high-level is responsible to transform bytecode into machine
code and it’s also part of the Java Runtime Environment(JRE).
Jit Compiler: Just-in-time compiler that performs optmizations when compiling the byte code to
native machine code during runtime. It’s possible to pass special instructions to enable an important
concept called happens-before which will manage variable values where its write and subsequent
read are always consistent. We will see more details in the next sections.
Notice that in the Java memory model there are the L2 and L3 caches. To make the program
execution more performant, the JVM implements optimization strategies. One of those are the order
of execution which means that the code will be executed in an optmized way and this sometimes
will cause data inconsistency in a multi-thread environment.
The other strategy is that when the JVM finds suitable it will cache values which in most cases this
behaviour will be benefitial. However, the variable value caching might cause data inconsistency
between threads.
The volatile keyword will will solve this caching problem. With volatile we will ensure that the
latest value will be available to be read after modification between threads. The volatile keyword
16 Advanced Concurrency 502
will ensure that the data will be always available in the main memory.
Another great way to explain what the volatile keyword does is the following:
“…the volatile modifier guarantees that any thread that reads a field will see the most
recently written value.” - Josh Bloch
Output:
First thread - Value of i: 1
First thread - Value of i: 2
First thread - Value of i: 3
First thread - Value of i: 4
First thread - Value of i: 5
Changed ready flag: true
The program never ends…
Code analisis:
16 Advanced Concurrency 503
Notice that we have an issue in the code above, the program will never end! This happens because
the JVM will use its optmization strategies. Therefore, since the variable ready is being used more
in the second thread
Notice that the JVM will use its optmization strategies in the code above and will put the variable
value into a cache. This means that even though the first thread changes the value from ready true,
the second thread will read cached value false.
Output:
First thread - Value of i: 1
First thread - Value of i: 2
First thread - Value of i: 3
First thread - Value of i: 4
First thread - Value of i: 5
Second thread - Value of i: 10375341
Changed ready flag: true
Notice that the code above is finished normally. That’s because when we use the volatile keyword
the variable value will be always in the main memory. Therefore, the program above is guaranteed
to be finished since the ready variable value will always be updated and visible between threads.
1 import java.util.stream.IntStream;
2
3 public class VolatileCounterChallenge {
4 private static volatile int counter = 0;
5
6 public static void main(String[] args) {
7 incrementCounterWithNewThread();
8 incrementCounterWithNewThread();
9 System.out.println("Last: " + counter);
10 }
11
12 private static void incrementCounterWithNewThread() {
13 Runnable runnableTask = () -> IntStream.range(0, 2).forEach((a) -> {
14 incrementCounter();
15 });
16 new Thread(runnableTask).start();
17 }
18
19 static void incrementCounter() {
20 try {
21 Thread.sleep(10);
22 System.out.print(++counter);
23 } catch (InterruptedException e) {}
24 }
25 }
Explanation:
The important point to realize regarding the volatile operator is that it doesn’t synchronize threads.
The main purpose of the volatile operator is to use the latest updated value from a variable, handle
happens-before principles.
Therefore, if the variable is not atomic, chances are that the value will be repeated because the
threads will increment the shared variable at the same time in the code above.
16 Advanced Concurrency 505
Keep in mind that the volatile keyword won’t also synchronize your code. It will keep the latest
updated value and will help you with the happens-before rule.
With that in mind, the correct alternative is:
The variable assigning from number1 and number2 might be reordered since there is no impact in
the code. However, the volatile variable assigning from number3 is guaranteed to not be reordered
with number1 and number2. It will always be assigned after number1 and number2 because number1 is
a volatile variable.
The compiler could execute the above code as follows:
16 Advanced Concurrency 506
1 int number2 = 8; // Might be reordered but will always happens before volatile
2 int number1 = 7; // Might be reordered but will always happens before volatile
3 volatile int number3 = 9;
There are more situations for the happens-before rule. However, in the day-to-day work it’s not
really necessary to know all of the rules.
The important thing is for you to know that there is such a concept and if you really need to
implement asynchronous code at that level then you know that this concept is there and you can go
into more details in the Java docs:
https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html⁴
16.5 CompletableFuture
The CompletableFuture api is an upgrade from the Future api introduce in Java 5. The Completable-
Future interface is capable of executing code in parallel, handle exceptions, and combine values in
order.
16.5.1 supplyAsync
The supplyAsync method uses the functional interface Supplier which basically returns a value.
Therefore, let’s return a String value from the supplyAsync method:
1 CompletableFuture<String> completableFuture
2 = CompletableFuture.supplyAsync(() -> "Challenger supply!");
3
4 System.out.println(completableFuture.get());
Output:
Challenger supply!
Notice in the code above that the completableFuture.get() will wait if necessary until the
computation from the supplyAsync is done.
1 CompletableFuture<String> completableFuture =
2 CompletableFuture.supplyAsync(() -> "Java");
3
4 CompletableFuture<String> combinedFuture =
5 completableFuture.thenApply(previousResult ->
6 previousResult + " Challenger");
7
8 System.out.println(combinedFuture.get());
Output:
Java Challenger
Notice in the code above that firstly declare a CompletableFuture, then we implement the
supplyAsync method and return “Java”.
Notice that the thenApply receives the Function funtional interface which receives and returns a
value. Then, we use the thenApply method that receives the result from the first completableFuture
and then we combine the result with the combinedFuture.
16.5.3 thenCompose
The thenCompose method combines CompletableFutures. This is useful if we want to have more
robustness in the pipeline from the CompletableFuture.
The thenCompose method signature is the following:
As you can see in the code above, this method receives a Function that gets the parameter from
the CompletableFuture and returns a CompletableStage that will be basically a CompletableFuture
since it extends CompletableStage as you can see in the following code:
Therefore, we are passing a method reference that receives as a parameter a String and returns a
CompletableFuture.
16 Advanced Concurrency 508
1 CompletableFuture<String> completableFuture
2 = CompletableFuture.supplyAsync(() -> "Java");
3
4 CompletableFuture<String> combinedFuture =
5 completableFuture.thenCompose(SupplyAsyncCompose::composeFutures);
6
7 System.out.println(combinedFuture.get());
8
9 private static CompletableFuture<String> composeFutures(String supplyString) {
10 System.out.println(supplyString + " Composing...");
11
12 return CompletableFuture.supplyAsync(() -> "Challenger");
13 }
We could also pass the method implementation directly to the thenCompose method also but the code
would get confusing as you can see:
16.5.4 thenRun
To only run some code after another CompletableFuture, we can use the thenRun method.
As you can see, the thenRun method receives a Runnable:
Therefore, we can use a lambda expression that runs code without returning a value and receiving
a parameter:
16 Advanced Concurrency 509
1 CompletableFuture<Integer> completableFuture
2 = CompletableFuture.supplyAsync(() -> i++);
3
4 CompletableFuture<Void> combinedFuture =
5 completableFuture.thenRun(() -> System.out.println("Challenger"));
6
7 System.out.println(combinedFuture.get());
8 System.out.println(i);
Output:
Challenger
null
1
Notice that even though the get method returns null and the thenRun method does nothing with the
result from the first CompletableFuture, still it gets executed. This is very clear since the value from
the static variable i is being incremented when we execute the following combinedFuture.get().
The thenRun method is useful to run some code whenever the result from the previous
CompletableFuture is not relevant.
16.5.5 thenAccept
The thenAccept method receives a Consumer in its method signature as you can see in the following
code:
As seen before in the lambda chapter, a Consumer only consumes a value but doesn’t return anything.
Therefore, it will receive the result from the previous CompletableFuture and will process it but
won’t be a return value:
1 CompletableFuture<String> completableFuture
2 = CompletableFuture.supplyAsync(() -> "Java");
3
4 CompletableFuture<Void> combinedFuture =
5 completableFuture.thenAccept(previousResult
6 -> System.out.println(previousResult + " Challenger"));
7
8 System.out.println(combinedFuture.get());
16 Advanced Concurrency 510
Output:
Java Challenger
null
Notice also that it returns a CompletableFuture<Void>, this means that the generic type value can’t
be returned. That’s why when we print the information from combinedFuture, we get the output of
null.
16.5.6 thenApply
The thenApply method is simpler than the thenCompose method. Instead of having to return a
CompletableFuture we simply return the generic type from the CompletableFuture.
1 CompletableFuture<String> completableFuture
2 = CompletableFuture.supplyAsync(() -> "Java");
3
4 CompletableFuture<String> combinedFuture = completableFuture.thenApply \
5 (previousResult -> previousResult + " Challenger");
6
7 System.out.println(combinedFuture.get());
Output:
Java Challenger
As you can see in the above code we can simply return the generic type declared in the second
CompletableFuture.
1 import java.util.List;
2 import java.util.concurrent.CompletableFuture;
3 import java.util.concurrent.ExecutorService;
4 import java.util.concurrent.Executors;
5 import java.util.function.Supplier;
6 import java.util.stream.Collectors;
7
8 public class SanFranCompletableFutureChallenge {
9 static ExecutorService executor = Executors.newCachedThreadPool();
10
11 public static void main(String... sanFranciscoAdventure) {
12 CompletableFuture<List<String>> adventureStart =
13 new CompletableFuture<>();
14
15 Supplier<List<String>> sanFranSightSupplier = () ->
16 List.of("Alcatraz", "Cable Car", "Golden Gate", "Lombard Street");
17
18 adventureStart.completeAsync(sanFranSightSupplier, executor)
19 .thenCompose(sights -> CompletableFuture.supplyAsync(() -> \
20 sights.stream()
21 .map(String::length)
22 .collect(Collectors.toList())))
23 .thenAccept(ratings -> {
24 var rating = ratings.stream()
25 .dropWhile(sightRating -> sightRating <= 12)
26 .findFirst()
27 .orElse(0);
28 System.out.print("Rating: " + rating + " ");
29 });
30 System.out.print("time to go home :( ");
31 }
32 }
Explanation:
Notice that when we the completeAsync method is invoked, we are also passing a thread pool. Then
we pass a newCachedThreadPool() to it.
Now we start composing actions with the thenCompose method. Then we pass a lambda with
CompletableFuture.supplyAsync. Then we get a stream of data from sights, transform it to the
lenght of each String and collect it as a list.
Then we use the thenAccept method that receives a Consumer and returns nothing. We use the
dropWhile method that will drop elements until the condition is true. Considering that only "Lombard
Street" has the lenght of greater than 12, that’s the element that will remain in the list. Then, the
first element will be found.
Another very important detail is that we are not doing a shutdown in the threadpool we passed to
the CompletableFuture. Since we passed it manually, we have to also shutdown it manually if we
want the program to be finalized normally.
Therefore the output will be the following:
Output:
There was an error: java.lang.ArithmeticException: / by zero
Notice in the code above that we are causing the ArithmeticException to be thrown. Then in the
following CompletableFuture errorHandling, we are logging the error message. This is very useful
to debug what happened in a CompletableFuture process.
16 Advanced Concurrency 513
The last point to observe is that by using the exceptionally method, we can encapsulate what we
want for the Exception handling.
If no Exception happens, the ‘exceptionally method won’t be even invoked. Let’s see a code example:
1 CompletableFuture<String> future
2 = CompletableFuture.supplyAsync(() -> "Not causing Exception!");
3
4 CompletableFuture<String> errorHandling =
5 future.exceptionally(ex -> "There was an error: " + ex.getMessage());
6
7 System.out.println(errorHandling.get());
Output:
Not causing Exception!
Note that since in the above code we are not causing an Exception, the value from the supplyAsync
method will be printed successfully.
In the above code, we have a thread pool with 2 threads. Now let’s make use of those threads by
using the submit method:
16 Advanced Concurrency 514
Output:
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
The program execution continues…
Notice in the code above that the submit method receives a Runnable object and process them in the
thread pool.
Another important detail is that in the output, we can see one thread pool (pool-1) and two threads
(thread-1 and thread-2). As mentioned before, thread pools are useful when it’s necessary to run
tasks very often, because then performance is optimized significantly.
Also note that the program execution continues forever. Unless we say explicitly to the executor to
shutdown the program will keep running.
1 @FunctionalInterface
2 public interface Callable<V> {
3 V call() throws Exception;
4 }
Notice that Callable is a functional interface and has the call method returning a generic value of
V, this means any type passed via generics.
Now that we know how the Callable interface is, let’s see how a Callable works in code:
16 Advanced Concurrency 515
Output:
false
pool-1-thread-1
true
pool-1-thread-1
Notice that the overloadead submit method returns a Future in the following method signature:
It returns the same generic type passed in the Callable. Once we have the Future object then we
can use the get method. Simply put, the get waits for the computation to be complete and returns
the Callable value.
1 import java.util.ArrayList;
2 import java.util.Collections;
3 import java.util.List;
4 import java.util.Random;
5 import java.util.concurrent.Callable;
6 import java.util.concurrent.ExecutionException;
7 import java.util.concurrent.ExecutorService;
8 import java.util.concurrent.Executors;
9
10 public class SortingInvokeAny {
11
12 public static void main(String[] args) throws ExecutionException,
13 InterruptedException {
14 List<String> heroes = new ArrayList<>(List.of("Spider-Man",
15 "Batman", "Iron-Man"));
16
17 Callable<List<String>> naturalSort = () -> { // #A
18 System.out.println("Natural Sort");
19 Thread.sleep(new Random().nextInt(500)); // #B
20 heroes.sort(String::compareTo);
21 return heroes;
22 };
23
24 Callable<List<String>> revertSort = () -> { // #C
25 System.out.println("Revert Sort");
26 Thread.sleep(new Random().nextInt(500));
27 heroes.sort(Comparator.reverseOrder());
28 return heroes;
29 };
30
31 List<Callable<List<String>>> taskList = List.of(naturalSort, revertSort);
32 ExecutorService executor = Executors.newCachedThreadPool(); // #D
33
34 try {
35 List<String> sortedArray = executor.invokeAny(taskList); // #E
36 System.out.println(sortedArray);
37 executor.shutdown();
38 } catch (ExecutionException e) {
39 e.printStackTrace();
40 }
41 }
42
43 }
16 Advanced Concurrency 517
Output (Random):
Revert Sort
Natural Sort
[Spider-Man, Iron Man, Batman]
Code Analisis:
Notice that the above output may vary because the naturalSort might be faster than revertSort
and vice-versa. That’s the whole point of using the invokeAny method.
Let’s now see the highlighted areas of the code:
• #A: Callable that returns a List<String> is created here and returns a list sorted in the
natural order.
• #B: To simulate processing, a Thread.sleep method is invoked at a random time. So that we
can more easily see a random output.
• #C: A similar Callable from the above one but this time it will return the list in the reverse
order.
• #D: A CachedThreadPool is created to invokeAny of the Callables.
• #E: We invoke both Callables, we can see clearly that this is happening by looking at the
output:
Revert Sort
Natural Sort
Then, the first Callable to return the value will be the one effectively used.
1 import java.util.List;
2 import java.util.Random;
3 import java.util.concurrent.Callable;
4 import java.util.concurrent.ExecutionException;
5 import java.util.concurrent.ExecutorService;
6 import java.util.concurrent.Executors;
7 import java.util.concurrent.Future;
8
9 public class RandomNumberInvokeAll {
10
11 public static void main(String... invokeAll) throws InterruptedException {
12 ExecutorService executor = Executors.newFixedThreadPool(2);
13 Callable<String> delayedRandomNumberTask = () -> {
14 Thread.sleep(1000);
15 return "Delayed: " + new Random().nextInt(1000);
16 };
17 Callable<String> nonDelayedRandomNumberTask = () -> "Non-delayed: "
18 + new Random().nextInt(1000);
19
20 List<Callable<String>> taskList = List.of(delayedRandomNumberTask, \
21 nonDelayedRandomNumberTask);
22 List<Future<String>> resultList = executor.invokeAll(taskList);
23
24 executor.shutdown();
25
26 resultList.forEach(e -> {
27 try {
28 System.out.println(e.get());
29 } catch (InterruptedException | ExecutionException ex) {
30 ex.printStackTrace();
31 }
32 });
33 }
34
35 }
Output (Random):
Delayed: 945
Non-delayed: 658
As you can see in the code above, even though the delayed random number task takes longer than
the non-delayed to be executed, the order will be delayed and then non-delayed.
16 Advanced Concurrency 519
Output:
pool-1-thread-1
pool-1-thread-1
Notice in the program above that the application will be finished.
If we want to wait until tasks that are submitted to be finished, we have to use the awaitTermination
method:
Notice that in the awaitTermination method if the tasks in execution take long to finish the await
will finish after the timeout of 500 milliseconds.
16.6.6 SingleThreadExecutor
As the name suggests, the SingleThreadExecutor will create one thread and will guarantee that
tasks will be executed in sequentially. Since there is only one thread, only one task will be active per
thread execution.
Also, if there is any error in the thread, the task will be executed again.
Output:
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
As you can see in the above output, the three tasks were executed in the same thread-1. The program
was also terminated because of the shutdown method.
16.6.7 newFixedThreadPool
Notice that we already used the newFixedThreadPool method that returns a thread pool that
represents a FixedThreadPool in previous examples but didn’t explain it in detail.
16 Advanced Concurrency 521
Simply put, the FixedThreadPool will create a fixed amount of threads to execute needed tasks. All
threads will be active and ready to receive tasks. In case all threads are in use, the tasks will be
waiting in a LinkedBlockingQueue until a thread is available.
If there is an error in any of the threads, there will be a mechanism to create and replace them. The
threads will be shutdown only if we do that explicitly.
Let’s first explore the factory method newFixedThreadPool:
Notice that the only argument we pass is the number of threads. Also, notice that we are
instantiating a ThreadPoolExecutor actually. Let’s see what the ThreadPoolExecutor is expecting
in its constructor:
By invoking the factory method newFixedThreadPool() we have only one argument we have
control that is the number of threads. Also, we don’t have any control over the amount of elements
we can put into the LinkedBlockingQueue. This means that we will be able to put 2147483647 tasks
in the queue since this is the default max elements from this queue. This might hide a serious issue
in your application.
Let’s see how this limit works in the LinkedBlockingQueue works in practice in code:
Output:
java.util.concurrent.RejectedExecutionException…
16 Advanced Concurrency 522
As you can see in the code above we will have the RejectedExecutionException. That’s because
we created the LinkedBlockingQueue with the limit of 100 elements but in the for looping we are
executing more than 1000 tasks.
16.6.8 SingleThreadScheduledExecutor
The SingleThreadScheduledExecutor will create one thread and will delay the next task execution
by the configured time:
Output:
pool-1-thread-1
pool-1-thread-1
The executor from the code above receives the task, delay time and time unit. This means that when
the task is executed, there will be a delay of 500 milliseconds and then the next task is executed.
Notice also that only one thread is created with the newSingleThreadScheduledExecutor.
16.5.9 CachedThreadPool
The CachedThreadPool is meant to execute short-timed tasks. Therefore, if you have tasks that will
take a long time to execute it’s better to avoid the use of aCachedThreadPool.
The threads from CachedThreadPool will be kept alive for 60 seconds when idle. After that, the
thread will be terminated. Ideally, threads from CachedThreadPool should be executing tasks as
often as possible.
Before seeing how to use the CachedThreadPool factory method. Let’s see how is its implementation:
16 Advanced Concurrency 523
Notice that in the code above we don’t even pass the number of threads which means that threads
will be created on demand.
Let’s see a code example with CachedThreadPool:
Output (Random):
…
pool-1-thread-165
pool-1-thread-106
Notice a limitation when working with the newCachedThreadPool method which is that we can’t
control the number of threads we will create. This means that 2147483647 threads might be created
depending on the number of tasks. This might become a bottleneck for your software. Therefore, it’s
better to use this thread pool for short-lived tasks so the thread cache can be used effectively.
In the code above, we are creating a CachedThreadPool and then we create 1000 tasks printing the
thread pool followed by the thread number. Then we submit all of them to the CachedThreadPool.
Another important point is that in the output, the CachedThreadPool created multiple threads. The
thread number 165 was created for example.
16.6.10 ScheduledThreadPool
The ScheduledThreadPool is usually used to execute tasks in a short interval period of time.
16 Advanced Concurrency 524
Output:
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
pool-1-thread-1
Notice in the code above that the scheduled thread pool will wait 1 second and then will start
executing each task.
16.6.11 scheduleAtFixedRate
The scheduleAtFixedRate method will invoke tasks at a fixed time interval considering the time
that the task will take.
For example, if the fixed rate time is 1 second and the task takes half second to be finished, the next
task will be executed after 1 second.
If the task takes more than 1 second, then the next task will be executed right after the task is finished.
There is no extra delay, only a fixed rate.
Let’s see first how is the method signature from scheduleAtFixedRate:
The first parameter is the task itself which is basically the implementation of the run method with
the Runnable interface.
The initialDelay is the time it will take until the first task starts running.
The period means the interval time between tasks whenever they finish.
TimeUnit is the time configuration, it can be all of the following: NANOSECONDS, MICROSECONDS,
MILLISECONDS, SECONDS, MINUTES, HOURS, and DAYS.
Now that we know the the scheduleAtFixedRate method signature. Let’s see a code example to
understand it more clearly:
16 Advanced Concurrency 525
Output:
Never stop learning! 2022-06-30T14:16:12.650819
Never stop learning! 2022-06-30T14:16:13.660476
Never stop learning! 2022-06-30T14:16:14.661136
16.6.12 scheduleWithFixedDelay
The scheduleWithFixedDelay on the other hand will wait until the task is finished and will add a
delay time on top of that.
This means that if a task takes 1 second to be finished and there is a delay of 1 second, the next task
will take 2 seconds to start.
Before seeing the code in action, let’s see how is the method signature from the
scheduleWithFixedDelay method:
Notice that it’s very similar to the scheduleAtFixedRate method. The difference is the delay though.
Let’s see that in code:
Output:
Never stop learning! 2022-07-01T15:21:24.643794
Never stop learning! 2022-07-01T15:21:26.654136
Never stop learning! 2022-07-01T15:21:28.660036
Notice the time difference between each task to run. The Runnable task takes 1 second to run and
since we passed 1 second to the scheduleWithFixedDelay the next task will take 2 seconds to run.
16 Advanced Concurrency 526
16.6.13 CountdownLatch
The CountdownLatch class is useful to await the execution of threads to take some action.
It counts down how many times a task should be executed and helps in the orchestration of threads
execution.
Let’s see a code example where we will create a CountDownLatch receiving the count of 3 in
the constructor. So we can count and control how many times the ScheduledThreadPool will be
executing:
Output:
Never stop learning! 2022-07-01T16:51:33.944476
Never stop learning! 2022-07-01T16:51:34.930132
Never stop learning! 2022-07-01T16:51:35.929230
Process finished with exit code 0
Code analisis:
Another great benefit from CountDownLatch is that it’s possible to pass it around via method
parameter or constructor. This is something the Thread.sleep method can’t do for example.
16 Advanced Concurrency 527
1 import java.time.LocalTime;
2 import java.time.format.DateTimeFormatter;
3 import java.util.concurrent.CountDownLatch;
4 import java.util.concurrent.Executors;
5 import java.util.concurrent.ScheduledExecutorService;
6 import java.util.concurrent.TimeUnit;
7
8 public class ThreadDelayChallenge {
9
10 public static void main(String[] args) throws InterruptedException {
11 CountDownLatch countDownLatch = new CountDownLatch(3);
12 Runnable fixedRateTask = createScheduledTask(countDownLatch,
13 "Fixed rate task: ");
14 Runnable fixedDelayTask = createScheduledTask(countDownLatch,
15 "Fixed delay task: ");
16
17 ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
18
19 executor.scheduleAtFixedRate(fixedRateTask, 1, 1, TimeUnit.SECONDS);
20 executor.scheduleWithFixedDelay(fixedDelayTask, 1, 1, TimeUnit.SECONDS);
21 countDownLatch.await();
22 executor.shutdown();
23 }
24
25 private static Runnable createScheduledTask(CountDownLatch countDownLatch, \
26 String message) {
27 return () -> {
28 try {
29 Thread.sleep(1000);
30 countDownLatch.countDown();
31 var count = countDownLatch.getCount();
32 System.out.println("Count: " + count + " - " + message +
33 LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
34 } catch (InterruptedException e) {
16 Advanced Concurrency 528
35 e.printStackTrace();
36 }
37 };
38 }
39 }
Explanation:
One part of the puzzle on this code challenge is the CountDownLatch class. This class has the purpose
of counting how many times a task should be executed.
In the case of this challenge, we are setting the count to 3. However, notice that we are cre-
ating two threads in our ThreadPool. This means that there will be parallel processing in the
createScheduledTask method.
1 Thread.sleep(1000);
2 countDownLatch.countDown();
What will happen here is that two threads will be sleeping for 1 second and only then the countDown
will be executed.
Notice also that the counter will start with 3. Therefore, it’s possible that one thread will decrease
the value to 2 and the other to 1. Then, both threads will read the same value of 1.
Therefore, the following first part of result is possible:
The same logic might happen for the countdown of 0 with the difference that when it’s 0 it won’t be
possible to decrease the countDown number anymore. However, the tasks will stop their execution
since the countDown reached to 0.
Therefore, the following last part of the result is also possible:
We are also exploring the mechanism from the scheduleAtFixedRate and scheduleWithFixedDelay.
Notice that the scheduleAtFixedRate method will not add an extra delay to the task execution.
Therefore, the interval between a fixedRate task from the other will be of 1 second even with the
Thread.sleep of 1 second within the createScheduledTask.
The scheduleWithFixedDelay method behaves differently because this one will add a delay between
another delay task. Therefore, considering that we have a delay of 1 second and we have a
Thread.sleep of 1 second in the task process, we will have 2 seconds interval between two tasks.
16.7 ConcurrentHashMap
The ConcurrentHashMap class is useful to avoid data collision when working with a Map. Therefore,
whenever you are working with Map in a multi-thread environment, it’s recommended to use the
ConcurrentHashMap.
Let’s see the issues that might happen when not using HashMap instead of a ConcurrentHashMap:
16 Advanced Concurrency 530
Output:
java.util.ConcurrentModificationException…
Notice in the code above that we have a ConcurrentModificationException when trying to remove
an element from the HashMap in a looping. This happens because of a fail-fast mechanism of
this class attempting to avoid tricky data collisions. Which means that when the structure from the
HashMap is modified within a loop, it’s very likely that the ConcurrentModificationException will
be thrown.
It’s also not possible to rely on the ConcurrentModificationException because it might not be
thrown sometimes.
Now, if we want to guarantee that the ConcurrentModificationException will not be thrown, we
can use the ConcurrentHashMap class:
Output:
In the code above, it’s guaranteed that the ConcurrentModificationException will not be thrown.
Therefore, it’s safe to modify the structure of the Map within the loop. As a rule of thumb, use the
ConcurrentHashMap only when working on a multi-thread environment.
If you want to only remove elements from a map without having ConcurrentModificationException
it’s better to use the removeIf method as you can see in the following code:
Output:
As you can see in the code above, the removeIf method will remove the first element without any
issue. This way is preferable to handle an element removal from a Collection.
The same principle as above can be used for a List or a Set. We can also use the removeIf method
with those Collections.
16.8 ConcurrentSkipListMap
This ConcurrentSkipListMap class behaves similarly to the TreeMap with the difference of being
thread-safe. Since we already went through a TreeMap, let’s see the basics of this class:
Output:
1 : Cyclops
2 : Wolverine
3 : Xavier
As you can see in the code above, only the key will be sorted according to what is implemented in
the compareTo method, in this case, the natural order.
Reinforcing the crucial point of this collection class, is that it can be safely used in a multi-thread
environment and data collision will be avoided.
16.8 ConcurrentSkipListSet
This class is similar to TreeSet but the difference is that it is thread-safe. The
ConcurrentSkipListSet class will also sort elements based on the compareTo method
implementation.
Let’s see a code example:
16 Advanced Concurrency 532
Output:
Bart
Homer
Marge
1 import java.util.Random;
2 import java.util.concurrent.*;
3
4 public class ProducerConsumer {
5
6 public static void main(String[] args) throws InterruptedException {
7 BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(); // #A
8
9 Runnable producerTask = () -> {
10 try {
11 int i = new Random().nextInt(100);
12 blockingQueue.put(i); // #B
13 System.out.println("Produced: " + i);
14 } catch (InterruptedException e) {
15 e.printStackTrace();
16 }
17 };
18 Runnable consumerTask = () -> {
19 try {
20 System.out.println("Consumed : " + blockingQueue.take()); // #C
21 } catch (InterruptedException ex) {
22 ex.printStackTrace();
23 }
16 Advanced Concurrency 533
24 };
25
26 ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
27 executor.scheduleAtFixedRate(producerTask, 0, 1000, TimeUnit.MILLISECONDS);
28 executor.scheduleAtFixedRate(consumerTask, 0, 500, TimeUnit.MILLISECONDS);
29 }
30 }
Output (Random):
Produced: 45
Consumed : 45
Produced: 88
Consumed : 88
…
Code analisis:
• #A: The BlockingQueue interface is effective to synchronizely add and remove elements from
a queue. Therefore, instead of using a synchronized block or a ReentrantLock, we can use a
BlockingQueue to handle a collection of data.
• #B: The put method is will put elements in order and is going to use a ReentrantLock on its
implementation as you can see:
• #C: The take method will wait until there is at least one element in the queue and then the value
will be taken. Similarly to the put method, the take method is also synchronized or holds the
monitor lock with the ReentrantLock class:
16 Advanced Concurrency 534
1 // ...
2 public E takeFirst() throws InterruptedException {
3 final ReentrantLock lock = this.lock;
4 lock.lock();
5 try {
6 E x;
7 while ( (x = unlinkFirst()) == null)
8 notEmpty.await();
9 return x;
10 } finally {
11 lock.unlock();
12 }
13 }
As you can see in the above code, it’s very important to know the available tools in the Java language.
Otherwise, it’s easy to reinvent the whell and implement all this logic of the producer and consumer
manually.
For the vast majority of the day-to-day problems you can be sure that there is something ready for
the Java language. Therefore, try to be up to date with the tools Java has to offer so you can reuse
them.
16.10 Summary
• Locking a thread monitor with more sophistication using ReentrantLock
• Locking only read and write oprations with ReadWriteLock
• Using thread-safe objects with atomic variables
• Making sure to use shared data always with the updated value with volatile
• Executing methods and handling Exceptions concurrently with the powerful
CompletableFuture API
• Creating threadpools with Executors
• Creating tasks with Callable to process and return a value
• Using shutdown in the threadpool
• Creating a thread pool with a single thread using SingleThreadExecutor
• Caching threads in the threadpool with CachedThreadPool to execute many proccesses
• ScheduledThreadPool to run tasks and workers in a pre-configured delay
• CountdownLatch to count how many times a process should run
• ConcurrentHashMap to use key-value thread-safe object
• ConcurrentSkipListMap to use sorted in the natural order thread-safe key-value object
• ConcurrentSkipListSet to use sorted in the natural order thread-safe key-value object
• BlockingQueue to create an optimized producer consumer
17 Next Steps
Congratulations! You finished the Java Challengers book, that’s not an easy task! By absorbing all
the content of this book now you have an incredible foundation to deliver high-quality code. Also,
it’s much easier now to learn a new programming language since you mastered the fundamentals
of Java.
Now it’s time to put your knowledge in practice, work on an amazing project or even make a project
code amazing so you don’t need to deal with bad code and bugs on your day-to-day job.
One action that makes you a better software engineer is to learn a new programming language, what
about picking one that you like and learn it?
• Date API: In almost every real Java project we will need to handle dates. We will need to
create and compare date and time very often. Fortunately, Java has a very powerful API
to work with dates. The old classes before Java 8 such as Date and Calendar should be
avoided because the code will get more complicated and it’s not thread-safe. To solve those
problems, since Java 8 we have the LocalDate, LocalTime and LocalDateTime classes that
will easily handle any date and time problem we have. For more, check the following link:
https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html⁵
• File handling: It’s a feature that is quite rare to be used in real applications but still it’s
useful to know it exists since in some situations we will need to know it. The most effective
Java API to handle files is the java.nio.file. Basically, we can easily create, update,
delete, read, and process a file with this API. For more, check the following documentation:
https://docs.oracle.com/javase/8/docs/api/java/nio/file/package-summary.html⁶
• Tokenization: To break down a String we can use any token such as, , | _, this means any
character. This is useful when it’s necessary to search a specific String in a big text. There is
⁵https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html
⁶https://docs.oracle.com/javase/8/docs/api/java/nio/file/package-summary.html
17 Next Steps 536
an interesting class to achieve that which is the StringTokenizer. For more information, check
the following link: https://docs.oracle.com/javase/7/docs/api/java/util/StringTokenizer.html⁷
• Regex: To search a specific pattern from a String, we can use the regex patterns. The regex
patterns are universal for every programming language, and simply put, regex is a set of
pre-defined and sophisticated patterns that enables developers to search specific String by
controlling, only characters, only numbers, size of text, beggining and end of line or text, text
repetition, and the possibilities go on and on! For more information, check it out the following
link: https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html⁸
• Serialization: To transfer objects as bytes through the network we can use the serialization
technique. We don’t really need to do low-level implementation in the day-to-day work but
it’s important to know that this concept is available in Java. For more, check the following
documentation:
https://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html⁹
• Virtual Threads (Coroutines): Programming languages such as Go and Kotlin have the concept
of coroutines for a while already. Coroutines is actually a quite old concept invented in 1958.
To deal with a massive amount of threads, the virtual threads concept is really powerful. This
happens because virtual threads are really suspended when not needed. Therefore, if you are
dealing with 100k threads, for example, the processing time will be dramatically reduced. For
more information, check the following documentation:
https://openjdk.org/jeps/425¹⁰
• Pattern Matching: This is a concept that will improve your code design with Java. Simply put,
by fully using the benefits of pattern matching, you will be able to save code and have more
control over your code. That’s because you will be able to use
• Code naming
⁷https://docs.oracle.com/javase/7/docs/api/java/util/StringTokenizer.html
⁸https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html
⁹https://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html
¹⁰https://openjdk.org/jeps/425
17 Next Steps 537
• SOLID
• Design Patterns
• Separation of concerns
• Low coupling
• High cohesion
• Object Oriented Programming
• Functional Programming
• Microservices Design Patterns: you will face many challenges with Microservices. Applications
will fail and you will have to build resilient code. The most common patterns are: Service
Discovery, API Gateway External Configuration, Health Check, Circuit Breaker, Database per
Service, Blue-Green Deployment, Saga Pattern, CQRS…
• Continuous Integration Continuous Delivery CICD (Jenkins, Github Actions): it’s crucial to
integrate software often by using Git for example and then run your pipeline. In the process
of continuous integration, you need to be able to integrate your code, build and test it. In the
delivery side, you need to deploy it do multiple environments, such as dev, qa, staging and
production. All of this need to be automated.
• Container (Docker): containerizing an application is one very common skill. The goal is to
have everything you need encapsulated in a container with your application so you can run it
anywhere. Also, by using containers, you will end the “works in my machine” problem. Since
once you are able to run your application in the container, for sure you can run it anywhere.
• Event Driven Architecture: Kafka, SQS, RabbitMQ are popular tools nowadays. They enable
Microservices to communicate asynchronously via messages. This architecture helps to decou-
ple Microservices and make them resilient to failure.
• Version Control System: the most popular is definitely Git. It’s crucial and necessary skill to
have as a software engineer since the vast majorty (if not every companies) are using it. Simply
put, Git will integrate your code in a centralized server, it will create versions of the code and
will enable you to work on your features separately.
• Infrastructure as a Service - IAAS (AWS, Azure, GCP): developers don’t need to be specialists
on a cloud vendor but it’s important to know it enough to be able to solve problems. You need
to need at least the basic services so you are able to put an application in the cloud up and
running.
17 Next Steps 538
• Microservices 12 factors: those are principles to make your Microservice application robust and
resilient. Those factors are Codebase, Dependencies, Config, Backing services, Build, release,
run, Processes, Port binding, Concurrency, Disposability, Dev/prod parity, Logs, Admin
processes. For more go to https://12factor.net/¹¹.
• Platform as Service - PaaS (Heroku, Pivotal Cloud Foundry): a platform as a service does most of
the hard work for you. It’s much easier to deploy your application with Heroku or Pivotal Cloud
Foundry. That’s because the internal configurations such as scaling, load balancing, routes
will be automatically created. The disavantages of using PaaS is that you don’t have so much
control.
• Software as a Service - SaaS (Sales Force, Stripe): those are entire applications running in the
cloud. In simple words you consume the endpoints of a service in the cloud. In this case, you
don’t need to take care of infrastructure or anything. You just need to use the service directly.
• Infrastructure as Code (Terraform, Pulumi, Chef): of course you don’t need to know all those
tools but the principles of infrastructure as code. The idea is to have all your infrastructure
written in code where you can easily replicate and recreate your whole environment. Another
advantage is that the truth of your environment is in the code.
• Monitoring (Datadog, Prometheus): the complexity of Microservices archictures increase a lot.
So, it’s necessary to monitor what is happening on them by measuring their time-response,
requests amount, errors, tracing between Microservices and more…
• You can ask a recruiter (be aware that they might not give you accurate information since they
will potentially negotiate with you)
• You can take a look at http://glassdoor.com¹⁴ (Keep in mind that the salaries there are not
accurate - it’s in average from 20% - 40% less than the real salary)
• You can check the https://www.levels.fyi¹⁵ website to be aware of how much the big companies
such as Google, Amazon, Microsoft are paying.
• You can use brute force by giving a big number to the company or recruiter. If they accept it
you are good, otherwise, you will know that the salary you asked is unrealistic.
Once you know how much to ask, then it’s time to start the negotiation process. One very important
rule is to not disclose your current salary information.
This is even more important when you know that your salary is low. That’s because even if you
know you are being underpaid the company you are negotiating with won’t usually care about it.
What will happen is that your whole negotiation process will be based on your last salary. As a
consequence, usually you will get something between 15% raise other than potentially making the
double of your salary.
There are much more to go into about negotiation but that’s an introduction to open your eyes and
make you aware of how important it is. There are several books and I also prepared a course that
has much more information about negotation to help you get a much better salary!
¹⁴http://glassdoor.com/
¹⁵https://www.levels.fyi/
¹⁶https://javachallengers.com/challenger-developer-course