Interactive Java workbook
Interactive Java workbook
Bojan Tomić
Buy on Leanpub

Table of Contents

Introduction

About this workbook

This workbook covers the basics of object-oriented programming in the Java programming language. The assumption is that the user knows nothing about programming and starts “from scratch.”

It consists of a free companion book (in PDF or EPUB formats) which you are reading right now but, more importantly, a multi-volume collection of tasks which are provided as ZIP files (extras) containing complete courses for the IntelliJ Academy plugin. Solution explanation videos are also available for more complex tasks and are sold separately.

Programming is one of those skills that is best learned through practice. That’s why you often hear and read:

1 "Programming is learned through programming."

No one has learned to program simply by reading books or watching tutorials. Theory is essential, but it’s necessary to “roll up your sleeves” and program what you’re learning. This means: creating your own program, doing a large number of examples, trying different solution variants, analyzing and improving other people’s programs, etc.

This is exactly the motivation behind writing this workbook. There are numerous online courses, textbooks, tutorials, and educational software that contain important lessons about programming. However, there are very few interactive task collections that allow you to solidify that knowledge through concrete programming examples and diverse tasks.

Unfortunately, most of the available task collections are part of online courses, meaning that you cannot practice programming in a real-world software development environment, but through a web-browser and online-based environments with very limited features. That’s why this workbook is provided as an addition of the IDEA IntelliJ enterprise-grade software development environment (actually, its Academy plugin), so one can get used to working in such a setting where all the features and tools available to a professional programmer are present.

You can see the “about” video here.

Introductory video tutorials

Here are some short introductory video tutorials regarding IntelliJ, the Academy plugin, and the Interactive Java workbook just to get you started:

All of these videos, together with the ones explaining the types of tasks (see the following text) are organized in a playlist for convenience.

Types of tasks in this workbook

Programming is learned just like most other skills: gradually, from easier to harder things. For example, when someone starts learning to play tennis, they first stand still and learn to hit the ball properly. Then they practice and repeat the same move dozens and hundreds of times until they master it. Only then do they move on to the next, more difficult lessons: serving, moving on the court, etc. Only after some time and more lessons do they tackle the most difficult things and move on to playing matches.

The tasks in this workbook are organized according to the same principle. In each chapter, a concept (or multiple concepts) is introduced in a lesson, and then that concept is explored through examples. Then, various and diverse tasks are put upon the user to practice each presented concept (see the Task types overview video):

Each subsequent chapter builds on the previous ones. Therefore, you should not “skip” lessons. There are several hundred tasks in total, they are arranged according to difficulty and are divided into three categories:

  • EASY (E)
  • MEDIUM (M)
  • HARD (H)

Specifically, if a task is named Question 8 E, it means that it is a multiple choice question, that it is the eighth in the lesson, and that it is easy (E). Or if a task is named Code Completion 6 M, it is a task of medium difficulty (M) in which you have to complete the unfinished code, and it is the sixth such task in that lesson.

There are several things that keep this workbook interactive so you can get feedback on your task solution and help if you get stuck solving a task (Video: What happens if I cannot solve a task). A predefined solution is provided for each task, and often a hint. The user’s solution is verified by running the provided code tests. A side-by-side comparison of the predefined solution and the user’s solution is also available. What if this still is not enough, and you cannot solve the task? Solution explanation videos are provided for more complex tasks to help you learn and move along.

Solution video explanations are, at this time, sold separately, and are available via Ko-fi memberships (please see here) based on a monthly subscription.

How is this workbook organized

The Interactive Java workbook consists of several parts due to the many topics and even more tasks.

NOTE: Not all parts are completed at this time, but please see the Leanpub page, for updates and purchases.

This is work in progress, so the following list is not yet final. The parts and the topics they cover are as follows:

  1. Interactive Java workbook part 1 - demo + full version (COMPLETED)
    • Introduction
    • Class
    • Attribute
    • Objects, program execution and output to the screen
  2. Interactive Java workbook part 2 (COMPLETED)
    • Methods and arithmetic operators
    • Constructor
    • Global variables and methods
    • Constants
    • Relationships
    • Enumerated type
  3. Interactive Java workbook part 3 (COMPLETED)
    • Algorithms and control statements (commands)
    • The if statement
    • The switch statement
    • The for statement
    • The while statement
    • The do-while statement
  4. Interactive Java workbook part 4 (80% COMPLETED, NOT PUBLISHED YET)
    • Introduction to arrays
    • The for-each statement
    • Algorithms for working with arrays - overview
    • Search and print array elements
    • Adding elements to an array
    • Replacing array elements
  5. Interactive Java workbook part 5 (80% COMPLETED, NOT PUBLISHED YET)
    • Deleting array elements
    • Group operations on array elements
    • Array sorting
    • Working with multiple strings
    • Cumulative test 1
    • Cumulative test 2
    • Cumulative test 3
    • Cumulative test 4
    • Cumulative test 5
    • Cumulative test 6
    • Cumulative test 7
    • Cumulative test 8
  6. Interactive Java workbook part 6 (50% COMPLETED, NOT PUBLISHED YET)
    • Arrays of objects
    • Working with text (String, StringBuilder)
    • Working with dates and times (LocalDate, LocalTime, LocalDateTime)
  7. Interactive Java workbook part 7 (10% COMPLETED, NOT PUBLISHED YET)
    • Packages and access levels (private, public, protected, default)
    • JavaBeans specification
    • Inheritance
    • The Object class
    • Cumulative test 9
    • Cumulative test 10
    • Cumulative test 11
    • Cumulative test 12
    • Cumulative test 13
    • Cumulative test 14
    • Cumulative test 15
    • Cumulative test 16
  8. Interactive Java workbook part 8 (0% COMPLETED, NOT PUBLISHED YET)
    • Abstract classes and inheritance
    • Interfaces
    • Lists in Java
    • Error handling - exceptions
  9. Interactive Java workbook part 9 (0% COMPLETED, NOT PUBLISHED YET)
    • Input and output streams in Java
    • Working with the keyboard
    • Working wih textual files
    • Serialization of objects
    • Cumulative test 17
    • Cumulative test 18
    • Cumulative test 19
    • Cumulative test 20
    • Cumulative test 21
    • Cumulative test 22
    • Cumulative test 23
    • Cumulative test 24

What is Programming

Humans use natural language (spoken language) to communicate. Sounds, letters, words, and sentences (with the appropriate grammar) serve to convey a message. Although there are many natural languages, they are based on similar concepts.

However, what happens if we need to convey a message to a computer? Computers do not understand natural language; some form of translation is necessary.

Computers use machine language (binary language). In binary language, everything is represented using only zeros (0) and ones (1). These are the only two “letters.” This language arose due to the way hardware functions (microchips, printed circuit boards, electrical circuits, etc.). At the hardware level, zero means no electricity, and one means there is electricity.

We, humans, use the decimal system to represent numbers. This means ten digits: 0, 1, 2, 3, 4, 5, 6, 7, 8, and 9. However, computers only have “digits” 0 and 1. So, how are numbers represented in a computer?

For example, the number 0 is written as 0 in binary, and the number 1 is written as 1. However, the number 2 is represented as 10 in binary because the digit 2 does not exist in binary. The number 10 is the first number larger than 1 that can be written using only the digits 0 and 1. The number 3 is represented as 11, the number 4 as 100, the number 5 as 101, and so on.

 1   Decimal   Binary
 2   0         0
 3   1         1
 4   2         10
 5   3         11
 6   4         100
 7   5         101
 8   6         110
 9   7         111
10   8         1000
11   ..............

How are letters represented in binary language? Also as numbers. Each letter is assigned a number that represents it according to the corresponding (character) table. You may have heard of the ASCII table, or UTF-8 and UTF-16?The capital letter “A” is represented as the number 65 (or 1000001 in binary),and the capital letter “B” as 66 (or 1000010), and so on. The Swedish pop band name “ABBA” would be represented as a series of four numbers: 65 66 66 65 (1000001 1000010 1000010 1000001).

1   Characters:   A         B         B         A
2   Char codes:   65        66        66        65
3   Binary:     1000001   1000010   1000010   1000001

If we need to give a command to the computer to add two numbers (for example, 65 and 5), it would look something like this in binary:

1     Decimal:  65    +   5 =  70
2     Binary: 1000001 + 101 = 1000110

Let’s say we need to give the computer a more complex command: to load data from a file, search a database, and return the name of a movie, etc. For such a command, we would need to write a lot of zeros and ones. This is extremely complicated if done manually. The potential for errors is enormous.

In the past, commands were indeed written in binary code, but now a different approach is used.

Now, we use (higher) programming languages. These are intermediary languages between humans and computers. A programming language (depending on its complexity) is somewhere between a natural language and machine language. It is similar enough to a natural language so that humans can easily write commands for the computer. Also, it can be easily and quickly (automatically) translated into machine language so that the computer “understands” and executes those commands.

There are many programming languages. Some are “closer” to natural language (“higher” programming languages), while others are closer to binary language (“lower” programming languages). Some are procedural, some are functional, and some are object-oriented (Java is one of them). No programming language is perfect, and each has its advantages, disadvantages and preferred uses.

Every natural language has its grammar. Similarly, every programming language has its syntax. The syntax of a programming language is the set of symbols, reserved words, expressions, and rules that govern the proper use of the language.

In natural languages, violating grammatical rules is undesirable. However, it is often allowed in practice because your conversation partner will likely understand you. In programming languages, any violation of syntax is not allowed and leads to an inability to translate the code into machine language. These violations of syntax are called syntax errors.

Programming is the process in which a person writes commands for a computer in a programming language (always respecting its syntax). Often, the word coding is used instead of programming in casual speech.

As a result of programming, a (computer) program or (computer) application is created.

Source code is the textual record of commands in a programming language, as written by a person. It is usually saved in form of multiple textual files - source code files. Source code is often referred to simply as code.

Executable code is obtained when the commands from the source code are translated into binary language that the computer “understands.” Executable code is called so because it can be executed on the computer without any further translation.

The process (of automatic) translation of source code into binary code is called compiling. Compiling is done by a program called a compiler, and it is only possible if there are no syntax errors in the source code.

The process of running a program, i.e., executing the executable code on a computer, is called program execution or program run.

About the Java programming language

Java is an object-oriented programming language developed by Sun Microsystems, and it is now owned by the Oracle Corporation. This language is free to use and can be downloaded from the Oracle Corporation website.

One of the main features of Java is that it is a platform-independent language. This means that, by using Java, programs can be written for Windows Linux, Android, Mac OS X, or any other operating system for which there is a so-called Java Virtual Machine (JVM).

The Java Virtual Machine is specialized software that allows code written in Java to be translated into instructions for the corresponding operating system and hardware during execution (Figure 1). In this way, the same Java program can be used, completely unchanged, on any computer with any operating system (for which there is a JVM, that is). In other words, in order for a Java program to be executed, the JVM for the operating system used must be installed.

Java Virtual Machine - (JVM)
Figure 1. Java Virtual Machine - (JVM)

The Java installation which only includes the JVM and allows the execution of pre-written Java programs, is called a JRE (Java Runtime Environment).

However, if someone wants to create Java programs (and run them), they need to install the JDK (Java Development Kit). The JDK includes the JVM, as well as the Java compiler and the standard Java component library. The Java compiler allows the translation of the source code of a Java program into Java bytecode, which can then be executed on the JVM.

The basic process of creating and running a program in Java can be seen in the following image (Figure 2).

  1. The programmer first writes the Java code (source code) and saves it in a file with the “.java” extension.
  2. The next step is to compile this source code, i.e., convert it into Java bytecode. The compiler performs this step upon request and generates files with the “.class” extension.
  3. Only then can the program be run (execution). When it is executed, the JVM converts the Java bytecode into executable code for the operating system and hardware, and the program begins to execute.

One of the unique features of the Java programming language — platform independence — is achieved precisely because the source code is not directly compiled into executable code but into Java bytecode, which the JVM converts into executable machine code for the specific operating system when the program is run.

Figure 2: The process of creating and running a program in Java
Figure 2. Figure 2: The process of creating and running a program in Java

The JDK contains only the basic libraries necessary for creating, compiling, and running Java programs. However, the JDK does not include any editor or integrated environment, making it extremely difficult and impractical to write Java programs without these additional tools.

For this reason, there are special classes of programs that use the JDK but add a complete graphical interface and many other tools, all aimed at making it easier and faster to create Java programs. The general term for such a program is Integrated Development Environment (IDE).

Some of the most popular IDEs for Java are:

Only after installing an IDE along with the JDK can you start writing Java programs. Most IDEs now include a JDK distribution as well so only an IDE installation is needed.

However, there is also an alternative. Recently, online code editors and online IDEs have become very popular, and they do not require any installation. Instead, you can work with them online through a web browser.

Some popular free online environments and editors are:

The advantages of these online environments are that installation is not required and you work directly through the browser, which makes programming possible even on other devices that have a browser (smartphones, tablets, etc.). Therefore, they can be a good choice for someone just starting to learn programming, and they typically support multiple programming languages at once.

On the other hand, these online environments can cumbersome to work with - an accidental page refresh can erase your code. They are very simple and have very limited features, so they are not a good choice for creating more complex programs or applications. Some online environments only support working with a single file. Additionally, these online environments tend to be slower than standard IDEs and do not allow the use of additional Java libraries or the creation of Java applications with graphical interfaces, further limiting their usability.

What is a class?

NOTE

This is the first theory lesson in this workbook so please see the introductory video on theory lessons if you haven’t seen it already.

Lesson content

  • Class, definition and elements
  • Layout of keys on the keyboard
  • Reserved words in Java
  • Comments in Java
  • Convention for naming classes
  • Code formatting: “breaking” code into multiple lines
  • What are syntax errors and how are they marked in code
  • Most common errors

Class, definition and elements

A class is a general representation of a set of objects (things or phenomena) that have the same structure and behavior. A class is a simplified model of these real objects and phenomena and encompasses their:

  • characteristics (attributes)
  • behaviors (methods)
  • relationships with other classes (relations)

Attributes, methods, and relations are called elements (members) of the class. Attributes are sometimes also called fields or properties.

Example 1

An example of a class is Car (Figure 3). The class is graphically represented by a rectangle consisting of three parts. The top part only contains the class name - Car.

Car class
Figure 3. Car class

The Car class has the following characteristics: brand, model, production year, and registration number. These characteristics are located in the middle part of the rectangle in the image.

Some behaviors of a car include: start, stop, accelerate, brake. These behaviors are found in the bottom part of the rectangle in the image.

In the image, these characteristics are represented by attributes, while behaviors are represented using methods. It’s important to note that this is a simplified version of a real car because a car can have many other characteristics (serial number, color…) and behaviors (turn, open door…).

It is also important to mention that the UML class diagram (Unified Modelling Language) is used here to graphically represent the Car class. This graphical notation will be used throughout the text, and more about UML can be read here.

Example 2

Another example of a class could be Person (Figure 4).

Person and Car classes and their relatio
Figure 4. Person and Car classes and their relatio

Let’s say a person has a first name, last name, and identification number, but in this example, it doesn’t have any defined behaviors.

If we consider the Car class from the previous example and the Person class, there is a certain relationship between them. A person can be the owner of a car. This relationship is represented by a relation between the classes.

Details about attributes, methods, and relations will be explained in the following lessons. For now, it’s important to see how to define a class in Java. The general format of class definition in Java is as follows:

1 class ClassName {
2 
3     // definition of attributes...
4     
5     // definition of methods...
6 }

The definition begins with the reserved word “class”, followed by the class name. This first line is the class header. The next element is an open curly brace ({). After the curly brace begins the body of the class, which contains the definitions of attributes and methods. The class definition ends with a closing curly brace (}).

Whenever multiple related statements need to be written together, a block of statements is written. A block of statements in Java begins with an open curly brace ({), followed by the desired statements, and ends with a closing curly brace (}). In other words, the body of the class, as it contains multiple related statements (definitions of attributes and methods), is written as a block of statements. When writing a block of statements, both braces must be written, meaning the block must both begin and end.

Layout of keys on the keyboard

Now, if you are not that familiar with the standard keyboard layout, you are probably wondering where some special characters are placed, like the curly brackets we just mentioned - { and }. There are a lot of keyboard types out there, and a lot of keyboard layouts. Different languages can have different alphabets, hence the letters printed on the keyboard reflect this. In this workbook, we will focus on a regular keyboard with a US-English (EN-US) layout. The keyboard should look like this (Figure 5). Even if your keyboard has some different characters printed on some of its keys, it can be configured to use the EN-US layout in the language and keyboard settings in your operating system.

Regular keyboard with EN-US layout
Figure 5. Regular keyboard with EN-US layout

The special character keys are marked with ellipses in the figure (Figure 5). From top to bottom, the first line of keys are mostly numbers, where each pressed key produces a digit (1-9 and 0 towards the end), while the same key, when pressed together with the Shift key (there are two - bottom left and bottom right), produces a special character. For each key, both characters are printed on the key:

1 Without Shift    1   2   3   4   5   6   7   8   9   0   -   =
2 ---------------------------------------------------------------
3 With   Shift     !   @   ##   $   %   ^   &   *   (   )   _   +

In the second keyboard row (after the P key) are positioned the bracket keys, square and curly brackets:

1 Without Shift    [  ]
2 ---------------------
3 With   Shift     {  }

In the third keyboard row (after the L key) are positioned the column/semicolumn, apostrophy/quotation marks and backslash/vertical line keys:

1 Without Shift    ;  '   \
2 -------------------------
3 With   Shift     :  "   |

Finally, in the fourth row (after the M key) are the less-than/comma, greater-than/dot, and question mark/slash keys:

1 Without Shift    ,  .   /
2 -------------------------
3 With   Shift     <  >   ?

A laptop keyboard has a similar layout, so you should also be able to find the special characters (Figure 6). Here, the | key is placed in the second keyboard row instead of the third, and all other keys have the same placement.

Laptop keyboard with EN-US layout
Figure 6. Laptop keyboard with EN-US layout

Reserved words in Java

And what exactly are reserved words? Reserved words are words chosen (reserved) to be part of the programming language, i.e., to be used for writing its statements. Java has around 40 reserved words, some of which are:

1 class, int, double, boolean,
2 char, void, for, if, while...

Reserved words cannot be used as names for classes, attributes, variables, etc.

Java is a programming language in which case matters (i.e., it is “case-sensitive”). This means that class names, attribute names, method names can differ depending on whether they are written in uppercase or lowercase letters. For example, “CurrencyConverter”, “currencyconverter”, and “CURRENCYCONVERTER” are treated as completely different names.

Reserved words in Java are written exclusively in lowercase, so it’s class and not Class nor CLASS.

Comments in Java

Currently, within the class body given as an example, there are only two comments. In general, comments are used to write a message to the person who will read or modify the source code. This can be an explanation or note that helps with better understanding of the written code. During compilation, the Java compiler ignores comments and does not interpret them as program code, so the content of the comments can be any text.

Comments in Java can be written in two ways. One variant is single-line comments. The beginning of such a comment is marked by two slashes, and the entire text must fit on one line. An example of a single-line comment is:

1 // definition of attributes...

The other type of comment is a multi-line comment. These are used when a more detailed or longer explanation is needed. Of course, multiple single-line comments can be written, but it’s much simpler to frame the entire text as one multi-line comment. A multi-line comment starts with a slash and an asterisk (“/*”) and ends with an asterisk and a slash (“*/”):

1 /* This is
2    a multi-line
3    comment */

It’s important to note that not all slashes are the same. When writing comments, only the regular slash (“/”) is used, not the backslash (“\”), which has a different meaning in Java.

Example 3

Create (define) the Car Class. This class should not have any attributes or methods. Write this fact as a single-line comment.

1 class Car {
2     // This class has no attributes or methods.
3 }

Example 4

Create the Person Class. This class should not have any attributes or methods. Write this fact as a multi-line comment.

1 class Person {
2     /* This class has no
3        attributes or methods. */      
4 }

Convention for naming classes

It’s important to note that in Java (and in many other object-oriented programming languages), there is an unwritten rule (Java class naming convention) that class names should start with an uppercase letter and continue with lowercase letters (“Car”, not “car” nor “CAR”). If the class name consists of multiple words, then all words are written together, with the first letter of each word capitalized (“CurrencyConverter”, “BusinessCenter”). The name must not contain any spaces (this applies to the names of all other elements - attributes, methods…).

Code formatting: “breaking” code into multiple lines

There are also rules regarding breaking statements into multiple lines (text) and blank spaces (’ ’). The basic rule in this regard is that each statement should start on a new line. Additionally, almost every statement, declaration, etc., can be written on one or more lines (text) with an arbitrary number of blank spaces, provided that breaking (moving the remainder of the statement to the next line) or adding new blank spaces can occur in places where there would normally be a blank space. The exception is breaking within a String value or a single-line comment, which cannot be done this way.

The goal of breaking statements into multiple lines is to make the code as readable as possible (for the programmer). During compilation, all unnecessary blank spaces are removed, and the code is joined into one line, followed by syntax checking, so in that sense, how the code is broken up doesn’t matter.

Example 5

Create the Chameleon Class. Write a multi-line comment in the method body stating that it is an animal. Break the code of the class in different ways and add blank spaces where allowed (several solutions are given below, with the first solution being the most readable). Open the class code and try modifying it by breaking the lines in different ways.

1 class Chameleon {
2     /* This is an animal.*/      
3 }

or

1 class      Chameleon      {
2              /* This is an animal.*/      
3         }

or

1 class Chameleon {/*This is an animal.*/}

or

1 class      Chameleon      {      /* This is an animal.*/          }

or

1 class  
2 Chameleon         
3 {
4 /*
5 This is an animal.
6 */
7 }

Usually, the source code of each class is saved in a separate file. For example, the source code of the “Car” class might be saved in a file named “Car.java”, and the “Person” class in “Person.java”. When compiled, the Java executable code will be saved in “Car.class” and “Person.class”.

However, it is possible to save the source code of multiple classes in one file. In other words, the source code for both the “Car” and “Person” classes could be saved in one file, such as “Car.java”. But when compiled, two files will still be generated: “Car.class” and “Person.class”.

What are syntax errors and how are they marked in code

Just as every natural (human) language has its own rules — grammar,
every programming language also has its own rules — syntax. If during coding the rules of the programming language are not followed, syntax errors occur, and such code cannot be compiled until these errors are corrected.

It is completely normal and expected that someone who is just learning a programming language makes many syntax errors until they master the basics of the language. Because of this, many programming environments have additional features to help identify syntax errors, fix them, and make it easier to write code in general.

In the IntelliJ environment, syntax errors are usually highlighted in red. More precisely, the environment marks them in red within the code editor, so it’s enough to look and see “if something is red“to spot the error. The file name in which the error is located (e.g., “Motorcycle.java”) will also be marked in red, as well as the commands within that file that are not written correctly.

Of course, if there are multiple errors, IntelliJ will highlight each one, and in the top-right corner of the code editor, the total number of errors in that class or file will be shown.

It is important to know that with a syntax error, there is always a message from the compiler explaining what the error is. For example, “insert ; to complete statement” (insert a semicolon at the end of the statement), “unclosed comment” (the multiline comment is not closed), etc.

Example 6

Consider the class Motorcycle, which contains several syntax errors (Figure 7). You can see on the left side that the file name “Motorcycle.java” is underlined in red.

The first syntax error is that the reserved word “class” is written in uppercase letters (“CLASS”). It can be seen (on the first line of code) that this word is highlighted in red, and a red line appears along the right edge, indicating that the first line of code is syntactically incorrect (Figure 7).

Syntax errors in IntelliJ’s code editor
Figure 7. Syntax errors in IntelliJ’s code editor

The second syntax error is that the multiline comment is not closed (with */). It can be seen that along the right edge of the editor, there is a red rectangle that highlights these lines of code (from the start of the comment to the end of the file) as syntactically incorrect (Figure 7).

Finally, the total number of syntax errors in that class (or file) is shown by the number two in the top-right corner of the editor (Figure 7).

Also, if the mouse cursor is placed on the syntax error, i.e., the word “CLASS”, an error description will appear: “Cannot resolve symbol ‘CLASS’”, meaning that the compiler does not recognize the word CLASS as a reserved word or as the name of a class, attribute, etc. The same text will appear if the mouse cursor is placed on the right edge of the editor, over the red line that marks the first line of code. If the mouse cursor is placed over the red rectangle that marks the syntax error due to the unclosed comment, the message “Unclosed comment” will appear.

If the syntax errors are corrected, the colors and indicators in the code editor change immediately (Figure 8).

Syntax coloring in IntelliJ’s code editor
Figure 8. Syntax coloring in IntelliJ’s code editor

From all of this, it can be concluded that IntelliJ’s code editor has several features related to syntax checking:

  1. Syntax checking is real-time, while the programmer types the code.
  2. All syntax errors are highlighted in red.
  3. With each syntax error, a message from the compiler is displayed explaining what the error is.
  4. There is also syntax coloring where reserved words are shown in yellow, local variables in gray, etc.

All these features are aimed at helping the programmer write and check code for syntax correctness more easily. Additionally, there is something called automatic code completion (also known as “code completion”), which helps write code faster.

When the programmer starts typing a reserved word, class name, or variable name (usually the first two or three letters), the editor suggests the completion of that word, name, etc., and it’s enough to press the “Return/Enter” key (for a new line) for that word to be inserted into the code (Figure 9). If there are multiple suggestions, the user can choose between them using the up and down arrows before pressing Enter.

Automatic code completion in IntelliJ code editor
Figure 9. Automatic code completion in IntelliJ code editor

Moreover, there are shortcuts that can be activated for:

  1. Performing an action (e.g., Shift+F10 to run the program or Ctrl+F to search for a word within a file).
  2. Adding a large chunk of code into the editor (e.g., typing “sout” and pressing “Enter” to insert the command
    “System.out.println();” — Figure 10, or typing “main” and “Enter” to insert the main method — Figure 11).
Automatic code completion in IntelliJ code editor
Figure 10. Automatic code completion in IntelliJ code editor
Automatic code completion in IntelliJ code editor
Figure 11. Automatic code completion in IntelliJ code editor

Most common errors

Some of the most common syntax errors related to class declarations and writing comments are:

  • Incorrectly writing the reserved word class in uppercase, e.g., CLASS, Class, ClAss, etc.
  • Forgetting to write an open ({) or closed brace (}) when forming the body of a class or any other block of statements.
  • Writing a single-line comment with one slash (/) instead of two (//).
  • Forgetting to close a multi-line comment with the asterisk and slash (*/).
  • Forgetting to start a multi-line comment with the asterisk and slash (/*).
  • Adding a blank space in the class name, e.g., writing My Car instead of MyCar.
  • Writing comments with backslashes () instead of regular slashes (/).

Time for tasks

Now that you have read the theory lesson, it is time for tasks. If you haven’t already, install IntelliJ with the Jetbrains Academy plugin, and load the Interactive Java workbook part 1. It is contained in the “Interactive Java workbook 1.zip” file accompanying this book.

Installation and introductory video tutorials can be found in this playlist for convenience.

Happy coding!!!

What are attributes

Lesson Content

  • Attributes - definition, and meaning
  • Simple and complex data types in Java
  • Java naming convention for attributes
  • Default and initial values
  • Code formatting: indentation
  • Most common errors

Attributes - definition, and meaning

As already mentioned, attributes represent certain characteristics (properties) of classes.
For the class Person, these might be: name, surname, gender, age, etc. These characteristics are often expressed through some number, letter, or string of characters.

Defining attributes is relatively simple and looks like this:

1 data_type attributeName;

Simple and complex data types in Java

A data type represents a set of possible values for an attribute – integers, real numbers, letters, strings of characters, or something else.

Some of the most commonly used data types in Java are:

  • int – integers, e.g., 1, -55, 0, 1000000.
  • double – real numbers, e.g., 12.55, -234.77, 0.21, 6.371e6 (which is the same as 6371000.0), 6.371e-2 (which is the same as 0.06371).
  • char – a single character (letter, digit, or some other character), e.g., ‘a’, ‘A’, ‘e’, ‘!’, ‘;’, ’ ’ (space), ‘4’, ‘9’.
  • boolean – a logical variable, can only have the values true or false (one of these two values).
  • String – a string of characters, e.g., “River”, “Pera”, “123”, “zx!”.
  • LocalDateTime – date and time, e.g., 2008-12-31 10:50.

It should be noted that the double type can also receive an integer value (for example, -34), but Java will automatically convert this to a real number (i.e., decimal format -34.0) before assigning it. The reverse does not apply, i.e., if we try to assign 2.55 to an int variable, it will result in a syntax error.

Java naming convention for attributes

According to the Java naming convention, attribute names should start with a lowercase letter:

  • age
  • brand
  • model, etc.

If the attribute name consists of multiple words, all words are written together. The first word starts with a lowercase letter, and all other words start with uppercase letters:

  • socialSecurityNumber
  • firstName
  • engineNumber, etc.

An important rule in Java is that all commands must end with a semicolon (;). So, the attribute declaration ends with a semicolon.

Default and initial values

Types like int, double, char, and boolean are simple data types, while String and LocalDateTime are complex types. String is a sequence of characters, while LocalDateTime contains day, month, year, hour, minute, etc. All complex data types in Java are represented by using classes, so String and LocalDateTime are two predefined Java classes.

It is important to note that there are other simple data types, but they are not used as frequently: long (a set of integers with a range greater than the int type), short (a set of integers with a smaller range than int), and float (a set of real numbers, but with fewer decimal places than the double type).

Each variable in Java gets a default value when declared unless a different initial value is explicitly specified in the code. The default value depends on the type, so for example:

  • int and other integer variables receive the default value of zero (0).
  • double and other real number variables receive the default value of zero in decimal format (0.0).
  • boolean variables receive false.
  • All complex-type variables (any class) receive the default value of null.

Also, in Java, char values (characters) must be written in single quotation marks (e.g., ‘A’), while String values must always be written in double quotation marks. For example, “BANANA” is a String value, but “A” is also a String value (even though it contains only one letter) because it’s enclosed in double quotation marks (a char value would be ‘A’).

Example 1

Create a class called CashMachine. This class should only have the attribute “balance” representing the amount of money currently in the machine (real number).

1 class CashMachine {
2     double balance;
3 }

Example 2

Create a class called Computer. This class should have the following attributes: processor speed (real number, e.g., 4.0 GHz), ram (real number, e.g., 2.0 GB), and hard disk (integer, e.g., 120 GB).

1 class Computer {
2     double processorSpeed;
3     double ram;
4     int hardDisk;
5 }

The order in which attributes are defined within the class is not important. What matters is that all necessary attributes are declared with the appropriate types and names.

Example 3

Write the code for the Computer class from the previous example in multiple ways, changing the order of the attribute declarations (multiple solutions are given).

1 class Computer {
2     double ram;
3     double processorSpeed;
4     int hardDisk;
5 }

or

1 class Computer {
2     int hardDisk;
3     double ram;
4     double processorSpeed;
5 }

or

1 class Computer {
2     double processorSpeed;
3     int hardDisk;
4     double ram;
5 }

It’s possible to assign an initial value to an attribute when defining the class. This is not the default value assigned by Java, but the programmer can provide an initial value for the attribute if necessary. The default value is assigned where the programmer hasn’t specified an initial value. The initial value is assigned to an attribute in the following way:

1 data_type attributeName = value;

Example 4

Modify the Computer class from previous examples so that the initial value for the processor speed is 4.0 (GHz), ram is 16.0 (GB), and the hard disk has 500 (GB). Save the class as a new class, Computer2.

1 class Computer2 {
2     double processorSpeed = 4.0;
3     double ram = 16.0;
4     int hardDisk = 500;
5 }

To shorten the declaration of attributes, it’s possible to combine declarations of attributes of the same type into one declaration as follows:

1 data_type attributeName1, attributeName2, attributeName3;

Or, if initial values are required for these attributes, they can be assigned like this:

1 data_type attributeName1 = value1, attributeName2 = value2, attributeName3 = value3;

Example 5

Create a class called Person. This class should have the following attributes:

  • name (String, initial value “unknown”)
  • surname (String, initial value “unknown”)
  • height (real number, e.g., 186.5 cm)
  • weight (real number, e.g., 80.5 kg)
  • gender (char)

Write the code for this class in two ways:

  • Without combining declarations of attributes of the same type – class Person.

  • With combining declarations of attributes – class Person2.

    class Person { String name = “unknown”; String surname = “unknown”; double height; double weight; char gender; }

    class Person2 { String name = “unknown”, surname = “unknown”; double height, weight; char gender; }

Code Formatting: Indentation

It has already been mentioned that breaking commands into multiple lines and adding spaces is done to make the code more readable for the programmer – in terms of compiling, it doesn’t matter. Here, we should also mention that a typical transformation is used to increase readability: indentation of code (shifting lines of code to the right).

Code indentation, along with breaking commands into multiple lines, is part of the code formatting rules. The idea of indentation is that all code within a block of commands (e.g., the body of a class) is “shifted to the right” relative to commands outside that block (e.g., relative to the class declaration). This means that the code is not written from the left edge but is moved a few spaces to the right (usually four to eight spaces, or one tab character). The closing curly brace that marks the end of the block remains aligned with the left edge. Also, any code within the body of a method (a new block of commands) should be indented an additional 4-8 spaces compared to the method declaration, and so on. Usually, the programming environment, or more precisely its code editor, automatically handles indentation.

Example 6

Let’s take the Person class from the previous example with the attributes name, surname, height, weight, and gender. Without following the code formatting rules, this class could be written like this:

1 class Person {String name = "unknown";String surname = "unknown";double height;double weight;char gender;}

This code is less readable because everything is written in one line. If the rules for breaking commands into new lines are followed, the code becomes more readable:

1 class Person {
2 String name = "unknown";
3 String surname = "unknown";
4 double height;
5 double weight;
6 char gender;
7 }

This code is more readable, but each command is written at the left edge without indentation. This makes it hard to see that the attributes name, surname, height, weight, and gender are part of the block of commands that form the class body. If this class had methods (and each method has its own declaration and body forming a new block of commands), this would be even harder to notice.

If we now introduce the rules for code indentation (each command within the body of the class is indented six spaces or one tab relative to the class declaration), the code becomes even more readable – which is the ultimate goal:

1 class Person {
2     String name = "unknown";
3     String surname = "unknown";
4     double height;
5     double weight;
6     char gender;
7 }

Most common errors

Some of the most common syntax errors related to attribute declarations and assigning initial values are:

  • Incorrectly writing reserved words int, double, char, boolean in uppercase, e.g., INT, DOUBLE, CHAR, etc. There are certain classes in Java that are somewhat equivalent to these simple types, so Double and Integer won’t trigger a syntax error, but the code won’t behave as expected.

  • Incorrectly writing the names of predefined complex types String, LocalDateTime in lowercase, e.g., string, localdatetime, etc. These are classes, so the naming convention for classes applies: the first letter of each word is uppercase, and all words are connected.

  • Forgetting to write a semicolon (;) at the end of each command.

  • Writing char values with double quotation marks instead of apostrophes (e.g., “W” instead of ‘W’).

  • Writing String values with apostrophes instead of double quotation marks (e.g., ‘sun’ instead of “sun”).

  • Including a space in the attribute name, e.g., writing processor Speed instead of processorSpeed.

Other common errors that aren’t syntax errors, but affect the program’s functionality, include:

  • Using real data types (float, double) in situations where an integer data type is appropriate (int, short, long). For example, the number of students is an integer, even though someone may declare it as a real number. Although this is not a syntax error, it may cause issues later because some operators in Java work only with integers.

Time for tasks

Now that you have read the theory lesson, it is time for tasks. If you haven’t already, install IntelliJ with the Jetbrains Academy plugin, and load the Interactive Java workbook part 1. It is contained in the “Interactive Java workbook 1.zip” file accompanying this book.

Installation and introductory video tutorials can be found in this playlist for convenience.

Happy coding!!!

Objects, program execution and printing on the screen

Lesson Content

  • Objects - definition, and purpose
  • Executing (running) a Java program (main method)
  • Printing on the screen (print and println methods)
  • Most common errors

Objects - definition, and purpose

An object represents a specific instance or occurrence of a class. Therefore, a class can be defined as “a set of objects that have the same properties and behaviors”. The relationship between a class and an object is shown in the following example.

Example 1

The car represents a class because it is a general blueprint of some characteristics and behaviors that every car has (Figure 12, top).

The Car Class and three specific objects (instances) of that class
Figure 12. The Car Class and three specific objects (instances) of that class

However, “Aston Martin DB9” with a production year of 2008 and registration “A123-456” represents a specific instance of a car, i.e., an object of the car class. The relationship between the class and the object is shown in the following image (Figure 12, bottom).

In Java, objects are declared similarly to class attributes. First, the class name is mentioned, and then the name of the specific object. In this way, a variable is created that will reference a specific object. As mentioned earlier, if no initial value is assigned, every object gets the default value of null.

1 ClassName objectName;

However, an object needs to be initialized before it can be used (make calls to its methods, change the values of its attributes, etc.). If you try to access an object without initialization, Java will report an error. Initialization is done using the “new” command as follows:

1 objectName = new ClassName();

Initialization can also be done immediately during object declaration (for shorter code), similar to how initial values can be assigned to attributes:

1 ClassName objectName = new ClassName();

What actually happens when an object is initialized (Figure 13)? When an object (variable) is declared, only a pointer is created that will reference the object, and it gets the default value null (upper half of Figure 13).In Java, variables that represent objects only contain the memory address of the object (where the object is located), not the object itself. This null value actually means that the pointer does not point to any memory address , i.e., the object does not yet exist.

The object initialization process
Figure 13. The object initialization process

When initialization is performed, only then a part of the computer’s memory (RAM memory) is allocated for the object and linked to the pointer. From that point on, the pointer contains the object’s memory address (lower half of Figure 13). Only then can the object be used.

It would also be interesting to explain how objects are deleted in Java (Figure 14). It is quite normal in the execution of a regular program that hundreds or thousands of objects are created (initialized) in a short period. Each of these objects occupies a part of the computer’s memory, and it often happens that many of them are no longer needed and should be deleted, i.e., removed from memory. If this is not done, memory overload can occur.

Deleting objects in Java (Garbage collection mechanism)
Figure 14. Deleting objects in Java (Garbage collection mechanism)

Java has a mechanism called “Garbage collection” (Figure 14) that automatically deletes unnecessary objects. An object is unnecessary if no variable or pointer points to it. In other words, it is enough to assign null to the variable (pointer) or initialize a new object through the same variable, and the old object will be automatically deleted after some time.

Executing (running) a Java program (main method)

For a Java program to run, it must have the so-called “main” method. Methods are covered in detail in the next lesson, so only the specifics related to the main method are given here.

Similar to a class, the main method has its header (declaration), followed by a block of statements that forms the body of the main method (it starts with ‘{’ and ends with ‘}’).

The main method is written within the body of the class, and its header is always written in the same way (public static void main(String[] args)). The desired statements are written within the body of the main method (which represents a new block of statements), so it looks like this:

1 public static void main(String[] args) {            
2     //statements...
3 }

Each Java class can have a main method (at most one), so the program can be run from any class. Code formatting rules also apply here, so additional indentation is performed on the statements within the block of the main method. In this way, it is immediately clear which statement belongs to which part of the program, i.e., which block of statements.

Example 2

Create a class Motorcycle that has:

  • Attribute makeModel.
  • Attribute engineCapacity (integer).

Add a main method to the Motorcycle class and, within it, create two objects of the Motorcycle class.

 1 class Motorcycle {
 2     String makeModel;
 3     int engineCapacity;
 4 
 5     public static void main(String[] args) {
 6         Motorcycle m1;
 7         Motorcycle m2;
 8 
 9         m1 = new Motorcycle();
10         m2 = new Motorcycle();
11     }
12 }

Although the previous example demonstrated the rule that each class can have a main method, it is recommended that the main method be separated into its own class, which will only serve to run the program. This way, the code for running the program is separated from the code for attributes and regular methods. This can be seen in the next example.

Example 3

Create a class Motorcycle2 that has:

  • Attribute makeModel.

  • Attribute engineCapacity (integer).

    class Motorcycle2 { String makeModel; int engineCapacity; }

Create a class Test that contains the main method and, within it, creates two objects of the Motorcycle class, but with the objects initialized immediately during the declaration.

 1 class Test {
 2 
 3     public static void main(String[] args) {
 4         Motorcycle2 m1 = new Motorcycle2();
 5         Motorcycle2 m2 = new Motorcycle2();
 6 
 7         //Or the variables can first be declared and
 8         //then initialized in the following lines.
 9 
10         //Motorcycle2 m1;
11         //Motorcycle2 m2;
12         //m1 = new Motorcycle2();
13         //m2 = new Motorcycle2();
14     }
15 }

An object is an occurrence of a class that has specific attribute values. To change or read the values of an attribute, it must be accessed in a specific way. Access to object attributes is done through the object name and the attribute name, separated by a dot:

1 objectName.attributeName

Example 4

Use the class Motorcycle2 from the previous example and create the class Test2 that, wihin its main method creates two objects of the Motorcycle2 class.

  • The first motorcycle should be “Suzuki GS” with 500 cc engine capacity (assign these values to the first object’s attributes).

  • The second motorcycle should be “Yamaha R6” with 600 cc engine capacity (assign these values to the second object’s attributes).

    class Test2 {

     1   public static void main(String[] args) {
     2       Motorcycle2 m1;
     3       Motorcycle2 m2;
     4 
     5       m1 = new Motorcycle2();
     6       m2 = new Motorcycle2();
     7 
     8       m1.makeModel = "Suzuki GS";
     9       m1.engineCapacity = 500;
    10 
    11       m2.makeModel = "Yamaha R6";
    12       m2.engineCapacity = 600;
    13   }
    

    }

In Java, commands are executed in the order in which they are written (“top to bottom”). For example, if commands for accessing attributes are written first within the main method, and object initialization follows, Java will report an error.

Printing on the screen (print and println methods)

The previous two examples contain the main method and can be executed. However, the result of their execution will not be visible—objects will be initialized, values will be assigned to attributes, but nothing will be printed on the screen.

The command for printing on the screen (in Java, the screen is called “standard output”) is as follows:

1 System.out.println( ...some text or String and/or value... );

The result of executing this command is printing (on the screen) the content inside the parentheses and moving to a new line. This content can be some text (String value written in quotation marks), the value of some attribute (or variable), or a combination of both.

If there are multiple values (e.g., a String and another number), the concatenation operator (+) is used to combine parts of the message into one whole.

Here, System is a predefined Java class representing the computer on which the program is executed and is written with an uppercase letter. Also, out is an attribute of the System class that represents the standard output (screen), and it is invoked as System.out.

The println command is actually a method of the out attribute that prints something to the screen. In the next example, it is shown exactly what this command does and how to use it.

Every time a program is executed, a special “Run” frame appears on the bottom of the IntelliJ window which shows the status of the program, and if it ended correctly (“Process finished with exit code 0”). If the program prints something on the screen, the output text is also displayed there (in the “Run” frame). This frame can be shown or hidden by clicking on the grey triangle (“Play”) button in the lower left part of the screen.

Example 5

Here are several examples of using the “println” command to print on the screen and the results that will be displayed. All examples are written in the main method of the class PrintExamples1.

 1 class PrintExamples1 {
 2 
 3     public static void main(String[] args) {
 4         System.out.println ("Nice day today");
 5         // Prints on the screen:
 6         // Nice day today
 7 
 8         int number;
 9         number = 12;
10         System.out.println (number);
11         // Prints on the screen:
12         // 12
13 
14         System.out.println ("This sentence prints in the first row");
15         System.out.println ("This sentence prints in the second row");
16         // Prints on the screen:
17         // This sentence prints in the first row
18         // This sentence prints in the second row
19 
20         int temperature;
21         temperature = 21;
22         System.out.println ("Temperature is: " + temperature);
23         // The role of the plus (+) sign is to allow the
24         // concatenation (joining) of the message "Temperature
25         // is: " with the temperature value (21).
26         // Prints on the screen:
27         // Temperature is: 21
28     }
29 
30 }

Run the program with the Ctrl+F2; command or by clicking the run program icon (green triangular “Play” button)

Since this program is printing something on the screen, the “Run” frame will appear on the bottom and display the text as well as program finishing status.

Example 6

Use the Motorcycle2 class from previous examples and the Test2 class that contains the main method.

Modify the main method of the Test2 class so that it prints the values of both objects’ attributes on the screen.

 1 class Test2 {
 2 
 3     public static void main(String[] args) {
 4         Motorcycle2 m1;
 5         Motorcycle2 m2;
 6 
 7         m1 = new Motorcycle2();
 8         m2 = new Motorcycle2();
 9 
10         m1.makeModel = "Suzuki GS";
11         m1.engineCapacity = 500;
12 
13         m2.makeModel = "Yamaha R6";
14         m2.engineCapacity = 600;
15 
16         System.out.println(m1.makeModel);
17         System.out.println(m1.engineCapacity);
18 
19         System.out.println(m2.makeModel);
20         System.out.println(m2.engineCapacity);
21         //It will print:
22         //Suzuki GS
23         //500
24         //Yamaha R6
25         //600
26     }
27 }

One of the variants of the command for printing on the screen is the “print” command.
The only difference compared to the “println” (print-line) command is that after printing the text, the cursor does not move to the next line, and it continues printing in the same line.

1 System.out.print( ...some text and/or value... );

Example 7

Here are a few examples of using the “print” command for printing on the screen and the results that will be displayed. All examples are entered in the main method of the PrintExamples2 class.

 1 class PrintExamples2 {
 2 
 3     public static void main(String[] args) {
 4         System.out.print("Word1");
 5         System.out.print("Word2");
 6         //Notice that it prints both words in the same
 7         // line, and we have not added any "space"
 8         // between them so the words are "stuck" together
 9         // Prints on the screen:
10         // Word1Word2
11 
12         // Prints a sign for the new line i.e.
13         // moves the cursor to a new line
14         System.out.println();
15 
16         int temperature;
17         temperature = 21;
18         System.out.print("The temperature is: "+temperature+" degrees");
19         System.out.print (" in London");
20         // Prints on the screen:
21         // The temperature is 21 degrees in London
22     }
23 
24 }

It is important to note something about concatenating message parts. Usually, there should be at least one space between these parts so that the text is not “stuck” together. This space can be added directly in the String value before or after the concatenation operator, or as a completely separate space if no part of the message is a text value (i.e. only numeric or boolean values).

When concatenating, if none of the message parts is a String value, the concatenation operator will be interpreted as an addition operator (the same operator is written as +) and it will print the result of the addition on the screen.

Example 8

Here are several examples of using the “println” commands for printing on the screen and the results that will be displayed, with attention paid to how the message is concatenated. The code can be found in the main method of the PrintExamples3 class.

 1 class PrintExamples3 {
 2 
 3     public static void main(String[] args) {
 4 
 5         System.out.println ("beautiful"+"day"+"today");
 6         // Prints on the screen (no space sign entered
 7         // to separate words when concatenating the
 8         // message parts, so words are "stuck"):
 9         // beautifuldaytoday
10 
11         System.out.println ("beautiful" + "day" + "today");
12         // Prints THE SAME on the screen (no space sign entered
13         // WITHIN the String values to separate words when
14         // concatenating the message parts, spaces only outside
15         // String values, around the + operator):
16         // beautifuldaytoday
17 
18         System.out.println ("beautiful " + "day" + " today");
19         // Prints on the screen (entered space sign at the end
20         // of the string value "beautiful " and at the beginning
21         // of the String value " today"):
22         // beautiful day today
23 
24         System.out.println ("beautiful" + " day " + "today");
25         // Prints on the screen (entered space sign at the beginning
26         // and the end of the String value " day "):
27         // beautiful day today
28 
29         int temperature;
30         temperature = 21;
31         System.out.println ("The temperature is"+temperature);
32         // print on the screen (message parts are "stuck"
33         // because no space sign is entered at the end
34         // of String value "The temperature is"):
35         // The temperature is21
36 
37         System.out.println ("The temperature is" + temperature);
38         // Prints on the screen (still "stuck" as space sign
39         // is not entered in the string but around the + operator):
40         // The temperature is21
41 
42         System.out.println ("The temperature is " + temperature);
43         // Prints on the screen:
44         // The temperature is 21
45 
46         System.out.println (21 + 32);
47         // Prints on the screen (no part of the message is
48         // String value, so the operator + is interpreted
49         // as addition and the numbers are added 21 + 32 = 53):
50         // 53
51 
52         System.out.println (21 + " " + 32);
53         // Prints on the screen:
54         // 21 32
55 
56         // Will print because the number code
57         // for character 'A' is 65, and no part od the
58         // message is a String value. The number 65 will
59         // be added to 32 which gives 97
60         System.out.println ('A' + 32);
61         // Prints on the screen:
62         // 97
63     }
64 }

One of the common questions that beginner programmers have is: “Why is my program not running even though it has a main method?” If there are no syntax errors, the problem is often that the program is running, but it is not printing anything to the screen. If there are no calls to the print or println methods anywhere in the program, the program will not print anything, even though it will execute normally.

Example 9

Here is an example of a program that runs normally but does not print anything to the screen. The Meal class has two attributes, and in the main method of the Test3 class an object of the Meal class is created and values are entered into its attributes.

 1 class Meal {
 2     String name;
 3     boolean isVegan;
 4 }
 5 
 6 class Test3 {
 7 
 8     public static void main(String[] args) {
 9         Meal m1 = new Meal();
10 
11         m1.name = "baked beans";
12         m1.isVegan = true;
13     }
14 
15 }

However, neither the print nor the println method is called. Therefore, nothing will be printed when the program is run. This creates the illusion that the program is not working.

Most common errors

Some common syntax errors related to the declaration and initialization of objects, accessing attributes, printing to the screen, and the main method are:

  • Incorrectly writing the class name when creating an object with a lowercase first letter:

    motorcycle m; // Incorrect

Instead of (correct):

1 Motorcycle m;  // Correct
  • Forgetting to initialize the object before using it:

    Motorcycle m; m.make = “Honda”;

Instead of (correct):

1 Motorcycle m = new Motorcycle();
2 m.make = "Honda"; 
  • Incorrectly writing the class name System when calling System.out.println or System.out.print with a lowercase first letter, i.e., “system.out.println” or “system.out.print”.

  • Forgetting to write parentheses when calling the println command, e.g.:

    System.out.println “some text”; // Incorrect

Instead of (correct):

1 System.out.println("some text");  // Correct
  • Writing String (textual) values within the println command with apostrophes instead of quotes, e.g.:

    System.out.println(‘Some text’); // Incorrect

Instead of (correct):

1 System.out.println("Some text");  // Correct
  • Forgetting to write the + operator for concatenating message parts, e.g.:

    System.out.println(“Some text: “ temperature); // Incorrect

Instead of (correct):

1 System.out.println("Some text: " + temperature);  // Correct
  • Forgetting to write double quotes at the beginning and end of each String value when concatenating multiple message parts, e.g.:

    System.out.println(“Some text: + temperature); // Incorrect

Instead of (correct):

1 System.out.println("Some text: " + temperature);  // Correct
  • Incorrectly writing the declaration of the main method (reversed order of reserved words or incorrectly writing those words), e.g.:

    void static public main (String[] args); // Incorrect order

Instead of (correct):

1 public static void main(String[] args);  // Correct order
  • Forgetting to begin and/or close a new block of statements with curly braces {} after declaring the main method, or incorrectly starting and ending the body of the main method.

Other common errors that are not syntax errors, but affect the program’s behavior:

  • Misjudging that the program is not working, when in fact it is executing but just not printing anything on the screen.

  • Printing a message to the screen where no part of the message is a String value. In that case, the concatenation operator will be interpreted as an addition operator (even if one part of the message is a char). For example:

    // Will print 53 System.out.println(21 + 32);

    // Will print 97 because the code for the letter A is 65, so // adding 32 results in 97 System.out.println(‘A’ + 32);

Time for tasks

Now that you have read the theory lesson, it is time for tasks. If you haven’t already, install IntelliJ with the Jetbrains Academy plugin, and load the Interactive Java workbook part 1. It is contained in the “Interactive Java workbook 1.zip” file accompanying this book.

Installation and introductory video tutorials can be found in this playlist for convenience.

Happy coding!!!

What’s next?

The accompanying book containing only the theory lessons is free (PDF, EPUB).

If you have finished evaluating this demo version of the Interactive Java workbook collection of tasks (tasks from the “Interactive Java workbook 1.zip” file), and you like the lessons and tasks, you may purchase the full version with all workbook parts containing all tasks.

As stated in the introduction, the Interactive Java workbook consists of several parts due to the many topics and even more tasks.

NOTE: Not all parts are completed at this time, but please see this LeanPub page, for updates and purchases. If you like this workbook, please feel free to share the link to the workbook’s page.

Once purchased, you can load the next part of the workbook (here is the how-to video).

This is work in progress, so the following list is not yet final. The parts and the topics they cover are as follows:

  1. Interactive Java workbook part 1 - demo + full version (COMPLETED)
  • Introduction
  • Class
  • Attribute
  • Objects, program execution and output to the screen
  1. Interactive Java workbook part 2 (COMPLETED)
  • Methods and arithmetic operators
  • Constructor
  • Global variables and methods
  • Constants
  • Relationships
  • Enumerated type
  1. Interactive Java workbook part 3 (COMPLETED)
  • Algorithms and control statements (commands)
  • The if statement
  • The switch statement
  • The for statement
  • The while statement
  • The do-while statement
  1. Interactive Java workbook part 4 (80% COMPLETED, NOT PUBLISHED YET)
  • Introduction to arrays
  • The for-each statement
  • Algorithms for working with arrays - overview
  • Search and print array elements
  • Adding elements to an array
  • Replacing array elements
  1. Interactive Java workbook part 5 (80% COMPLETED, NOT PUBLISHED YET)
  • Deleting array elements
  • Group operations on array elements
  • Array sorting
  • Working with multiple strings
  • Cumulative test 1
  • Cumulative test 2
  • Cumulative test 3
  • Cumulative test 4
  • Cumulative test 5
  • Cumulative test 6
  • Cumulative test 7
  • Cumulative test 8
  1. Interactive Java workbook part 6 (50% COMPLETED, NOT PUBLISHED YET)
  • Arrays of objects
  • Working with text (String, StringBuilder)
  • Working with dates and times (LocalDate, LocalTime, LocalDateTime)
  1. Interactive Java workbook part 7 (10% COMPLETED, NOT PUBLISHED YET)
  • Packages and access levels (private, public, protected, default)
  • JavaBeans specification
  • Inheritance
  • The Object class
  • Cumulative test 9
  • Cumulative test 10
  • Cumulative test 11
  • Cumulative test 12
  • Cumulative test 13
  • Cumulative test 14
  • Cumulative test 15
  • Cumulative test 16
  1. Interactive Java workbook part 8 (0% COMPLETED, NOT PUBLISHED YET)
  • Abstract classes and inheritance
  • Interfaces
  • Lists in Java
  • Error handling - exceptions
  1. Interactive Java workbook part 9 (0% COMPLETED, NOT PUBLISHED YET)
  • Input and output streams in Java
  • Working with the keyboard
  • Working wih textual files
  • Serialization of objects
  • Cumulative test 17
  • Cumulative test 18
  • Cumulative test 19
  • Cumulative test 20
  • Cumulative test 21
  • Cumulative test 22
  • Cumulative test 23
  • Cumulative test 24

Methods

Lesson Content

  • Methods (header, naming convention, body, return value)
  • Variables
  • The assignment operator (=)
  • Method parameters
  • Most common errors

Methods (header, naming convention, body, return value)

It has already been stated that classes can have certain behaviors, and these behaviors are expressed in the form of methods. For example, methods in the “Car” class might include: start, stop, turnLeft, turnRight, etc.

Methods actually contain one or more commands that need to be executed as a whole when the method is called. Method definitions in Java are made within the body of the class as follows:

1 return_data_type methodName(...parameters...) {
2     // Method body
3 }

The return data type of the method, its name, and the parameters form the method header (declaration or signature), while all the code written between the curly braces (including the braces themselves) forms the method body (methods block of commands). Like any block of commands, the method body must start wit h an opening curly brace { and end with a closing curly brace }.

Methods define the behavior of the class. In addition to performing a task, a method may also return a value as a result of its execution (methods’ return value). For example, the “add” method of a “Calculator” class could return a number representing the result of an addition.

On the other hand, some methods do not have a return value. An example of such a method would be the “printName” method of the “Person” class, which simply prints the person’s name on the screen. When a method does not return any value, the return data type is void, and when it returns something, the return data type can be any simple or complex data type (int, double, char, boolean, String, LocalDateTime or any class…).

A method can have at most one return value.

The Java naming convention states that, method names are written the same way as attribute names. So, the first word starts with a lowercase letter, and if there are multiple words, the remaining ones start with an uppercase letter, i.e.:

  • add
  • subtract
  • printName
  • convertCurrency, etc.

Finally, methods can also have input values - parameters. Parameters represent the values that need to be passed to a method so it can execute correctly. For example, the “add” method of the “Calculator” class might have two numbers as parameters that need to be added. If the method has no parameters, the space between the parentheses is left empty (only “()” is written). Parameters will be explained in more detail later.

Example 1

Create the TelevisionSet class. This class should have:

  • A volume attribute, which is an integer representing the current volume of the television. The initial value of this attribute is 0 (the volume is turned off).

  • A currentChannel attribute, representing the channel currently being shown on the television (e.g., channel 5 is on). The initial value of this attribute is 1.

  • An isOn attribute, indicating whether the television is turned on or off (TRUE if it is on, FALSE if it is off). The television is assumed to be off initially.

  • A method turnOn that turns the television on (sets the “isOn” attribute to true).

  • A method turnOff that turns the television off (sets the “isOn” attribute to false).

    class TelevisionSet { int volume = 0; int currentChannel = 1; boolean isOn = false;

    1   void turnOn(){
    2       isOn = true;
    3   }
    4 
    5   void turnOff(){
    6       isOn = false;
    7   }
    

    }

In this example, the TelevisionSet class has only two methods – turnOn and turnOff. The “turnOn” method has no return value because it only changes the value of the isOn attribute. Therefore, its return type is void. This method has no parameters because none are needed (it always sets the value of the attribute to true), so the space between the parentheses is empty. The body of the turnOn method contains only one command, which assigns the value true to the isOn attribute. The same applies to the turnOff method.

From the previous example, we can also observe a rule: class attributes can be directly accessed from within any method of the same class. Access is achieved, only by calling the attribute name. In other words, it is not necessary to specify the object name and the attribute name, as when accessing them from outside the class, i.e. from the main method.

Example 2

Add the following methods to the TelevisionSet class and save it as a new class TelevisionSet2:

  • A method increaseVolume that increases the value of the volume attribute by one.
  • A method decreaseVolume that decreases the value of the volume attribute by one.
  • A method muteVolume that completely mutes the volume (sets the volume attribute to 0).

After the modification, the class code looks like this.

 1 class TelevisionSet2 {
 2 
 3     int volume = 0;
 4     int currentChannel = 1;
 5     boolean isOn = false;
 6 
 7     void turnOn(){
 8         isOn = true;
 9     }
10 
11     void turnOff(){
12         isOn = false;
13     }
14 
15     void increaseVolume(){
16         volume = volume + 1;
17     }
18 
19     void decreaseVolume(){
20         volume = volume - 1;
21     }
22 
23     void muteVolume(){
24         volume = 0;
25     }
26 }

The increaseVolume, decreaseVolume, and muteVolume methods have no return value (they do not return anything as a result), so their return type is void. These methods have no parameters.

On the other hand, they increase/decrease current volume settings by adding/subtracting one from the current volume level and storing the new value back in the volume attribute, i.e.:

1 volume = volume + 1;
2 
3 volume = volume - 1;

The methods provided in the previous examples do not return any value as a result. However, there are situations where it is necessary to return a value – sometimes it’s needed to return the current value of an attribute or the result of a calculation.

In those cases, the return data type is written instead of void in the method header. If the method returns an integer, the return type is int; if it returns a floating-point number, the return type is double; if it returns a string of characters, the return type is String, etc.

Additionally, within the method body, the return statement must be written to indicate the value that should be returned. Here’s an important note: when the “return” statement is executed, the method execution is terminated. In other words, any command written after the return statement will have no effect (as it will never be executed) - also, it is considered a syntax error.

Example 3

Add the following methods to the TelevisionSet2 class and save it as a new class TelevisionSet3:

  • A method changeChannelUp that increases the value of the currentChannel attribute by one.
  • A method changeChannelDown that decreases the value of the currentChannel attribute by one.
  • A method getCurrentChannel that returns the value of the currentChannel attribute.
  • A method getVolume that returns the current value of the volume attribute.
  • A method isTurnedOn that returns the current value of the isOn attribute.
  • A method printParameters that prints all the attribute values of the television on the screen with appropriate messages.

After all modifications, the class code looks like this.

 1 class TelevisionSet3 {
 2 
 3     int volume = 0;
 4     int currentChannel = 1;
 5     boolean isOn = false;
 6 
 7     void turnOn(){
 8         isOn = true;
 9     }
10 
11     void turnOff(){
12         isOn = false;
13     }
14 
15     void increaseVolume(){
16         volume = volume + 1;
17     }
18 
19     void decreaseVolume(){
20         volume = volume - 1;
21     }
22 
23     void muteVolume(){
24         volume = 0;
25     }
26 
27     void changeChannelUp(){
28         currentChannel = currentChannel +1 ;
29     }
30 
31     void changeChannelDown(){
32         currentChannel = currentChannel - 1;
33     }
34 
35     int getCurrentChannel(){
36         return currentChannel;
37     }
38 
39     int getVolume(){
40         return volume;
41     }
42 
43     boolean isTurnedOn(){
44         return isOn;
45     }
46 
47     void printParameters(){
48         System.out.println("TV is turned on: " + isOn);
49         System.out.println("Volume: " + volume);
50         System.out.println("Current channel: " + currentChannel);
51     }
52 }

The changeChannelUp and changeChannelDown methods do not return any value. However, the methods getCurrentChannel, getVolume, and isTurnedOn do have return values. The getCurrentChannel method is supposed to return the current value of the currentChannel attribute. Practically, this means that it returns the number of the channel currently being shown on the television (e.g., 5). Since it is an integer (the currentChannel attribute is of type int), the return type of the method will also be int. The body of the getCurrentChannel method contains only the return statement to return the value.

For example, if the following line is written in the body of this method, the method would always return the number 2:

1 return 2;

The getVolume method is very similar to the previous one and also returns an int value representing current volume settings. However, the isTurnedOn method returns the value of the isOn attribute, so its return type is boolean.

Finally, the printParameters method only prints the values of all attributes on the screen with appropriate messages. It’s important to note that printing values on the screen is not the same as returning return values. Values printed on the screen cannot be used later (for calculations or other operations), so it is considered that the printParameters method does not actually return any value. Therefore, the return type of this method is void.

It is important to note that the return value of a method has nothing to do with whether the method prints anything to the screen. If a method has a return value, it means that it returns some number, letter, string, etc., which can be saved in a variable and used later in the program for further calculations, processing, and so on. The return type of such a method is not void. When a method only prints a value to the screen, that value is only visually displayed and cannot be retrieved or saved, further processed, etc. (the return type is void).

It is also possible for a method to do both things. For example, of the three methods shown in the following listing, the first only returns the value of the mathematical constant Pi and does not print anything to the screen, the second only prints the value of Pi to the screen and does not return it, and the third does both.

 1 double returnPi(){
 2     return 3.141592;
 3 }
 4      
 5 void printPi(){
 6     System.out.println(3.141592);
 7 }
 8      
 9 double printAndReturnPi(){
10     System.out.println(3.141592);
11     return 3.141592;
12 }

The question arises, how are methods called? It is similar to calling attributes. In order for methods to be called, an object must first be initialized, and then its method can be called using the object name (variable name) and method name separated by a dot. The difference from calling attributes is that when calling a method, brackets must always be written after the method name (even if the method has no parameters):

1 objectName.methodName();

If a method has a return value, it is recommended to first declare a variable that will receive the method’s return value, and then call the method (though this is not required, the return value can be ignored and discarded). The variable data type must match the return data type of the method:

1 dataType variableName;
2 variableName = objectName.methodName();

Variables

Before moving on to a specific example, it is necessary to clarify what a variable is and what types of variables exist.

A variable represents an identifier (name, letter, expression, etc.) that is linked to some value, and that value can change. Variables are physically represented as a place in the computer’s memory where a value can be stored. The data type of the variable determines the type of the value that can be assigned to it.

For example, a variable of type int can hold an integer value and is physically represented as part of the computer’s memory where an integer value can be stored.

Depending on where they are declared in the code, there are four types of variables:

  • Local variables (or just variables) - variables declared within the method’s body.
  • Attributes - variables directly belonging to objects of a class. They are defined within the body of the class.
  • Global variables - variables that do not belong to individual objects of a class but are used at class level or program level.
  • Parameters - variables of a method that are declared in the method’s header (represent the input values for the method). Although they are technically also local (method) variables, we will explicitly differentiate parameters from other local variables for clarity in the rest of the material.

Attributes were explained in detail in the previous lesson, and local variables and parameters will be explained in this lesson. Global variables will be covered in the following lessons.

Example 4

Use the TelevisionSet3 class from the previous example. Create a class TestTelevisionSet that creates an object of the TelevisionSet3 class and calls some of its methods. After each method call, call the printParameters method and notice the changes in the attribute values.

 1 class TestTelevisionSet {
 2 
 3     public static void main (String[] args){
 4         TelevisionSet3 t = new TelevisionSet3();
 5         boolean on;
 6         int channel;
 7 
 8         t.printParameters();
 9         //It will print:
10         //TV is turned on: false
11         //Volume: 0
12         //Current channel: 1
13 
14         t.turnOn();
15         t.printParameters();
16         //It will print:
17         //TV is turned on: true
18         //Volume: 0
19         //Current channel: 1
20 
21         t.increaseVolume();
22         t.printParameters();
23         //It will print:
24         //TV is turned on: true
25         //Volume: 1
26         //Current channel: 1
27 
28         t.changeChannelUp();
29         t.printParameters();
30         //It will print:
31         //TV is turned on: true
32         //Volume: 1
33         //Current channel: 2
34 
35         on = t.isTurnedOn();
36         System.out.println("The television set is turned on: " + on);
37         //It will print:
38         //The television set is turned on: true
39 
40         channel = t.getCurrentChannel();
41         System.out.println("The television set is currently displaying "+ 
42         "channel "+ channel);
43         //It will print:
44         //The television set is currently displaying channel 2
45     }
46 }

When calling a method that has no return value (for example, the printParameters method), the call is similar to calling an object’s attribute. The only difference is that after the method name, you must write parentheses, where the actual values for the parameters are listed. If the method has no parameters, the space between the parentheses remains empty (“()”):

1 t.printParameters();

Calling a method that has a return value is slightly different. This is the case with methods isTurnedOn and getCurrentChannel. The example shows that a local variable on is declared, within the main method’s body. This variable is of type boolean (the same type as the return value of the method isTurnedOn) and receives the return value of the method:

1 on = t.isTurnedOn();

In this way, the method’s return value is not lost (it is saved in the on variable) and can be used in further program execution. This is illustrated by the following line of code from the example. The on variable is used in a print command to display whether the TV is currently on:

1 System.out.println("The television set is turned on: " + on);

Also, this local variable on has limited scope and can only be used within the main method. Finally, if one looks closely, variable t (object pointer) is also a local variable declared in the main method.

The assignment operator (=)

In many of the previous examples and exercises, one operator has frequently been used. This is the assignment operator (=). A few examples related to this operator are given in the following listing:

1 a = 2;
2 m1.cubic = 750;
3 person1.name = "Peter";
4 ind = obj.method1();
5 object1 = object2;

Essentially, this operator allows assigning a value to a variable, attribute, or object.

On the left side, there must always be a variable (local variable, parameter, attribute…) that will receive the value from the right side of the equality sign.

On the right side of the equality sign, there can be:

  • a concrete value (-34, true, “E”…),
  • a variable (local, parameter, attribute…) of simple or complex data type (object),
  • an expression, or
  • a method call.

It is important that the data type of the left side matches the data type of the right side (e.g., it’s not possible to assign the value 23.44 to an int variable).

When the assignment operator is used in combination with attributes, variables, and methods that contain or return simple type values, the process is relatively simple. The variable on the left side always gets the value from the right side.

However, things get a bit more complicated if the operands are variables representing objects or pointers (Figure 15). Suppose the first operand is a pointer to an object (variable “object1”), and the second operand is also a pointer to some object (variable “object2”). The assignment operator assigns the contents of the variable on the right side to the variable on the left side. In this case, the memory address stored in the variable “object2” will be assigned to the variable “object1”, so both pointers will point to the same memory location, i.e., the same object. The memory location that no pointer is pointing to will soon be freed by the garbage collection mechanism.

Assigning values when variables are of complex data types (objects)
Figure 15. Assigning values when variables are of complex data types (objects)

An additional effect that occurs in this case is that now both pointers access the same memory location (the same object). This can be seen in a practical example.

Example 5

Write a class Product that has only one attribute - name, and one method printProduct that prints the value of this attribute to the screen.

1 class Product {
2     String name;
3 
4     void printProduct(){
5         System.out.println("Product name is: " + name);
6     }
7 }

Create a TestProduct class that creates two objects of the Product class with product names car and tractor and:

  • Print the attribute values of both products.

  • Assign the address of the first object to the second object. Print the attribute values again.

  • Change the name of the second product to combine harvester and print the values again.

    class TestProduct {

     1   public static void main(String[] args) {
     2       Product p1 = new Product();
     3       Product p2 = new Product();
     4 
     5       p1.name = "car";
     6       p2.name = "tractor";
     7 
     8       p1.printProduct();
     9       p2.printProduct();
    10       //It will print:
    11       //Product name is: car
    12       //Product name is: tractor
    13 
    14       //Both variables p1 and p2 now point to the
    15       // same Product object in memory, the
    16       // one named car
    17       p2 = p1;
    18 
    19       p1.printProduct();
    20       p2.printProduct();
    21       // It will print:
    22       //Product name is: car
    23       //Product name is: car
    24 
    25       //It will change the product name
    26       // car to combine harvester
    27       p2.name = "combine harvester";
    28 
    29       p1.printProduct();
    30       p2.printProduct();
    31       // It will print:
    32       //Product name is: combine harvester
    33       //Product name is: combine harvester
    34   }
    

    }

At the moment when the variable (pointer) p2 is assigned the content of the pointer p1, both pointers will reference the same memory location, i.e., the same object. From that point on, any changes made to the object pointed to by p2 will also be “visible” to the object pointed to by p1 and vice versa.

Method parameters

Method parameters are values that need to be passed to the method so it can execute properly. A method can have zero, one, or more parameters, and parameters are declared in the method header in a similar way to variables (first the data type, then the parameter name).

This type of parameter is called a formal parameter (or just a parameter). If a method has more than one parameter, they are separated by commas:

1 return_data_type methodName(parameter_data_type parameterName){
2     // Method body
3 }

Calling methods that have parameters is done by placing concrete values or variables between parentheses. When these concrete values or variables are passed as parameters, they are called arguments (real or actual parameters). When calling such a method, the number of parameters, their types, and the order must be respected, otherwise a syntax error will appear.

In other words, if a method takes two parameters of some data type, every call to that method must ensure that exactly two arguments of the corresponding data type and order are passed.

1 methodName(argument);

It’s also important to note that methods of one class can be (if needed) called from another method of the same class relatively simply — by calling the method’s name and passing parameters. It’s not necessary to create an object for this.

Also, the order in which methods are written in the class is not important, so a method can be called even if it’s written “below” the calling method, for example:

 1 class X {
 2     
 3     void methodB(){
 4         methodA();
 5         System.out.println("B");
 6     }
 7     
 8     void methodA(){
 9         System.out.println("A");
10     }
11 }

Example 6

Create a class CashMachine. This class should have:

  • An attribute balance that represents the current amount of money in the machine (a real number). The initial value of this attribute is 5200.0 dollars.

  • A method printBalance that prints the current amount of money in the machine (the value of the balance attribute) to the screen.

  • A method withdrawAmount that takes as a parameter the amount of money the user wants to withdraw (a real number, e.g., 550.5) and reduces the value of the balance attribute by that amount. Then, it prints the current amount of money to the screen by calling the printBalance method.

  • A method depositAmount that takes as a parameter the amount of money the user wants to deposit (a real number) and increases the value of the balance attribute by that amount. Then, it prints the current amount of money to the screen by calling the printBalance method.

  • A method getBalance that returns the current amount of money in the machine (the value of the balance attribute).

    class CashMachine { double balance = 5200.0;

     1   void printBalance(){
     2       System.out.println("The current balance is: "+ balance);
     3   }
     4 
     5   //"amount" is a formal parameter
     6   void withdrawAmount(double amount){
     7       balance = balance - amount;
     8 
     9       //It is possible to call a method from the
    10       // same class within another method, if needed
    11       printBalance();
    12   }
    13 
    14   //"amount" is a formal parameter
    15   void depositAmount(double amount){
    16       balance = balance + amount;
    17 
    18       //It is possible to call a method from the
    19       // same class within another method, if needed
    20       printBalance();
    21   }
    22 
    23   double getBalance(){
    24       return balance;
    25   }
    

    }

Create a class TestCashMachine in whose main method two objects of the CashMachine class are created. In the first cash machine, you need to deposit 1002.03 dollars and print the balance before and after the deposit. You also need to withdraw 234.55 dollars from the second machine and print the balance before and after the deposit.

 1 class TestCashMachine {
 2 
 3     public static void main (String[] args){
 4         CashMachine c1 = new CashMachine();
 5         CashMachine c2 = new CashMachine();
 6 
 7         //It will print:
 8         //The current balance is: 5200.0
 9         c1.printBalance();
10 
11         //The concrete value 1002.03 is an argument
12         //(a real parameter) which the method
13         // depositAmount receives upon method call.
14         c1.depositAmount(1002.03);
15         //It will print:
16         //The current balance is: 6202.03
17 
18         //It will print:
19         //The current balance is: 5200.0
20         c2.printBalance();
21 
22         //The concrete value 234.55 is an argument
23         //(a real parameter) which the method
24         // withdrawAmount receives upon method call.
25         c2.withdrawAmount(234.55);
26         //It will print:
27         //The current balance is: 4965.45
28     }
29 }

The methods withdrawAmount and depositAmount each have one parameter. In both cases, the parameter is a real number, so the parameter type is double. These are formal parameters because they are defined within the method header. The parameter name is always arbitrary, but it’s important to use this name within the method body when the parameter is needed. These two methods don’t return a value since they only modify the value of the balance attribute. In addition, both methods call the printBalance method to perform the printing of the balance after each deposit/withdrawal. The call is made simply by invoking the method name and passing the parameters.

In the main method of the TestCashMachine class, you can see the difference between parameters (formal parameters) and arguments (actual parameters). The formal parameter of the method depositAmount is amount, which is of type double, while the argument is the specific number or variable that is passed to the method during the call (in this case, the number 1002.03).

Method parameters can be simple data types (int, double, boolean, char), but also complex types (classes). In other words, a method can have an object as a parameter. Additionally, a method can return an object as a result.

Most common errors

Some of the most common syntax errors related to writing methods are:

  • Forgetting to specify void as the return type in the method declaration if the method does not return a value.
  • Forgetting to call the return statement in the method body when the method has a return type that is not void.
  • Writing the return statement in the method body so that it’s not the last statement in that block of commands.
  • Returning a value of the wrong type from the method body via the return statement (e.g., returning a double value when the method is declared to return an int).
  • Forgetting to write the opening and/or closing curly braces at the start and end of the method body (command block).
    • Forgetting to write parentheses when calling a method, e.g.:

      m.turnOn;

instead of (correctly):

1 m.turnOn();
  • Forgetting to pass arguments when calling a method with parameters, e.g.:

    m.turnOn();

instead of (correctly):

1 m.turnOn(true);
  • Calling a method with the wrong order or type of arguments. For example, a method with the declaration

    double power(double number, int exponent)

should NOT be called like this:

1 double result = power(2, 9.56);

but instead (correctly):

1 double result = power(9.56, 2);
  • Calling a method with a return value and storing that return value in a variable of the wrong type. For example, a method with the declaration

    double power(double number, int exponent)

should NOT be called like this:

1 int result = power( 9.56, 2 );

but instead (correctly):

1 double result = power( 9.56, 2 );
  • On the left side of the assignment operator, there is no variable (but instead a method call, expression, etc.), or the left and right sides are “flipped”, e.g.:

    m.calculate(12) = x; 2 + 3.14 = a;

instead of (correctly):

1 x = m.calculate(12);
2 a = 2 + 3.14;

Other common mistakes that are not syntax errors, but affect the program’s behavior are:

  • Incorrectly calling a similarly named method – the name doesn’t match the declaration exactly (case sensitivity, etc.)
  • Incorrectly calling a similarly named method with similar parameters – the order or number of parameters doesn’t match the declaration exactly.

Arithmetic operators

Lesson content

  • Arithmetic operators in Java
  • Operator precedence
  • Most common errors

Arithmetic operators in Java

In the Java programming language, it is possible to write various arithmetic expressions. Some of the previous examples already include the use of the addition and subtraction operators, but the complete list of arithmetic operators can be seen in the following table.

Operator Description Examples
+ Addition a + b; 12 + 5; m1.cubicVolume + 300;
- Subtraction a - b; 12 - 5; m1.cubicVolume - 300;
* Multiplication a * b; 12 * 5; m1.cubicVolume * 300;
/ (integers) Integer Division (DIV) 2 / 12; a / b;
/ (real numbers) Division a / 12.0; 5.0 / 123.44;
% Remainder of integer division (MOD) 5 % 2; a % 10;
++ Increment by 1 (increment) a++; m1.cubicVolume++;
Decrement by 1 (decrement) a—; m1.cubicVolume—;
+= Increment the value of the same variable a += 3;
-= Decrement the value of the same variable a -= 3;
*= Multiply the value of the same variable a *= 3;
/= Divide the value of the same variable a /= 3;
%= Remainder of dividing the same variable a %= 3;

The addition, multiplication, and subtraction operators are used in a similar way as they would be in mathematics. These operators can be used with both integer and real values and variables. Java also allows the use of parentheses, so expressions like this are common:

1 b * 2 * ((234.55 + 20) - (a * 3.4))

Increment and decrement operators increase or decrease the value of an integer variable by one. They provide a shorthand for certain arithmetic expressions, so the following two sets of expressions are entirely equivalent:

1 a = a + 1;
2 b = b - 1;
3 
4 // Is the same as...
5 
6 a++;
7 b--;

Operators +=, -=, *=, /=, and %= were introduced to allow for shorter notation when new values of variables are calculated based on their existing values. Therefore, the following two sets of expressions are equivalent:

 1 a = a + 3;
 2 a = a - 4;
 3 a = a * 5;
 4 a = a / 6;
 5 a = a % 7;
 6 
 7 // Is the same as...
 8 
 9 a += 3;
10 a -= 4;
11 a *= 5;
12 a /= 6;
13 a %= 7;

The division operators in Java are slightly more complex than they may initially seem. First, the operator for real number division and the operator for integer division are both represented by the same symbol - the forward slash (/).

Real number division is “normal division” (e.g., 11.0 / 3 = 3.66666).

Integer division, on the other hand, represents division without remainder (e.g., 11 / 3 = 3). In this case, the decimal part (the part after the decimal point 0.66666) is discarded, and the integer part is returned as the result (3).

Since both operators are written the same way, the question arises as to how the Java compiler distinguishes when to use one and when to use the other. The answer is quite simple – it checks whether the operands (numbers or variables being divided) are integers or real numbers, and applies the appropriate operator.

If both numbers are integers, integer division is used.

If at least one of the numbers is real, real number division is used.

Additionally, division by zero is not allowed, so the following expressions would cause an error:

1 int a = 55 / 0;
2 double b = 123.45 / 0.0;
3 
4 int c = 0;
5 int d = 54 / c;

The operator for calculating the remainder of integer division can only be used with integer values. The result represents the whole number that remains as the remainder when two integers are divided. The division and remainder operators are best explained through a few simple examples.

Example 1

The following expressions in Java will be interpreted as real number division expressions (class ArithmeticExpressions):

 1 class ArithmeticExpressions {
 2 
 3     public static void main(String[] args) {
 4         double x = 3.5;
 5         double y = 7;
 6         double z;
 7 
 8         z = y / x;
 9         //The result is 2.0
10         //Both x and y are decimal numbers.
11 
12         z = y / 7;
13         //The result is 1.0
14         //It is sufficient if just one operand is a real number - y
15 
16         z = 35.0 / 7;
17         //The result is 5.0
18         //It is sufficient if just one operand is a real number - 35.0
19 
20         z = 35 / 7.0;
21         //The result is 5.0
22         //It is sufficient if just one operand is a real number - 7.0
23 
24         //However, the following expressions will be evaluated as integer
25         //division,
26         int a = 2;
27         int b = 12;
28         int c;
29         double r;
30 
31         c = b / a;
32         //The result is 6 (not 6.0 but just 6)
33         //Both a and b are integer variables
34 
35         c = b / 4;
36         ////The result is 3
37         //b is an integer variable
38 
39         c = 123 / 12;
40         ////The result is 10 (the decimal part is omitted)
41         //Both 123 and 12 are integer numbers
42 
43         r = 123 / 12;
44         //The result is 10.0.
45         //Both 123 and 12 are integer numbers, so the first operation
46         // is integer division, giving the result 10, which is then
47         // transformed into a real number value 10.0 and stored in
48         // the double variable r.
49     }
50 }

As seen in the last example, it is possible for unexpected results to occur during division if the wrong operator is used. The cause is always the incorrect use of the integer division operator instead of the real number division operator.

To ensure the previous expression is interpreted as real number division, it needs to be written in one of the following ways:

1 r = 123.0 / 12;
2 r = 123.0 / 12.0;
3 r = 123 / 12.0;
4 
5 // The result is 10.25
6 // It’s important that at least one of the numbers is real

Here are a few examples using the operator for calculating the remainder of integer division:

 1 c = 5 % 2;
 2 // The result is 1 (because 5 = 2 * 2 + 1)
 3         
 4 c = 10 % 2;
 5 // The result is 0 (because 10 = 5 * 2 with no remainder)
 6     
 7 c = 14 % 4;
 8 // The result is 2 (because 14 = 3 * 4 + 2)
 9     
10 c = 55 % 5;
11 // The result is 0 (because 55 = 11 * 5 with no remainder)

This operator can be used to check if a number is even, as the remainder when divided by two will always be zero if the number is even and one if it is odd. Similarly, it can check divisibility by other numbers (the remainder is zero if the number is divisible and non-zero otherwise).

Operator precedence

Finally, as in mathematics, not all arithmetic operators in Java have the same precedence (the order in which they are evaluated in an expression). Java has operator precedence rules which define that:

  • Increment (++) and decrement (—) operators have higher precedence than all other operators.
  • Multiplication (*), division (/), and remainder (%) have higher precedence than addition (+) and subtraction (-).
  • In an expression where parentheses have been used, the part of the expression within the innermost parentheses will be evaluated first, and so on.
  • In an expression where all operators that have been used have the same precedence and no parentheses have been used, the expression will be evaluated left to right.

In other words, if parentheses are not used in an arithmetic expression, operator precedence will be followed, for example:

 1 c = 5 + 3 * 5;
 2 // The result is 20
 3 
 4 c = (5 + 3) * 5;
 5 // The result is 40
 6 
 7 c = 2 * 8 / 8 + 8;
 8 // Result is 10
 9 
10 c = 2 * 8 / (8 + 8);
11 // Result is 1

Most common errors

Some of the most common syntax errors related to using arithmetic operators and the assignment operator are:

  • The variable on the left side and the expression on the right side of the assignment operator are not of the same type, e.g.:

    int number = 12.3; int result = 35.4 - 2; int result2 = m.multiply(154.3, 122.02);

instead of (correct):

1 double number = 12.3;
2 double result = 35.4 - 2;
3 double result2 = m.multiply(154.3, 122.02);
  • Division by zero (zero is the divisor):

    34 / 0;

Other common mistakes that are not syntax errors, but affect program behavior:

  • Incorrectly using integer division instead of real number division. This often results in (unexpected) zero:

    // The result is zero, not 0.5 12 / 24

    // The percentage of girls will be 0.0, not 50.0 int numberOfStudents = 30; int numberOfGirls = 15; double percentageOfGirls = numberOfGirls / numberOfStudents * 100.0;

instead of:

1 // The result is 0.5 in all variations
2 12.0 / 24
3 12 / 24.0
4 12.0 / 24.0
5 
6 // The percentage of girls will be 50.0
7 int numberOfStudents = 30;
8 int numberOfGirls = 15;
9 double percentageOfGirls = (numberOfGirls * 100.0) / numberOfStudents;

Variable scope

Lesson content

  • Variable visibility (variable scope)
  • Visibility conflict and reserved word this
  • Most common errors

Variable visibility (variable scope)

As a reminder, depending on where they are declared in the code, there are four types of variables:

  • Local variables (or simply variables) – variables declared within the body of a method.
  • Attributes – variables directly belonging to the objects of a class. They are defined within the body of the class.
  • Global variables – variables not belonging to individual objects of a class, but used throughout the program. They are defined similarly to attributes.
  • Parameters – variables of a method declared in the method’s signature (representing input values for the method). Technically, they are also local (method) variables.

To fully understand the functioning of methods, it is necessary to introduce and explain the concept of variable visibility. Variable visibility determines from which part of the program a variable can be accessed. In literature, instead of the term “variable visibility” the term variable scope is often used.

The rules defining variable scope in the Java programming language are related to variable types:

  • All local variables (including parameters) are “visible” (can be accessed) only within the body of that method and nowhere else.
  • All attributes of a class are always visible within the body of that class and can be accessed outside the class if not restricted (see lesson on access levels).
  • Global variables can be accessed within the class body and more broadly, unless restricted (see lesson on access levels).

Example 1

Let’s say the class Calculator has:

  • An attribute pi representing the corresponding mathematical constant (the value is roughly 3.14).

  • A method add that takes two integers as parameters and returns the result of their addition.

  • A method calculateCircleCircumference that takes the radius of a circle as a parameter and returns the circumference as the result (the formula is 2 * radius * pi).

    class Calculator { double pi = 3.14;

     1   int add(int a, int b){
     2       int result;
     3       result = a + b;
     4       return result;
     5 
     6       //Or shorter, all in one command
     7       //return a + b;
     8   }
     9 
    10   double calculateCircleCircumference(double radius){
    11       //Not allowed since a and b are local
    12       //variables visible only in the add method
    13       //System.out.println(a + b);
    14 
    15       double result;
    16       result = 2 * radius * pi;
    17       return result;
    18 
    19       //Or shorter, all in one command
    20       //return 2 * radius * pi;
    21   }
    

    }

Attribute pi is an attribute of the class Calculator, so it is visible (it can be accessed and used) within the body of any method of the Calculator class (it is used in the method body of calculateCircleCircumference).

On the other hand, variables a and b are parameters of the method add. This means they are visible (can be used and accessed) only within the body of this method. If an attempt is made to access variables a and b from the body of the method calculateCircleCircumference, a syntax error will occur.

The same applies to the parameter radius – it is visible only within the body of the calculateCircleCircumference method and nowhere else.

A quick review of the code might suggest there is a conflict because two variables named result are declared — one in the body of the first method, and the other in the body of the second method. However, this is not the case. In Java, these two variables are considered completely different because the result variable in the add method is only visible within the body of the add method, while the result variable in the calculateCircleCircumference method is only visible within the body of the calculateCircleCircumference method.

Visibility conflict and reserved word this

A conflict in “visibility” between parameters and attributes can arise if a method’s parameter or variable has the same name as an attribute of the class. In such cases, the reserved word this is used to distinguish the attribute from other variables. This reserved word is essentially a reference (pointer) to the object itself.

Example 2

Let’s say the class Person has:

  • An attribute name

  • A method setName that takes a name as a parameter and assigns this value to the attribute. The parameter is also called name.

    class Person { String name;

    1   void setName(String name){
    2       //Incorrect
    3       name = name;
    4 
    5       //Correct, use this to distinguish between
    6       //attribute and local variable
    7       //this.name = name;
    8   }
    

    }

What can first be noticed is that the assignment statement to the attribute name is very unclear. Which one is the attribute, and which is the parameter? Both are called “name”. Therefore, what is the effect of the command:

1 name = name;

The answer is that Java will interpret both sides as the parameter, so the assignment to the attribute name will not happen. To make the method do what is needed, the reserved word this is used to refer to the attribute. After the correction, the method should look like this:

1 void setName(String name) {
2     this.name = name;
3 }

The expression on the left-hand side of the equality sign refers to the attribute name (this.name), while the expression on the right-hand side refers to the parameter name.

Most common errors

Some of the most common syntax errors related to variable visibility are:

  • Attempting to access a local variable or parameter from a method where it is not declared (from another method or class):

    class X {

    1     void methodA(int parameterA){
    2         System.out.println(parameterA);
    3     }
    4 
    5     void methodB(){
    6         System.out.println(parameterA);
    7     }
    

    }

instead of (correct):

 1 class X {
 2 
 3       void methodA(int parameterA){
 4           System.out.println(parameterA);
 5       }
 6 
 7       void methodB(){
 8           System.out.println("Some message");
 9       }
10 }

Other common mistakes that are not syntax errors but affect the program’s behavior include:

  • Incorrectly accessing a local variable instead of an attribute — if both the attribute and the variable have the same name, and the reserved word this is not used, for example:

    class Car { String owner;

    1   void setOwner(String owner) {
    2       owner = owner;
    3   }
    

    }

    instead of (correct):

    class Car { String owner;

    1   void setOwner(String owner) {
    2       this.owner = owner;
    3   }
    

    }

Method overloading

Lesson content

  • What is method overloading
  • Most common errors

What is method overloading

Attribute names within a class must be unique - two attributes cannot have the same name.

With methods, this is not the case. Namely, it is possible to write two or more methods with the same name within a class.

However, in order to distinguish between them, each of them must have a different list of parameters — whether it is the number of parameters or their type (or order, if changing the order results in a different list of parameters, for example, (int, String) vs. (String, int)). When calling a method, the method whose parameters exactly match the input values will be invoked. In this case, it is method overloading.

Furthermore, the return type of the method is not relevant for overloading. If two methods in the same class have the same name and the same list of parameters, differing only in the return type, overloading will not be possible, and Java will report a syntax error.

Example 1

Create the Calculator class that has four methods:

  • A method named addIntegers that takes two integers as parameters and returns their sum (an integer).

  • A method named addDecimals that takes two real numbers as parameters and returns their sum (a real number).

  • A method named addIntegersThree that takes three integers as parameters and returns their sum (an integer).

  • A method named addDecimalsThree that takes three real numbers as parameters and returns their sum (a real number).

    class Calculator {

     1   int addIntegers (int x, int y){
     2       int result = x+y;
     3       return result;
     4   }
     5 
     6   double addDecimals (double x, double y){
     7       double result = x+y;
     8       return result;
     9   }
    10 
    11   int addIntegersThree (int x, int y, int z){
    12       int result = x+y+z;
    13       return result;
    14   }
    15 
    16   double addDecimalsThree (double x, double y, double z){
    17       double result = x+y+z;
    18       return result;
    19   }
    

    }

It is important to note that these four methods are essentially almost identical, but they have been given different names. If there were a need to create more versions of these methods for summing, additional names would have to be added.

The following example contains the code for a different version of the Calculator class - with method overloading used.

Example 2

Create the Calculator2 class that has four overloaded methods with the same name - add:

  • The first method takes two integers as parameters and returns their sum (an integer).

  • The second method takes two real numbers as parameters and returns their sum (a real number).

  • The third method takes three integers as parameters and returns their sum (an integer).

  • The fourth method takes three real numbers as parameters and returns their sum (a real number).

    class Calculator2 {

     1   int add (int x, int y){
     2       int result = x+y;
     3       return result;
     4   }
     5 
     6   double add (double x, double y){
     7       double result = x+y;
     8       return result;
     9   }
    10 
    11   int add (int x, int y, int z){
    12       int result = x+y+z;
    13       return result;
    14   }
    15 
    16   double add (double x, double y, double z){
    17       double result = x+y+z;
    18       return result;
    19   }
    

    }

Write a TestCalculator class that calls all of these methods.

 1 class TestCalculator {
 2 
 3     public static void main(String[] args) {
 4         Calculator2 c = new Calculator2();
 5 
 6         double r = c.add(12.0, 15.0);
 7         System.out.println(r);
 8         //It will print:
 9         //27.0
10 
11         int r2 = c.add(12, 15);
12         System.out.println(r2);
13         //It will print:
14         //27
15 
16         double r3 = c.add(12.0, 15.0, 34.0);
17         System.out.println(r3);
18         //It will print:
19         //61.0
20 
21         int r4 = c.add(12, 15, 34);
22         System.out.println(r4);
23         //It will print:
24         //61
25     }
26 }

The first call to the add method activates the method that sums two real numbers and returns a real number as a result.

The second call to the add method activates the method that sums two integers and returns an integer as a result.

In the first case, real numbers were entered as parameters, and in the second case, integers.

Similarly, the next two calls activate the overloaded versions of the add method that sum three real or three integer numbers respectively and return a real or integer number as the result.

What is the purpose of method overloading? In cases where multiple similar versions of a method are needed (with the same or almost the same functionality), overloading is used so that:

  • You don’t have to come up with new names for each
  • version (e.g., “addIntegers”, “addReals”, “addIntegersThree”…)
  • It shows the relationship between these methods through the same name.
  • It makes it easier to find the most suitable version of the method that needs to be called.

Most common errors

Some of the most common syntax errors when writing overloaded methods include:

  • Writing overloaded methods (with the same name) that do NOT differ in parameters (number, type, and/or order), but only in the return value, for example:

    int add (int a, int b){ return a + b; }

    double add (int a, int b){ return a + b; }

instead of (correctly):

1 int add (int a, int b){
2   return a + b;
3 }
4 
5 double add (double a, doubl b){
6   return a + b;
7 }

Parameter passing

Lesson content

  • The concept of parameter passing
  • Passing method parameters by value and by reference
  • Most common errors

The concept of parameter passing

What is (method) parameter passing? It was already explained that a method has:

  • Formal parameters (stated as part of the method’s header)
  • Real parameters (arguments - real values passed on to the method upon method call)

Parameter passing is, commonly speaking, the way real parameters (arguments) are passed to the method upon calling the method to execute.

Why is this important? Because some programing languages automatically make copies of argument values and pass those copies to methods (thus making the “original” value intact even if the copied value changes during method execution), while others support passing the “original”, so it can be altered during method execution. Some languages support both options.

Passing method parameters by value and by reference

As stated, in programming languages, there are usually at least two types of ways to pass parameters to methods:

  • Passing parameters by value (make and pass copy)
  • Passing parameters by reference (pass original)

In the Java programming language, all parameters are passed by value (formally speaking), but this means different things depending on whether the parameter is of a primitive type or not. This can lead to confusion, making it seem like changes made to a parameter won’t affect the original value. This is explained in more detail in the following examples.

When primitive type arguments are passed to a method, the parameter is passed by value in the “expected” way. This means that the value of the argument is copied. Each time a parameter is referenced inside the method’s body, it is actually a copy of the passed argument. Therefore, changes made to the parameter during the method’s execution will not persist (since changes are made to the copy, not the original). After the method finishes, the copy is discarded.

Example 1

Let’s consider the class ParameterPassing, which has two methods:

  • The method calculateSquare takes an integer as a parameter, squares it (modifies the parameter itself), and prints the result to the screen.

  • The method swapNumbers takes two integers as parameters and tries to swap their values.

    class ParameterPassing {

     1   void calculateSquare(int number) {
     2       number = number * number;
     3 
     4       System.out.println("It's square is: " + number);
     5   }
     6 
     7   void swapNumbers(int a, int b) {
     8       int pom = a;
     9       a = b;
    10       b = pom;
    11   }
    

    }

And, let’s consider the TestParameterPassing class which calls both methods of the ParameterPassing class and prints the results to the screen.

 1 class TestParameterPassing {
 2 
 3     public static void main(String[] args) {
 4         ParameterPassing pp = new ParameterPassing();
 5 
 6         int k = 13;
 7 
 8         System.out.println("The number is: " + k);
 9 
10         pp.calculateSquare(k);
11 
12         System.out.println("After squaring, the number is: " + k);
13 
14         int x = 22;
15         int y = 15;
16 
17         System.out.println("Numbers before the swap: " + x + " " + y);
18 
19         pp.swapNumbers(x, y);
20 
21         System.out.println("Numbers after the swap: " + x + " " + y);
22     }
23 }

When the program runs, the following values will be printed on the screen (the initial values of the variables k, x, and y remain unchanged):

1 The number is: 13
2 It's square is: 169
3 After squaring, the number is: 13
4 Numbers before the switch: 22 15
5 Numbers after the switch: 22 15

It can be seen that the variable k has the same value before and after squaring, even though it was passed to the calculateSquare method. Also, neither the variables x nor y have swapped values after calling the second method. The reason is that these values were passed to the method as primitive parameters, i.e., they were passed by value.

What does passing a parameter by value actually mean, and how does it look in the computer’s memory (Figure 16)?

Passing parameters by value (primitive types)
Figure 16. Passing parameters by value (primitive types)

When the calculateSquare method is called, the value of the variable k, which is 13, is passed to the calculateSquare method as an argument. This means a new local variable number is created in the calculateSquare method, and the value 13 is stored in it — this is a copy. The squaring operation inside this method only changes the value of the variable number (the copy of the number 13), while the variable k keeps the same value. When the square is printed, the value of number, which is 169, is printed. Once the method finishes, all its local variables are discarded, including the number variable. Finally, when the value of k is printed after the square method finishes, it prints 13.

Something similar happens when the swapNumbers method is called (Figure 17). The values of the variables x and y, i.e., the numbers 22 and 15, are passed to the swapNumbers method as arguments for the formal parameters a and b. This means that new local variables a and b are created in the swapNumbers method, and the values 22 and 15 are stored in them — these are copies. Then, a new local variable temp is introduced to perform the swap. Swapping the values inside this method only changes the values of the variables a and b, while the variables x and y retain their original values. When the method execution finishes, all its local variables are discarded — variables a, b and temp. This means that the changes to the numbers are not retained. When the values of x and y are printed after the swapNumbers method finishes execution, the original values of 22 and 15 are printed (not 15 and 22 as one might expect).

Passing parameters by value (primitive types)
Figure 17. Passing parameters by value (primitive types)

If an object (a complex type) is passed to a method as an argument, the parameter is still passed by value. However, the value of the parameter in this case is the object’s address, so it would be more accurate to say that the parameter is passed by reference.

Here’s the explanation. Passing an object as an argument is done by specifying the corresponding pointer to the object — the variable. In this case, the argument’s value is copied, and since it’s a pointer, the memory address of the original object is copied. When the parameter is referenced inside the method, it accesses the object at the address that was previously copied, which is where the original object is.

The consequence of this approach is that changes made to the object passed as an argument during the method’s execution are retained after the method finishes (such as changes to the values of the object’s attributes, its relations, or calling its methods). However, changes made to the address of the passed object (within the method’s body) won’t be retained after the method finishes (such as creating a new object or replacing the address with null or another value) because the changes are made to a copy of the address, and the original pointer still holds the original address.

Example 2

Let’s consider the class Person, which has the attributes height (integer) and weight (floating-point number).

1 class Person {
2     int height = 0;
3     double weight = 0;
4 }

Let’s also consider the class ParameterPassing2, which has three methods:

  • The first method takes a Person object as a parameter and adds 5 kilograms to the person’s existing weight (modifies the weight attribute).

  • The second method takes two Person objects as parameters and swaps their places by swapping the addresses of the passed objects.

  • The third method takes a Person object as a parameter and creates a new object at a new address.

    class ParameterPassing2 {

     1   void addWeight(Person person) {
     2       person.weight = person.weight + 5;
     3   }
     4 
     5   void swapPersons(Person person1, Person person2) {
     6       Person tmp = person1;
     7       person1 = person2;
     8       person2 = tmp;
     9   }
    10 
    11   void makeNewPerson(Person newPerson) {
    12       newPerson = new Person();
    13   }
    

    }

The TestParameterPassing2 class calls all three methods from ParameterPassing2 and prints the results to the screen.

 1 class TestParameterPassing2 {
 2 
 3     public static void main(String[] args) {
 4         Person p1 = new Person();
 5         p1.height = 196;
 6         p1.weight = 95;
 7 
 8         Person p2 = new Person();
 9         p2.height = 172;
10         p2.weight = 55;
11 
12         ParameterPassing2 pp2 = new ParameterPassing2();
13 
14         System.out.println("Person with initial height and weight: "+ 
15         p1.height+"cm "+ p1.weight+"kg");
16 
17         pp2.addWeight(p1);
18 
19         System.out.println("Person with final height and weight: "+ 
20         p1.height+"cm "+ p1.weight+"kg");
21 
22         System.out.println("Persons before swapping:");
23         System.out.println("First person: "+ p1.height+"cm "+
24          p1.weight+"kg");
25         System.out.println("Second person: "+p2.height+"cm "+
26         p2.weight+"kg");
27 
28         pp2.swapPersons(p1, p2);
29 
30         System.out.println("Persons after swapping:");
31         System.out.println("First person: "+ p1.height+"cm "+
32          p1.weight+"kg");
33         System.out.println("Second person: "+p2.height+"cm "+
34         p2.weight+"kg");
35 
36         pp2.makeNewPerson(p2);
37         System.out.println("New person: "+ p2.height+"cm "
38         +p2.weight+"kg");
39     }
40 }

When the main method of the TestParameterPassing2 class is run, the following will be printed:

1 Person with initial height and weight: 196cm 95.0kg
2 Person with final height and weight: 196cm 100.0kg
3 Persons before swapping:
4 First person: 196cm 100.0kg
5 Second person: 172cm 55.0kg
6 Persons after swapping:
7 First person: 196cm 100.0kg
8 Second person: 172cm 55.0kg
9 New person: 172cm 55.0kg

The addWeight method works as expected because the person’s weight has truly been permanently changed from 95 to 100 kilograms.

Then, we see that the persons were not swapped after executing the swapPersons method. The reason is that the addresses of the entire objects cannot be swapped.

Finally, the new object created by the makeNewPerson method was not actually passed back to the variable p2 — the values for weight and height remain the same and are not changed to 0 cm and 0 kg (which would be expected for a new Person object - the default values for its int and double attributes height and weight).

What does passing a parameter by reference actually mean, and how does it look in the computer’s memory?

Based on the previous example, we can see the following (Figure 18). When the addWeight method is called and the variable p1 is passed as a parameter, the address from this variable (e.g., 450) is copied to the new local variable person in this method. The object itself is not copied; there are now two pointers pointing to the same object in memory (p1 and person). When the addWeight method is executed, it accesses the address passed (the original object) and changes the value of its weight attribute from 95 to 100. After the addWeight method finishes, the local variable person is discarded, but the object at address 450 is not deleted (since p1 still points to it). Finally, p1 retains the address 450, pointing to the same object, which now has the updated weight. When the method for printing is called, it shows that the person has a height of 196 cm and a weight of 100 kg.

Passing parameters by reference (complex types)
Figure 18. Passing parameters by reference (complex types)

A similar thing happens during the execution of the swapPersons method, but the objects will not swap places because the addresses of the passed objects cannot be swapped (Figure 19). When the method is called, the values of the parameters p1 and p2 (addresses 450 and 335) are first copied to the new local variables person1 and person2. Then, the addresses of person1 and person2 are swapped using the new local variable temp. After this swap, only the variables person1 and person2 have swapped addresses (now 335 and 450, respectively), but the variables p1 and p2 still hold their initial addresses (450 and 335). When the swapPersons method finishes, the local variables person1, person2, and temp are discarded, but p1 and p2 retain their original addresses, so the effect of this method is invalid. The objects at addresses 450 and 335 are not deleted because p1 and p2 still point to them, and the screen output will show the unchanged details for both persons.

Passing parameters by reference (complex types)
Figure 19. Passing parameters by reference (complex types)

Finally, when the makeNewPerson method is executed, a new object is created (address 620). This new object is passed to the local variable newPerson in the method, but it does not replace the reference from the p1 variable, since p1 still points to the original address 450. Therefore, the new object is discarded once the method ends, and the output on the screen does not show any changes to p1.

Memory overview (complex types)
Figure 20. Memory overview (complex types)

Finally, if the methods swapPersons and makeNewPerson should be rewritten to behave as expected, they would need to swap the values of the attributes of the input objects (first method) and return the address of the new object as the method’s return value, which should then be stored in the initial variable, while removing the parameter (second method). The modified code could look like this:

 1 void switchPersons(Person person1, Person person2) {
 2     Person tmp = new Person();
 3     
 4     tmp.height = person1.height;
 5     tmp.weight = person1.weight;
 6         
 7     person1.height = person2.height;
 8     person1.weight = person2.weight;
 9         
10     person2.height = tmp.height;
11     person2.weight = tmp.weight;
12 }
13 
14 Person makeNewPerson() {
15     return new Person();
16 }

Most common errors

Common mistakes related to parameter passing in methods that are not syntax errors, but affect the program’s behavior are:

  • Passing a primitive type variable as a parameter to a method and expecting the method to change the value of the passed variable. Since it’s working with a copy, the value remains unchanged.
  • Passing a complex type (object) variable as a parameter to a method and expecting that the method will not change the attribute values of the passed variable. Since it’s working with the original object through its address, the values of its attributes will be modified.
  • Passing a complex type (object) variable as a parameter to a method and expecting that a new object will be initialized within that variable. Since it’s working with the original object through its address, the initial variable (which was passed to the method) will still hold the address of the original object.

Constructor

Lesson content

  • What is a constructor - idea, declaration
  • Default, no-argument, and parameterized constructors
  • Most common errors

What is a constructor - idea, declaration

Every time it is necessary to initialize an object, the new command is used. After this reserved word, the class name is always followed by an open and close parentheses. It has already been explained that during initialization, memory space is reserved for the new object. However, after the memory space is reserved, one more thing happens: the constructor is called.

A constructor is a special method that can assign initial values to the object’s attributes during its initialization. This means the class constructor is automatically called every time the new command is used.

The constructor definition is almost the same as a method definition, with two limitations:

  • The constructor’s name is always the same as the class name (if named differently, Java will not consider it a constructor, but a regular method).
  • Constructors never have a return value (other than the object of that class), so the header does not include even “void.”

Default, no-argument, and parameterized constructors

In previous examples, constructors were not explicitly written for the class. When a class in Java does not have an explicitly written constructor, Java automatically assigns a default constructor to it.

Upon calling, the default constructor sets the attribute values to default values for their data type (int to 0, double to 0.0, boolean to false , String to null, etc.), unless the attribute values are already set to initial values during the declaration.

If a class has an explicitly written constructor, Java does not create a default constructor.

However, in some situations, it is convenient to explicitly write a constructor because initial values can be assigned to attributes during class initialization, or some messages can be printed, etc.

Constructors are divided into two types depending on whether they have parameters or not. The first type is parameterized constructors, and the second is no-argument constructors (which include the default constructor). One default and one explicit constructor are shown in the following example.

Example 1

Create a class FoodItem with two attributes:

  • name and
  • caloricValue

Assign the initial value “unknown” to the first attribute, and 0.0 to the second.

1 class FoodItem {
2     String name = "unknown";
3     double caloricValue = 0.0;
4 }

Create a class FoodItem2 with two attributes:

  • name and
  • caloricValue

Create a constructor that assigns the initial value “unknown” to the first attribute, and 0.0 to the second.

1 class FoodItem2 {
2     String name;
3     double caloricValue;
4 
5     FoodItem2(){
6         name = "unknown";
7         caloricValue = 0.0;
8     }
9 }

Class FoodItem does not have an explicitly written constructor, so Java assigns a default constructor to it (it is not visible in the code), and initial values are assigned immediately upon attribute declaration.

On the other hand, class FoodItem2 has an explicitly written no-argument constructor (it has no parameters) that assigns initial values to the attributes.

First, it should be noted that, unlike a regular method, the constructor does not have a return value - it does not even use “void”, because it always returns an object of FoodItem class. The constructor header just starts with its name. Second, the constructor’s name is exactly the same as the class name (FoodItem2 - the first letter of each word is capitalized). Finally, it can be seen that the effect of executing the constructor is exactly the same as assigning initial values to attributes immediately upon their declaration.

This means that when initializing an object of class FoodItem, the default constructor, which Java automatically assigned to the class, is called, and the values are assigned to the attributes through their declaration.

1 FoodItem f = new FoodItem();

When initializing an object f2 of class FoodItem2, the explicitly written no-argument constructor is called, which assigns values to the attributes.

1 FoodItem2 f2 = new FoodItem2();

The purpose of the constructor is to initialize an object during creation, bringing it into an initial state. However, in some cases, the constructor needs to do more than just assign initial values to attributes. This is illustrated in the following example.

Example 2

Create a class FoodItem3 with two attributes:

  • name and
  • caloricValue

Create a constructor that assigns the initial value “unknown” to the first attribute, 0.0 to the second, and prints a message about the initial values of the attributes on the screen.

 1 class FoodItem3 {
 2     String name;
 3     double caloricValue;
 4 
 5     FoodItem3(){
 6         name = "unknown";
 7         caloricValue = 0.0;
 8         System.out.println("Name: " + name);
 9         System.out.println("Caloric value: " + caloricValue);
10     }
11 }

The constructor of class FoodItem3 also prints some messages to the screen. In other words, every time an object of this class is initialized, the initial values of its attributes will be printed to the screen:

1 FoodItem3 f3 = new FoodItem3();
2 //It will print to the screen:
3 //Name: unknown
4 //Caloric value: 0.0

Default constructors always set attributes to default values (unless specific values are assigned during declaration). Parameterized constructors, on the other hand, can set attributes to values provided as parameters during object creation. This way, through a single constructor call, one or more attribute values can be assigned, making it much easier to use the class. This can be seen in the following example.

Example 3

Create a class Person with two attributes:

  • firstName and
  • lastName

Create a constructor that has two parameters, where the first contains a new value for the first name, and the second contains a new value for the last name. This constructor should assign the entered values to the attributes.

 1 class Person {
 2 
 3     String firstName;
 4     String lastName;
 5 
 6     Person(String firstName1, String lastName1){
 7         firstName = firstName1;
 8         lastName = lastName1;
 9     }
10 }

Create a class TestPerson in which an object of class Person is created and immediately assigned the values “Peter” for the first name and “Smith” for the last name through the constructor.

 1 class TestPerson {
 2 
 3     public static void main(String[] args) {
 4         // This command will not be accepted because
 5         // class Person has one constructor, and it
 6         // has two parameters.
 7         //Person p1 = new Person();
 8 
 9         //This is how the parameterized constructor of
10         // the Person class is called correctly.
11         Person p1 = new Person("Peter", "Smith");
12     }
13 }

It is common for the parameters of a constructor to have the same name as the attributes (e.g., not firstName1 but firstName), but in this case, the reserved word this must be used inside the constructor to distinguish between the attributes and the parameters. If this is applied to the Person class, the constructor would look like the one in class Person2.

 1 class Person2 {
 2 
 3     String firstName;
 4     String lastName;
 5 
 6     Person2(String firstName, String lastName){
 7         this.firstName = firstName;
 8         this.lastName = lastName;
 9     }
10 }

A class can have multiple constructors, but they must differ in the number and/or types of parameters. This is called constructor overloading, and it is similar to method overloading.

Example 4

Create a class Car with two attributes:

  • licensePlate (e.g., “AA-131”) and
  • owner (owner’s full name, e.g., “Mike Stone”)

Create three constructors for this class:

  • No-argument constructor
  • Parameterized constructor with one parameter for the car owner’s name. This constructor should assign the input value to the owner attribute.
  • Parameterized constructor with two parameters, where the first parameter contains the new value for the owner’s name, and the second contains the new value for the license plate. This constructor should assign the input values to the attributes.

Make sure the parameterized constructors have parameters with the same names as the attributes.

 1 class Car {
 2 
 3     String licensePlate;
 4 
 5     String owner;
 6 
 7     public Car() {}
 8 
 9     public Car(String owner) {
10         this.owner = owner;
11     }
12 
13     /*
14     It is not possible to make this constructor because
15     the parameter list would not differentiate from the
16     previous constructor's parameter list (just one
17     string as a parameter). Overloading the constructor
18     in this way would not be possible.
19 
20     public Car(String licensePlate) {
21         this.licensePlate = licensePlate;
22     }*/
23 
24     public Car(String owner, String licensePlate) {
25         this.licensePlate = licensePlate;
26         this.owner = owner;
27     }
28 }

Create a class TestCar where three objects of the Car class are created and immediately initialized through constructors with the following values:

  1. No values are assigned, just the object is initialized

  2. The owner is “Peter Smith”

  3. The owner is “Mike Stone”, and the registration is “AA-931”

    class TestCar {

    1  public static void main(String[] args) {
    2      Car a1 = new Car();
    3 
    4      Car a2 = new Car("Peter Smith");
    5 
    6      Car a3 = new Car("Mike Stone","AA-931");
    7  }
    

    }

From the TestCar class code, you can see that if a class has multiple constructors, any of them can be called when initializing an object. Furthermore, it is important to note two more things:

  • In the Car class, there is a version of the constructor that cannot be written because overloading would not be possible - the list of parameters would be the same as for an existing constructor.
  • If the Car class did not explicitly write a no-argument constructor along with the two parameterized constructors, it would not be possible to call it (the call “Car c1 = new Car();” would cause a syntax error). This is because Java would not assign a default constructor to the Car class, as explicit constructors already exist in that class.

Most common errors

Some of the most common syntax errors related to writing constructors are:

  • Calling a constructor with the wrong order, number, or type of arguments. For example, if a constructor with the declaration “Room (double numberOfBeds, boolean apartment)” is called like this:

    Room room = new Room(true, 4);

instead of (correct):

1 Room room = new Room(4, true);
  • Calling a no-argument or default constructor if only parameterized constructors are explicitly declared in the class.

Other common errors that are not syntax errors but affect the program’s behavior include:

  • Calling a constructor with the wrong order of parameters (which are of the same type, so it is not a syntax error). For example, the constructor “Person (String firstName, String lastName)” is called like this:

    Person p = new Person(“Smith”, “Peter”);

Here, the attributes firstName and lastName are assigned the wrong values (first name should be “Peter” and last name should be “Smith” - the order is wrong). Instead, the call should be:

1 Person p = new Person("Peter", "Smith");
  • Writing the constructor name differently from the class name. Java will treat this as a regular method, not a constructor. When attempting to call “such” a constructor, you will get a message that it does not exist.

  • Writing void or any other return type for a constructor. Although this may not cause a syntax error, Java will treat it as a regular method, not a constructor. When you try to call such a constructor, you will get a message saying it does not exist.

  • Although it is not a syntax error, novice programmers often forget to use the reserved word this when the constructor parameter has the same name as an attribute. In this case, attempting to assign a value to the attribute will have no effect.

Global variables and methods (static)

Lesson content

  • What is a global (static) variable?
  • Calling and using a global (static) variable
  • What is a global (static) method?
  • Calling and using a global (static) method
  • Most common errors

What is a global (static) variable?

Objects have attributes and methods that can be accessed and used only once the object is initialized. Also, each object has its own separate attribute values.

In some situations, however, it is useful for multiple objects (from the same or different classes) to share a single variable. Such variables are visible throughout the entire program and are called global variables.

In Java, global variables are marked with the reserved word static and are written within the class body as class attributes, so they are also often called static variables or static attributes:

1 static dataType variableName;

Calling and using a global (static) variable

Because a global variable is not tied to any specific object, using this variable does not require initializing an object of the class to which it belongs. Instead, it is accessed directly via the class name within which it is defined:

1 ClassName.variableName

Although static variable calls can also be made through initialized objects of that class, this is not considered to be good practice. It unnecessarily initializes the object and takes up memory space.

Example 1

Create a class Hotel and define a global variable remainingFreeRooms within it. Set its initial value to 100.

1 class Hotel {
2     static int remainingFreeRooms = 100;
3 }

Create a class TestHotel that sets the value of this global variable to 5.

 1 class TestHotel {
 2 
 3     public static void main(String[] args) {
 4         // No initialization of a Hotel
 5         // object is required to use the
 6         // global variable. The call is
 7         // made through the class name.
 8         Hotel.remainingFreeRooms = 5;
 9 
10         System.out.println("There are: " + Hotel.remainingFreeRooms +
11          " free rooms remaining");
12     }
13 }

It is important to note again that, unlike regular attributes where each object has its own attribute values, for global variables (static variables), all objects of the class share the same static variable.

Example 2

Use the Hotel class from the previous example.
Create a class TestHotel2 that creates two objects of the Hotel class, changes the value of this global variable, and prints its value via these objects.

 1 class TestHotel2 {
 2 
 3     public static void main(String[] args) {
 4         // Let's say we decide to make two Hotel objects
 5         Hotel h1 = new Hotel();
 6         Hotel h2 = new Hotel();
 7 
 8         // If we change the value of a global variable,
 9         // the change affects all objects of that class
10         // because they share that global variable.
11         Hotel.remainingFreeRooms = 5;
12 
13         //It will print "There are: 5 free rooms remaining"
14         // in both cases because all Hotel objects share the
15         // same global variable.
16         System.out.println("There are: " + h1.remainingFreeRooms + 
17         " free rooms remaining");
18         System.out.println("There are: " + h2.remainingFreeRooms + 
19         " free rooms remaining");
20     }
21 }

What is a global (static) method?

Sometimes, there is a need for methods that provide some general functionality that is not strictly tied to a specific class. Such methods are often good candidates to become global methods.

In Java, global methods are also marked with the reserved word static and are often called static methods. These methods are defined within the class body the same way as any other method:

1 static returnType methodName(...parameters...) {
2     // Method body
3 }

Calling and using a global (static) method

The difference compared to a regular method is that you do not need to initialize an object of that class to use the method. Instead, the call is made via the class name:

1 ClassName.methodName(arguments);

Although static variable calls can also be made through initialized objects of that class, this is not considered to be good practice. It unnecessarily initializes the object and takes up memory space.

Because of this, static attributes and methods cannot be called using the reserved word this — unless the object has been initialized. The call should be made by referencing the class name.

In fact, if you take a closer look, you can see that the main method is a static method. This is necessary because it is called at the beginning of the program execution, before any objects are created.

Example 3

Create a Calculator class and define two global methods within it: add and subtract. Both methods take two integers as parameters and return the result of the addition or subtraction.

1 class Calculator {
2     static int add(int a, int b){
3         return a+b;
4     }
5 
6     static int subtract(int a, int b){
7         return a-b;
8     }
9 }

Create a TestCalculator class that, within its main method, calls the add and subtract methods to add and subtract numbers 5 and 10.

 1 class TestCalculator {
 2 
 3     public static void main(String[] args) {
 4         int a = 5;
 5         int b = 10;
 6 
 7         int result1 = Calculator.add(a, b);
 8         int result2 = Calculator.subtract(a, b);
 9 
10         System.out.println(result1);
11         System.out.println(result2);
12     }
13 }

Every class in Java can have both regular attributes and static attributes, as well as regular methods and static methods. There are no restrictions in this regard, so a class can have all of them at once. Just keep in mind that:

  • From regular methods, you can directly call everything: both regular and static attributes, and both regular and static methods.
  • From static methods, you can directly call ONLY static attributes and methods (unless you create an object of the class within a static method or pass it as a parameter to access regular attributes and methods).

Example 4

Create a Circle class which has:

  • A static (global) variable pi with the value of 3.141592.

  • An attribute diameter which is a real number.

  • A static (global) method calculateCircumference that receives the circle’s diameter as a parameter, multiplies it by pi, and calculates and returns the circumference of the circle.

  • A method printCircle that prints the diameter and circumference of the circle on the screen and calls the calculateCircumference method to obtain the latter.

    class Circle {

     1   //Static variable
     2   static double pi = 3.141592;
     3 
     4   //"Regular", non-static attribute
     5   double diameter;
     6 
     7   //Static method
     8   static double calculateCircumference(double diameter) {
     9       //The diameter is entered as a parameter because the
    10       // non-static attribute diameter cannot be called from
    11       //this method because it is a static method.
    12       return diameter * pi;
    13   }
    14 
    15   //"Regular", non-static method
    16   void printCircle() {
    17       //Here, the static method calculateCircumference is called
    18       //from a non-static method printCircle, but the attribute diameter
    19       //must be passed to it as a parameter - the static method
    20       //calculateCircumference cannot access it directly.
    21       System.out.println("Circle diameter is " + diameter
    22               + " and its circumference is " +
    23                calculateCircumference(diameter));
    24   }
    

    }

Create a TestCircle class and within its main method:

  • First, directly call the static calculateCircumference method to calculate the circumference of a circle with a diameter of 300 and then print the result on the screen.

  • Second, create an object of the Circle class with a diameter of 20. Calculate and print the circumference of the created circle, as well as the diameter by calling the printCircle method.

    class TestCircle {

     1   public static void main(String[] args) {
     2       //Calling a static method without initializing an object
     3       double circumference = Circle.calculateCircumference(300);
     4 
     5       System.out.println(circumference);
     6 
     7       //We make an object and use its ordinary attributes and methods
     8       Circle k = new Circle();
     9 
    10       k.diameter = 2.5;
    11 
    12       k.printCircle();
    13   }
    

    }

A bit of discussion is needed about the provided solution. The calculateCircumference method of the Circle class is static, so if we only want to calculate the circumference of a circle, we can call it directly and do not need to create an object of the Circle class. For this, we must pass the circle’s diameter as a parameter, because this method cannot access the diameter attribute of the Circle class (since it is not a static attribute). It can access the pi attribute (since it is static), which is also needed for the calculation, so we do not pass it as a parameter.

The Circle class is somewhat mixed because, in addition to static attributes, it also has regular attributes and methods. If we create an object of the Circle class and enter a value for the diameter attribute (in this case, it is 2.5), we have the option to store the diameter of the circle within this objects attribute and use the printCircle method to calculate both the circumference and print the diameter and circumference. The printCircle method actually calls the static calculateCircumference method and passes the value of the diameter attribute to it (because the calculateCircumference method cannot access it directly), then prints the returned value (the circumference) and the diameter on the screen.

In the case where we are only calling the static method (the first scenario), the diameter of the circle is not saved anywhere, so it “disappears” after the calculateCircumference method has terminated (finished execution). In the second scenario, we created an object of the Circle class that stores the diameter of the circle, and this object (as well as the diameter) can be used later.

When is a method a good candidate to be a static method? Possible cases include:

  • The method follows the input-output principle: it receives all the necessary data through parameters and returns the result as a return value or prints it on the screen.
  • The method does not use attributes in its operation.
  • The method has general functionality or forms a separate, encapsulated unit, such as various calculators, converters, etc.
  • The method is generally called once, so the object is no longer needed once the method has finished execution.

Most common errors

Some of the most common syntax errors related to writing and using static attributes and methods are:

  • Forgetting to write the word static before an attribute or method, then trying to call them as if they are static (i.e., using the class name).

  • Calling regular methods or attributes using the class name as if they are static.

  • Writing the word static after the attribute type instead of before it, e.g.:

    int static number;

Instead of (correct):

1 static int number;
  • Writing the word static after the return type of method instead of before it, e.g.:

    int static add(int a, int b) { return a + b; }

Instead of (correct):

1 static int add(int a, int b) {
2     return a + b;
3 }
  • Calling non-static attributes and methods from a static method within the same class.
  • Calling static attributes and methods using the word this — if the object has not been created.

Other common mistakes that are not syntax errors but affect the program’s functionality are:

  • Creating an object and then calling a static attribute or method through that object. Although this is not a syntax error, it unnecessarily initializes the object and takes up memory space. For example, if the add method is static, this kind of call is not the best solution:

    Calculator c = new Calculator(); int sum = c.add(34, 88);

Instead, it is much better like this:

1 int sum = Calculator.add(34, 88);

Constants (final)

Lesson content

  • What is a constant (reserved word final)
  • Declaring and calling constants, naming conventions for constants
  • Static constants
  • Most common errors

What is a constant (reserved word final)

Sometimes, there is a need to store some value (in the program) which never changes. Examples include mathematical constant values (like pi which always has the value 3.141592) but also other values which never change (birthdate of a person, eye color, number of apartments in a building). These values ae called constant values (or constants for short), and the Java language supports their declaration and usage in a similar way as variables are declared and used.

Constants are similar to variables in that they hold a value. The difference is that a variable can be reassigned a new value at any time and an unlimited number of times, while constants must receive an initial value that cannot be changed later. Hence, the name “constant.”

Declaring and calling constants, naming conventions for constants

If you need to define a constant within a Java class, the reserved word final is used. Constants are defined as attributes, with the word final placed before the attribute type:

1 final value_type CONSTANT_NAME = value;

By convention, constant names are written in all uppercase letters (“PI”, “INTEREST_RATE”). If the constant name consists of multiple words, the words are separated by underscores (“MIN_INTEREST_RATE”).

A constant must be assigned a value immediately upon declaration or possibly in the constructor, as it cannot be changed later. If an attempt is made to assign a new value to a constant later, the Java compiler will interpret it as a syntax error.

Calling a constant is done in the same way as calling a class attribute. The only difference is that constants can never be assigned a new value.

Example 1

Create a Hotel class wih has:

  • an attribute name

  • a constant TOTAL_NUMBER_OF_ROOMS with the value 130

    class Hotel {

    1   String name;
    2 
    3   final int TOTAL_NUMBER_OF_ROOMS = 130;
    

    }

Create a TestHotel class that, within it’s main method, creates a new Hotel object, sets the hotel name to “Hilton,” and prints the hotel’s name and the total number of rooms on the screen.

 1 class TestHotel {
 2 
 3     public static void main(String[] args) {
 4         Hotel h = new Hotel();
 5 
 6         h.name = "Hilton";
 7 
 8         // The following command is not permitted,
 9         // constants cannot have their initial value changed
10         //h.TOTAL_NUMBER_OF_ROOMS = 32;
11 
12         System.out.println("Hotel: " + h.name + 
13             ", total number of rooms: " +
14                 h.TOTAL_NUMBER_OF_ROOMS);
15     }
16 }

From the example, you can see that the constant TOTAL_NUMBER_OF_ROOMS receives the value 130 immediately upon declaration, while name is a regular attribute.

Later, in the main method of the TestHotel class, both the attribute and the constant can be accessed, but the constant’s value cannot be reassigned.

Practically speaking, this example isn’t ideal because it implies that all hotels must have exactly 130 rooms. In the next example, the constant will be assigned a value in the constructor, allowing each hotel to have a different total number of rooms (which cannot be changed later).

Example 2

Create a Hotel2 class which has:

  • an attribute name

  • a constant TOTAL_NUMBER_OF_ROOMS

  • a constructor that takes the hotel name and total number of rooms as parameters and sets these values for the name attribute and the constant TOTAL_NUMBER_OF_ROOMS

    class Hotel2 {

    1   String name;
    2 
    3   final int TOTAL_NUMBER_OF_ROOMS;
    4 
    5   Hotel2 (String name, int TOTAL_NUMBER_OF_ROOMS){
    6       this.name = name;
    7       this.TOTAL_NUMBER_OF_ROOMS = TOTAL_NUMBER_OF_ROOMS;
    8   }
    

    }

Create a TestHotel2 class that, within it’s main method, creates two Hotel2 objects and:

  • sets the first hotel’s name to “Continental” and a total of 300 rooms

  • sets the second hotel’s name to “Hilton” and a total of 250 rooms

  • prints all data for both hotels on the screen.

    class TestHotel2 {

     1   public static void main(String[] args) {
     2       Hotel2 h = new Hotel2("Continental", 300);
     3 
     4       Hotel2 h2 = new Hotel2("Hilton", 250);
     5 
     6       // Next commands are not allowed, constants
     7       // cannot have the initial value changed
     8       //h.TOTAL_NUMBER_OF_ROOMS = 32;
     9       //h2.TOTAL_NUMBER_OF_ROOMS = 155;
    10 
    11       System.out.println("Hotel: " + h.name + 
    12       ", total number of rooms: " +
    13               h.TOTAL_NUMBER_OF_ROOMS);
    14 
    15       System.out.println("Hotel: " + h2.name + 
    16           ", total number of rooms: " +
    17               h2.TOTAL_NUMBER_OF_ROOMS);
    18   }
    

    }

From the example, you can see that the constant TOTAL_NUMBER_OF_ROOMS is assigned a value within the constructor. This means that each object of the Hotel2 class can have a different total number of rooms, and the value can be passed when the object is created (through the constructor).

As in the previous example, in the main method of the TestHotel2 class, both the attribute and the constant can be accessed, but the constant’s value cannot be reassigned.

Static constants

For some constants, it’s desirable for them to be globally visible, so, in addition to the word “final,” they are also marked with the reserved word “static”. Calling these global constants (static constants) is the same as making calls to global variables — it is done through the class name.

1 static final value_type CONSTANT_NAME = value;

Unlike regular constants, static constants cannot be initialized in the constructor, but only upon declaration (or possibly in so-called static blocks of code).

Example 3

Create a Mathematics class which has:

  • a static constant PI with a value of 3.141592

  • a static constant E with a value of 2.71

    class Mathematics {

    1   static final double PI = 3.141592;
    2 
    3   static final double E = 2.71;
    

    }

Create a TestMathematics class that, in the main method, calculates the circumference of a circle with a diameter of 2 (using the formula diameter * PI) and prints it on the screen. Additionally, the method prints the value of the constant E on the screen.

 1 class TestMathematics {
 2 
 3     public static void main(String[] args) {
 4 
 5         // Static constants are called by
 6         // using the class name - same as
 7         // global variables
 8         double circumference = 2 * Mathematics.PI;
 9 
10         System.out.println("Circle circumference is: " + circumference);
11 
12         System.out.println("Constant E is: " + Mathematics.E);
13     }
14 }

For clarity, let’s now summarize the differences between “regular” (non-static) attributes, global variables (static attributes), constants, and global constants (static constants):

  • “Regular” (non-static) attribute (i.e. String firstName;)
    • can change values
    • each object has its own value for this attribute
    • access through an initialized object.
  • Global variable (static attribute) (i.e. static String franchiseName;)
    • can change values
    • all objects of that class share one value for this attribute (global value at the class level)
    • access through class name (no object is created)
  • Constant (final) (i.e. final int BIRTH_YEAR;)
    • cannot change values (constant value)
    • each object has its own value for this constant
    • value is assigned either during declaration or through the constructor
    • access through an initalized object
  • Global constant (static final constant) (i.e. static final int NUMBER_OF_PARENTS;)
    • cannot change values (constant value)
    • all objects of that class share one value for this attribute (global constant value at the class level)
    • value is assigned either during declaration or in a static block of code
    • access through class name (no object is created).

The reserved word “final” can also be used with class and method definitions, but in these cases, it has a completely different meaning. This type of usage is closely tied to inheritance and will be explained in detail in the chapter on inheritance.

Most common errors

Some of the most common syntax errors related to writing and using constants and static constants are:

  • Forgetting to assign an initial value to a constant (whether static or not) during declaration.

  • Trying to assign a different value to a constant (whether static or not) after the initial value has been assigned (during declaration).

  • Writing the words static and/or final after the type of the constant instead of before it, e.g.:

    int final static NUMBER = 4;

Instead of (correct):

1 static final int NUMBER = 4;

or (also correct):

1 final static int NUMBER = 4;

Other common errors that are not syntax errors, but affect the program’s behavior:

  • Creating an object and then calling a static constant through that object. Although this is not a syntax error, it unnecessarily initializes an object and consumes memory space.

Relationships

Lesson content

  • What are relationships
  • Types of relationships
  • Declaring and referencing related objects
  • Most common errors

What are relationships

Living things, objects and phenomena in the real world often form relationships. For example, two persons can be in love (the relationship being “in love”), or one person can be a sibling to several persons (“is sibling” relationship). On the other hand, a fuel tank “is part of” a car (also a type of relationship), as well as an inventory item being “a part of” an inventory list. Cars, trucks, motorcycles and vans are all “types of” motorized vehicles (“is type of” relationship) etc.

Classes, as simplified representations of real-world entities, can also form mutual relationships that reflect real ones. Relationships between classes are also just called relationships. Just as objects inherit and use attributes and methods of the class they belong to, they also inherit relationships.

There are two basic types of relationships:

  1. Association and its specialized forms:
    • Composition - decomposition (“HAS-A” relationship, “PART-OF” relationship)
    • Generalization - specialization (“IS-A” relationship, inheritance)
  2. Usage relationship (“USING” relationship)

Association and its specialized forms (composition-decomposition and generalization-specialization) pertain to structure, as they establish structural links between classes, while the usage relationship pertains to behavior.

Association is the most general form of relationship in which objects of one class have a structural link or relation to objects of another class. Every association is defined through three elements:

  1. Cardinality
  2. Navigation (direction)
  3. Name (role)

Cardinality refers to how many objects of one class can be related to an object of another class. It determines constraints such as: “an order must have exactly one customer” or “a company must have at least one employee.”

Cardinality doesn’t have to be a single number — it can be a range, which means there is a lower and upper bound of cardinality. The most commonly used cardinalities are:

Cardinality Example
1..1 (or just 1) An order must have exactly one customer
0..1 A stock market client may have an exclusive broker, but doesn’t have to
0..* (or just *) A person may own one or more cars, or none
1..* A company must have at least one employee, but may have more

Navigation (direction) of an association refers to which class it is directed from and to. For example, the association “drives” goes from the class Person to the class Car. Based on this, associations can be unidirectional or bidirectional, with the latter being visible from both directions.

Each association can also have a name (role) that describes the relationship it represents. Unidirectional associations can have one name, while bidirectional ones can have two names based on the direction. For example, the association between the class Company and the class Employee might be:

  • “employs” from Company to Employee
  • “is employed by” from Employee to Company

In Java, associations are usually implemented as ordinary class attributes, but the type of the attribute is the class itself — by referencing the class name. In general, the implementation depends on the cardinality. For example, many-to-many relationships are often implemented via an additional class. Graphically, associations are shown as solid lines between classes, with possible cardinality and association name indicated. If unidirectional, the line may end with an arrow showing the direction.

Example 1

An example of an association is the relationship between Car and Person classes, where a person is the owner of the vehicle (Figure 21). This is a bidirectional association (no arrows).

Association example
Figure 21. Association example

Pay attention to the cardinality: next to the Car class is “0..*”, meaning a person can own many cars or none. The other direction shows “1”, meaning each car has exactly one owner.

This association has one name (“owner”), but could be written with two names:

  • “is owner” (from Person to Car)
  • “has owner” (from Car to Person)

Java implementation:

 1 class Car {
 2 
 3     String brand;
 4     String model;
 5     String licensePlate;
 6     int engineCapacity;
 7 
 8     // Associations are implemented as class attributes.
 9     // The type of the attribute is the name of the second class
10     // from the association. Here, the attribute owner is the
11     // object of the class of Person.
12     Person owner;
13 
14 }
15 
16 class Person {
17     String firstName;
18 
19     String lastName;
20 
21     String address;
22 
23     String socialId;
24 }
  • Class Person has only basic attributes.

  • Class Car includes basic attributes plus an attribute owner of type Person:

    Person owner;

This implementation allows cardinality of 0 or 1, because if owner is not initialized, its value is null. To enforce exact cardinality of 1, use a constructor that either:

  • Requires an owner as a parameter
  • Or initializes the owner within the constructor

It is important to note that associations are implemented as class attributes, but the name of the second class is listed as the attribute type.

Example 2

Same relationship, but implemented differently:

 1 class Car2 {
 2 
 3     String brand;
 4     String model;
 5     String licensePlate;
 6     int engineCapacity;
 7 
 8 }
 9 
10 class Person2 {
11     String firstName;
12 
13     String lastName;
14 
15     String address;
16 
17     String socialId;
18 
19     // A person can be the owner of one or more cars.
20     // Therefore, in addition to the name of the Car2 class,
21     // the attribute owner has angular brackets ("[]") to indicate
22     // that there can be more objects (specifically, an array of
23     // Car2 objects).
24     Car2[] owner;
25 }
  • The Car2 class would contain only “regular” attributes
  • The Person2 class would be a class that contains both “regular” attributes and the owner attribute, which represents the relationship. In this case, the relationship involves a higher cardinality (a person can own multiple cars)

The attribute owner is an array of Car2 objects in the Person2 class (to reflect one person owning multiple cars).

1 Car2[] owner;

The implementation of a relationship when the cardinality is greater than one is done using appropriate data structures that can hold multiple objects. Some of these data structures are: arrays, lists, maps, etc. Arrays and lists are covered in detail in the following lessons and will not be explained here.

Example 3

Finally, this same relationship between the classes Person and Car (Figure 21) can also be implemented in a third way, by adding the owner relation as an attribute in both classes (merging the previous two solutions). In this way, it becomes a bidirectional implemented relationship.

The third variant of implementing this association would look like this:

 1 class Car3 {
 2 
 3     String brand;
 4     String model;
 5     String licensePlate;
 6     int engineCapacity;
 7 
 8     Person3 owner;
 9 
10 }
11 
12 class Person3 {
13     String firstName;
14 
15     String lastName;
16 
17     String address;
18 
19     String socialId;
20 
21     Car3[] owner;
22 }
  • The Car3 class would contain only “regular” attributes and the owner attribute that represents the relation
  • The Person3 class would contain both “regular” attributes and the owner attribute that represents the relation. In this case, the relation encompasses a greater cardinality (a person can own multiple cars)

The question arises: Which of these three implementations of the given association is the best? The answer depends on the specific situation, but for simplicity, perhaps in this case, the first variant (from Example 1) is the best, where the association is such that the connection is made from the Car class and each car contains data about its owner (Person class). So, even though the association from Figure 21 is bidirectional, for simplicity, it does not have to be implemented as bidirectional and can be implemented only in one class.

Calling an object through the association is done in a similar way to calling a regular attribute, with the difference that the object needs to be initialized before its first use – just like when using other objects.

1 c.owner = new Person();
2 c.owner.name = "John";

Example 4

Write a class TestCar that in the main method creates an object of the Car class (from Example 1) with the brand “Ford”, model “Focus”, license plate “W123-456”, and engine capacity 1799.

Set the owner of this car to be Peter Smith, social ID 710128, who lives at Elm Street 27. Use the Person class (from Example 1).

The TestCar class would look like this:

 1 class TestCar {
 2 
 3     public static void main(String[] args) {
 4         Car c = new Car();
 5 
 6         c.brand = "Ford";
 7         c.model = "Focus";
 8         c.licensePlate = "W123-456";
 9         c.engineCapacity = 1799;
10 
11         // Attribute owner is an object
12         // so it must be initialized before use
13         c.owner = new Person();
14 
15         //Owners attributes (Person attributes)
16         // can be accessed through the Car objects
17         // name (c) followed by the owner attribute
18         // name (owner) all separated by dots.
19         c.owner.firstName = "Peter";
20         c.owner.lastName = "Smith";
21         c.owner.socialId = "710128";
22         c.owner.address = "Elm street 27";
23     }
24 
25 }

Before using the owner attribute, it is necessary to initialize the object of the Person class that represents the car owner:

1 c.owner = new Person();

The attributes of the Person class object, which is the car owner, can be accessed indirectly — first by calling the Car class object (c), and then calling the owner attribute. Since the owner is also an object, it has its attributes which can further be accessed by their name (i.e. address). Each element in the call must be separated with a dot.

1 Object name (c)
2 |  Attribute name (owner), which is also an object of the Person class
3 |  |    Attribute name (address)
4 |  |    |
5 c.owner.address = "Elm street 27";

Composition - Decomposition represents a special type of association in which an object of one class, as a component, contains one or more objects of another class. There are two types of composition-decomposition relations.

If the contained object (or objects) cannot exist independently of the object that contains it, it is composition, and if it can exist independently, it is aggregation. In composition, when the object that contains other objects is deleted, those objects are also deleted. In aggregation, this is not the case.

The composition-decomposition relationship is graphically represented by a line with a diamond at one end. If the diamond is filled, it indicates composition; if it is empty, it indicates aggregation. Composition-decomposition can have a name, and the cardinality is written only on one side because the other side is always 1.

Composition-decomposition is implemented in the same way as a regular association - via class attributes. The only difference is in how the association is interpreted.

Example 5

A commonly cited example to describe the composition relation is the relationship between the classes Invoice and InvoiceItem (Figure 22).

Composition example
Figure 22. Composition example

Each invoice contains one or more items (the lower cardinality is 1, and the upper cardinality is many), and each item belongs to exactly one invoice (both the lower and upper cardinality are always 1, so they are not even written).

It would make no sense to have invoice items that do not belong to any invoice or items that belong to several invoices at once. Also, when an invoice is deleted, all its items are deleted as well.

*The implementation of this composition could look like this (with similar alternative implementations as for Example 1, Example 2, and Example 3):

 1 class InvoiceItem {
 2 
 3     int itemNumber;
 4 
 5     String product;
 6 
 7     double unitPrice;
 8 
 9     double quantity;
10 
11     double itemPrice;
12 
13     // Calculates the item price by
14     // multiplying unitPrice with quantity
15     void calculateItemPrice(){
16         itemPrice = unitPrice * quantity;
17     }
18 
19 }
20 
21 class Invoice {
22     // Special Java classes are used for dates,
23     // but String is put here for simplicity
24     String date;
25 
26     int invoiceId;
27 
28     double totalPrice;
29 
30     InvoiceItem[] items;
31 
32     void calculateTotalPrice(){
33         // some code which sums all individual item prices from
34         // the "items" array
35     }
36 }

Invoice item contains the data for a single invoice item and has a method that calculates how much that single item costs - calculateItemPrice (e.g., two loaves of bread costing 54 cents each would total 108 cents). The invoice date is set to be of type String, but in Java, special classes are used for this, which are covered in later lessons.

Invoice contains a composition to multiple invoice items, implemented as an attribute called items (an arbitrary name, the composition name is not specified in Figure 22) which is an array of InvoiceItem objects:

1 InvoiceItem[] items;

The method calculateTotalPrice should loop through the array of invoice items and add up the individual amounts of each item, then enter the total sum into the attribute totalAmount. Since arrays of objects (as a topic) are covered in later lessons, the code for this method is omitted in this example.

Example 6

An aggregation relationship can be demonstrated through the relationship between the classes Plane and Person, where the people are passengers on the plane (Figure 23).

Aggregation example
Figure 23. Aggregation example

Unlike composition, aggregation implies that the aggregated objects can exist independently of the object that contains them. In this case, objects of the Person class can exist as separate entities – completely unrelated to the context of the Plane and its passengers.

For example, an object of the Person class can form an association “owner” with objects of the Car class.

The implementation of this aggregation could look like this (with similar alternative implementations as in Example 1, Example 2, and Example 3):

  • Class Person (from Example 1)

  • Class Plane

    class Plane {

    1   String brand;
    2 
    3   String model;
    4 
    5   int numberOfSeats;
    6 
    7   String airlineName;
    8 
    9   Person[] passengers;
    

    }

Class Plane contains an aggregation to multiple Person objects, implemented as an attribute called passengers, which is an array of Person objects:

1 Person[] passengers;

Generalization - Specialization is a type of relationship where one class inherits from another, and is therefore often called inheritance. Inheritance involves taking all attributes and all methods (except private ones) from the superclass (the class being inherited – also called the “parent” class) and transferring them to the subclass (the class that inherits – also called the “child” class). The end result is that the subclass has all the characteristics and (public or protected) behaviors of the superclass, but can also have additional characteristics and behaviors of its own.

Inheritance, public/protected methods, and access levels in general are explained in more detail in later lessons. For now, it’s enough to know that the graphical representation of an inheritance relationship is a directed line with a hollow triangular arrowhead. Cardinality and relationship names are not used with this type of relationship.

Example 7

Let’s say the Person class has the attributes: firstName, lastName, address, and socialId. Now, suppose we need to create a Student class that has the same attributes, but also an additional attribute studentId.

In this case, the simplest solution is to make the Student class inherit from the Person class.

The Student class will inherit the attributes firstName, lastName, address and socialId from the Person class, so we only need to add the new attribute studentId (see Figure 24).

Example of an inheritance relationship (generalization-specialization)
Figure 24. Example of an inheritance relationship (generalization-specialization)

The implementation of this inheritance relationship would look like this:

  • Person class (from Example 1)

  • Student class

    // The inheritance is declared using // the reserved word extends and stating // the parental class name (superclass) class Student extends Person {

    1   String studentId;
    

    }

As you can see, inheritance is marked by the reserved keyword extends, followed by the name of the superclass within the subclass declaration. Specifically, in the Student class declaration, we have:

1 class Student extends Person

In the Student class, there’s no need to redeclare the attributes firstName, lastName, address and socialId — they’re inherited. We only declare the attribute studentId. This avoids duplicating similar or identical code in similar classes. If the Person class had any non-private methods, they would also be inherited by the Student class.

In the main method of the TestStudent class, you can see that an object of the Person class has attributes such as firstName, lastName, address and socialId assigned with values. The Student object, thanks to inheritance, has those same attributes plus the additional studentId attribute, which does not exist in the Person class.

 1 class TestStudent {
 2 
 3     public static void main(String[] args) {
 4         Person p = new Person();
 5 
 6         p.firstName = "Peter";
 7         p.lastName = "Smith";
 8         p.address = "Elm street 27";
 9         p.socialId = "710322";
10 
11         Student s = new Student();
12 
13         // The Student class inherits attributes
14         // and methods from the Person class
15         s.firstName = "Mike";
16         s.lastName = "Stone";
17         s.address = "Park street 3";
18         s.socialId = "715065";
19 
20         // Student also has an additional attribute studentId
21         // that does not exist in the Person class
22         s.studentId = "123/2023";
23     }
24 }

A usage relationship occurs when one class uses an object of another class within one of its methods. It doesn’t matter whether that object is a method parameter, a return value, or simply used within the method body.

This type of relationship typically does not have explicitly defined cardinality or a name, and it is represented graphically with a dashed line directed toward the class that is being used.

Example 8

Create a class Employee with the attributes: firstName, lastName and yearsOfExperience.

Create a class RewardSystem with a static method called calculateBonus. This method receives an object of the Employee class as a parameter and calculates and returns the percentage representing the variable part of the salary using the formula:

1 yearsOfExperience * 1.2

In the figure (Figure 25), you can see how the usage relationship is represented graphically. Like other relationships, this one is shown with a line between classes — but this time it’s dashed and directed: the RewardSystem class uses the Employee class. In usage relationships, names and cardinalities are usually omitted.

Example of a usage relationship
Figure 25. Example of a usage relationship

The implementation of this usage relationship would look like this:

  • Employee class.

  • RewardSystem class.

    class Employee {

    1   String firstName;
    2   String lastName;
    3   int yearsOfExperience;
    

    }

    class RewardSystem {

    1   // The method receives the entire Employee object as parameter
    2   static double calculateBonus(Employee employee){
    3       double bonus;
    4       bonus = employee.yearsOfExperience * 1.2;
    5       return bonus;
    6   }
    

    }

In this example, the RewardSystem class uses an Employee object as a parameter in one of its methods. Methods can also return objects as results.

In the main method of the TestEmployee class, you can see that an Employee object named Peter Smith with 30 years of experience is passed entirely to the calculateBonus method.

 1 class TestEmployee {
 2 
 3     public static void main(String[] args) {
 4         Employee employee1 = new Employee();
 5 
 6         employee1.firstName = "Peter";
 7         employee1.lastName = "Smith";
 8         employee1.yearsOfExperience = 30;
 9 
10         double bonus = RewardSystem.calculateBonus(employee1);
11 
12         System.out.println("The bonus for "+
13                 employee1.firstName + " " +
14                 employee1.lastName + " is: " +
15                 bonus + "%");
16     }
17 }

Most common errors

Some of the most common syntax errors related to declaring and using relationships include:

  • Using an attribute that is an object of another class (i.e., implementing a relationship) without initializing that object. For example:

    Car c = new Car(); c.owner.firstName = “Peter”;

Instead of (correct):

1 Car c = new Car();
2 c.owner = new Person();
3 c.owner.firstName= "Peter";

Enumerated type (enum)

Lesson content

  • What is the enumerated type (reserved word enum)
  • Declaring and calling an enumerated type, naming convention
  • Attributes and methods in enums
  • Most common errors

What is the enumerated type (reserved word enum)

In previous lessons, simple types in Java had been explained (int, double, char, boolean), as well as the String class – all of which allow the declaration of simple attributes and cover a wide range of possible data types or values. When that’s not enough, complex types (classes) are used, and class declarations can introduce attributes, enabling an object of that class to hold multiple related values.

However, there are cases when we need to create a new data type that is not a simple type but has a strictly limited set of possible values. For example: eye color, which can only be one of four values – blue, green, brown, and violet. It could also be a list of continents (seven values): Europe, Asia, Africa, North America, South America, Australia, and Antarctica. Or transaction status: completed, canceled, or failed, etc.

It’s important to notice that using existing types like String is not suitable in these cases because it does not limit input to only allowed values. For example, if we used String for eye color and expected values like “blue”, “green”, “brown”, or “violet”, someone could enter “dog”, “tree”, or any other String value - all being invalid for eye color.

In Java, the solution for these cases is the enumerated type (or enum for short). Enums are Java classes that have a limited set of predefined values — specific instances (objects) defined at the time of declaration.

Declaring and calling an enumerated type, naming convention

An enum is declared similarly to a class, but instead of the keyword class, the reserved word enum is used. The enum name follows the same naming convention as a class (“CamelCase” with capitalized first letters of each word). The possible values (instances) of the enum are listed inside the body, separated by commas. Enum instance names follow the same convention as constants, i.e., all uppercase letters (e.g., BLUE, GREEN, LIGHT_BROWN) and words separated with underscores (_).

1 enum EnumTypeName {
2     INSTANCE_1, INSTANCE_2, ... INSTANCE_N;
3 }

The order in which instances are listed doesn’t matter. Enum values are accessed in the same way as static attributes — using the enum type name:

1 EnumTypeName.INSTANCE_1

Also note: variables of an enum type aren’t initialized using a constructor like regular class instances, but they do need to be initialized before use. Simply assign the value like this:

1 EnumTypeName variable = EnumTypeName.INSTANCE_3;

Just like with all classes and objects, the default value of an enum variable is null.

Example 1

Create an enum EyeColor with the following values:

  • BLUE, GREEN, LIGHT_BROWN, DARK_BROWN, VIOLET

    enum EyeColor { BLUE, GREEN, LIGHT_BROWN, DARK_BROWN, VIOLET; }

Create a class Person with:

  • Attributes firstName and lastName

  • Attribute eyeColor, representing the person’s eye color

  • Method printInfo, which prints all data about the person

    class Person {

     1   String firstName;
     2 
     3   String lastName;
     4 
     5   EyeColor eyeColor;
     6 
     7   void printInfo() {
     8       System.out.println(firstName + " " + lastName + 
     9       ", eye color: " + eyeColor);
    10   }
    

    }

Create a class TestPerson which in the main method creates a new Person object named Larry Stone, with light brown eyes, and prints this person’s data to the screen.

 1 class TestPerson {
 2 
 3     public static void main(String[] args) {
 4         Person o = new Person();
 5 
 6         o.firstName = "Larry";
 7         o.lastName = "Stone";
 8 
 9         o.eyeColor = EyeColor.LIGHT_BROWN;
10 
11         o.printInfo();
12     }
13 }

This example shows that the eye color is defined by a strictly limited set of values. If any other value were assigned, Java would throw a syntax error.

Also, when the main method is run, the output for the eye color will be LIGHT_BROWN, exactly as defined in the enum.

Although enum instances behave like (static) constants in how they are defined and accessed, there is one key difference: constants always have an associated value (e.g., PI = 3.141592), while enum instances usually do not — the instance name alone is enough (e.g., eye color does not need an associated numeric or string value).

Example 2

Create an enum Continent with the following values:

  • Europe, Asia, Africa, Australia, Antarctica, South America, North America

    enum Continent { EUROPE, ASIA, AFRICA, AUSTRALIA, ANTARCTICA, SOUTH_AMERICA, NORTH_AMERICA; }

Create a class Country with:

  • Attribute name

  • Attribute area (surface area in square km)

  • Attribute continent, representing the continent the country is located on

  • Method printInfo to print all country data

    class Country {

     1   String name;
     2 
     3   int area;
     4 
     5   Continent continent;
     6 
     7   void printInfo(){
     8       System.out.println(name + ", area " + area + 
     9       " km2, is on continent "+ continent);
    10   }
    

    }

Create a class TestCountry which in the main method creates a country named Egypt with a surface area of 1,001,450 km², located in Africa, and prints its data.

 1 class TestCountry {
 2 
 3     public static void main(String[] args) {
 4         Country d = new Country();
 5 
 6         d.name = "Egypt";
 7         d.area = 1001450;
 8         d.continent = Continent.AFRICA;
 9 
10         d.printInfo();
11     }
12 }

When the main method runs, the output will be:

1 Egypt, area 1001450 km2, is on continent AFRICA

This shows that the name of the enum instance (AFRICA) is printed exactly as defined.

Attributes and methods in enums

As mentioned, an enum is actually a special kind of Java class. In the previous examples, enums were used in their simplest form — just listing the instances. But it’s also possible to define attributes, methods, and constructors inside the enum itself. This allows each instance to have additional data and behaviors.

Example 3

Create an enum Continent2 with the following values:

  • Europe, Asia, Africa, Australia, Antarctica, South America, North America

Each continent should have a surface area (in square kilometers), stored in the attribute area:

  • Europe: 10,180,000 km²
  • Asia: 43,810,000 km²
  • Africa: 30,370,000 km²
  • Australia: 7,600,000 km²
  • Antarctica: 13,720,000 km²
  • South America: 17,840,000 km²
  • North America: 24,490,000 km²

Define a parameterized constructor to assign the surface area, and a method printInfo to print all the data about the continent.

 1 enum Continent2 {
 2 
 3     EUROPE (10180000), ASIA (43810000),
 4     AFRICA (30370000), AUSTRALIA (7600000),
 5     ANTARCTICA (13720000), SOUTH_AMERICA (17840000),
 6     NORTH_AMERICA (24490000);
 7 
 8     // The continent's surface area in square kilometers
 9     int area;
10 
11     //A constructor receiving the surface area as a parameter
12     Continent2(int area){
13         this.area = area;
14     }
15 
16     void printInfo(){
17         System.out.println(this.name() + " (area: " +
18          area + " km2)");
19     }
20 
21 }

Also create the classes Country2 and TestCountry2, similar to the previous example but using Continent2.

 1 class Country2 {
 2 
 3     String name;
 4 
 5     int area;
 6 
 7     Continent2 continent;
 8 
 9     void printInfo(){
10         System.out.print (name + ", area " + area +
11          " km2, is on continent ");
12         continent.printInfo();
13     }
14 }
15 
16 class TestCountry2 {
17 
18     public static void main(String[] args) {
19         Country2 d = new Country2();
20 
21         d.name = "Egypt";
22         d.area = 1001450;
23         d.continent = Continent2.AFRICA;
24 
25         d.printInfo();
26     }
27 }

This example shows that the enum Continent2 now behaves more like a regular class: it includes an attribute (area), a constructor, and a method (printInfo). Notably:

  • Enum instances are listed with specific surface areas in parentheses, which effectively calls the constructor and assigns a value.
  • The name of the enum instance (continent name) is accessed via the method name() (i.e., this.name()).

When the main method runs, the screen will display:

1 Egypt, area 1001450 km2, is on continent AFRICA (area: 30370000 km2)

Most common errors

Some common syntax errors when writing and using enums include:

  • Using the keyword class instead of enum when declaring an enum.

  • Forgetting to assign values to enum instances via the constructor — if a parameterized constructor is defined.

  • Forgetting to initialize enum-type variables before using them.

  • Using semi-columns instead of commas to separate declarations of enum instances.

Algorhitms and algorithmic structures - introduction

Lesson content

  • Algorithm – definition and examples
  • Alternative algorithms
  • Algorithmic structures – linear, branching, and looping

Algorithm – definition and examples

An algorithm can be defined as a procedure with clearly defined steps that lead to the solution of a problem. An algorithm can refer to any process, activity, or problem-solving method. Therefore, we use algorithms all the time in our everyday lives.

Example 1

Let’s say we want to make lemonade. There are specific steps we need to follow in a certain order to accomplish this. It’s a recipe for lemonade but, in an informatics sense, it’s also an algorithm:

  1. Take a pitcher and pour one liter of water into it.
  2. Take one lemon and wash it.
  3. Cut the lemon in half and squeeze it using a juicer.
  4. Pour the lemon juice into the pitcher.
  5. Add one tablespoon of sugar to the pitcher.
  6. Stir the liquid in the pitcher for about half a minute to mix everything together.

In an algorithm, steps are executed in the exact order they are written (in this case, from step 1 to 6). This order is important because if steps are done out of sequence, the desired result may not be achieved.

For instance, if you stir the liquid (step 6) before adding all the ingredients (steps 4 and 5), the sugar may not dissolve properly, and the result won’t be a uniformly flavored lemonade. Also, you can’t pour the lemon juice (step 4) if you haven’t first cut and squeezed the lemon (step 3) and washed it (step 2).

Alternative algorithms

Of course, in real life, there are often multiple ways to reach a solution. There are almost always alternative procedures and steps. Therefore, for every algorithm, there’s usually an alternative algorithm that achieves the same result using different steps or a different order.

Example 2

Some steps from the lemonade-making algorithm above can be rearranged, resulting in an alternative algorithm that still leads to the same outcome. For example, the water can be added toward the end, just before stirring, and the sugar can be added at the beginning. So we get the following alternative lemonade algorithm (by swapping steps 1 and 5 from Example 1):

  1. Take a pitcher and add a tablespoon of sugar.
  2. Take one lemon and wash it.
  3. Cut the lemon in half and squeeze it using a juicer.
  4. Pour the lemon juice into the pitcher.
  5. Add one liter of water to the pitcher.
  6. Stir the liquid in the pitcher for about half a minute to mix everything together.

Algorithmic structures – linear, branching, and looping

Any sequence of Java commands written within a method body (a block of statements) actually represents an algorithm (i.e., its implementation in Java).

The Java commands in the examples above are executed strictly in the order in which they are written – “top to bottom”. However, in some situations, it’s desirable to execute certain commands only if a specific condition is met. Other situations may require a command to be repeated multiple times.

Each of these three cases corresponds to a different algorithmic structure:

  • Linear (sequential) – each command (step) is executed once, in the order written, unconditionally, and without repetition.
  • Branching (selection) – a condition is checked first; if it is true, one set of commands is executed; if not, a different set is executed.
  • Looping (iteration, cyclic) – a command (or a group of commands) is executed multiple times in a row.

The algorithms from Examples 1 and 2 are purely linear structures, with no branching or looping. They don’t contain any conditions, decisions, or repeated steps. The next example introduces the concept of a branching structure.

Example 3

Let’s say that the lemonade-making algorithms from the previous examples aren’t good enough because they don’t take into account that the lemonade should be tasted and possibly sweetened further if it’s not sweet enough.

If we want to taste the lemonade to check if it’s sweet enough, and then add more sugar if necessary (or do nothing if it is sweet enough), we’re introducing a branching structure (decision-making) into the algorithm. After this change, the algorithm would look like this:

  1. Take a pitcher and pour one liter of water into it.
  2. Take one lemon and wash it.
  3. Cut the lemon in half and squeeze it using a juicer.
  4. Pour the lemon juice into the pitcher.
  5. Add a tablespoon of sugar to the pitcher.
  6. Taste the lemonade. Is it sweet enough? (CONDITION for branching)
  • BRANCH_YES: Do not add anything.
  • BRANCH_NO: Add one more tablespoon of sugar.
  1. Stir the liquid in the pitcher for about half a minute to mix everything together.

Note that BRANCH_YES will be executed only if the condition is met, i.e., the lemonade is sweet enough. BRANCH_NO (adding another tablespoon of sugar) will only be executed if the lemonade is not sweet enough. Both branches are never executed at the same time.

After these changes, this algorithm now contains both a linear and a branching (selection) structure.

The next example explains the concept of a looping structure. As mentioned earlier, there are cases where certain steps need to be repeated, and looping algorithmic structures are used in such situations.

Example 4

Let’s say that the lemonade-making algorithm from the previous example still isn’t quite right, and instead of adding just one tablespoon of sugar before tasting, we want to add three tablespoons first.

The simplest way to write the algorithm is to repeat step 5 three times, like this:

  1. Take a pitcher and pour one liter of water into it.
  2. Take one lemon and wash it.
  3. Cut the lemon in half and squeeze it using a juicer.
  4. Pour the lemon juice into the pitcher.
  5. Add a tablespoon of sugar to the pitcher.
  6. Add a tablespoon of sugar to the pitcher.
  7. Add a tablespoon of sugar to the pitcher.
  8. Taste the lemonade. Is it sweet enough? (CONDITION for branching)
  • BRANCH_YES: Do not add anything.
  • BRANCH_NO: Add one more tablespoon of sugar.
  1. Stir the liquid in the pitcher for about half a minute to mix everything together.

But what if this step needs to be repeated fifty times or until a certain condition is met? Simply copying the step would make the algorithm unreadable (in the first case) or unworkable (in the second case) if we don’t know in advance how many times it needs to repeat.

A better solution is to introduce a looping algorithmic structure that repeats a step multiple times until a condition is met. With that change, the algorithm would look like this:

  1. Take a pitcher and pour one liter of water into it.
  2. Take one lemon and wash it.
  3. Cut the lemon in half and squeeze it using a juicer.
  4. Pour the lemon juice into the pitcher.
  5. Counter = 0. Repeat until the counter reaches 3: (loop structure with a counter)
  • 5.1. Add a tablespoon of sugar to the pitcher.
  • 5.2. Increase the counter by 1.
  1. Taste the lemonade. Is it sweet enough? (CONDITION for branching)
  • BRANCH_YES: Do not add anything.
  • BRANCH_NO: Add one more tablespoon of sugar.
  1. Stir the liquid in the pitcher for about half a minute to mix everything together.

Note that step 5 now contains a loop structure with a counter that repeats the action of adding sugar exactly three times. The counter and step 5.2 are part of the loop and control its execution.

This loop works as follows: The counter starts at zero. After each sugar addition (step 5.1), the counter is increased by one (step 5.2). After the first tablespoon, the counter becomes 1. After the second, it becomes 2, and after the third, it becomes 3. The loop stops when the counter reaches 3.

It’s worth noting that this algorithm now contains all three types of algorithmic structures: linear, branching, and looping.

It is common practice for algorithms to include multiple algorithmic structures. Therefore, more complex algorithms are usually created this way: by combining several algorithmic structures (both the same and different types) into one algorithm. The more structures an algorithm has, the more complex it is.

Moreover, these structures are often nested within each other, so you may have a selection structure inside a loop, or vice versa. Sometimes, the nesting is even deeper, making the algorithm increasingly complex.

The next chapter provides an overview of how these algorithmic structures are implemented in the Java programming language, i.e., which Java statements are used to realize them.

Each of these statements is then explained in detail in its own chapter.

Overview of control (flow) statements in Java

Lesson content

  • Implementation of algorithmic structures in Java - the control (flow) statements
  • Overview of branching statements: if, switch
  • Overview of looping statements (so-called loops): for, while, do-while, for-each

Implementation of algorithmic structures in Java - the control (flow) statements

Any sequence of Java commands written within the body of a method (a block of statements) actually represents an algorithm (i.e., its implementation in Java). So, how are specific algorithmic structures (sequential, branching, looping) implemented in Java?

Java commands from previous examples are executed strictly in the order in which they are written – “top to bottom”. This effectively represents the implementation of the sequential algorithmic structure in Java, and it is very simple. It’s enough to write the commands “one below the other”, and they will be executed sequentially in that order. If a method within this block calls another method, the commands in that called method will also be executed “top to bottom”, as if they were directly written in the calling method.

However, the implementation of branching and looping algorithmic structures in Java is achieved using special statements known as control (flow) statements, which are divided into two groups depending on the structure they implement:

  • Conditional branching statements (implement the branching algorithmic structure):
    • if statement
    • switch statement
  • Looping statements (implement the looping algorithmic structure, also known as “loops”):
    • for statement
    • while statement
    • do-while statement
    • for-each statement (covered in the chapter on arrays)

It is common for algorithms to contain combinations of multiple algorithmic structures, so control statements can be freely combined as needed.

Below is a brief overview of these statements with descriptions and simple examples. Each of these statements is explained in detail in the following lessons.

The if statement

The if statement implements conditional branching in Java when there are only two branches based on a condition:

  • A branch that executes when the condition is true
  • A branch that executes when the condition is false

Example 1

Create a class TemperatureCheck that has:

  • A method checkTemperature that takes water temperature as a parameter and prints whether the water is warm enough for swimming (it must be greater than 22°C).

    class TemperatureCheck {

    1   void checkTemperature(double temperature){
    2       if (temperature > 22)
    3           System.out.println("The water is warm enough to swim");
    4       else
    5           System.out.println("The water is too cold to swim");
    6   }
    

    }

Create a class TestTemperatureCheck that in its main method checks if water temperatures of 19°C and 25°C are warm enough for swimming.

 1 class TestTemperatureCheck {
 2 
 3     public static void main(String[] args) {
 4         TemperatureCheck tc = new TemperatureCheck();
 5 
 6         tc.checkTemperature(19);
 7 
 8         tc.checkTemperature(25);
 9     }
10 }

The switch statement

The switch statement is used when there are more than two branches. The branching condition is based on the value of a variable, and each branch corresponds to a specific value.

Example 2

Create a class GradeCheck that has:

  • A method checkGrade that takes a student grade (1 to 5) and prints its name: 5 - excellent, 4 - very good, 3 - good, 2 - sufficient, 1 - insufficient.

    class GradeCheck {

     1   void checkGrade(int grade){
     2       switch (grade){
     3           case 5: System.out.println("excellent");break;
     4           case 4: System.out.println("very good");break;
     5           case 3: System.out.println("good");break;
     6           case 2: System.out.println("sufficient");break;
     7           case 1: System.out.println("insufficient");break;
     8           default: System.out.println("The grade must be in range 1-5");
     9       }
    10   }
    

    }

Create a class TestGradeCheck that tests grades 5 and 3 in the main method.

 1 class TestGradeCheck {
 2 
 3     public static void main(String[] args) {
 4         GradeCheck gc = new GradeCheck();
 5 
 6         gc.checkGrade(5);
 7 
 8         gc.checkGrade(3);
 9     }
10 }

The for statement

The for statement implements a looping structure when the number of repetitions is known. It uses a loop counter to control the number of iterations.

Example 3

Create a class Printer with:

  • A method printHello that prints the word “Hello” ten times.

    class Printer {

    1   void printHello(){
    2       for (int i=1; i<=10; i++)
    3           System.out.println("Hello");
    4   }
    

    }

Create a class TestPrinter that calls the above method.

1 class TestPrinter {
2     public static void main(String[] args) {
3         Printer p = new Printer();
4 
5         p.printHello();
6     }
7 }

The while statement

The while statement implements a loop when the number of iterations is not known in advance. The loop runs as long as the condition is met, and the condition is checked before each iteration.

Example 4

Create a class InterestCalculator with:

  • A method duplicationOfMoney that takes an investment amount and an annual interest rate (e.g., 12.5%). It prints the account balance after each year until the amount is doubled, assuming compound interest.

    class InterestCalculator {

    1   void duplicationOfMoney(double investmentAmount, double interestRate){
    2       double total = investmentAmount;
    3 
    4       while (total < investmentAmount * 2){
    5           total = total * (100.0 + interestRate)/100.0;
    6           System.out.println(total);
    7       }
    8 
    9   }
    

    }

Create a class TestInterestCalculator that tests the above method with an investment of 20,000 dollars at 10% interest.

1 class TestInterestCalculator {
2 
3     public static void main(String[] args) {
4         InterestCalculator ic = new InterestCalculator();
5 
6         ic.duplicationOfMoney(20000, 10);
7     }
8 }

The do-while statement

The do-while statement is similar to while, but the condition is checked after each iteration. This ensures that the loop always executes at least once.

Example 5

Create a class InterestCalculator2 with the method duplicationOfMoney from the previous example, but using do-while.

 1 class InterestCalculator2 {
 2 
 3     void duplicationOfMoney(double investmentAmount, double interestRate){
 4         double total = investmentAmount;
 5 
 6         do {
 7             total = total * (100.0 + interestRate)/100.0;
 8             System.out.println(total);
 9         } while (total < investmentAmount * 2);
10 
11     }
12 }

Create a class TestInterestCalculator2 that tests the method with an investment of 20,000 dollars at 10% interest.

1 class TestInterestCalculator2 {
2 
3     public static void main(String[] args) {
4         InterestCalculator2 ic = new InterestCalculator2();
5 
6         ic.duplicationOfMoney(20000, 10);
7     }
8 }

The for-each statement

The for-each statement is used for looping specifically over arrays or collections. It means “for each item” and executes the same statement(s) for every element in a collection.

Example 6

Create a class Student with:

  • Attributes: name, surname, and allGrades (an array of integers from 1 to 5).

  • A method printStudent that prints the student’s full name and all grades.

    class Student {

     1   String name;
     2 
     3   String surname;
     4 
     5   int[] allGrades;
     6 
     7   void printStudent(){
     8       System.out.println(name + ", " + surname);
     9 
    10       for (int grade : allGrades)
    11           System.out.println(grade);
    12   }
    

    }

Create a class TestStudent that, within its main method, creates a student named Mike Stone with grades 5, 4, 3, 4, 5, 5, and prints all of his data on the screen.

 1 class TestStudent {
 2 
 3     public static void main(String[] args) {
 4         Student s = new Student();
 5 
 6         s.name = "Mike";
 7         s.surname = "Stone";
 8         s.allGrades = new int[]{5, 4, 3, 4, 5, 5};
 9 
10         s.printStudent();
11     }
12 }

The if statement

Lesson content

  • What is the if statement – declaration and elements
  • Comparison operators
  • Alternative ways of writing if statements
  • Logical operators and their precedence, compound conditions
  • Unsatisfiable (contradiction) logical expression
  • Always true (tautology) logical expression
  • Nested if statements
  • Most common errors

What is the if statement – declaration and elements

Programmers often encounter situations where it is necessary to check a condition and, depending on whether it is met, do one thing or another. For example, it might be necessary to check whether an entered height falls within the range of 80 cm to 240 cm, and if it does not, display an error message.

This can be accomplished using a branching algorithmic structure, that is, the if control statement as implemented in Java. The declaration of an if statement is done as follows:

1 if ( ...condition... ) command_1;
2 else command_2;

The declaration begins with the reserved word if (lowercase). After that, within parentheses, you write the logical condition to be checked. After the condition comes the first branch (the “YES” branch), where you write the command to be executed if the condition is true (command_1). Then comes the second branch (the “NO” branch), which starts with the reserved word “else” and includes the command to be executed if the condition is not true (command_2).

It is important to note that the if statement does not need to have both branches. If not required, it can have only the “YES” or only the “NO” branch.

Comparison operators

The condition written in the parentheses of an if statement represents a logical expression that can be evaluated as true or false. In the simplest case, this expression checks whether one number is greater than, less than, or equal to another. This is done using comparison operators, as shown in the following table:

Operator Description Examples
> Greater than a > b, x > 2
< Less than a < b, x < 2
>= Greater or equal a >= b, x >= 135.5
<= Less or equal a <= b, x <= 13
== Equal (two equals signs) a == b, x == 45.22
!= Not equal a != b, x != 1

Comparison operators can only be used with primitive data types, as they don’t work when comparing objects like Strings, LocalDateTime or from any other class (Person, Car, etc.). If you try to compare two objects using “==”, you are comparing their memory addresses, not their content.

It’s also important to note that the operators “=” and “==” are not the same. The first is the assignment operator, while the second is the equality comparison operator. Beginners often confuse these two, so pay close attention.

Example 1

Create a class IntegerCheck that has:

  • A method checkSign that checks whether a passed integer is positive, negative, or zero, and prints the result in the format: “Number #### is ####.”

  • A method checkLessGreaterEqual that takes two integers A and B as parameters and checks whether A > B, A < B, or A == B, and prints the result accordingly.

    class IntegerCheck {

     1   void checkSign(int a){
     2       if (a == 0)
     3           System.out.println("Number "+a+" is equal to zero");
     4 
     5       if (a > 0)
     6           System.out.println("Number "+a+" greater than zero");
     7 
     8       if (a < 0)
     9           System.out.println("Number "+a+" less than zero");
    10   }
    11 
    12   void checkLessGreaterEqual (int a, int b){
    13       if (a > b)
    14           System.out.println("Number "+a+" is greater than number "+b);
    15 
    16       if (a == b)
    17           System.out.println("Number "+a+" is equal to number "+b);
    18 
    19       if (a < b)
    20           System.out.println("number "+a+" is less than number "+b);
    21   }
    

    }

Create a TestIntegerCheck class with a main method that uses the IntegerCheck class to compare 23 and -17 and check the sign of -55.

 1 class TestIntegerCheck {
 2 
 3     public static void main(String[] args) {
 4         IntegerCheck ic = new IntegerCheck();
 5 
 6         ic.checkLessGreaterEqual(23, -17);
 7 
 8         ic.checkSign(-55);
 9     }
10 }

Each condition in the checkSign method is checked independently using three separate if statements, without else branches. That means all conditions are always checked, regardless of previous outcomes.

The condition checked in the first command is whether the number is equal to zero. If it is, the command in the extension will be executed, i.e., a message will be displayed on the screen stating that the number is equal to zero. If the condition is not met, i.e., if the number is not equal to zero, nothing happens because there is no “else” branch (no message will be displayed on the screen).
Then, the program moves on to the next IF statement, which checks if the number is greater than zero.
If it is, a message about it will be displayed. If the condition is not met, nothing happens because there is no “else” branch, and the program moves on to the third IF statement, which checks if the number is less than zero and displays a message about it only if the condition is met.

In the checkLessGreaterEqual method, the same approach is used: three independent if statements without else branches.

In the previous example, the formatting of the code is again shown, specifically “breaking” Java commands into multiple lines and indentation. In other words, an IF statement can be written across multiple lines to improve code readability:

1 if ( ...condition... )
2     command_1;

This is useful in situations where the condition or the command that follows it is quite long, so it cannot fit on a single line in the code editor (on the screen), or cannot be fully seen without scrolling the text or resizing the editor.

Also, it’s important to note that command_1 is indented to the right relative to the reserved word if (indentation). This makes it easier to see that this command is written within the IF statement.

As already mentioned, the IF statement can have a second branch (the “NO” branch), i.e., an “else” part (but it’s not mandatory). After the reserved word else, you write the command that is executed only if the condition is not met (command_2). The declaration of an IF statement with an “else” part looks like this:

1 if ( ...condition... ) command_1;
2 else command_2;

If the code formatting principles (breaking into multiple lines and indentation) are applied here, the statement can also be written like this:

1 if ( ...condition... )
2     command_1;
3 else
4     command_2;

Example 2

Create a class IntegerCheck2 that has:

  • A method notEqual that takes two numbers as parameters and returns TRUE if the numbers are not equal, and FALSE otherwise.

  • A method isEven that takes an integer as a parameter and checks whether it is even or odd. The method returns TRUE if the number is even, and FALSE if it is odd.

    class IntegerCheck2 {

     1   boolean isNotEqual(int a, int b){
     2       if (a != b)
     3           return true;
     4       else
     5           return false;
     6   }
     7 
     8   boolean isEven(int a){
     9       if ((a%2) == 0)
    10           return true;
    11       else
    12           return false;
    13   }
    

    }

Create a class TestIntegerCheck2 which, in the main method, uses the IntegerCheck2 class to compare the numbers 12 and 13, and checks the parity of the number 33.

 1 class TestIntegerCheck2 {
 2 
 3     public static void main(String[] args) {
 4         IntegerCheck2 ic = new IntegerCheck2();
 5 
 6         boolean notEqual = ic.isNotEqual(12, 13);
 7         if (notEqual)
 8             System.out.println("The numbers are not equal");
 9         else
10             System.out.println("The numbers are equal");
11 
12         boolean even = ic.isEven(33);
13         if (even)
14             System.out.println("Number 33 is even");
15         else
16             System.out.println("Number 33 is odd");
17     }
18 }

In the isEven method, it shows how to check whether a number is even or odd. If the remainder when dividing the number by two is zero, the number is even; otherwise, it is odd.

It’s important to note that the IF statements in both methods also have an else branch, because in these cases, there are commands that need to be executed if the condition is not met.

The reason is that these methods always have to return a boolean value, so if the “YES” branch doesn’t execute and returns true, the “NO” branch (the else part) will definitely be executed and will return false. Without this else branch, there could be a situation where the method would not return anything if the condition is not met — neither true nor false — which would be syntactically incorrect.

Alternative ways of writing if statements

Each IF statement can usually be written in multiple ways (at least two), while still having the exact same functionality, but with the code looking different. This is typically achieved through two steps:

  • Writing the condition in a different way (reverse condition, “negation” of the original condition)
  • Then, swapping the “YES” and “NO” branches.

It is often necessary to do both things to maintain the same functionality of the IF statement. So, the original IF statement:

1 if ( ...condition... )
2     command_1;
3 else
4     command_2;

Could become the alternative IF statement (notice that command_1 and command_2 have swapped branches):

1 if ( ...reversed condition... )
2     command_2;
3 else
4     command_1;

Writing the condition in a different way, i.e., its “negation,” can, in the simplest case, be reduced to swapping the comparison operators:

  • If the condition uses the equal to (==) operator, it is replaced with the not equal to (!=) operator, for example:

    (x == 2) becomes (x != 2) (a == f) becomes (a != f)

  • If the condition uses the greater than (>) operator, it is replaced with the less than or equal to (<=) operator, for example:

    (x > 2) becomes (x <= 2) (a > f) becomes (a <= f)

  • If the condition uses the less than (<) operator, it is replaced with the greater than or equal to (>=) operator, for example:

    (x < 2) becomes (x >= 2) (a < f) becomes (a >= f)

The same applies in reverse, so:

  • If the condition uses the not equal to (!=) operator, it is replaced with the equal to (==) operator, for example:

    (x != 2) becomes (x == 2) (a != f) becomes (a == f)

  • If the condition uses the greater than or equal to (>=) operator, it is replaced with the less than (<) operator, for example:

    (x >= 2) becomes (x < 2) (a >= f) becomes (a < f)

  • If the condition uses the less than or equal to (<=) operator, it is replaced with the greater than (>) operator, for example:

    (x <= 2) becomes (x > 2) (a <= f) becomes (a > f)

Example 3

Create a class IntegerCheck2A that has the same methods as the IntegerCheck2 class, but with differently written IF statements — using reversed conditions and swapped branches.

 1 class IntegerCheck2A {
 2 
 3     boolean isNotEqual(int a, int b){
 4         //Instead of this if statement
 5         //if (a != b) return true;
 6         //else return false;
 7 
 8         //You can use this alternative if statement
 9         if (a == b) return false;
10         else return true;
11     }
12 
13     boolean isEven (int a){
14         //Instead of this if statement
15         //if ((a%2) == 0) return true;
16         //else return false;
17 
18         //This alternative if statement can be used
19         if ((a%2) != 0) return false;
20         else return true;
21     }
22 }

Create a class TestIntegerCheck2A which, in the main method, uses the IntegerCheck2A class to compare the numbers 12 and 13, and checks the parity of the number 33.

 1 class TestIntegerCheck2A {
 2 
 3     public static void main(String[] args) {
 4         IntegerCheck2A ic = new IntegerCheck2A();
 5 
 6         boolean notEqual = ic.isNotEqual(12, 13);
 7         if (notEqual)
 8             System.out.println("The numbers are not equal");
 9         else
10             System.out.println("The numbers are equal");
11 
12         boolean even = ic.isEven(33);
13         if (even)
14             System.out.println("Number 33 is even");
15         else
16             System.out.println("Number 33 is odd");
17     }
18 }

In the isNotEqual method, you can see that instead of checking the condition (a != b)— whether a is different from b - the reversed condition (its negation) (a == b) is checked — whether a is equal to b. The “not equal” (!=) operator is replaced with the “equal” (==) operator.

To ensure the IF statement still functions the same, it is necessary to swap the “YES” and “NO” branches, i.e., if (a == b) is true, the method now returns false because the numbers are not different; if not, it returns true because the numbers are indeed different.

Similarly, for the isEven method, it now checks whether the remainder of division is different from zero ((a % 2) != 0) (the “equal” operator is replaced with the “not equal” operator), and the branches are also reversed.

There is another possibility for writing an IF statement alternatively, but with a slightly different use of the return statement. As already mentioned, besides returning a value from a method, the return statement immediately terminates the execution of that method. This sometimes means that the “NO” (else) branch does not have to be explicitly mentioned, and the code can be written outside the IF statement, i.e., in the continuation after the “YES” branch (which contains the return statement). If the “YES” branch is executed, the method will terminate, and if it is not executed, the code will continue in the continuation.

Example 4

Create a class IntegerCheck2AA that has the same methods as the IntegerCheck2 class, but with differently written IF statements — without the else branch, using the return statement.

 1 class IntegerCheck2AA {
 2 
 3     boolean isNotEqual (int a, int b){
 4         //Instead of this
 5         //if (a != b) return true;
 6         //else return false;
 7 
 8         // It can be written like this
 9         // (without the else branch) because
10         // the return command terminates the method
11         if (a != b) return true;
12 
13         return false;
14     }
15 
16     boolean isEven(int a){
17         //Instead of this
18         //if ((a%2) == 0) return true;
19         //else return false;
20 
21         // It can be written like this
22         // (without the else branch) because
23         // the return command terminates the method
24         if ((a%2) == 0) return true;
25 
26         return false;
27     }
28 }

Create a class TestIntegerCheck2AA which, in the main method, uses the IntegerCheck2AA class to compare the numbers 12 and 13, and checks the parity of the number 33.

 1 class TestIntegerCheck2AA {
 2 
 3     public static void main(String[] args) {
 4         IntegerCheck2AA p = new IntegerCheck2AA();
 5 
 6         boolean notEqual = p.isNotEqual(12, 13);
 7         if (notEqual)
 8             System.out.println("The numbers are not equal");
 9         else
10             System.out.println("The numbers are equal");
11 
12         boolean even = p.isEven(33);
13         if (even)
14             System.out.println("Number 33 is even");
15         else
16             System.out.println("Number 33 is odd");
17     }
18 }

Here, it’s important to note that the IF statements do not have the “NO” branch (else part), yet the code behaves exactly the same. In the isNotEqual method, if the numbers are different, the method executes the return statement inside the “YES” branch, returns true, and terminates. In other words, the “return false;” statement written below will not be executed. Similarly, if the numbers are the same, the “YES” branch does not execute, and the program exits the IF statement (condition not met) and moves on to the next statement in the block, i.e., “return false;”.

It is also important to mention that the return statement can be used even when the method has no return value (void) in order to terminate the method. In this case, the return statement is written without specifying a value or variable name:

1 return;

In the context of the IF statement, this means that alternative writing without the “NO” branch can be achieved through the return statement, even though the method does not have a return value.

Finally, if an IF statement checks the value of a logical (boolean) variable, the condition can be shortened, resulting in an alternatively written condition. You only need to specify the variable name in the condition. For example, instead of:

1 boolean ind = ...;
2 
3 if (ind == true) 
4     command_1;
5 else
6     command_2;

The IF statement condition can be written more succinctly (but only because the variable ind is of boolean type):

1 boolean ind = ...;
2 
3 if (ind) 
4     command_1;
5 else
6     command_2;

The reason for this is that the expressions (ind == true) and (ind) are logically equivalent. In other words:

  • If the variable ind has the value true, the expression (ind == true) will evaluate as true because both sides of the expression are the same (true == true -> true).
  • If the variable ind has the value false, the expression (ind == true) will evaluate as false because both sides of the expression are different (false == true -> false).

The examples so far cover cases where only one command needs to be executed after the condition is checked. In the case where multiple commands need to be executed after the condition is checked, the commands must be enclosed in a block of commands using curly braces:

 1 if ( ...condition... ) {
 2     command_1_1;
 3     command_1_2;
 4     ...
 5     command_1_n;
 6 }
 7 else {
 8     command_2_1;
 9     command_2_2;
10     ...
11     command_2_m;
12 }

Example 5

Create a class DecimalCheck that has:

  • A method lessThanPi that takes a real number as a parameter and returns TRUE if the number is smaller than 3.141592 (Pi), and FALSE otherwise. In both cases, a message should be printed on the screen indicating whether the number is smaller than Pi or not.

    class DecimalCheck {

     1   boolean lessThanPi(double x){
     2       if (x < 3.141592) {
     3           System.out.println("Number "+x+" is less than Pi");
     4           return true;
     5       }
     6       else {
     7           System.out.println("Number "+x+
     8               " is greater than or equal to Pi");
     9           return false;
    10       }
    11   }
    

    }

Create a class TestDecimalCheck that in the main method, using the DecimalCheck class, checks whether the number 3.333 is smaller than Pi or not.

1 class TestDecimalCheck {
2 
3     public static void main(String[] args) {
4         DecimalCheck dc = new DecimalCheck();
5 
6         dc.lessThanPi(3.333);
7     }
8 }

The lessThanPi method contains an IF statement that executes two commands if the condition is met and two others if it is not. These commands must be enclosed in a block of commands using curly braces. The block of commands for the “YES” branch starts immediately after the condition and ends before the reserved word else, while the block of commands for the “NO” branch starts after the reserved word else.

Here, code formatting is also applied to enhance readability, so these two blocks of commands are “indented” to the right relative to the reserved words if and else.

The previous example also highlights another concept. In addition to returning a value using the return statement, the execution of the method is also instantly terminated. In other words, every time the return command is executed, the method stops, and all commands written after (below) this command are not executed. Therefore, it’s important to make sure that the return statement is the last command in a block of commands, otherwise Java will report a syntax error.

Logical operators and their precedence, compound conditions

The condition being checked in an IF statement can also be complex, meaning it consists of several simpler conditions. Forming more complex conditions is done by using logical operators: AND (&&), OR (||), and NOT (!), which originate from the mathematical field of predicate logic - Boolean Algebra. The specification of these operators is given in the following table.

Operator Description Examples
! NOT (negation) !(a > b) — a must not be greater than b for the condition to be true.
&& AND (a > 2) && (a < 5) — a must be greater than 2 and less than 5 for the condition to be true.
㆐㆐ OR (x < 0) ㆐㆐ (x > 33.3) — x must be less than 0 or greater than 33.3 (any one or both) for the condition to be true.

It is important to note that forming complex conditions is done in a similar way as in natural language. Often, it is enough to simply state the condition, and it can be directly implemented in Java by connecting several simple conditions using logical operators.

Similar to arithmetic operators, each logical operator has a priority, with NOT (!) being the highest, followed by AND (&&), and OR (||) having the lowest priority, as indicated in the table above.

This practically means that adding extra parentheses around parts of a complex condition is common, as with arithmetic expressions. Sometimes, this is mandatory (due to operator priority), and other times it is just recommended (for better readability). Of course, the rule must still be followed that every opening parenthesis “(” must have a matching closing parenthesis “)”.

Example 6

If the check is to determine if a number is in the range from 0 to 100 (including both 0 and 100), the condition would be checking two simple conditions that must both hold true, namely:

  • Greater than or equal to zero: (x >= 0)
  • AND (both must be true at the same time)
  • Less than or equal to 100: (x <= 100)

In Java, this is written as:

1 x >= 0 && x <= 100

Optionally, the same expression with additional parentheses for better readability:

1 (x >= 0) && (x <= 100)

It is important to note that both simple conditions must be true simultaneously, meaning that the number must be both greater than or equal to zero and less than or equal to 100. Therefore, the logical AND (&&) operator is used.

Why can’t the logical OR (||) operator be used in this case? Or why is the following condition written incorrectly?

1 (x >= 0) || (x <= 100)

The reason is that this condition would check if at least one of these two simple conditions holds true (either one or the other or both), and it could happen that a number like -5 satisfies the condition since it’s less than 100 (x <= 100), but it’s not greater than or equal to zero (x >= 0), or even a number like 105 satisfies it since it’s greater than or equal to zero (x >= 0), but it’s not less than or equal to 100 (x <= 100). Effectively, any integer number would always satisfy this condition, resulting in a logically valid expression (tautology).

As in the previous example, a logical expression can be mistakenly written so that it always evaluates to true, regardless of the values of the variables involved in the expression. This is known as a tautology. Why is this a mistake, and why does it represent a problem when executing the program? If used within an IF statement, a tautology leads to the fact that the “YES” branch is always executed, and the “NO” branch (if it exists) will never be executed. This is like writing “true” instead of a condition in the IF statement (the “YES” branch will always execute):

1 if (true)
2     System.out.println("YES");
3 else
4     System.out.println("NO");

On the other hand, it is also possible to accidentally create a logically invalid expression (contradiction). This is a logical expression that always evaluates to false. If such a contradiction is used in an IF statement as a condition, only the “NO” branch will always execute (if it exists), and the “YES” branch will never execute. This is like writing “false” instead of a condition in the IF statement (the “NO” branch will always execute):

1 if (false)
2     System.out.println("YES");
3 else
4     System.out.println("NO");

Example 7

If you need to check if a number is outside the range of 0 to 100, then it’s still a check for two simple conditions, but this time at least one of them should be true (they don’t need to be true at the same time), i.e., the number should be:

  • Less than zero (x < 0)
  • OR (either of these two conditions must be true)
  • Greater than 100 (x > 100)

In Java, this is written as:

1 x < 0 || x > 100

Or, optionally, the same expression with additional parentheses for better readability:

1 (x < 0) || (x > 100)

If a number is outside a certain range, it usually means that it’s either smaller than the lower boundary (zero) or greater than the upper boundary (100). This means that it’s enough for at least one of these simple conditions to be true for the entire complex condition to be satisfied. Therefore, the logical OR (||) operator is used.

Why can’t the logical AND (&&) operator be used in this case? Or why is the following condition written incorrectly?

1 (x < 0) && (x > 100)

The reason is that this would check if both simple conditions hold true at the same time (both must be true), which results in a complex condition that can never be satisfied (an unsatisfiable condition). No number can be both less than zero (negative) and greater than 100 at the same time.

Example 8

Create a class DecimalCheck2 which has:

  • A method checkRange1 that checks if the parameter a (a real number) is in the range from 100 to 200, including those values. If true, the method returns TRUE, otherwise it returns FALSE.

  • A method checkRange2 that checks if the parameter a (a real number) is outside the range from 0 to 33.3 (i.e., if it’s less than zero or greater than 33.3). If it’s outside the range, the method returns TRUE, otherwise it returns FALSE. In both cases, an appropriate message should be printed on the screen.

    class DecimalCheck2 {

     1   boolean checkRange1(double a){
     2       if ((a >= 100) && (a <= 200))
     3           return true;
     4       else
     5           return false;
     6   }
     7 
     8   boolean checkRange2(double a){
     9       if ((a < 0) || (a > 33.3)) {
    10           System.out.println("The number " + a + 
    11            " is less than zero or greater than 33.3");
    12           return true;
    13       }
    14       else {
    15           System.out.println("The number " + a + 
    16               " is within the range 0 - 33.3");
    17           return false;
    18       }
    19   }
    

    }

Create a class TestDecimalCheck2 which in the main method, using the DecimalCheck2 class, checks if the number 123.5 is within the range 100 to 200 and if the number 34 is outside the range 0 to 33.3.

 1 class TestDecimalCheck2 {
 2 
 3     public static void main(String[] args) {
 4         DecimalCheck2 dc = new DecimalCheck2();
 5 
 6         System.out.println( "Number "+ 123.5 + 
 7             " is inside range 100-200: "+ dc.checkRange1(123.5));
 8 
 9         boolean check2 = dc.checkRange2(34);
10         System.out.println(check2);
11     }
12 }

In the method checkRange1, you can see that the IF condition is complex, meaning that it has two simple conditions joined by the logical AND operator. The syntax of the IF statement requires that parentheses surround the entire condition, so the whole statement looks like this:

1 if ((a >= 100) && (a <= 200))
2     return true;
3 else
4     return false;

Again, this checks if the number is in the range 100-200, i.e., it checks if the number is both greater than or equal to 100 AND less than or equal to 200.

On the other hand, in the method checkRange2, it checks if the number is outside the range 0 to 33.3, i.e., if it’s less than zero (the first simple condition) OR greater than 33.3 (the second simple condition). It’s enough for one of these simple conditions to be true for the number to be outside the range.

1 if ((a < 0) || (a > 33.3)) {
2     System.out.println("The number " + a + " is less than zero or greater than 33.3");
3     return true;
4 }
5 else {
6     System.out.println("The number " + a + " is within the range 0 - 33.3");
7     return false;
8 }

Example 9

Create a class DecimalCheck2A which has all the same methods as the DecimalCheck2 class, but the IF statements inside them are written differently.

 1 class DecimalCheck2A {
 2 
 3     boolean checkRange1(double a){
 4         /* Instead of this if statement
 5         if ((a >= 100) && (a <= 200))
 6             return true;
 7         else
 8             return false;
 9         */
10 
11         //An alternative if statement can be used
12         if ((a < 100) || (a > 200))
13             return false;
14         else
15             return true;
16     }
17 
18     boolean checkRange2(double a){
19         /* Instead of this if statement
20         if ((a < 0) || (a > 33.3)) {
21             System.out.println("Number "+a+
22                 " is less than 0 or greater than 33.3");
23             return true;
24         }
25         else {
26             System.out.println("Number "+a+
27                 " is within range 0 - 33.3");
28             return false;
29         }
30         */
31 
32         //An alternative if statement can be used
33         if ((a >= 0) && (a <= 33.3)) {
34             System.out.println("Number "+a+" is within range 0 - 33.3");
35             return false;
36         }
37         else {
38             System.out.println("Number "+a+
39                 " is less than 0 or greater than 33.3");
40             return true;
41         }
42     }
43 }

Create a class TestDecimalCheck2A which in the main method, using the DecimalCheck2A class, checks if the number 123.5 is within the range 100 to 200 and if the number 34 is outside the range 0 to 33.3.

 1 class TestDecimalCheck2A {
 2 
 3     public static void main(String[] args) {
 4         DecimalCheck2A dc = new DecimalCheck2A();
 5 
 6         System.out.println( "Number "+ 123.5 +
 7             " is in range 100-200: "+ dc.checkRange1(123.5));
 8 
 9         boolean pr2 = dc.checkRange2(34);
10         System.out.println(pr2);
11     }
12 }

In the method checkRange1, you can see that instead of checking the condition ((a >= 100) && (a <= 200)), which checks if the number is in the range 100-200, it checks the negated condition, i.e., ((a < 100) || (a > 200)), which checks if the number is outside the range. The simple parts of the condition are negated (a >= 100 becomes a < 100, a <= 200 becomes a > 200), and then the logical AND (&&) operator is replaced with the logical OR (||) operator. To make sure the IF statement still works as expected, the “YES” and “NO” branches must be swapped, so the else branch returns true, and if the (new) condition is satisfied, it returns false.

In the method checkRange2, you can see that instead of checking the condition ((a < 0) || (a > 33.3)), which checks if the number is outside the range 0-33, it checks the negated condition, i.e., ((a >= 0) && (a <= 33.3)), which checks if the number is inside the range. The simple parts of the condition are negated (a < 0 becomes a >= 0, a > 33.3 becomes a <= 33.3), and then the logical OR (||) operator is replaced with the logical AND (&&) operator. As before, to make the IF statement work as expected, the “YES” and “NO” branches are swapped.

As mentioned earlier, similar to arithmetic operators, logical operators also have their priority, with NOT (!) being the highest, followed by AND (&&), and OR (||) having the lowest priority. In some more complex logical expressions that use multiple different logical operators, omitting additional parentheses can lead to the formation of an incorrect expression. In such cases, the expression is evaluated according to the priority of the operators, which may not necessarily align with the intended logic. This is similar to when parentheses are omitted in arithmetic expressions, causing multiplication and division to be performed before addition and subtraction when it was meant to be the other way around.

Nested if statements

In the previous examples, some methods contained multiple sequentially connected if statements. These statements, as written, are considered independent (separate) commands. However, in some situations, after checking one condition, additional conditions need to be checked, but only if the first condition is satisfied. The solution to such problems involves writing what are called nested statements, specifically nested if statements:

1 if ( ...condition 1... )
2     if ( ...condition 2... )  command_1_1;
3     else  command_2_1;
4 else 
5     if ( ...condition 3... )  command_1_2;
6     else  command_2_2;

From the declaration, it can be seen that instead of the usual commands that follow the condition, there are new if statements. This way, “condition 2” will be checked only if “condition 1” is satisfied. If both “condition 1” and “condition 2” are true, then command_1_1 will be executed. If “condition 1” is true and “condition 2” is false, command_2_1 will be executed. Similarly, if “condition 1” is false, then “condition 3” will be checked. If “condition 3” is true, command_1_2 will be executed, otherwise, command_2_2 will be executed.

If statements can also be nested and placed within a block of commands together with other statements, so the following situation is common:

 1 if ( ...condition... ) {
 2   command_1;
 3   command_2;
 4   if ( ...condition 2... )
 5       command_1_1;
 6   else   
 7       command_2_1;
 8   ...
 9   command_n;
10 }
11 else  {
12   if ( ...condition 3... )
13       command_1_2;
14   else
15       command_2_2;
16   command_3_1;
17   command_3_2;
18   ...
19   command_3_m;
20 }

The if statement is often used when it is necessary to check whether some input value falls within predefined boundaries for an attribute or variable (e.g., whether height is within the range of 120-240 cm). This process of checking input values is commonly called logical input validation.

Example 10

Create a class HeightAgeGroups with the following methods:

  • Method checkHeight takes the height of a person in centimeters (a real number) as a parameter and prints whether the person is classified as short (less than or equal to 158 cm), medium height (greater than 158 cm and less than or equal to 179 cm), or tall (greater than 179 cm). If the entered height is outside the range of 120-240 cm, an error message should be displayed on the screen.

  • Method checkAge takes the person’s age in years as a parameter. The method first checks if the entered age is within the range of 0 - 120 years. If not, an error message is displayed on the screen. If the age is valid, the method checks whether the person is young (0-30 years), middle-aged (31-55 years), or old (56 years and older), and displays the appropriate message on the screen.

    class HeightAgeGroups {

     1   void checkHeight(double height){
     2       if ((height < 120) || (height > 240))
     3           System.out.println("Height is out of range");
     4       else{
     5           if (height <= 158)
     6               System.out.println("Short person");
     7           if ( (height > 158) && (height <= 179))
     8               System.out.println("Medium height person");
     9           if (height > 179)
    10               System.out.println("Tall person");
    11       }
    12   }
    13 
    14   void checkAge(int age){
    15       if ((age < 0) || (age > 120))
    16           System.out.println("Age is out of range");
    17       else{
    18           if (age <= 30)
    19               System.out.println("Young person");
    20           if ( (age > 30) && (age <= 55))
    21               System.out.println("Middle-aged person");
    22           if (age > 55)
    23               System.out.println("Old person");
    24       }
    25   }
    

    }

Create a class TestHeightAgeGroups which creates an object of the HeightAgeGroups class and calls its methods to check the age groups for people aged 5, 35, and 56, as well as to check the heights of people at 145 cm, 185 cm, and 175 cm.

 1 class TestHeightAgeGroups {
 2 
 3     public static void main(String[] args) {
 4         HeightAgeGroups hg = new HeightAgeGroups();
 5 
 6         hg.checkAge(5);
 7         hg.checkAge(35);
 8         hg.checkAge(56);
 9 
10         hg.checkHeight(145);
11         hg.checkHeight(185);
12         hg.checkHeight(175);
13     }
14 
15 }

In the checkHeight method, there is an if statement that checks the height range (120-240 cm). If the height is outside this range, the branch that prints an error message is executed. If the height is within the range, three nested if statements are executed in the “else” branch to check the ranges for short, medium, and tall people.

Similarly, in the checkAge method, there is one “outer” if statement to check the age range (0-120), followed by three nested if statements to check the individual age categories (young, middle-aged, old).

When multiple related checks are performed, as in the previous examples, and there are several separate if statements, multiple levels of nested statements can often be used. This nested structure results in less readable code, but it makes the code execute faster because not all if statements are evaluated (conditions are checked only when necessary). Additionally, it usually reduces the number of if statements. This can be seen in the following example.

Example 11

Create a class IntegerCheckNE that contains the same methods as the IntegerCheck class from Example 1, but with the if statements nested within each method.

 1 class IntegerCheckNE {
 2 
 3     void checkSign(int a){
 4         /* Instead of three separate if statements
 5         if (a == 0)
 6             System.out.println("Number "+a+" is equal to zero");
 7 
 8         if (a > 0)
 9             System.out.println("Number "+a+" greater than zero");
10 
11         if (a < 0)
12             System.out.println("Number "+a+" less than zero");
13             */
14 
15         //Two nested if statements can be used
16         if (a == 0)
17             System.out.println("Number "+a+" is equal to zero");
18         else if (a > 0)
19                 System.out.println("Number "+a+" is greater than zero");
20             else
21                 System.out.println("Number "+a+" is less than zero");
22     }
23 
24     void checkLessGreaterEqual(int a, int b){
25         /* Instead of three separate if statements
26         if (a > b)
27             System.out.println("Number "+a+" is greater than number "+b);
28 
29         if (a == b)
30             System.out.println("Number "+a+" is equal to number "+b);
31 
32         if (a < b)
33             System.out.println("number "+a+" is less than number "+b);
34         */
35 
36         //Two nested if statements can be used
37         if (a > b)
38             System.out.println("Number "+a+" is greater than number "+b);
39         else if (a == b)
40                 System.out.println("Number "+a+" is equal to number "+b);
41             else
42                 System.out.println("number "+a+" is less than number "+b);
43     }
44 }

Create a class TestIntegerCheckNE which, in the main method, uses the IntegerCheckNE class to compare the numbers 23 and -17 and check the sign of the number -55.

 1 class TestIntegerCheckNE {
 2 
 3     public static void main(String[] args) {
 4         IntegerCheckNE ic = new IntegerCheckNE();
 5 
 6         ic.checkLessGreaterEqual(23, -17);
 7 
 8         ic.checkSign(-55);
 9     }
10 }

Before nesting, the checkSign method had three independent if statements that checked whether a number was equal to zero, greater than zero, or less than zero. Since all three conditions were written separately, it meant that all three conditions would be checked every time, which is often unnecessary since any number can only fall into one of these three categories: zero, positive, or negative.

In the non-nested version, if the number 5 is entered, the first if statement checks the number is equal to zero. Since it isn’t, the first if statement is skipped (there is no “else” branch), and the second if statement checks whether the number is greater than zero. Since it is, a message is displayed. However, the third if statement would still check if the number is negative, even though it was already established that the number is greater than zero. If the number zero were entered, the conditions for all three if statements would be checked, even though the first check would already confirm that the number is zero and the other checks would be unnecessary.

After nesting, there are actually just two dependent if statements doing the same job, but without unnecessary checks. When a solution is found, the checks stop. Specifically, in the first (“outer”) if statement, the program checks if the number is equal to zero. If it is, a message is displayed. If it is not, the first “else” branch is executed, where a second (nested, “inner”) if statement checks whether the number is greater than zero. If it is, a message is displayed. If it is not, the “else” branch of the second if statement is executed, and a message is displayed saying that the number is less than zero.

It is important to note that the second if statement is executed only if the “else” branch of the first if statement is executed, meaning it is only evaluated if the number is not equal to zero. In other words, if zero is entered as the method’s parameter, only the first if statement will execute, and the second will not.

Additionally, why is there no check for whether the number is less than zero (a < 0) in the nested version? Why does the code not look like this:

1 if (a == 0)
2   System.out.println("Number "+a+" is equal to zero");
3 else if (a > 0)
4       System.out.println("Number "+a+" is greater than zero");
5 else if (a < 0)
6       System.out.println("Number "+a+" is less than zero");

It can be written that way, but it is not necessary. The reason is that the if statements are nested, so if the number is not zero, the “else” branch of the first if statement is executed, where the second if statement checks whether the number is greater than zero. If it is not greater than zero, the second else branch is executed, and it is implicitly understood that the number must be less than zero, so no additional check for a < 0 is needed.

Similar logic can be seen in the checkLessGreaterEqual method.

In summary, it can be said that if statements can be written in multiple ways by reversing conditions and swapping branches, but groups of multiple if statements are often more efficiently written by nesting these statements.

Example 12

Create a class HeightAgeGroupsA that has the same methods as the HeightAgeGroups class from Example 9, but with the if statements in those methods written alternatively by nesting (where possible) and by reversing conditions and branches (where nesting is not possible).

 1 class HeightAgeGroupsA {
 2 
 3     void checkHeight(double height){
 4         /* Instead of these if statements
 5         if ((height < 120) || (height > 240))
 6             System.out.println("Height is out of range");
 7         else{
 8             if (height <= 158)
 9                 System.out.println("Short person");
10             if ( (height > 158) && (height <= 179))
11                 System.out.println("Medium height person");
12             if (height > 179)
13                 System.out.println("Tall person");
14         }
15         */
16 
17         // These can be used
18         if ((height >= 120) && (height <= 240))
19             if (height <= 158)
20                 System.out.println("Short person");
21             else if ( (height > 158) && (height <= 179))
22                     System.out.println("Medium height person");
23                 else
24                     System.out.println("Tall person");
25         else
26             System.out.println("Height is out of range");
27     }
28 
29     void checkAge(int age) {
30         /* Instead of these if statements
31         if ((age < 0) || (age > 120))
32             System.out.println("Age is out of range");
33         else{
34             if (age <= 30)
35                 System.out.println("Young person");
36             if ( (age > 30) && (age <= 55))
37                 System.out.println("Middle-aged person");
38             if (age > 55)
39                 System.out.println("Old person");
40         }*/
41 
42         // These can be used
43         if ((age >= 0) && (age <= 120))
44             if (age <= 30)
45                 System.out.println("Young person");
46             else if ((age > 30) && (age <= 55))
47                 System.out.println("Middle-aged person");
48             else
49                 System.out.println("Old person");
50         else
51             System.out.println("Age is out of range");
52     }
53 
54 }

Create a class TestHeightAgeGroupsA that creates an object of the HeightAgeGroupsA class and calls its methods to check the age groups of people aged 5, 35, and 56 years, as well as the heights of people at 145 cm, 185 cm, and 175 cm.

 1 class TestHeightAgeGroupsA {
 2 
 3     public static void main(String[] args) {
 4         HeightAgeGroupsA hg = new HeightAgeGroupsA();
 5 
 6         hg.checkAge(5);
 7         hg.checkAge(35);
 8         hg.checkAge(56);
 9 
10         hg.checkHeight(145);
11         hg.checkHeight(185);
12         hg.checkHeight(175);
13     }
14 
15 }

In the checkHeight method, there is now one if statement that checks if the height is within the range of 120-240 cm (with reversed condition and branches), and then two nested if statements within the “YES” branch of that if statement to check height categories. It is important to note that the “else” branch, which is written last, actually belongs to the first if statement — within it, an error message is printed if the height is out of bounds.

Similar changes can be seen in the checkAge method.

Most common errors

Some of the most common syntax errors when writing if statements are:

  • Writing the assignment operator (=) instead of the equality comparison operator (==), or vice versa:

    if (a = 0) System.out.println(“The number “ + a + “ is zero”);

    Instead, it should be:

    if (a == 0) System.out.println(“The number “ + a + “ is zero”);

  • Forgetting to place parentheses around the logical condition:

    if a == 0 System.out.println(“The number “ + a + “ is zero”);

    Instead, it should be:

    if (a == 0) System.out.println(“The number “ + a + “ is zero”);

  • Forgetting to close the parentheses of the logical condition or part of it (missing a closing parenthesis at the end):

    if ((a < 0) || (a > 33.3)) return true;

    Instead, it should be:

    if ((a < 0) || (a > 33.3)) return true;

  • Forgetting to open parentheses around the logical condition or part of it (missing an opening parenthesis in the middle after the OR operator):

    if ((a < 0) || a > 33.3)) return true;

    Instead, it should be:

    if ((a < 0) || (a > 33.3)) return true;

  • Forgetting to create a block of commands in the “YES” branch if there are multiple commands, and there is an “NO” branch (else), for example:

    if ((a < 0) || (a > 33.3)) System.out.println(“The number “ + a + “ is less than zero or greater than 33.3”); System.out.println(“End”); else System.out.println(“The number “ + a + “ is in the range 0 - 33.3”); System.out.println(“End”);

    Instead, it should be:

    if ((a < 0) || (a > 33.3)) { System.out.println(“The number “ + a + “ is less than zero or greater than 33.3”); System.out.println(“End”); } else { System.out.println(“The number “ + a + “ is in the range 0 - 33.3”); System.out.println(“End”); }

  • Placing a return statement in one of the IF branches, but not as the last statement in that branch, for example:

    if ((a < 0) || (a > 33.3)) { return true; System.out.println(“The number “ + a + “ is less than zero or greater than 33.3”); } else { return false; System.out.println(“The number “ + a + “ is in the range 0 - 33.3”); }

    Instead, it should be:

    if ((a < 0) || (a > 33.3)) { System.out.println(“The number “ + a + “ is less than zero or greater than 33.3”); return true; } else { System.out.println(“The number “ + a + “ is in the range 0 - 33.3”); return false; }

  • Forgetting to place a return statement in each branch of the IF statement when a method is supposed to return a value or, at least, a return outside the IF statement that returns a default value in every case, for example:

    boolean returnValue(int a) { if ((a < 0) || (a > 33.3)) return true; }

    Instead, it should be:

    boolean returnValue(int a) { if ((a < 0) || (a > 33.3)) return true; else return false; }

    Or like this with a default return statement outside the IF statement:

    boolean returnValue(int a) { if ((a < 0) || (a > 33.3)) return true;

    1   return false;
    

    }

Other non-syntax errors, which can still affect the behavior of the program, include:

  • Writing an always-satisfied logical expression (a tautology) as the condition for the IF statement, for example:

    if (x > 1 || x < 100)

    Instead (correctly):

    if (x > 1 && x < 100)

  • Writing an always-unsatisfied logical expression (a contradiction) as the condition for the IF statement, for example:

    if (x < 1 && x > 100)

    Instead (correctly):

    if (x < 1 || x > 100)

  • Forgetting to create a block of commands in the “YES” branch if there are multiple commands, and there is no “NO” branch (else). This will cause only the first command to be executed if the condition is satisfied, and all subsequent commands will “fall out” of the IF statement, i.e., they will always be executed, for example:

    if ((a < 0) || (a > 33.3)) System.out.println(“The number “ + a); System.out.println(” is less than zero or greater than 33.3“);

    This will cause the message “ is less than zero or greater than 33.3“ to always be printed. The correct code should be:

    if ((a < 0) || (a > 33.3)) { System.out.println(“The number “ + a); System.out.println(” is less than zero or greater than 33.3“); }

  • Forgetting to create a block of commands in the “NO” branch if there are multiple commands. This will cause only the first command to be executed if the condition is not satisfied, and all subsequent commands will “fall out” of the IF statement, i.e., they will always be executed, for example:

    if ((a < 0) || (a > 33.3)) { System.out.println(“The number “ + a); System.out.println(” is less than zero or greater than 33.3“); } else System.out.println(“The number “ + a); System.out.println(” is in the range 0 - 33.3“);

    This will always print the message “ is in the range 0 - 33.3“. The correct code should be:

    if ((a < 0) || (a > 33.3)) { System.out.println(“The number “ + a); System.out.println(” is less than zero or greater than 33.3“); } else { System.out.println(“The number “ + a); System.out.println(” is in the range 0 - 33.3“); }

  • Forgetting to place parentheses around parts of a complex logical condition in cases where a different condition will result due to the precedence of logical operators. For example, the following IF statement returns true if both b is zero and a is outside the range of 0 to 33.3:

    if (b == 0 && (a < 0 || a > 33.3)) return true;

    If written without inner parentheses, a different condition is produced, which is not what is intended:

    if (b == 0 && a < 0 || a > 33.3) return true;

    Or like this:

    if ( (b == 0 && a < 0) || a > 33.3) return true;

  • Writing a semicolon (;) immediately after the IF condition. This will cause the statement written in the “YES” branch to fall outside the IF statement, meaning it will execute every time, for example:

    if ((a < 0) || (a > 33.3)); System.out.println(“The number “ + a + “ is outside the range 0-33.3”);

    The correct code would be:

    if ((a < 0) || (a > 33.3)) System.out.println(“The number “ + a + “ is outside the range 0-33.3”);

The switch statement

Lesson content

  • What is the switch statement - declaration and elements
  • The break statement
  • Most common errors

What is the switch statement - declaration and elements

The if statement allows conditional branching, i.e., executing one group of commands if a logical condition is satisfied and another group of commands if it is not. This is also known as “single” branching since only two branches are offered (the “YES” branch and the “NO” branch), from which only one is selected and executed.

If it is necessary to check multiple conditions, you can write multiple if statements, each checking one condition. However, the same effect can be achieved using the switch statement. The switch statement allows checking multiple conditions at once and represents a way of expressing “multiple branching” in Java. Multiple branching is such that more alternative branches are offered, from which, depending on the fulfillment of conditions, none, one, or more branches can be executed at once. The declaration of the switch statement is done as follows:

1 switch ( ...selector... ){
2     case value_1:     command_1;
3                       break;
4     case value_2:     command_2;
5                       break;
6     ...
7 
8     default:          command_d;  
9 }

The declaration of the switch statement begins with the reserved keyword switch, followed by the selector written in parentheses. The selector can be any variable of type integer, char, enumerated, or String. Variables of any other type (e.g., double, boolean…) cannot be selectors. The selector can also be an expression whose result is one of the listed types, e.g., “A%2”.

The switch statement also has a body in which all possible branches are defined, and the commands to be executed within the branch. A branch is defined by the reserved word “case” followed by a value. When the switch statement runs, it compares the current value of the selector with the values written after the word case. When a branch is found with a value equal to the selector’s value, the commands written in the body (e.g., command_1) are executed. If no matching branch is found, the default branch is executed, marked with the reserved word default (command_d). The switch statement does not have to have a default branch.

As written, this switch statement represents multiple branching in which only one branch out of several offered is selected and executed. This is exactly the purpose of the break statement, which is written as the last command of each branch. When all commands of a branch are executed, the break command is encountered, which ends the switch statement. If the break statement is omitted, the selected branch would be executed, but also all others written “below” it (including the default branch). A switch statement written this way would represent multiple branching where more than one branch is executed.

Additionally, it is important to note that the commands written within a single branch are not enclosed in curly braces, so the following code is also syntactically correct:

 1 switch ( ...selector... ){
 2     case value_1:     command_1_1;
 3                       command_1_2;
 4                       command_1_3;
 5                       break;
 6     case value_2:     command_2_1;
 7                       command_2_2;
 8                       command_2_3;
 9                       break;
10     ...
11 
12     default:          command_d_1;
13                       command_d_2;
14                       command_d_3;
15 }

Example 1

Create a class DNADecryption which has:

  • A static method decryptDNA which takes as a parameter a character representing the first letter of one of the nucleotides that make up the DNA chain.
    • If the character is ‘A’, ‘C’, ‘G’, or ‘T’, it should print the corresponding nucleotide name that starts with that letter: Adenine, Cytosine, Guanine, and Thymine.

    • If the input character does not have any of those values, print an error message.

      class DNADecryption {

       1 static void decryptDNA(char nucleotide) {
       2     switch (nucleotide) {
       3         case 'A':
       4             System.out.println("Adenine");
       5             break;
       6         case 'C':
       7             System.out.println("Cytosine");
       8             break;
       9         case 'G':
      10             System.out.println("Guanine");
      11             break;
      12         case 'T':
      13             System.out.println("Thymine");
      14             break;
      15         default:
      16             System.out.println("Error!");
      17     }
      18 }
      

      }

Create a class TestDNADecryption which in the main method calls the decryptDNA method of the DNADecryption class to decrypt the letter ‘C’.

1 class TestDNADecryption {
2 
3     public static void main(String[] args) {
4         DNADecryption.decryptDNA('C');
5     }
6 }

From this example, it is clear that the switch statement is a possible alternative to multiple if statements, and this is the case here because the condition boils down to the value of the char variable nucleotide.

In this specific case, instead of writing a separate if statement for each DNA nucleotide value and another one for the error case, we have one switch statement. This allows writing slightly more readable code. The alternative code written by using multiple if statements would look like the one as written in the DNADecryptionA class:

 1 class DNADecryptionA {
 2 
 3     static void decryptDNA(char nucleotide) {
 4         if (nucleotide == 'A')
 5             System.out.println("Adenine");
 6         else if (nucleotide == 'C')
 7                 System.out.println("Cytosine");
 8         else if (nucleotide == 'G')
 9                 System.out.println("Guanine");
10         else if (nucleotide == 'T')
11                 System.out.println("Thymine");
12         else System.out.println("Error!");
13     }
14 }

One of the advantages of the switch statement is seen in cases where the same code needs to be executed in multiple branches. In that case, it is enough to write that code in the last branch and omit the break statement.

Example 2

Create a class GradeCheck which has:

  • A static method isPassingGrade that returns false if the grade is 1, and true if the grade is 2, 3, 4, or 5. In all other cases, the method prints “Error” and returns false.

    class GradeCheck {

     1   static boolean isPassingGrade(int grade){
     2       switch (grade){
     3           case 1: return false;
     4           case 2:
     5           case 3:
     6           case 4:
     7           case 5: return true;
     8           default: System.out.println("Error");
     9                   return false;
    10       }
    11   }
    

    }

Create a class TestGradeCheck which in the main method checks whether the grades 1, 4, and 6 are passing grades.

 1 class TestGradeCheck {
 2 
 3     public static void main(String[] args) {
 4         System.out.println( GradeCheck.isPassingGrade(1) );
 5 
 6         System.out.println( GradeCheck.isPassingGrade(4) );
 7 
 8         System.out.println(GradeCheck.isPassingGrade(6) );
 9     }
10 }

In the code of the isPassingGrade method, it can be seen that the branch for the value 1 is written with a command to return false and without the break statement. This is fine because the return statement will terminate the method anyway. After that, the branches for the values 2, 3, and 4 are written without commands, but also without the break statement, so in the case of grades 2, 3, or 4, all commands will be executed, including the branch for the value 5 - which will return true.

The default branch will execute if the method is not terminated by the previous branches, i.e., if the entered grade is not within the range of 1 to 5. In this branch, in addition to printing the “Error” message, it is necessary to return a boolean value (false), as the method is expected to return some boolean value in every case.

Finally, the SWITCH statement is often used in practice with enumerated types, where the branches of the SWITCH statement correspond to instances of an enumerated type.

Example 3

Create an enumerated type OrderStatus which has instances:

  • DELIVERED, IN_TRANSPORT, CANCELLED, LOST

    enum OrderStatus { DELIVERED, IN_TRANSPORT, CANCELLED, LOST; }

Create a class OrderClaimProcessor which has:

  • A static method resolveOrderClaim which takes the order status (enumerated type) as a parameter and prints a suggestion on how to resolve the order claim:
    • If the status is DELIVERED or CANCELLED, print “No action needed”

    • If the status is LOST, print “Pay compensation to the sender”

    • If the status is IN_TRANSPORT, print “Wait another two days”

      class OrderClaimProcessor {

       1 static void resolveOrderClaim(OrderStatus status){
       2     switch (status){
       3         case DELIVERED:
       4         case CANCELLED:
       5             System.out.println("No action needed");
       6             break;
       7         case LOST:
       8             System.out.println("Pay compensation to the sender");
       9             break;
      10         case IN_TRANSPORT:
      11             System.out.println("Wait another two days");
      12     }
      13 }
      

      }

Create a class TestOrderClaimProcessor which in the main method resolves an order claim with the status IN_TRANSPORT.

1 class TestOrderClaimProcessor {
2 
3     public static void main(String[] args) {
4         OrderClaimProcessor.resolveOrderClaim(OrderStatus.IN_TRANSPORT);
5     }
6 }

Here, the branches for the statuses DELIVERED and CANCELLED are “merged”. However, the break statement is necessary as the last statement in each branch to terminate the SWITCH statement. This does not need to be done in the last branch (for the IN_TRANSPORT status) because it is the last branch and there are no further commands after it.

Most common errors

Some of the most common syntax errors related to writing the switch statement include:

  • Specifying a variable as a selector, but it is not of type char, integer, String, or an enumerated type.

  • Writing a logical condition within the selector instead of specifying a char, integer, String, or enumerated variable, or an expression that returns a char, integer, String, or enumerated value.

    switch (day <= 5){ case 1: case 2: case 3: case 4: case 5: System.out.println(“Weekday”); break; case 6: case 7: System.out.println(“Weekend”); }

Instead (correct version):

 1 switch (day){
 2   case 1:
 3   case 2:
 4   case 3:
 5   case 4:
 6   case 5:
 7     System.out.println("Weekday");
 8     break;
 9   case 6:
10   case 7:
11     System.out.println("Weekend");
12 }
  • Writing the same value of the selector on multiple branches:

    switch (day){ case 1: case 1: case 2: case 3: case 4: case 4: case 5: System.out.println(“Weekday”); break; case 6: case 7: System.out.println(“Weekend”); }

Instead (correct version):

 1 switch (day){
 2   case 1:
 3   case 2:
 4   case 3:
 5   case 4:
 6   case 5:
 7     System.out.println("Weekday");
 8     break;
 9   case 6:
10   case 7:
11     System.out.println("Weekend");
12 }
  • Forgetting to write a return statement in each branch of the switch statement, as well as in the default branch, so that the method always returns a value.

    switch (day){ case 1: case 2: case 3: case 4: case 5: return true; case 6: case 7: return false; }

Instead (correct version), with a default branch which is mandatory here:

 1 switch (day){
 2   case 1:
 3   case 2:
 4   case 3:
 5   case 4:
 6   case 5:
 7     return true;
 8   case 6:
 9   case 7:
10     return false;
11   default:
12     return false;
13 }

Other common non-syntax errors that affect program behavior:

  • Forgetting to put the break statement as the last command in each branch (if there is no return statement to terminate the SWITCH statement).

    switch (day){ case 1: System.out.print(“Monday”); case 2: System.out.print(“Tuesday”); case 3: System.out.print(“Wednesday”); case 4: System.out.print(“Thursday”); case 5: System.out.print(“Friday”); case 6: System.out.print(“Saturday”); case 7: System.out.print(“Sunday”); }

Instead (correct version):

 1 switch (day){
 2   case 1:
 3     System.out.print("Monday");
 4     break;
 5   case 2:
 6     System.out.print("Tuesday");
 7     break;
 8   case 3:
 9     System.out.print("Wednesday");
10     break;
11   case 4:
12     System.out.print("Thursday");
13     break;
14   case 5:
15     System.out.print("Friday");
16     break;
17   case 6:
18     System.out.print("Saturday");
19     break;
20   case 7:
21     System.out.print("Sunday");
22 }
  • If multiple branches have the same commands, placing those commands (by mistake) only in the first one, not in the last one.

    switch (continent){ case NORTH_AMERICA: System.out.println(“Northern Hemisphere”); break; case EUROPE: case AUSTRALIA: System.out.println(“Southern Hemisphere”); break; case ANTARCTICA: case SOUTH_AMERICA: System.out.println(“Both Hemispheres”); case ASIA: case AFRICA: }

Instead (correct version):

 1 switch (continent){
 2     case EUROPE:
 3     case NORTH_AMERICA:
 4         System.out.println("Northern Hemisphere");
 5         break;
 6     case ANTARCTICA:
 7     case AUSTRALIA:
 8         System.out.println("Southern Hemisphere");
 9         break;
10     case ASIA:
11     case AFRICA:
12     case SOUTH_AMERICA:
13         System.out.println("Both Hemispheres");
14 }

The for statement (loop)

Lesson content

  • What is the for statement - declaration and elements
  • Loop counter
  • Nesting statements within the for loop
  • The break, return, and continue statements in loops
  • Alternative writing of the for loop
  • Infinite loop and how to stop it
  • Un-executable loop
  • Most common errors

What is the for statement - declaration and elements

Controlling the flow of program execution also involves resolving situations where it is necessary to repeat one or more commands multiple times. In these cases, looping commands are used. These commands are also called loops. One type of loop is the for loop. The declaration of the for loop is made as follows:

1 for (initial_command; condition; increment_command)
2   command_to_execute;

The declaration starts with the reserved word “for”, followed by parentheses containing two commands and a condition:

  • The first command in the parentheses (initial_command) is executed only once at the beginning. This is usually the command that introduces a variable representing the loop counter.
  • The condition (condition) is a logical expression that is checked before executing each iteration (cycle). If the condition is true, the loop will continue with another iteration. If it is false, the loop stops. This condition often checks the value of the loop counter to control the number of iterations (repetitions).
  • The last command in the parentheses (increment_command) is executed at the end of each iteration. Typically, this command increases or decreases the value of the loop counter at the end of each iteration.

After the parentheses, there is a command to be executed multiple times (command_to_execute). This command is executed once per iteration. When the loop starts, the sequence of command execution and condition checking is as follows:

  • START OF LOOP
    • Execute initial_command
    • Check condition (condition is true)
      • 1st ITERATION
        • Execute command_to_execute
        • Execute increment_command
    • Check condition (condition is true)
      • 2nd ITERATION
        • Execute command_to_execute
        • Execute increment_command
    • Check condition (condition is true)
      • 3rd ITERATION
        • Execute command_to_execute
        • Execute increment_command …
    • Check condition (condition is false - loop terminates)
  • END OF LOOP

If multiple commands need to be repeated in a loop, they should be enclosed in a block of commands using curly braces:

1  for (initial_command; condition; increment_command) {
2    command_to_execute_1;
3    command_to_execute_2;
4    ...
5    command_to_execute_n;
6  }

Example 1

Create a class Printer that has a static method printFiveTimes that prints the message “Good day” five times on the screen.

1 class Printer {
2 
3     static void printFiveTimes(){
4         for (int i=1; i<=5; i++)
5             System.out.println("Good day");
6     }
7 }

Create a class TestPrinter that calls the method from the Printer class from its main method. Run the main method and check the program execution.

1 class TestPrinter {
2 
3     public static void main(String[] args) {
4         Printer.printFiveTimes();
5     }
6 }

The printFiveTimes method could also be implemented by writing the System.out.println command five times in a row, but that would not be practical.

1 static void printFiveTimes(){
2   System.out.println("Good day");
3   System.out.println("Good day");
4   System.out.println("Good day");
5   System.out.println("Good day");
6   System.out.println("Good day");
7 }

First, the number of System.out.println commands would need to be changed each time the required number of prints changes. If the number of required prints increased to one hundred or a thousand, this approach would not be practically feasible.

Second, what if multiple commands need to be repeated? This would mean that the entire block of commands would need to be written multiple times inside the method body, making the code bulky, hard to read, and very difficult to maintain.

Therefore, the for loop is used. In this case, a loop counter - variable “i” (short for “iterator”, though any name can be used) is created and initialized with the value 1.

Before each iteration, it is checked whether “i” is less than or equal to 5. If true, the iteration is executed - the message “Good day” is printed on the screen. Then “i” is incremented by 1 (i++). This continues until “i” reaches the value 6, at which point the loop terminates.

The expression “i++” is the shortest form of the command “i = i + 1” (it is shorter than the equivalent “i += 1”). The “++” operator is the increment operator that increases the current value of the variable by one. This shorthand is common in for loops, as is the use of the decrement operator “—” when the counter needs to be decreased at the end of the loop.

Here is a brief description of what happens when the loop is executed:

  • START OF LOOP
    • int i = 1; (declares the loop counter “i” and sets it to 1)
    • i<=5 ? (1<=5 - condition is true)
      • 1st ITERATION (i = 1)
        • System.out.println(“Good Day”);
        • i++; (i = 2 - variable incremented by 1)
    • i<=5 ? (2<=5 - condition is true)
      • 2nd ITERATION (i = 2)
        • System.out.println(“Good Day”);
        • i++; (i = 3 - variable incremented by 1)
    • i<=5 ? (3<=5 - condition is true)
      • 3rd ITERATION (i = 3)
        • System.out.println(“Good Day”);
        • i++; (i = 4 - variable incremented by 1)
    • i<=5 ? (4<=5 - condition is true) 4th ITERATION (i = 4)
      • System.out.println(“Good Day”);
      • i++; (i = 5 - variable incremented by 1)
    • i<=5 ? (5<=5 - condition is true) 5th ITERATION (i = 5)
      • System.out.println(“Good Day”);
      • i++; (i = 6 - variable incremented by 1)
    • i<=5 ? (6>5 - condition is false, loop terminates)
  • END OF LOOP

The loop counter (“i”) is, therefore, used to ensure that the for loop executes a specific number of iterations (in the previous example, five iterations). It is common practice to declare the cycle counter and assign it an initial value in the first statement within the for loop parentheses.

The condition for exiting the loop is when the counter reaches a certain value, and the third command inside the parentheses changes the current value of the counter (in the previous example, by incrementing it by one). Since this third command is executed at the end of each iteration, the counter gets a new value, which is passed to the next iteration.

When the loop counter is declared within the first command in the for loop parentheses, it is a local variable “visible” only within the for loop, meaning it cannot be used or called outside it. On the other hand, it is quite common for the loop counter to be used within the commands that need to be repeated inside the for loop, not just for controlling the number of iterations.

Example 2

Create a class Printer2 so that it includes the following:

  • A static method printNTimes that takes a message (String) and a positive integer n as parameters. This method prints the entered message n times on the screen.

  • A static method print0To30 that prints the numbers from 0 to 30 on the screen.

  • A static method print30To0 that prints the numbers from 30 to 0 on the screen in reverse order (30, 29, 28, 27, …, 2, 1, 0).

    class Printer2 {

     1   static void printNTimes(String message, int n){
     2       for (int i=1; i<=n; i++)
     3           System.out.println(message);
     4   }
     5 
     6   static void print0To30(){
     7       for (int i=0; i<=30; i++)
     8           System.out.println(i);
     9   }
    10 
    11   static void print30To0(){
    12       for (int i=30; i>=0; i--)
    13           System.out.println(i);
    14   }
    

    }

Create a class TestPrinter2 that calls all the methods of the Printer class from its main method. Be sure to run the main method and check the program’s behavior.

 1 class TestPrinter2 {
 2 
 3     public static void main(String[] args) {
 4         Printer2.printNTimes("Hello", 2);
 5 
 6         Printer2.print0To30();
 7 
 8         Printer2.print30To0();
 9     }
10 }

In the method printNTimes, the parameter n is used to control the loop’s exit condition. This parameter controls the number of loop iterations. For example, if 5 is entered as the value of n, the loop will perform 5 iterations (the message will be printed 5 times on the screen), and if 10 is entered, it will perform 10 iterations.

The method print0To30 prints all integers from this range to the screen. The simplest solution is to set the loop counter to take values from 0 to 30, printing the current counter value for each iteration. In this case, the loop counter is used to control both the number of iterations and the repeated statement within the loop (printing on the screen).

The counter starts at zero. In the first iteration, the current value of the counter (zero) is printed, and the counter is incremented by 1. In the second iteration, the current value of the counter (now one) is printed, and the counter is incremented by 1. In the third iteration, the current value (two) is printed, the counter is incremented, and so on. In the last iteration, the counter reaches 30, which is printed (because 30<=30, the condition holds). When the counter is incremented to 31, the condition no longer holds (31>30), and the loop stops. The total number of iterations is 31 because the counter starts at zero.

The method print30To0 shows that the loop counter can be decremented after each iteration if needed (i—). This, of course, requires that both the starting value of the counter and the loop exit condition be written differently.

Specifically, the starting value of the counter is 30. The loop will run as long as the counter is greater than or equal to zero (i>=0). This works perfectly, as the counter will decrease in each iteration (first 30, then 29, then 28, and so on). In the last iteration, the counter will have the value zero, then be decremented to -1, at which point the for loop will terminate (the counter is no longer greater than or equal to zero). The current value of the counter is printed in each iteration, which is what is requested for this method — printing the numbers 30, 29, 28, …, 2, 1, 0.

Nesting statements within for loops

A for statement can be combined (nested) with any other program flow control statements. So, it’s possible for an if or switch statement to be inside the block of a for loop, or vice versa — where a for (or another) loop is nested inside the block of an if or switch statement. This nesting can also occur in a way that a for statement contains another for loop or some other cycle.

When an if statement is nested inside a for loop, it usually looks like this:

1 for(int i = 1; i <= 5; i++)
2   if (some condition)
3     statement_p1;
4   else
5     statement_p2;

Effectively, this means that in each iteration of the for loop, the if statement will execute once and check the condition, executing statement_p1 if the condition is true (YES branch) or statement_p2 if the condition is false (NO branch), as shown in the following listing. Of course, it’s quite possible that in some iterations, the if condition will be true, and in others, it won’t. Also, it’s important to note that the condition in the for loop and the condition in the if statement are independent and refer to different things (controlling the number of iterations vs. checking some logical condition for branching).

  • START OF LOOP
    • int i = 1; (declares the loop counter “i” and sets it to 1)

    • i<=5 ? (1<=5 - loop condition is true)

    • 1st ITERATION (i = 1)

    • Checks the condition of the if statement and executes

    • statement_p1 if the condition is true (YES branch)

    • statement_p2 if the condition is false (NO branch)

    • i++; (i = 2 - variable is incremented by 1)

    • i<=5 ? (2<=5 - loop condition is true)

      • 2nd ITERATION (i = 2)
        • Checks the condition of the if statement and executes
        • statement_p1 if the condition is true (YES branch)
        • statement_p2 if the condition is false (NO branch)
        • i++; (i = 3 - variable is incremented by 1)
    • i<=5 ? (3<=5 - loop condition is true)

      • 3rd ITERATION (i = 3)
        • Checks the condition of the if statement and executes
        • statement_p1 if the condition is true (YES branch)
        • statement_p2 if the condition is false (NO branch)
        • i++; (i = 4 - variable is incremented by 1)
    • i<=5 ? (4<=5 - loop condition is true)

      • 4th ITERATION (i = 4)
        • Checks the condition of the if statement and executes
        • statement_p1 if the condition is true (YES branch)
        • statement_p2 if the condition is false (NO branch)
        • i++; (i = 5 - variable is incremented by 1)
    • i<=5 ? (5<=5 - loop condition is true)

      • 5th ITERATION (i = 5)
        • Checks the condition of the if statement and executes
        • statement_p1 if the condition is true (YES branch)
        • statement_p2 if the condition is false (NO branch)
        • i++; (i = 6 - variable is incremented by 1)
    • i<=5 ? (6>5 - loop condition is false, loop breaks)

  • END OF LOOP

If a loop is nested within another loop (or multiple loops), we have “double”, “triple”, or “multiple” loops. If one for loop is nested within another, we get a double for loop (this is the most common case).

The first of these two loops is called the “outer” for loop, and the second loop, which is nested inside the first, is called the “inner” for loop. For example:

1 for (int i = 1; i<=3; i++)
2     for(int j = 1; j<=2; j++) statement_p;

First, it should be noted that each loop has a separate counter with a different name. The outer loop has counter “i”, while the inner loop has counter “j”. The outer loop will have 3 iterations (i goes from 1 to 3), and the inner loop will have 2 iterations (j goes from 1 to 2).

What’s important is that in each iteration of the outer loop, the entire inner loop executes (all of its iterations). So, in the first iteration of the outer loop, both iterations of the inner loop execute. In the second iteration of the outer loop, both iterations of the inner loop execute again, and so on, as shown in the following listing:

  • START OF OUTER LOOP
    • int i = 1; (declares the loop counter “i” and sets it to 1)

    • i<=3 ? (1<=3 - outer loop condition is true)

      • 1st ITERATION (i = 1)

      • START OF INNER LOOP

      • int j = 1; (declares the loop counter “j” and sets it to 1)

      • j<=2 ? (1<=2 - inner loop condition is true)

      • 1st ITERATION (j = 1)

      • statement_p;

      • j++; (j = 2 - variable is incremented by 1)

      • j<=2 ? (2<=2 - inner loop condition is true)

      • 2nd ITERATION (j = 2)

      • statement_p;

      • j++; (j = 3 - variable is incremented by 1)

      • j<=2 ? (3>2 - inner loop condition is false, loop breaks)

      • END OF INNER LOOP

    • i++; (i = 2 - variable is incremented by 1)

    • i<=3 ? (2<=3 - outer loop condition is true)

      • 2nd ITERATION (i = 2)

      • START OF INNER LOOP

      • int j = 1; (declares the loop counter “j” and sets it to 1)

      • j<=2 ? (1<=2 - inner loop condition is true)

      • 1st ITERATION (j = 1)

      • statement_p;

      • j++; (j = 2 - variable is incremented by 1)

      • j<=2 ? (2<=2 - inner loop condition is true)

      • 2nd ITERATION (j = 2)

      • statement_p;

      • j++; (j = 3 - variable is incremented by 1)

      • j<=2 ? (3>2 - inner loop condition is false, loop breaks)

      • END OF INNER LOOP

      • i++; (i = 3 - variable is incremented by 1)

    • i<=3 ? (3<=3 - outer loop condition is true)

      • 3rd ITERATION (i = 3)

      • START OF INNER LOOP

      • int j = 1; (declares the loop counter “j” and sets it to 1)

      • j<=2 ? (1<=2 - inner loop condition is true)

      • 1st ITERATION (j = 1)

      • statement_p;

      • j++; (j = 2 - variable is incremented by 1)

      • j<=2 ? (2<=2 - inner loop condition is true)

      • 2nd ITERATION (j = 2)

      • statement_p;

      • j++; (j = 3 - variable is incremented by 1)

      • j<=2 ? (3>2 - inner loop condition is false, loop breaks)

      • END OF INNER LOOP

      • i++; (i = 4 - variable is incremented by 1)

    • i<=3 ? (4>3 - outer loop condition is false, loop breaks)

  • END OF OUTER LOOP

Example 3

Create a class ComplexPrinter that contains:

  • A static method printEven1To25 that prints all even numbers between 1 and 25 on the screen.

  • A static method printMatrix that prints a 6x4 matrix where all elements are equal to 1, in a format where each row’s elements are printed in a single line:

    1111 1111 1111 1111 1111 1111

Create a class TestComplexPrinter that calls the methods from the ComplexPrinter class in its main method and verifies their operation.

 1 class ComplexPrinter {
 2 
 3     static  void printEven1To25(){
 4         // This for statement contains a nested
 5         // if statement checking in every iteration if
 6         // the current value of the counter "i" is an even
 7         // number and prints it on the screen if it is
 8         for(int i = 1; i<=25; i++)
 9             if (i%2 == 0)
10                 System.out.println(i);
11     }
12 
13     static void printMatrix(){
14         // The for the statement has another for
15         // statement nested. The "inner" for statement prints
16         // all elements (number 1) in one line on the screen
17         // (one row with four elements, i.e. 1111).
18         // The "outer" for statement provides repetition of
19         // this "row" printing 6 times.
20         for (int i = 1; i<=6; i++){
21             for(int j = 1; j<=4; j++) System.out.print(1);
22             // This command does not belong to the "inner"
23             // for loop and is just used to move printing
24             // to the next line on the screen.
25             System.out.println();
26         }
27     }
28 }
29 
30 
31 class TestComplexPrinter {
32 
33     public static void main(String[] args) {
34         ComplexPrinter.printEven1To25();
35         ComplexPrinter.printMatrix();
36     }
37 }

The method printEven1To25 contains a for loop with a nested if statement. In each iteration, it checks whether the current counter value (variable “i”) is even, and if so, it prints it on the screen. Since the counter takes values from 1 to 25, the effect is that all even numbers in that range will be printed, as requested. In the first iteration, the counter has a value of 1, which is an odd number, so the if condition is not met, and nothing will be printed. In the second iteration, the counter has the value 2 (an even number), so the if condition is met, and the number 2 will be printed, and so on.

The method printMatrix contains an outer for loop and a nested inner for loop. First, it is important to note that each loop has a counter with a different name. The outer loop uses the counter “i,” while the inner loop uses the counter “j.”

If the entire printout is considered as a matrix (see below), the counter of the outer loop “i” represents the rows (6 rows), while the counter of the inner loop “j” represents the columns of the matrix (4 columns).

 1           j
 2 
 3         1234
 4         ----
 5     1|  1111
 6     2|  1111
 7 i   3|  1111
 8     4|  1111
 9     5|  1111
10     6|  1111

The inner loop prints the elements of one row of the matrix (four ones i.e. 1111). Since these elements need to be printed on the same line on the screen, the “print” statement is used, not “println.” The “System.out.println” statement following the inner loop does not belong to the inner loop, and it is used to move to the next line after printing the elements of one row of the matrix.

The outer loop ensures the printing of elements for all six rows, so this loop repeats the entire process of printing one row (the inner for loop and the command to move to the next row) six times.

The loop counter does not have to necessarily increment or decrement by one at the end of each iteration. In fact, it is allowed to write any arithmetic expression in the third part of this for loop. The counter can be incremented/decremented by more than one, multiplied, divided, etc.

Example 4

Create a class ComplexPrinterA that contains:

  • A static method printEven1To25 that prints all even numbers between 1 and 25 on the screen. However, the if statement should not be used; instead, the counter should be incremented by more than one.

    class ComplexPrinterA {

    1   static  void printEven1To25(){
    2       // The loop counter now starts at 2 (even number)
    3       // and increases by 2 after each iteration
    4       // so every subsequent value of this counter
    5       // is also an even number (4,6,8...), and an if
    6       // command to check for evenness is not required.
    7       for(int i = 2; i<=25; i = i + 2)
    8           System.out.println(i);
    9   }
    

    }

The method uses only a for loop, where the counter starts at 2 (an even number, “int i = 2”), and increments by 2 at the end of each iteration (“i = i + 2”, or more concisely “i += 2”). Consequently, each subsequent counter value is also even (2, 4, 6, 8, … 24), so no if condition for checking evenness is required.

In the previous examples, for loops were used to print certain values on the screen (messages, numbers), but this is not their only use. Loops (including for loops) are most often used for working with arrays or lists of data (which are covered in later chapters) or in some situations when it is necessary to perform calculations in multiple steps.

Example 5

Create a class ComplexCalculator that contains:

  • A static method sum1To10 that calculates and returns the sum of numbers from 1 to 10.
  • A static method multiply1To10 that calculates and returns the product of numbers from 1 to 10.
  • A static method sum1ToN that takes an integer n as a parameter and calculates and returns the sum of natural numbers from 1 to n.
  • A static method multiply1ToN that takes an integer n as a parameter and calculates and returns the product of natural numbers from 1 to n.
  • A static method calculateSavings that takes the following parameters: invested money (amount in dollars, e.g., 2200.53), annual interest rate (percentage, e.g., 3.5%), and the number of years the money is invested in the bank.
    • This method calculates and returns the final amount of money after the given number of years of investment, with interest compounded annually (interest on interest). For example:
Year Invested amount Interest Final amount (invested + interest)
1. 1000 USD 10% 1000 + 100 = 1100 USD
2. 1100 USD 10% 1100 + 110 = 1210 USD
3. 1210 USD 10% 1210 + 121 = 1331 USD
4.
  • A static method calculateSavingsValidation that works the same as the previous method, but first checks whether all parameters are greater than zero. If they are, it performs the calculation and returns the result. If not, it prints the word “Error” on the screen and returns -1.

    class ComplexCalculator {

     1   static int sum1To10(){
     2       int sum = 0;
     3 
     4       for (int i=1; i<=10; i++)
     5           sum = sum + i;
     6 
     7       return sum;
     8 
     9       // Instead of a for loop, this could also be used
    10       // because only numbers from 1 to 10 are summed.
    11       //return 1+2+3+4+5+6+7+8+9+10;
    12   }
    13 
    14   static int multiply1To10(){
    15       int product = 1;
    16 
    17       for (int i=1; i<=10; i++)
    18           product = product * i;
    19 
    20       return product;
    21 
    22       // Instead of a for loop, this could also be used
    23       // because only numbers from 1 to 10 are summed.
    24       //return 1*2*3*4*5*6*7*8*9*10;
    25   }
    26 
    27   static int sum1ToN(int n){
    28       int sum = 0;
    29 
    30       for (int i=1; i<=n; i++)
    31           sum = sum + i;
    32 
    33       return sum;
    34   }
    35 
    36   static int multiply1ToN(int n){
    37       int product = 1;
    38 
    39       for (int i=1; i<=n; i++)
    40           product = product * i;
    41 
    42       return product;
    43   }
    44 
    45   static double calculateSavings(double moneyInvested, double interest,
    46            int years){
    47 
    48       double savings = moneyInvested;
    49 
    50       for (int i = 1; i<= years; i++)
    51           savings = savings + savings * (interest /100);
    52 
    53       return savings;
    54   }
    55 
    56   static double calculateSavingsValidation(double moneyInvested, 
    57       double interest, int years){
    58 
    59       //Here, a for statement is nested inside an if statement
    60       if (moneyInvested > 0 && interest > 0 && years > 0) {
    61           double suma = moneyInvested;
    62 
    63           for (int i = 1; i <= years; i++)
    64               suma = suma + suma * (interest / 100);
    65 
    66           return suma;
    67       }
    68       else{
    69           System.out.println("Error");
    70           return -1;
    71       }
    72   }
    

    }

Create a class TestComplexCalculator that in its main method calls the methods from the ComplexCalculator class and prints the results of those methods on the screen: the sum of numbers from 1 to 10, the product of numbers from 1 to 10, the sum of numbers from 1 to 6, the product of numbers from 1 to 5, and the final amount if 1000 dinars were invested with 10% interest for 3 years.

 1 class TestComplexCalculator {
 2 
 3     public static void main(String[] args) {
 4         System.out.println(ComplexCalculator.sum1To10());
 5 
 6         System.out.println(ComplexCalculator.multiply1To10());
 7 
 8         System.out.println(ComplexCalculator.sum1ToN(6));
 9 
10         System.out.println(ComplexCalculator.multiply1ToN(5));
11 
12         System.out.println(
13             ComplexCalculator.calculateSavings(1000, 10, 3));
14 
15         System.out.println(
16             ComplexCalculator.calculateSavingsValidation(-1000, 10, 3));
17     }
18 }

The method sum1To10 shows how multiple arithmetic operations can be performed in steps using a for loop. Instead of summing all ten numbers at once (“return 1+2+3+4+5+6+7+8+9+10;”), a loop is created where these numbers are gradually added to a sum — one number per iteration.

A helper variable sum is initialized with a value of 0 (the neutral value for addition and subtraction).

1 int sum = 0;

The loop counter is set to take values from 1 to 10, and in each iteration, the current value of the counter is added to the sum.

1 for (int i = 1; i <= 10; i++)
2   sum = sum + i;

This effectively means that in the first iteration (“i” is 1), the number 1 is added to the initial sum (sum = 0 + 1 = 1). In the next iteration (“i” is 2), the sum from the previous iteration (1) is increased by 2 (sum = 1 + 2 = 3), and so on. In the final iteration, the value of the sum will be the sum of numbers 1 to 9 plus 10. The loop ends and the sum is returned.

1 return sum;

As noted, since these are integers from 1 to 10, it could have been done in a single line of code without the for loop by summing all ten numbers at once:

1 return 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10;

Similarly, the solution for the multiply1To10 method multiplies the numbers from 1 to 10 and returns the result.

The first difference is that the helper variable product is not initialized to zero (0), but to 1. This is because multiplying by zero always results in zero, and 1 is the neutral value for multiplication and division.

1 int product = 1;

Moreover, the loop counter also runs from 1 to 10, but in each iteration, the product variable is multiplied by the current counter value.

1 for (int i = 1; i <= 10; i++)
2   product = product * i;

This means that in the first iteration (“i” is 1), the initial product is multiplied by 1 (product = 1 * 1 = 1). In the next iteration (“i” is 2), the existing product (1) is multiplied by 2 (product = 1 * 2 = 2), and so on. In the last iteration, the product will be the multiplication of all the numbers from 1 to 9, and it will be multiplied by 10. After the loop ends, the product is returned.

1 return product;

Since these are integers from 1 to 10, it could have been done in one line of code without the for loop by multiplying all ten numbers at once:

1 return 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10;

From these two methods, you can see how for loops are used for multiple arithmetic operations, but both solutions could also have been done without the for loop — just in one line of code. For methods sum1ToN and multiply1ToN, loop usage is mandatory to obtain the solution.

The method sum1ToN contains almost identical logic as the sum1To10 method, except the loop counter takes values from 1 to n (the entered number n) instead of 1 to 10. Everything else is exactly the same.

It is not possible to write a one-line solution here by directly summing the numbers as in sum1To10 because the final number n is passed as a method parameter - it is not known at the time the code is being written, and it is expected to change with each method call.

The method multiply1ToN also contains almost identical logic as multiply1To10, except the loop counter takes values from 1 to n (the entered number n) instead of 1 to 10. Everything else is the same.

Similarly, it is not possible to write a one-line solution here by directly multiplying the numbers as in multiply1To10 because the final number n is passed as a method parameter.

The method calculateSavings shows a real-life example of using a for loop to calculate the amount of savings after multiple years of “compound interest” investment (interest on interest).

The helper variable savings is initialized with the invested money as the starting value. If, for example, 1000 dollars were invested, this is the initial value of the savings variable.

1 double savings = moneyInvested;

The for loop is set to have as many iterations as there are years of savings, i.e., the number of times interest is added to the previous savings. In each iteration, the current value of savings is increased by the amount of interest.

1 for (int i = 1; i <= years; i++)
2   savings = savings + savings * (interest / 100);

This means that in the first iteration (“i” is 1), the initial sum (invested money) is increased by the interest for the first year. In the next iteration (“i” is 2), the current savings (which has increased by the interest from the first iteration) is again increased by the interest, which is calculated based on the increased savings, and so on.

By transferring the increased savings from iteration to iteration and adding the interest to these increased savings, the method simulates the compounding interest. When the interest for the last year is added, the loop ends and the method returns the total savings (the value of the savings variable).

1 return savings;

Finally, the calculateSavingsValidation method contains a for loop that is nested within an if statement. First, the entered parameter values are checked (whether they are greater than zero), and only then is the calculation performed. If any of the values are not greater than zero, the method prints “Error” on the screen and returns -1. This is an example of logical control, as well as the “reverse” situation where a for loop is nested within an if statement.

The break, return, and continue statements in loops

The usual way to exit a for loop is for the condition checked before each iteration to no longer be valid.

However, it is possible to exit the for loop in another way, by using the break command during an iteration’s execution. This means that if the break command is executed in any iteration of the for loop, the loop is immediately interrupted.

A third way to break the for loop comes from the characteristics of the return command. Regardless of whether the method containing the for loop returns a value or not, the loop can be interrupted by calling the return command (also during an iteration’s execution). The side effect is that this also interrupts the execution of the entire method.

Just like with if and switch commands, break and return have the same effect on loops. While and do-while loops can also be interrupted using break and return commands.

In Java, there is also a command that ensures that the current iteration is interrupted but the loop continues to execute. This is the continue command. When executed, all commands that should still be executed in that iteration, which are written “below” this command in the same block, are skipped, and the counter is incremented, the loop condition is checked, and the next iteration begins. The only command that is executed regularly is the third command in the parentheses of the for loop, which increases or decreases the counter value.

The effects of the continue, break, and return commands in the context of their impact on the execution of loops (all types of loops, not just the for loop) are summarized in the following table:

Command Effect on loop execution
continue Only the current iteration is interrupted, the loop and method continue
break The loop is interrupted, the method continues
return Everything is interrupted: both the loop and the entire method

Example 6

Create the class ComplexPrinter2 with the following methods:

  • A static method printMinDivisible12And15And9 that prints the smallest number in the range from 10 to 1000 divisible by 12, 15, and 9 simultaneously.

  • A static method returnMinDivisible12And15And9 that returns the smallest number in the range from 10 to 1000 divisible by 12, 15, and 9 simultaneously.

  • A static method print1To100Without7 that prints numbers from 1 to 100, but skips all numbers divisible by 7.

    class ComplexPrinter2 {

     1   static void printMinDivisible12And15And9(){
     2       for(int i = 10; i<=1000;i++)
     3           if ( (i%12 == 0) && (i%15 == 0) && (i%9 == 0)){
     4               System.out.println(i);
     5               break;
     6           }
     7   }
     8 
     9   static int returnMinDivisible12And15And9(){
    10       for(int i = 10; i<=1000;i++)
    11           if ((i%12 == 0) && (i%15 == 0) && (i%9 == 0))
    12               return i;
    13 
    14       return 0;
    15   }
    16 
    17   static void print1To100Without7(){
    18       for (int i = 1; i <= 100; i++){
    19           if (i % 7 == 0)
    20               continue;
    21           System.out.println(i);
    22       }
    23   }
    

    }

Create the class TestComplexPrinter2 that calls all methods of the ComplexPrinter2 class in the main method.

 1 class TestComplexPrinter2 {
 2 
 3     public static void main(String[] args) {
 4         ComplexPrinter2.printMinDivisible12And15And9();
 5 
 6         System.out.println(
 7             ComplexPrinter2.returnMinDivisible12And15And9());
 8 
 9         ComplexPrinter2.print1To100Without7();
10     }
11 }

The first method loops through the numbers from 10 to 1000 using a for loop. This loop has a nested if statement that checks whether the current counter value is divisible by 12, 15, and 9 simultaneously. If it is not, the loop proceeds to the next iteration. However, when a number that is divisible by 12, 15, and 9 is found, it is printed to the screen, and the “break” command is executed, which stops the for loop. If the “break” command were not written, the for loop would continue and print all numbers in the range from 10 to 1000 that are divisible by 12, 15, and 9 simultaneously (not just the smallest such number).

The second method is almost identical to the first, except that as soon as a number divisible by 12, 15, and 9 is found, it is returned using the “return” command. This command stops the execution of both the for loop and the entire method.

In the print1To100Without7 method, the continue command is demonstrated. A nested if command checks whether the number is divisible by 7, and if it is, the continue command is executed. This effectively means that the iteration is interrupted at that point — but only the iteration, not the entire for loop (break) or the entire method (return). Specifically, this interruption means that the command to print the counter is not executed, but the counter is incremented, and the for loop continues. Thus, when the counter reaches 7, the iteration is interrupted, and the number is not printed, even though all numbers from 1 to 6 are printed. The printing continues with 8, 9, and so on. Similarly, the number 14 will be skipped.

Alternative writing of the for loop

As with the if statement, it is possible to write an alternative version of any for loop. This is usually done by:

  • Changing just the loop condition slightly.
  • Changing the starting value of the counter and the condition.
  • Changing the direction of the counter’s movement, the starting value, and the condition.

The simplest way is to write a slightly different loop condition. For example, instead of “i<=10”, you can write i<11 (the last allowed counter value will still be 10). Or instead of “i>0”, you can write “i>=1”, and so on.

Additionally, if the counter is set to take values from 1 to 10, it is possible to change the starting value of the counter and the condition so that the counter still increments on each iteration but moves from 0 to 9 (with 0 representing one iteration), or from 5 to 15, or from -10 to -1, and so on. The important thing is that the difference between the starting and ending values represents the same number of iterations (in this case, 10).

It is also possible to change the direction of the counter, the starting value, and the condition, so that instead of incrementing from 1 to 10, the counter decrements from 10 to 1, or from 9 to 0, from -5 to -15, and so on. The difference between the starting and ending values still represents the same number of iterations (10).

In the last two cases, if the counter is used in any of the statements within the loop, it is necessary to modify those statements according to the counter’s movement.

Example 7

Create the class Printer2A based on the class Printer2:

  • A static method printNTimes that takes a message (String) and a positive integer N as parameters. This method prints the entered message N times on the screen.
  • A static method print0To30 that prints the numbers from 0 to 30 on the screen.
  • A static method print30To0 that prints the numbers from 30 to 0 in reverse order (30, 29, 28, 27, …, 2, 1, 0).
 1     class Printer2A {
 2 
 3         static void printNTimes(String message, int n){
 4             // Instead of the original for loop...
 5             // for (int i=1; i<=n; i++)
 6             //    System.out.println(message);
 7 
 8             // An alternative loop with a slightly
 9             // different condition can be used, the
10             // counter still going from 1 to n.
11             // Instead of i<=n, we can write i<n+1.
12             // for (int i=1; i<n+1; i++)
13             //    System.out.println(message);
14 
15             // The counter can go from 0 to n-1 instead,
16             // keeping the total number of iterations the same (n)
17             for (int i=0; i<n; i++)
18                 System.out.println(message);
19 
20             //The counter can go from n to 1 (decreasing
21             // in each iteration instead), while keeping
22             // the total number of iterations the same (n)
23             // for (int i=n; i>=1; i--)
24             //    System.out.println(message);
25         }
26 
27         static void print0To30(){
28             //Instead of the original for loop...
29             //for (int i=0; i<=30; i++)
30             //    System.out.println(i);
31 
32             // An alternative loop with a slightly
33             // different condition can be used, the
34             // counter still going from 0 to 30.
35             // Instead of i<=30, we can write i<31.
36             // for (int i=0; i<31; i++)
37             //    System.out.println(i);
38 
39             // The counter can go from 1 to 31
40             // (the condition being i<32 or alternatively <= 31)
41             // but then "i-1" needs to be printed and not just "i"
42             for (int i=1; i<32; i++)
43                 System.out.println(i-1);
44 
45             //The counter can go from 30 to 0 (decreasing
46             // in each iteration instead), but then 30-i
47             // needs to be printed and not just i because
48             // (30-30=0, 30-29=1,..., 30-0=30)
49             // for (int i=30; i>=0; i--)
50             //    System.out.println(30-i);
51         }
52 
53         static void print30To0(){
54             // Instead of the original for loop...
55             // for (int i=30; i>=0; i--)
56             //    System.out.println(i);
57 
58             //A loop with a slightly different
59             //condition can be written, so instead
60             //of i>=0,we can write i>-1
61             // for (int i=30; i>-1; i--)
62             //    System.out.println(i);
63 
64             // The loop counter may go from 35 to 5
65             // but then we need to print i-5 because
66             // (35-5=30, 34-5=29,..., 5-5=0)
67             // for (int i=35; i>=5; i--)
68             //  System.out.println(i-5);
69 
70 
71             // The loop counter may go from 0 to 30,
72             // but then we need to print (30-i) and not
73             // only i because (30-0=30, 30-1=29,..., 30-30=0)
74             for (int i=0; i<=30; i++)
75                 System.out.println(30-i);
76         }
77     }

Create the class TestPrinter2A that calls all methods of the Printer2A class.

 1 class TestPrinter2A {
 2 
 3     public static void main(String[] args) {
 4         Printer2A.printNTimes("Hello", 2);
 5 
 6         Printer2A.print0To30();
 7 
 8         Printer2A.print30To0();
 9     }
10 }

In the printNTimes method, the original and three alternative versions of the starting for loop are given. All alternatives are commented out except one.

First, a slightly different loop condition (i<n+1 instead of i<=n) is written, without changing other elements (the counter still goes from 1 to n).

Then, with a change to the starting value and condition so that the counter moves from 0 to n-1.

Finally, with a change in the direction of the counter’s movement, starting value, and condition, where the counter moves from n to 1, decreasing by 1 after each iteration. This method does not use the counter in the loop’s print statement, so no additional modifications to this statement are needed.

In the print0To30 method, a similar approach is used, but two of the three alternative for loops include changes to the print statement.

The first alternative for loop only contains changes in the condition, so instead of i<=30, i<31 is used — which is effectively the same constraint, as we are dealing with integers and the last allowed value is 30.

In the second alternative for loop, the counter goes from 1 to 31 (instead of 0 to 30), so since we need to print numbers from 0 to 30 on the screen, the print statement receives the counter minus one (“i-1”), not just the counter (“i”). So, instead of 1 (the starting counter value), 0 will be printed (“1-1=0”), instead of 2, 1 will be printed (“2-1=1”), and so on, and instead of 31, 30 will be printed (“31-1=30”).

The third alternative for loop has the counter moving in reverse, with both the condition and the print statement changed. Since the counter moves from 30 to 0, and we need to print numbers from 0 to 30, in each step, the value printed will be “30 - counter”, not the counter itself. So, when the counter is 30, 0 will be printed (“30-30=0”), when the counter is 29, 1 will be printed (“30-29=1”), and so on, until finally, when the counter is 0, 30 will be printed (“30-0=30”).

Similar to the previous methods, the print30To0 method also includes the original and three alternative for loops.

Infinite loop and how to stop it

Cyclic algorithmic structures introduce two potential errors in program execution: infinite loop and un-executable loop. Neither of these errors is a syntax error (the code can be compiled and run), but they cause improper execution of the program.

Normal program execution implies that when the program is started, its commands are executed sequentially (or according to the algorithmic structures it contains), and the program finishes when the last command is executed.

An infinite loop is one that never (by itself) stops executing. It has an infinite number of iterations, and it usually requires external intervention or a command to stop because it will not stop on its own — there is no normal execution. If not stopped, it can slow down the computer and block work because all memory and/or processor resources are consumed by (infinite) execution of this loop.

The most common reasons for an infinite loop are:

-The loop condition forms a logically always true expression (tautology).

  • The loop counter does not change at all (so the condition remains true forever).
  • Or the counter’s value changes in a way that the condition is always satisfied (for example, it both increases and decreases during the same iteration).

Example 8

Create a class InfinitePrinter with the following methods:

  • A static method printNumbers that uses an infinite for loop to print numbers starting from 1. Create an infinite loop where the loop condition is always satisfied.
  • A static method printNumbers2 that uses an infinite for loop to print the number 1 an infinite number of times. Create an infinite loop where the counter value remains constant (does not change), and the loop condition is always satisfied due to that.
  • A static method printNumbers3 that uses an infinite for loop to print the number 1 an infinite number of times. Create an infinite loop where the counter value both increases and decreases within the same iteration, effectively keeping the counter value constant.
 1     class InfinitePrinter {
 2 
 3         static void printNumbers(){
 4             // The condition of the loop is always satisfied (tautology)
 5             for (int i=1; i>1 || i<=1;i++)
 6                 System.out.println(i);
 7         }
 8 
 9         static void printNumbers2(){
10             // The loop counter value does not change
11             // (i=1 instead of i++)
12             for (int i=1; i<10 ; i = 1)
13                 System.out.println(i);
14         }
15 
16         static void printNumbers3(){
17             // The value of the loop counter increases and
18             // decreases by one during the same iteration - so
19             // it actually always remains 1
20             for (int i=1; i<10 ; i++) {
21                 System.out.println(i);
22                 i--;
23             }
24         }
25     }

Create a class TestInfinitePrinter that calls the methods of the InfinitePrinter class from its main method and tests their behavior.

 1 class TestInfinitePrinter {
 2 
 3     public static void main(String[] args) {
 4         // Method calls are intentionally placed under comments
 5         // because methods contain infinite loops.
 6 
 7         // Remove comments from the desired code lines
 8         // and run the program
 9 
10         //InfinitePrinter.printNumbers();
11 
12         //InfinitePrinter.printNumbers2();
13 
14         //InfinitePrinter.printNumbers3();
15     }
16 }

WARNING - READ THIS BEFORE RUNNING THE EXAMPLES

  • In the main method of the TestInfinitePrinter class, calls to the methods of the TInfinitePrinter class are commented out to prevent accidental execution.
  • When these calls are uncommented, running the main method will lead to triggering an infinite loop within these methods and to SLOWING DOWN OR BLOCKING THE WORKING ENVIRONMENT OR COMPUTER.
  • Interrupting the infinite loop is ESSENTIAL, and it can be done using the shortcut Ctrl+F2 or by pressing one of the two red squares: in the execution window (bottom left) or in the top right above the code editor (Figure 26).
Interrupting program execution with Ctrl+F2
Figure 26. Interrupting program execution with Ctrl+F2
  • When the program execution is interrupted, the square representing ongoing execution, which was red, turns gray again (Figure 27), while the message “Process finished with exit code 130” is displayed on the screen (“exit code” that does not have a value of 0 means a manual or unexpected program interruption). In contrast, a normally executed program will show the message “Process finished with exit code 0” (“exit code 0” means the program finished normally).
Program is stopped and no longer executing
Figure 27. Program is stopped and no longer executing

In the printNumbers method, we can see the first example of an infinite for loop. Infinite execution is achieved by the loop condition (“i>1 || i<=1”) always being true, for any number. Specifically, any number will be either greater than 1 or smaller or equal to 1. Since the loop counter starts at 1 and increases after each iteration, the effect of this method’s execution will be printing numbers starting from 1. In the image (Figure 26), the effect of this method’s execution is shown, specifically the printing on the screen. At the moment of execution shown in the image, the last printed number was 1469005, but the program continued working and printing the next numbers. Before stopping the program, the last printed number was 3279503 (Figure 27).

In the printNumbers2 method, the infinite for loop is achieved by the counter value never changing. Instead of the incrementing command (“i++”), the loop assigns the counter value back to 1 (“i=1”). Since the counter is always 1, the condition is always satisfied (“i < 10”), so it leads to an infinite number of iterations. Here, the condition is written correctly — it is not a logically always true expression. Unlike the previous method, this one will always print the number 1 on the screen, not a growing sequence of numbers.

In the printNumbers3 method, the infinite for loop is achieved by both decreasing (“i—”) and increasing (“i++”) the counter’s value during the same iteration. Effectively, this means that the counter value will remain 1 at the start of each iteration, and this method will also always print the number 1 on the screen.

The previous examples of infinite loops are noticeable by the effect because, during execution, we see that something is continuously printed on the screen, and the program runs non-stop. A worse situation occurs when the method containing the infinite loop does not print anything on the screen. In that case, we can only deduce that the program is still running based on the appearance of the execution window (the square for execution is red, not gray). The program can still be interrupted the same way:

  • Using the shortcut Ctrl+F2;
  • Or by pressing one of the two red squares (Figure 26):
    • In the execution window (bottom left)
    • Or at the top right above the code editor

Example 9

Create a class InfiniteLoop with the following method:

  • A static method infiniteLooping that contains an infinite for loop but does not print anything on the screen.
1     class InfiniteLoop {
2 
3         static void loopInfinitely(){
4             for(int i=1; i<10;i=0){
5             }
6         }
7     }

Create a class TestInfiniteLoop that calls the infiniteLooping method and tests its behavior.

 1 class TestInfiniteLoop {
 2 
 3     public static void main(String[] args) {
 4         // The method call is intentionally placed under comments
 5         // because this method contains an infinite loop.
 6 
 7         // Remove comments from the following code line
 8         // and run the program
 9 
10         //InfiniteLoop.loopInfinitely();
11     }
12 }

WARNING - READ THIS BEFORE RUNNING THE EXAMPLES

  • In the main method of the TestInfiniteLoop class, the call to the InfiniteLoop class method is commented out to prevent accidental execution.

  • When this call is uncommented, running the main method will lead to triggering an infinite loop, EVEN THOUGH NO OUTPUT WILL BE VISIBLE ON THE SCREEN (Figure 28).

Infinite loop with no output on the screen
Figure 28. Infinite loop with no output on the screen
  • Interrupting the infinite loop is ESSENTIAL, and it can be done by the shortcut Ctrl+F2, or by pressing one of the two red squares representing ongoing program execution (Figure 28).

  • When the program execution is interrupted, the square representing ongoing execution, which was red, turns gray again (Figure 29), while the message “Process finished with exit code 130 (interrupted by signal 2:SIGINT)” is displayed on the screen (“exit code” that is not 0 means a manual or unexpected program interruption).

Interrupted program execution
Figure 29. Interrupted program execution

As already mentioned, running this method seemingly does not lead to an infinite loop. However, it does. This method simply does not print anything on the screen (the for loop’s statement block is empty), but it executes infinitely. The only true indicator is the fact that the square representing ongoing program execution remains red (Figure 28) because the program does not stop executing.

Un-executable Loop

Un-executable loop is, in a way, the opposite of an infinite loop. An un-executable loop always stops before it executes the first iteration or during the first iteration, so it seems as if that part of the program was never even started.

An un-executable loop does not represent a syntax error but an error during program execution, meaning the program does not run as expected.

The most common reasons for encountering an un-executable loop are:

  • The loop condition is an unsatisfiable logical expression (contradiction).
  • The initial value of the counter does not satisfy the condition at the start.
  • Or the loop is immediately stopped during the first iteration, by improperly using the break or return statement.
  • Or, as a special case of the previous situation, an if statement nested in the loop has both branches written with a break or return statement.

Unlike the infinite loop, after starting the un-executable loop, manual intervention to stop the program is not needed.

Example 10

Create a class UnexecutableLoop so that it has:

  • A static method print1To30 that should print the numbers from 1 to 30 on the screen, but due to an un-executable for loop, it doesn’t print anything on the screen. Set the loop condition to be an unsatisfiable logical expression.
  • A static method print1To30A that should print the numbers from 1 to 30 on the screen, but due to an un-executable for loop, it doesn’t print anything on the screen. Set the initial counter value so that it doesn’t satisfy the loop condition.
  • A static method print1To30AA that should print the numbers from 1 to 30 on the screen, but the un-executable for loop always breaks during the first iteration. Use the break statement to break the loop.
  • A static method print1To30AAA that should print the numbers from 1 to 30 on the screen, but the un-executable for loop always breaks during the first iteration. Use the return statement to break the loop.
  • A static method checkNumbers that takes two integer parameters a and b, and checks if there is at least one number divisible by 11 in the range from a to b (inclusive). If such a number exists, the method returns true, otherwise it returns false. Make the method so that due to the un-executable for loop, it always breaks during the first iteration. Use a nested if statement in which both branches use the return statement to break the for loop.
 1     class UnexecutableLoop {
 2 
 3         static void print1To30(){
 4             // The condition is unsatisfiable (contradiction)
 5             // No number is smaller and larger than 30 at the same time
 6             for(int i=1; i<30 && i>30; i++)
 7                 System.out.println(i);
 8         }
 9 
10         static void print1To30A(){
11             // The condition is that the loop counter is
12             // greater than 30 and its initial value is 1
13             for(int i=1; i>30; i++)
14                 System.out.println(i);
15         }
16 
17         static void print1To30AA(){
18             // After printing number 1 in the first iteration,
19             // the break command stops the loop
20             for(int i=1; i<30; i++) {
21                 System.out.println(i);
22                 break;
23             }
24         }
25 
26         static void print1To30AAA(){
27             // After printing number 1 in the first iteration,
28             // the return command terminates the method
29             for(int i=1; i<30; i++) {
30                 System.out.println(i);
31                 return;
32             }
33         }
34 
35         static boolean checkNumbers(int a, int b){
36             for (int i=a; i<=b; i++)
37                 if (i%11 == 0)
38                     return true;
39                 else
40                     return false;
41 
42             return false;
43         }
44     }

Create a class TestUnexecutableLoop that from the main method calls the methods of the UnexecutableLoop class and verifies their operation.

 1 class TestUnexecutableLoop {
 2 
 3     public static void main(String[] args) {
 4         UnexecutableLoop.print1To30();
 5 
 6         UnexecutableLoop.print1To30A();
 7 
 8         UnexecutableLoop.print1To30AA();
 9 
10         UnexecutableLoop.print1To30AAA();
11 
12         System.out.println(UnexecutableLoop.checkNumbers(3,14));
13     }
14 }

In the print1To30 method, the loop condition is written as an unsatisfiable logical expression (contradiction). Specifically, no number can be both greater and smaller than 30 at the same time. This means the loop condition will never be satisfied, so no iteration will occur.

In the print1To30A method, the initial value of the loop counter is 1, and the loop condition is that the counter must be greater than 30 (i > 30). This means the loop condition is not satisfied immediately, so the first iteration will not be executed.

In the print1To30AA and print1To30AAA methods, the break or return statement is used within the for loop command block. Effectively, this means that in the first iteration, the number 1 will be printed, and then it will immediately move to the next statement (the break or return) that will break the loop (and the return will also terminate the method). Both loops will always stop during the first iteration and will never proceed to the next iteration.

The checkNumbers method contains a very common but specific un-executable loop caused by a nested if statement with both branches containing break or return statements. This case needs to be explained in detail.

If we have a for loop with a nested if statement that has both branches, this means that in every iteration one of those two branches will be executed (depending on whether the if statement condition is satisfied or not in that iteration). If each of these branches contains a return or break statement, that means when one of them is executed, the loop (command break) or the entire method (command return) will stop. All considered, in the first iteration of the for loop, no matter which branch of the if statement is executed, the loop or the method will be stopped.

In the specific example of the checkNumbers method, the for loop begins by initializing the counter to the value of parameter a (“int i = a”). Let’s say the loop condition is satisfied (i.e., “a <= b”), and the first iteration begins. The if statement then checks if the loop counter “i” is divisible by 11. If it is, it goes to the “YES” branch, the method returns true, and immediately stops (which is fine, since we found a number divisible by 11 and don’t need to search further). However, if the number is not divisible by 11, it goes to the “NO” branch and immediately returns false and stops. It does not proceed to the next iteration, and the next numbers are not checked for divisibility by 11, because the return statement has already returned false and stopped both the loop and the method.

So what is the purpose of the last statement (return false;) in the method? It ensures that the method will return a value if the for loop doesn’t execute at all. For example, if the value of parameter b is less than the value of parameter a. What would the correctly written checkNumbers method look like? It would be sufficient to remove the “NO” branch (else) from the if statement:

1 static boolean checkNumbers(int a, int b) {
2     for (int i = a; i <= b; i++)
3         if (i % 11 == 0)
4             return true;
5 
6     return false;    
7 }

Written this way, the method will check all numbers in the for loop from a to b, and will return true if it finds a number divisible by 11. If no number divisible by 11 is found in the range from a to b, the for loop will finish iterating, and it will proceed to the next statement in the method, which is the return statement returning false. So, only when all numbers in the range from a to b are checked, and no number divisible by 11 is found, will the method exit the loop and return false.

Most common errors

Some of the most common syntax errors related to writing for loops are:

  • Using the loop counter outside the for loop when it is declared inside the for loop, where it is only visible, for example:

    for (int i = 1; i < 10; i++) System.out.println(i);

    // This statement is outside the for loop System.out.println(“The next number is “ + (i + 1));

Instead (correctly), if the counter needs to be used outside the for loop, it should be declared outside the for loop:

1 // The counter is declared OUTSIDE the for loop
2 int i;
3 
4 // The counter is only assigned a value inside the for loop
5 for (i = 1; i < 10; i++)
6     System.out.println(i);
7 
8 // Afterward, the counter is available outside the loop
9 System.out.println("The next number is " + (i + 1));
  • Separating the three statements within the for loop parentheses using a comma (,), instead of a semicolon (;), for example:

    for (int i = 1, i < 10, i++) System.out.println(i);

Instead (correctly):

1 for (int i = 1; i < 10; i++)
2     System.out.println(i);
  • Writing an expression as the condition for a for loop when that expression is not a logical expression, for example:

    for (int i = 1; i % 10; i++) System.out.println(i);

Instead (correctly):

1 for (int i = 1; i <= 10; i++)
2     System.out.println(i);
  • If a method returns a value, and the return statement exists only inside the for loop, forgetting to place another return statement outside the for loop to return something if the loop does not execute.

    static int find(int a, int b) { for (int i = a; i <= b; i++) if (i % 7 == 0) return i; }

Instead (correctly):

1 static int find(int a, int b) {
2     for (int i = a; i <= b; i++)
3         if (i % 7 == 0)
4             return i;
5 
6     return -1;
7 }

Other common mistakes that are not syntax errors, but affect the program’s execution, include:

  • Writing an infinite loop that never stops executing, for example:

    static void printNumbers() { // The loop condition is always satisfied (tautology) for (int i = 1; i > 1 || i <= 1; i++) System.out.println(i); }

    static void printNumbers2() { // The counter’s value doesn’t change // (i = 1) instead of (i++) for (int i = 1; i < 10; i = 1) System.out.println(i); }

    static void printNumbers3() { // The counter is increased and decreased by one // during the same iteration – so it always remains 1 for (int i = 1; i < 10; i++) { System.out.println(i); i—; } }

Instead (correctly):

 1 static void printNumbers() {
 2     for (int i = 1; i <= 100; i++)
 3         System.out.println(i);
 4 }
 5 
 6 static void printNumbers2() {
 7     for (int i = 1; i < 10; i++)
 8         System.out.println(i);
 9 }
10 
11 static void printNumbers3() {
12     for (int i = 1; i < 10; i++) {
13         System.out.println(i);
14     }
15 }
  • Writing an un-executable loop that never executes any iteration or always stops during the first iteration, for example:

    static void print1To30() { // The condition is unsatisfiable (contradiction) // no number can be both less and greater than 30 for (int i = 1; i < 30 && i > 30; i++) System.out.println(i); }

    static void print1To30A() { // The condition is that the counter is greater than 30 // but it starts at 1 for (int i = 1; i > 30; i++) System.out.println(i); }

    static void print1To30AA() { // After printing number 1, the break // statement breaks the loop for (int i = 1; i < 30; i++) { System.out.println(i); break; } }

    static void print1To30AAA() { // After printing number 1, the return // statement breaks the method for (int i = 1; i < 30; i++) { System.out.println(i); return; } }

    static boolean checkNumbers(int a, int b) { for (int i = a; i <= b; i++) if (i % 11 == 0) return true; else return false;

    1   return false;
    

    }

Instead (correctly):

 1 static void print1To30() {
 2     for (int i = 1; i <= 30; i++)
 3         System.out.println(i);
 4 }
 5 
 6 static void print1To30A() {
 7     for (int i = 1; i <= 30; i++)
 8         System.out.println(i);
 9 }
10 
11 static void print1To30AA() {
12     for (int i = 1; i <= 30; i++) {
13         System.out.println(i);
14     }
15 }
16 
17 static void print1To30AAA() {
18     for (int i = 1; i <= 30; i++) {
19         System.out.println(i);
20     }
21 }
22 
23 static boolean checkNumbers(int a, int b) {
24     for (int i = a; i <= b; i++)
25         if (i % 11 == 0)
26             return true;
27 
28     return false;
29 }
  • Forgetting to create a block of statements within the for loop if there are multiple statements in it. This will cause only the first statement to execute during the iterations, while all the others “fall out” of the loop, for example:

    for (int i = 1; i < 10; i++) System.out.println(i); System.out.println(“The next number is”);

Instead (correctly):

1 for (int i = 1; i < 10; i++) {
2     System.out.println(i);
3     System.out.println("The next number is");
4 }

The while statement (loop)

Lesson content

  • What is the while statement - declaration and elements
  • Nesting statements within a while loop
  • The break, return, and continue statements
  • Alternative writing of the while loop
  • Infinite while loop
  • Un-executable while loop
  • When to use a while loop and when to use a for loop, alternative writing of the while loop as a for loop and vice versa
  • Most common errors

What is the while statement - declaration and elements

It has already been said that looping statements are used in situations where it is necessary to execute a command (or more) multiple times. The for loop is most useful in situations where, at the start of the loop, the number of iterations is known in advance.

For example, if you need to print the numbers from 1 to 100 on the screen, you know that this can be done in 100 iterations, printing one number per iteration. Even if the number of iterations depends on some parameter or variable, it will still be known when the loop starts. However, there are situations where it is not known in advance how many iterations will be executed. In such cases, using a loop counter doesn’t make sense, since the exit condition from the loop doesn’t depend on the counter’s value but on some other condition.

The while statement was created precisely to be used in such cases – when the number of iterations is not known in advance. The declaration of a while loop is done as follows:

1 while (some condition) command_p;

The declaration starts with the reserved word while, followed by the loop condition (a logical expression) inside parentheses. After the loop condition, the command (or multiple commands) that need to be repeated multiple times is written (command_p).

The while loop repeats the execution of the command (command_p) as long as the condition in the parentheses holds true. Unlike the for loop, the while loop does not have a loop counter (iteration counter). The exit from the loop does not depend on the counter’s value, but happens when the condition no longer holds true. This condition is a regular logical expression and is formed in the same way as any condition in the if statement (using logical operators, comparison operators, and their combinations).

When the loop is triggered, the sequence of command executions and condition checks is as follows:

  • START OF LOOP
    • The condition is checked (condition holds true)
      • 1ST ITERATION
        • execute command_p
    • The condition is checked (condition holds true)
      • 2ND ITERATION
        • execute command_p
    • The condition is checked (condition holds true)
      • 3RD ITERATION
      • execute command_p etc.
    • The condition is checked (condition does NOT hold true - the loop ends)
  • END OF LOOP

It is important to note that, as with the for loop, the condition is checked before each iteration, meaning that the iteration will be executed if the condition holds true.

Like with the for loop, if it is necessary to repeat multiple commands in a loop, they must be enclosed in a block using curly braces:

1 while (some condition) {
2   command_p_1;
3   command_p_2;
4   
5   command_p_n;
6 }

Example 1

Create a class called NumberPower that has:

  • A static method greaterThan1000 that takes a positive integer a as a parameter and multiplies it by itself until it becomes greater than 1000. This method essentially calculates the smallest power of a that is greater than 1000. The result of this multiplication should be printed on the screen.

    class NumberPower {

    1   static void greaterThan1000(int a){
    2       int result = 1;
    3 
    4       while (result <= 1000) result = result * a;
    5 
    6       System.out.println(result);
    7   }
    

    }

Create a class TestNumberPower that in the main method checks which is the smallest power of the number 2 that is greater than 1000.

1 class TestNumberPower {
2 
3     public static void main(String[] args) {
4         // Should print 1024 (two on the power of ten)
5         NumberPower.greaterThan1000(2);
6     }
7 }

The while loop in the greaterThan1000 method is set so that the number is multiplied by itself until the result is greater than 1000. The moment the result exceeds 1000, the loop terminates, and the result is printed on the screen. The steps of the method and loop iterations when a is passed as the number 2 are as follows:

  • int result = 1; (declaring variable result and assigning the value 1)

  • START OF LOOP

    • result < 1000? (1 < 1000 - condition holds true)
      • 1ST ITERATION
        • result = result * a (result = 1 * 2 = 2 - the variable is multiplied by a, i.e., 2)
    • result < 1000? (2 < 1000 - condition holds true)
      • 2ND ITERATION
        • result = result * a (result = 2 * 2 = 4 - the variable is multiplied by a, i.e., 2)
    • result < 1000? (4 < 1000 - condition holds true)
      • 3RD ITERATION
        • result = result * a (result = 4 * 2 = 8 - the variable is multiplied by a, i.e., 2)
    • result < 1000? (8 < 1000 - condition holds true)
      • 4TH ITERATION
        • result = result * a (result = 8 * 2 = 16 - the variable is multiplied by a, i.e., 2)
    • result < 1000? (16 < 1000 - condition holds true)
      • 5TH ITERATION
        • result = result * a (result = 16 * 2 = 32 - the variable is multiplied by a, i.e., 2)
    • result < 1000? (32 < 1000 - condition holds true)
      • 6TH ITERATION
        • result = result * a (result = 32 * 2 = 64 - the variable is multiplied by a, i.e., 2)
    • result < 1000? (64 < 1000 - condition holds true)
      • 7TH ITERATION
        • result = result * a (result = 64 * 2 = 128 - the variable is multiplied by a, i.e., 2)
    • result < 1000? (128 < 1000 - condition holds true)
      • 8TH ITERATION
        • result = result * a (result = 128 * 2 = 256 - the variable is multiplied by a, i.e., 2)
    • result < 1000? (256 < 1000 - condition holds true)
      • 9TH ITERATION
        • result = result * a (result = 256 * 2 = 512 - the variable is multiplied by a, i.e., 2)
    • result < 1000? (512 < 1000 - condition holds true)
      • 10TH ITERATION
        • result = result * a (result = 512 * 2 = 1024 - the variable is multiplied by a, i.e., 2)
    • result < 1000? (1024 > 1000 - condition IS NOT true, loop ends)
  • END OF LOOP

  • System.out.println(result); (the value of result, i.e., 1024, is printed)

In the same example, it is also clear that care must be taken when writing the exit condition for the loop. If the number 1 is passed as an argument to the method, the while loop will become an infinite loop. This is because multiplying by 1 will always give the same result and will not increase (1^n = 1), so it will always be less than 1000 no matter how many iterations are executed.

Nesting statements within a while loop

Just like if, switch, and for statements, the while statement can also be combined (nested) with all other control flow statements. Therefore, it is possible that within the block of a while loop, there is an if or switch statement, and vice versa — there could be a nested while (or another) loop within the block of if or switch commands. This nesting of statements can even go as far as having a while statement containing another for statement or any other type of loop.

If there is a nested if statement within a while loop, it usually looks like this:

1 while (loop condition)
2   if (if statement condition)
3     statement_p1;
4   else
5     statement_p2;

Effectively, this means that for each iteration of the while loop, the if statement will execute once. The condition of the if statement will be evaluated, and either statement_p1 or statement_p2 will be executed depending on whether the condition is met (the “YES” branch) or not (the “NO” branch), as shown in the following listing. Of course, it is entirely possible that in some iterations, the if condition will be true, and in others, it will not. Furthermore, it is important to note that the condition of the while loop and the condition of the if statement are not connected.

  • START OF LOOP
    • The loop condition is checked (condition holds true)
      • 1st ITERATION
      • The if condition is checked, and:
      • statement_p1 is executed if the condition is true (the “YES” branch)
      • statement_p2 is executed if the condition is false (the “NO” branch)
    • The loop condition is checked again (condition holds true)
      • 2nd ITERATION
      • The if condition is checked, and:
      • statement_p1 is executed if the condition is true (the “YES” branch)
      • statement_p2 is executed if the condition is false (the “NO” branch)
    • The loop continues
    • The loop condition is checked (condition no longer holds true, loop terminates)
  • END OF LOOP

If a loop is nested within another loop (or more than one), this is referred to as “double”, “triple”, or “multiple” loops. If a while loop is nested inside another while loop, we have a double while loop. The first loop is called the “outer” while loop, and the second, nested loop is called the “inner” while loop. For example:

1 while (condition 1)
2   while (condition 2) statement_p;

The conditions of the outer loop (condition 1) and the inner loop (condition 2) do not have to be connected — they are usually independent.

Example 2

Create a class SavingsCalculator that contains:

  • A static method calculateDoublingTime that takes the amount of money invested in savings and the interest rate as parameters (e.g., 9.5%) given by the bank annually. The method calculates and prints the amount of savings, increased by the given interest, after each invested year, until the invested funds are doubled. The basic assumption is that the amount from the previous year, along with the interest for that year, will be reinvested in the next year, so that “compound interest” applies.

  • A static method calculateDoublingTimeWithTax, which works the same as the previous method, but reduces the interest rate by 0.5% (extra tax) every time the savings amount exceeds 100,000. Again, the “compound interest” principle is applied.

    class SavingsCalculator {

     1   static void calculateDoublingTime(double amount, double interest) {
     2       double savings = amount;
     3 
     4       while ( savings < 2 * amount) {
     5           savings = savings * (100 + interest) / 100;
     6           System.out.println(savings);
     7       }
     8   }
     9 
    10   static void calculateDoublingTimeWithTax(double amount,
    11    double interest) {
    12       double savings = amount;
    13 
    14       while ( savings < 2 * amount) {
    15           if (savings < 100000)
    16               savings = savings * (100 + interest) / 100;
    17           else
    18               savings = savings * (100 + interest - 0.5) / 100;
    19 
    20           System.out.println(savings);
    21       }
    22   }
    

    }

Create a class TestSavingsCalculator that checks how many years it takes to double the invested funds of 80,000 dollars with an interest rate of 9.5%, in both cases: without extra tax and with extra tax.

 1 class TestSavingsCalculator {
 2 
 3     public static void main(String[] args) {
 4         System.out.println("Calculation of savings without extra tax");
 5         SavingsCalculator.calculateDoublingTime(80000, 9.5);
 6 
 7         System.out.println("Calculation of savings with extra tax");
 8         SavingsCalculator.calculateDoublingTimeWithTax(80000, 9.5);
 9     }
10 }

From the code of the method calculateDoublingTime, we can see the application of the while loop when the exact number of iterations is not known in advance. The time it takes to double the money depends on the interest rate, and it is not possible to know exactly how many years of investment are required. In other words, there is no point in introducing a loop counter (like in a for loop), but reinvestment occurs until the condition — doubling the initial amount of money — is met. In the code, the condition is as follows:

1 while (savings < 2 * amount)

In each iteration, the savings are increased by the interest, and the increased sum is then printed to the screen:

1 savings = savings * (100 + interest) / 100;
2 System.out.println(savings);

Nesting the if statement within the while loop can be seen in the method calculateDoublingTimeWithTax, which also calculates the time to double the money like the first method, but applies different formulas based on the amount of money being invested. If the current investment amount is less than 100,000, the formula without extra tax is applied, with the full interest rate. If not, the formula with extra tax is used, where the interest rate is reduced by 0.5%.

1 if (savings < 100000)
2   savings = savings * (100 + interest) / 100;
3 else
4   savings = savings * (100 + interest - 0.5) / 100;

It is important to note that the loop condition remains the same throughout (savings < 2 * amount) and is not connected to the if condition (savings < 100000).

When the code is run in the main method, the difference in savings due to the extra tax can be seen. The first three years of savings give the same amounts: 87,600.0, 95,922.0, and 105,034.59 respectively, while in the following years, the difference due to the extra tax reduction becomes visible. Thus, at the end of the eighth year, the result is 165,349.5207161583 if there is no extra tax, or 161,608.73626709997 if there is.

The break, return, and continue statements

The usual way to exit a while loop is for the condition checked before each iteration to no longer hold true. Since the while loop does not have a counter, the loop condition typically refers to something else: an event that needs to happen (e.g., the end of a sentence, end of a file, exceeding a certain value at some point, etc.).

However, the return, break, and continue statements work the same way in all loops in Java, including while loops, and they can be used to exit the loop, exit the method, or skip an iteration.

The details of how these three statements work can be read in the lesson on for statements. Their effects in the context of loops (all types of loops, not just for loops) are summarized in the following table:

Statement Effect on Loop Execution
continue Skips only the current iteration; the loop and method continue
break Breaks the loop; the method continues
return Breaks everything: both the loop and the entire method

Alternative writing of the while loop

Just like with the if statement, it is possible to write an alternative version of any while loop. This is usually done by slightly changing the loop’s condition.

For example, assuming the variables sum and result are integer variables, instead of writing “sum <= 10”, you could write “sum < 11” (the last allowed value will still be 10). Or, instead of writing “result > 0”, you could write “result >= 1”, and so on.

Additionally, it is possible to perform a “negation of a negation (double negation)” of the logical expression representing the loop condition, similar to writing an alternative version of the if statement. For example, instead of:

1 while (sum <= 10) 

You could write:

1 while ( !(sum > 10) ) 

Example 3

Create a class NumberPowerA that represents an alternative version of the NumberPower class:

  • A static method greaterThan1000A that performs the same task as the greaterThan1000 method, but with a while loop written alternatively by slightly changing the loop condition.
  • A static method greaterThan1000AA that performs the same task as the greaterThan1000 method, but with a while loop written alternatively by applying double negation to the loop condition.
 1     class NumberPowerA {
 2 
 3         static void greaterThan1000A(int a){
 4             int result = 1;
 5 
 6             // Instead of result <= 1000, we can write
 7             // result <1001 but only because the result
 8             // is an integer variable
 9             while (result < 1001) result = result * a;
10 
11             System.out.println(result);
12         }
13 
14         static void greaterThan1000AA(int a){
15             int result = 1;
16 
17             // instead of result <= 1000, we can write
18             // !(result > 1000) as a double negation of
19             // the original condition
20             while ( !(result > 1000) ) result = result * a;
21 
22             System.out.println(result);
23         }
24     }

Create a class TestNumberPowerA that, in the main method, checks the smallest power of the number 2 that is greater than 1000 by calling both alternative methods: greaterThan1000A and greaterThan1000AA.

 1 class TestNumberPowerA {
 2 
 3     public static void main(String[] args) {
 4         // Should print 1024 (two on the power of ten)
 5         NumberPowerA.greaterThan1000A(2);
 6 
 7         // Should print 1024 (two on the power of ten)
 8         NumberPowerA.greaterThan1000AA(2);
 9     }
10 }

In the method greaterThan1000A, a slight change in the condition of the while loop is applied to create the alternative loop. Instead of “result <= 1000”, it is written as “result < 1001”. Everything else is identical to the original method.

In the method greaterThan1000AA, double negation of the while loop condition is applied to create the alternative loop. Instead of “result <= 1000”, it is written as “!(result > 1000)”. This means the loop will execute until the result exceeds 1000, which is simply another way of expressing that the loop should run while the result is less than or equal to 1000.

Infinite while loop

All the theoretical details about infinite and un-executable loops can be found in the lesson on the for statement. Here is a brief reminder, as well as the specifics of infinite and un-executable while loops.

Neither an infinite nor an un-executable loop represents a syntax error, but rather an error during the program’s execution, meaning the execution does not proceed normally.

To recap, an infinite loop is a loop that never (by itself) stops executing. It has an infinite number of iterations, and it usually requires an external intervention or command to stop the program, as it will never terminate on its own—it lacks normal execution. If this is not done, it is possible that the computer will slow down and block its operation because all memory and/or processing resources are consumed by the (infinite) execution of this loop.

Stopping the execution of an infinite loop is ESSENTIAL, and this can be done using the shortcut Ctrl+F2, or by pressing one of the two red squares: in the execution window (bottom left) or at the top right above the code editor (Figure 30).

Stopping the execution of the program Ctrl+F2
Figure 30. Stopping the execution of the program Ctrl+F2

The only reason for having an infinite while loop is that the loop condition is always true. This can happen for two reasons:

  • The loop condition is an always satisfied logical expression (a tautology). In this case, the logical statement itself does not depend on the values of the variables.
  • Or, the values of the variables involved in the condition change in such a way that the condition always holds true (for example, if the value of a variable involved in the condition does not change, the condition will always hold true, and so on).

Example 4

Create a class InfinitePrinter that contains:

  • A static method printMessage that uses an infinite while loop to print a message entered as a parameter an infinite number of times. The infinite loop should be created by ensuring the loop condition is always true, but variables will not participate in its formation.

  • A static method printMessageA that uses an infinite while loop to print a message entered as a parameter an infinite number of times. The infinite loop should be created by ensuring the loop condition is always true, and variables will participate in forming the condition.

  • A static method printPower that receives two positive integers x and y as parameters. The method uses an infinite while loop to multiply the number x by itself (raising x to a power) until it becomes greater than y. After each multiplication, the intermediate result should be printed on the screen.

  • A static method printPowerA that receives two positive integers x and y as parameters. The method multiplies the number x by itself until it exceeds y. After each multiplication, the intermediate result should be printed on the screen. When calling the method, pass the number 1 as parameter x to effectively create an infinite loop.

    class InfinitePrinter {

     1   static void printMessage(String message){
     2       while (true)
     3           System.out.println(message);
     4   }
     5 
     6   static void printMessageA(String message){
     7       while (message == null || message != null)
     8           System.out.println(message);
     9   }
    10 
    11   static void printPower(int x, int y){
    12       int result = 1;
    13 
    14       while (result < y || result >= y){
    15           result = result * x;
    16           System.out.println(result);
    17       }
    18   }
    19 
    20   static void printPowerA(int x, int y){
    21       int result = 1;
    22 
    23       while (result < y){
    24           result = result * x;
    25           System.out.println(result);
    26       }
    27   }
    

    }

Create a class TestInfinitePrinter that, in the main method, calls the methods of the InfinitePrinter class and tests their behavior.

 1 class TestInfinitePrinter {
 2 
 3     public static void main(String[] args) {
 4         // Method calls are intentionally placed under comments
 5         // because methods contain infinite loops.
 6 
 7         // Remove comments from the desired code lines
 8         // and run program
 9 
10         //InfinitePrinter.printMessage("Good morning");
11 
12         //InfinitePrinter.printMessageA("Good morning");
13 
14         //InfinitePrinter.printPower(3, 3000);
15 
16         //An infinite loop can be achieved here by
17         // providing 1 as the first argument - each
18         // power of 1 is always 1.
19         //InfinitePrinter.printPowerA(1, 23);
20     }
21 }

WARNING - READ THIS BEFORE RUNNING THE EXAMPLES

  • In the main method of the TestInfinitePrinter class, the calls to the methods of the InfinitePrinter class are commented out to prevent accidentally triggering their execution.
  • When these calls are uncommented, running the main method will cause an infinite loop within these methods and lead to SLOWING DOWN OR BLOCKING THE WORKING ENVIRONMENT OR COMPUTER.
  • It is NECESSARY to stop the execution of the infinite loop, which can be done by using the shortcut Ctrl+F2, or by pressing one of the two red squares: in the execution window (bottom left) or at the top right above the code editor (Figure 30).

In the method printMessage, you can see the simplest way to create an infinite while loop — by setting the logical value true as the loop condition. The condition does not depend on the values of the variables because they are not referenced in the condition.

In the method printMessageA, you can see another way to create an infinite while loop — by forming a condition that is always true (a tautology). Specifically, the condition checks whether the message is equal to null or not equal to null. One of these will always be true, so the entire condition will always hold (due to the or operator). Technically, the variable message is referenced in the condition, but the truth of the condition does not depend on its value.

In the method printPower, the while loop condition is also a tautology, but it is achieved by referencing numerical variables. The condition “result < y || result >= y” always holds true because, no matter the values of result and y, one will always be smaller than the other or greater than or equal to the other.

The method printPowerA does not technically have an infinite loop, except in special cases. This happens when the values of the input parameters cause the loop condition to always hold true. If the number 1 is entered as the first parameter (x), an infinite loop will occur. The reason is that the power of the number 1 is always 1, so the condition “result < y” will never be violated and will always hold true.

Un-executable while Loop

As previously mentioned, an un-executable loop is the “opposite” of an infinite loop because it always stops before executing its first iteration or during the first iteration.

The most common reasons for an un-executable while loop are:

  • The loop condition is an unsatisfiable logical expression (contradiction).
  • The initial values of the variables involved in the loop condition are such that the condition is false from the start.
  • Or, the loop immediately breaks during the first iteration due to a wrong break or return command.
  • Or, as a special case of the previous situation, an if statement nested within the loop contains both branches that use the break or return command.

Unlike the infinite loop, once an un-executable loop is started, there is no need to manually stop the program.

Example 5

Create the class UnexecutablePrinter so that it has:

  • A static method printMessage that uses an un-executable while loop to print a message entered as a parameter. Create an un-executable loop by making the loop condition always unsatisfiable (contradiction), and the variables will participate in forming the condition.

  • A static method printPower that receives two positive integers, x and y, as parameters. The method uses an un-executable while loop to multiply x by itself (raise x to a power) until it exceeds y. After each multiplication, the intermediate result is printed on the screen. The un-executable loop is created by making the condition unsatisfiable (contradiction).

  • A static method printPowerA that receives two positive integers, x and y, as parameters. The method uses an un-executable while loop to multiply x by itself until it exceeds y. After each multiplication, the intermediate result is printed on the screen. The un-executable loop is created by making the loop condition practically unsatisfiable (contradiction) due to the expected values of the parameters and variables in the method.

  • A static method calculateDoubleSavings that receives two parameters: the amount of money invested in savings and the interest rate (e.g., 9.5%) given by the bank for savings on an annual basis. The method calculates and prints the amount of savings increased by the given interest after each invested year, until the investment doubles. The method returns the last increased value of the savings that exceeded twice the initial amount. The basic assumption is that the previous year’s amount, together with the interest for that year, will be reinvested in the next year, so “compound interest” will be calculated.

    class UnexecutablePrinter {

     1   static void printMessage(String message){
     2       while (message == null && message != null)
     3           System.out.println(message);
     4   }
     5 
     6   static void printPower(int x, int y){
     7       int result = 1;
     8 
     9       while (result < y && result >= y){
    10           result = result * x;
    11           System.out.println(result);
    12       }
    13   }
    14 
    15   static void printPowerA(int x, int y){
    16       int result = 1;
    17 
    18       while (result > y){
    19           result = result * x;
    20           System.out.println(result);
    21       }
    22   }
    23 
    24   static double calculateDoubleSavings(double amount, double interest) {
    25       double savings = amount;
    26 
    27       while ( savings < 2 * amount) {
    28           savings = savings * (100 + interest) / 100;
    29           System.out.println(savings);
    30           return savings;
    31       }
    32 
    33       return savings;
    34   }
    

    }

Create the class TestUnexecutablePrinter that, in the main method, calls the methods of the UnexecutablePrinter class and tests their behavior.

 1 class TestUnexecutablePrinter {
 2 
 3     public static void main(String[] args) {
 4         UnexecutablePrinter.printMessage("Good morning");
 5 
 6         UnexecutablePrinter.printPower(3, 3000);
 7 
 8         UnexecutablePrinter.printPowerA(3, 23);
 9 
10         UnexecutablePrinter.calculateDoubleSavings(80000, 9.5);
11     }
12 }

In the method printMessage, an unsatisfiable condition is created, so the while loop will never execute. The condition “message == null && message != null” is unsatisfiable because the variable message cannot simultaneously be null and not null. Interestingly, it is not possible to write the value false as a while loop condition (“while (false)”) in the same way that true can be used. Writing false as the condition would be interpreted as a syntax error.

In the method printPower, an unsatisfiable condition “result < y && result >= y” is created because any two values of the variables result and y cannot simultaneously be both smaller and greater than or equal to each other.

The while loop condition in the method printPowerA is not (technically) unsatisfiable, but it is practically unsatisfiable according to the expected values of the variables result and y. Since y is a positive number and result has an initial value of 1, the condition “result > y” is almost certainly not satisfied because it reduces to “1 > y”, and y is certainly greater than 1.

Finally, in the method calculateDoubleSavings, an error occurs by returning the value after each year of savings. This causes the loop to break immediately during the first iteration due to the incorrect use of the return command.

When to use a while loop and when to use a for loop, alternative writing of the while loop as a for loop and vice versa

In many programming languages, there is only one statement for looping. In Java, it’s different, as there are for, while, do-while, and for-each loops. Why is this the case, and why were different types of loops introduced when everything can be done using just one?

In short, it’s to allow programmers to write code that is easier to read, understand, and maintain. Programming is almost always done in teams, and it’s very common for a programmer to read and modify code that someone else wrote. Therefore, for greater productivity, it is crucial that the code be written as clearly as possible. Some techniques that support this have already been discussed: code indentation and breaking statements into multiple lines. However, in Java, the approach goes even further, allowing you to use the loop type that results in the most readable source code.

In Java, every for loop can be alternatively written as a while or do-while loop, and vice versa. When this code is compiled, it results in similar or identical executable code (machine instructions), so it doesn’t matter (in terms of performance) which loop type is used in the source code. The only important difference is that the code may be more readable if written with a for loop rather than a while loop or vice versa.

The for-each loop is a bit more specific (covered in the lesson on arrays), and while every for-each loop can be written as a for, while, or do-while loop, the reverse isn’t necessarily true.

When should each type of loop be used to make the code more readable?

  • for loop:

    • When the number of iterations is known in advance
    • When it makes sense to introduce a loop counter (iteration counter)
    • When working with arrays (covered in the lesson on arrays)
  • while and do-while loops:

    • When the number of iterations is not known in advance (waiting for some condition, e.g., an unknown-length file end signal)
    • When introducing a loop counter doesn’t make sense
    • When it makes sense to introduce some kind of counter, but it doesn’t count iterations; instead, it acts as an auxiliary counter. This auxiliary counter typically doesn’t participate in the loop condition and often needs to be initialized and used outside the loop.

Example 6

Create a class SavingsCalculator2 with the following methods:

  • A static method calculateDoublingTime that takes two parameters: the amount of money to be invested in savings and the interest rate in percentage (e.g., 9.5%) given by the bank for annual savings. The method calculates and prints the amount of savings increased by the given interest after each year until the initial investment is doubled. The main assumption is that the funds from the previous year, together with the interest for that year, will be invested in the next year, and so the “compound interest” is calculated. Implement the method using a while loop.

  • A static method calculateDoublingTimeA that is an alternative version of the previous method, implemented using a for loop.

  • A static method calculateSavings that takes three parameters: the amount of money to be invested, the interest rate in percentage (e.g., 9.5%), and the number of years for which the money will be saved. The method calculates and prints the amount of savings increased by the given interest after each year until the savings are calculated for all years. The basic assumption is that the funds from the previous year, together with the interest for that year, will be reinvested (“compound interest”). Implement the method using a for loop.

  • A static method calculateSavingsA that is an alternative version of the previous method, implemented using a for loop.

  • A static method calculateYears that takes the interest rate as a parameter (e.g., 9.5%) and calculates and returns the number of years (an integer) needed for the initial investment to double (i.e., to exceed 200%). For this calculation, the exact amount of money invested is not needed. The basic assumption is that the funds from the previous year, along with the interest for that year, will be invested in the next year, and the “compound interest” will be calculated. Implement this method using a while loop.

  • A static method calculateYearsA that is an alternative version of the previous method, implemented using a for loop.

    class SavingsCalculator2 {

     1   static void calculateDoublingTime(double amount, double interest) {
     2       double savings = amount;
     3 
     4       while ( savings < 2 * amount) {
     5           savings = savings * (100 + interest) / 100;
     6           System.out.println(savings);
     7       }
     8   }
     9 
    10   static void calculateDoublingTimeA(double amount, double interest) {
    11       // First version: for loop without counter
    12       // and commands to increase counts.
    13       /*
    14       double savings = amount;
    15 
    16       for ( ; savings < 2 * amount; ) {
    17           savings = savings * (100 + interest) / 100;
    18           System.out.println(savings);
    19       }
    20       */
    21 
    22       // Second version: variable savings is inserted artificially
    23       // as a loop counter. The condition is the same and the
    24       // command for increasing the "counter" is actually a
    25       // formula for calculating savings based on annual interest,
    26       // with the fact that the print command is located where the
    27       // counter increment command should be (because printing is
    28       // supposed to happen after the savings calculation).
    29       for (double savings = amount; savings < 2 * amount; 
    30       System.out.println(savings))
    31           savings = savings * (100 + interest) / 100;
    32   }
    33 
    34   static void calculateSavings(double amount, double interest,
    35    int years) {
    36 
    37       double savings = amount;
    38 
    39       for (int i = 1; i <= years; i++) {
    40           savings = savings * (100 + interest) / 100;
    41           System.out.println(savings);
    42       }
    43   }
    44 
    45   static void calculateSavingsA(double amount, double interest,
    46    int years) {
    47 
    48       double savings = amount;
    49       // The loop counter must be declared
    50       // as local variable outside the while loop
    51       int i = 1;
    52 
    53       // While loop retains the condition, and you must not
    54       // forget to increase the counter inside the loop
    55       // body (i++).
    56       while (i <= years) {
    57           savings = savings * (100 + interest) / 100;
    58           System.out.println(savings);
    59           i++;
    60       }
    61   }
    62 
    63   static int calculateYears(double interest){
    64       double totalPercentage = 100;
    65       // Variable years acts as a counter
    66       // that does not count the iterations
    67       // in a classic sense (does not participate
    68       // in the loop condition), but the number
    69       // of years that need to pass.
    70       int years = 0;
    71 
    72       while (totalPercentage < 200){
    73           totalPercentage = totalPercentage * (100+ interest)/100;
    74           years++;
    75       }
    76 
    77       return years;
    78   }
    79 
    80   static int calculateYearsA(double interest){
    81       // The years variable is not a loop
    82       // counter in a classic sense, but more
    83       // as auxiliary counter.
    84       int years = 0;
    85 
    86       for (double totalPercentage = 100; totalPercentage < 200; years++)
    87           totalPercentage = totalPercentage * (100+ interest)/100;
    88 
    89       return years;
    90   }
    

    }

Create a class TestSavingsCalculator2 to test:

  • How many years it takes for an investment of 80,000 dollars to double if the interest rate is 9.5%, with savings printed at the end of each year.

  • How much money will be accumulated after 10 years with the same initial investment and interest rate.

  • How many years it will take for the initial investment to double at 9.5% interest.

    class TestSavingsCalculator2 {

     1   public static void main(String[] args) {
     2       System.out.println("Calculation for doubling of money");
     3       SavingsCalculator2.calculateDoublingTime(80000, 9.5);
     4       SavingsCalculator2.calculateDoublingTimeA(80000, 9.5);
     5 
     6 
     7       System.out.println("Calculation of savings after 10 years");
     8       SavingsCalculator2.calculateSavings(80000, 9.5, 10);
     9       SavingsCalculator2.calculateSavingsA(80000, 9.5, 10);
    10 
    11       System.out.println("The number of years for doubling");
    12       System.out.println(SavingsCalculator2.calculateYears(9.5));
    13       System.out.println(SavingsCalculator2.calculateYearsA(9.5));
    14   }
    

    }

In the calculateDoublingTime method, the while loop is used correctly because the number of years required to double the investment is not known in advance. A loop counter is not introduced because there is little point in counting iterations — iterations continue until the doubling condition is met.

Therefore, using a for loop in the calculateDoublingTimeA method results in less readable code. In the first version (which is commented out), the for loop is, more or less, converted into a while loop by removing the initialization of the counter (the first statement in parentheses) and the counter increment (the third statement in parentheses), leaving only the loop condition and two semicolons around it.

The second, more compact version of the for loop in this method is even less readable. Specifically, the loop counter is artificially introduced using the variable savings. The loop condition remains the same, but instead of a counter increment, there is a print statement. The formula for calculating interest (and incrementing the loop counter savings) is inside the for loop. This arrangement is very unusual and unexpected, which makes the code harder to understand on first reading and more difficult to modify and maintain.

In the calculateSavings method, a for loop is used correctly because the number of years for investment is known in advance — the number of years is passed as a parameter. The loop counter iterates exactly the specified number of years, and the interest is calculated the corresponding number of times.

Thus, using a while loop in the calculateSavingsA method is not the best choice because:

  • the loop counter must be introduced as a separate variable outside the loop (remaining visible outside the loop)
  • you must not forget to increment the counter at the end of each iteration (a common mistake in this case)
  • the code becomes less readable as it is written on more lines.

The calculateYears method uses a while loop because a counter (the years variable) is introduced, which is not a classic loop counter. Although the number of years of savings will match the number of iterations, this counter does not participate in the loop condition (the doubling amount), so it can be called an auxiliary counter. This is a borderline situation where a for loop version can be almost equally readable.

The calculateYearsA method uses a for loop that calls the years variable but also serves as an auxiliary counter (it is not a classic iteration counter). The totalPercentage variable more closely resembles a loop counter because it participates in the loop condition and is initialized within the loop — however, the years variable is called in the counter increment statement, and totalPercentage is incremented within the body of the for loop. This is also an unexpected arrangement of statements within the for loop, reducing the readability of the code.

Most common errors

Some of the most common syntax errors related to writing a while loop are:

  • Writing an expression as the condition of the while loop, but that expression is not a logical expression, e.g.:

    while (result % 1000) result = result * x;

Instead (correctly):

1 while (result <= 1000) result = result * x;
  • Writing the logical value false as the condition of the while loop, e.g.:

    while (false) result = result * x;

or

1 while (!true) result = result * x;

Instead (correctly):

1 while (result <= 1000) result = result * x;

Other common mistakes that are not syntax errors but affect the program’s behavior include:

  • Writing an infinite loop that never stops executing, e.g.:

    static void printMessage(String message){ while (true) System.out.println(message); }

    static void printMessageA(String message){ while (message == null || message != null) System.out.println(message); }

    static void printPower(int x, int y){ int result = 1;

    1 while (result < y || result >= y){
    2     result = result * x;
    3     System.out.println(result);
    4 }
    

    }

Instead (correctly) - only the third method is valid, as the first two are just demonstrations of an infinite loop:

1 static void printPower(int x, int y){
2   int result = 1;
3 
4   while (result < y){
5       result = result * x;
6       System.out.println(result);
7   }
8 }
  • Writing a loop that is never satisfied and never performs any iterations or always breaks in the first iteration, e.g.:

    static void printMessage(String message){ while (message == null && message != null) System.out.println(message); }

    static void printPower(int x, int y){ int result = 1;

    1 while (result < y && result >= y){
    2     result = result * x;
    3     System.out.println(result);
    4 }
    

    }

    static void printPowerA(int x, int y){ int result = 1;

    1 while (result > y){
    2     result = result * x;
    3     System.out.println(result);
    4 }
    

    }

    static double calculateDoubleSavings(double amount, double interest) { double savings = amount;

    1 while ( savings < 2 * amount ) {
    2     savings = stsavings * (100 + interest) / 100;
    3     System.out.println(savings);
    4     return savings;
    5 }
    6 
    7 return savings;
    

    }

Instead (correctly) - only the third and fourth methods are valid, as the first two are just demonstrations of an unsatisfiable loop:

 1 static void printPower(int x, int y){
 2   int result = 1;
 3 
 4   while (result < y){
 5       result = result * x;
 6       System.out.println(result);
 7   }
 8 }
 9 
10 static double calculateDoubleSavings(double amount, double interest) {
11   double savings = amount;
12 
13   while ( savings < 2 * amount ) {
14       savings = savings * (100 + interest) / 100;
15       System.out.println(savings);
16   }
17 
18   return savings;
19 }
  • Forgetting to create a block of statements in the while loop when there are multiple statements inside. This will cause only the first statement to repeat during iterations, and all subsequent statements will “fall out” of the loop, e.g.:

    while ( savings < 2 * amount ) savings = savings * (100 + interest) / 100; System.out.println(savings);

Instead (correctly):

1 while ( savings < 2 * amount ) {
2     savings = savings * (100 + interest) / 100;
3     System.out.println(savings);
4   }

The do-while statement (loop)

Lesson content

  • What is the do-while statement - declaration and elements
  • Nesting statements within the do-while loop -The break, return, and continue statements
  • Alternative writing of the do-while loop
  • Infinite do-while loop
  • Non-executable do-while loop
  • When to use for and while, and when to use do-while loop, alternative writing of one type of loop to another type
  • Most common errors

What is the do-while statement - declaration and elements

It has already been mentioned that the for loop is most useful in situations when the number of iterations is known before the start of the cycle, and the while statement was created specifically to be applied in situations where the number of iterations is not known in advance.

Likewise, if the condition for a for or while loop is not satisfied at the beginning, before the first iteration, no iteration will be executed and the loop will terminate. This is because the condition is checked before every iteration, before any commands in the loop are executed.

However, in some situations, it is necessary to ensure that at least one iteration of the loop is always executed. This is achieved by using the do-while loop. The declaration of the do-while loop is as follows:

1 do
2     command_p;
3 while (...condition...);

The do-while loop has almost the same syntax as the while loop. One could even say that the do-while loop was created as a modification of the while loop. Both loops are executed as long as the loop condition (the logical expression in parentheses) is true. The difference between these two loops is that in the do-while loop, the condition is checked at the end of the iteration, not at the beginning. The effect is that at least one iteration will always be executed even if the condition is false.

When the loop starts, the sequence of command execution and condition checks is as follows:

  • START OF LOOP
    • 1st ITERATION
      • command_p is executed
      • the condition is checked (condition is true)
    • 2nd ITERATION
      • command_p is executed
      • the condition is checked (condition is true)
    • 3rd ITERATION
      • command_p is executed
      • the condition is checked (condition is true) …
    • LAST ITERATION
      • command_p is executed
      • the condition is checked (condition is false - loop breaks)
  • END OF LOOP

As with other loops, if multiple commands need to be repeated cyclically, they must be enclosed in a block of commands using curly braces.

1 do {
2    command_p_1;
3    command_p_2;
4    ...
5    command_p_n;
6 } while (...condition...);

Example 1

Create a class SurePrinting which has:

  • A static method print that takes a message and an integer n as parameters. The method should print the message on the screen n times. The message should be printed at least once, even if the input number n is zero or less than zero.

  • A static method printA that does exactly the same thing as print, but using a for loop.

  • A static method printAA that does exactly the same thing as print, but using a while loop.

    class SurePrinting {

     1   static void print(String message, int n){
     2       int counter = 1;
     3 
     4       // There will be at least one iteration
     5       // because the condition is checked after
     6       // printing on the screen.
     7       do {
     8           System.out.println(message);
     9           counter++;
    10       } while (counter <= n);
    11   }
    12 
    13   static void printA(String message, int n){
    14       // The message must be printed outside the
    15       // loop in order to print at least once
    16       System.out.println(message);
    17 
    18       // The loop counter starts from 2 and not from 1
    19       // because we have already printed the message once
    20       for (int i = 2; i <= n; i++)
    21           System.out.println(message);
    22   }
    23 
    24   static void printAA(String message, int n){
    25       // The message must be printed outside the
    26       // loop in order to print at least once
    27       System.out.println(message);
    28 
    29       // The loop counter starts from 2 and not from 1
    30       // because we have already printed the message once
    31       int counter = 2;
    32 
    33       while (counter <= n) {
    34           System.out.println(message);
    35           counter++;
    36       }
    37   }
    

    }

Create a class TestSurePrinting which calls the methods of the SurePrinting class to print the message “Good morning” 5 times and “Good night” -5 (minus five) times.

 1 class TestSurePrinting {
 2 
 3     public static void main(String[] args) {
 4         // The message will be printed 5 times
 5         SurePrinting.print("Good morning", 5);
 6 
 7         // The message will be printed once
 8         SurePrinting.print("Good night", -5);
 9     }
10 }

From the first call to the print method, you can see that the do-while loop works normally with a later introduced counter, and that the loop condition is checked at the end of the iteration. The message “Good morning” is printed five times on the screen.

However, the real function of the do-while statement is seen in the second call to the print method, where the number -5 is passed as the number of iterations for printing the message “Good night”. One iteration will be executed even though the loop condition (counter <= n, i.e., 1 <= -5) is false. Of course, when writing the condition, care should be taken that the counter is checked after being incremented, i.e., after the iteration has been executed.

In the printA and printAA methods, you can see how the same result is achieved using for and while loops. In both cases, the first print of the message must be done outside the loop to ensure the message is printed. If the entered number n is zero or less, it will be the only time the message is printed. At the same time, the remaining number of iterations must be adjusted, so in both cases, the loop counter will start from 2, not from 1, because the first print has already been done outside the loop.

Nesting statements within the do-while loop

Like IF, SWITCH, while, and for statements, the do-while statement can also be combined (nested) with all other flow control statements.

The principles for doing this have been explained in previous lessons (for and while loops), so they will not be repeated here.

The break, return, and continue statements

The usual way to exit the do-while loop is to make the condition that is checked after each iteration no longer valid. Since neither the do-while loop (nor the while loop) has a counter, the loop condition usually refers to something else: some event that should happen (e.g., end of a sentence, end of a file, exceeding a certain value at some point, etc.).

However, the return, break, and continue statements work in the same way for all loops in Java, including the do-while loop, and can be used to interrupt the loop, exit the method, or skip an iteration.

Details about how these three statements work can be found in the lesson on the for statement, and their effects in the context of their impact on loop execution (all types of loops, not just the for loop) are summarized in the following table.

Statement Effect on Loop Execution
continue Only the current iteration is interrupted, the loop and method continue
break The loop is interrupted, the method continues
return Everything is interrupted: both the loop and the method

Alternative writing of the do-while Loop

Since it is very similar to the while loop, the do-while loop can also be written alternately using the same changes in the code. Here is a brief overview, and for details, refer to the lesson on the while statement.

First, this is usually done by writing the loop condition in a slightly different way.

For example, assuming that res and sum are integer variables, instead of sum <= 10, it can be written as sum < 11 (the last allowed value will still be 10). Or instead of res > 0, it can be written as res >= 1, and so on.

Additionally, it is possible to apply a “negation of negation (double negation)” to the logical expression representing the loop condition, similar to writing an alternative version of the if statement. For example, instead of:

1 do
2   command_p;
3 while (sum <= 10);

It can be written as:

1 do
2   command_p;
3 while (!(sum > 10));

Infinite do-while loop

All theoretical details about infinite and un-executable loops can be read in the lesson on the for statement. Here, we provide a quick reminder, as well as the specifics of infinite and un-executable do-while loops.

Neither the infinite nor the un-executable loop represents a syntax error, but rather an error in the execution of the program, i.e., when the execution is not normal.

As a reminder, an infinite loop is a loop that never (on its own) stops executing. It has an infinite number of iterations and usually requires external intervention or command to stop execution, since it will never stop on its own – there’s no normal termination. If this isn’t done, it is possible for the computer to slow down or block operations because all memory and/or processor resources are consumed by the (infinite) execution of this loop.

Interrupting the execution of an infinite loop is NECESSARY, and it is done via the shortcut Ctrl+F2, or by pressing one of the two red squares: in the execution window (bottom left) or at the top-right above the code editor (Figure 31).

Interrupting the execution of the program
Figure 31. Interrupting the execution of the program

The only reason for an infinite do-while loop is that the loop condition is always satisfied – and this is identical to the while statement. Here is a brief overview, and for details, refer to the lesson on the while statement.

An infinite do-while loop can be created in two ways:

  • The loop condition is an always satisfied logical expression (tautology). In this case, the logical expression itself doesn’t depend on the values of variables.
  • Or, the values of the variables involved in the condition are changed in such a way that the condition always holds true (for example, if the value of a variable involved in the condition doesn’t change, the condition will always hold true, and so on).

Un-executable do-while loop

As already mentioned, an un-executable loop is the “opposite” of an infinite loop because it always terminates before it executes the first iteration or during the first iteration. Unlike for and while loops, an un-executable do-while loop always executes the first iteration.

The most common reasons for an un-executable do-while loop are:

  • The loop condition is an unsatisfiable logical expression (contradiction).
  • The values of the variables involved in the loop condition are such that the condition does not hold true at the beginning.
  • Or the loop is immediately interrupted during the first iteration by an incorrect use of the break or return command.
  • Or, as a special case of the previous situation, an if statement nested within the loop has both branches written in such a way that both use the break or return command.

Unlike the infinite loop, after the execution of an un-executable loop, manual interruption of the program is not required.

When to use for and while loops vs. do-while loop, alternative writing of one type of loop using another

In Java, there are for, while, do-while, and for-each loops with the goal of allowing programmers to write code that is easier to read, understand, and maintain.

As already stated, in Java, each loop can be alternately written as another type of loop. When the code for the loop is compiled, the result is similar or identical executable code (machine instructions), so the only important difference is that the source code might be more readable if it is written using one type of loop rather than another.

Which loop should be used to make the code more readable?

  • for loop:

    • When the number of iterations is known in advance.
    • When it makes sense to introduce a loop counter (iteration counter).
    • When working with arrays (covered in the lesson on arrays).
  • while and do-while loops:

    • When the number of iterations is not known in advance (waiting for a certain condition, e.g., signal for the end of an unknown-length file).
    • When there is no need to introduce a loop counter (iteration counter).
    • When a counter is needed, but it does not count loop iterations, but something else, like an auxiliary counter. This auxiliary counter often does not participate in the loop condition, and it is often necessary to initialize and use it outside the loop.
  • do-while loop additionally:

    • When it is necessary to ensure that at least one iteration of the loop is always executed.

Most common errors

Some of the most common syntax errors when writing the do-while statement are almost identical to those in the while statement:

  • Writing an expression as the condition of the do-while loop that is not a logical expression, e.g.:

    do res = res * a; while (res % 1000);

Instead of (correct):

1 do
2    res = res * a;
3 while (res <= 1000);
  • Writing the logical value false as the condition of the do-while loop, e.g.:

    do res = res * a; while (false);

or

1 do
2    res = res * a;
3 while (!true);

Instead of (correct):

1 do
2    res = res * a;
3 while (res <= 1000);
  • Forgetting to create a block of statements in the do-while loop if it contains more than one statement:

    do savings = savings * (100 + interest) / 100; System.out.println(savings); while (savings < 2 * amount);

Instead of (correct):

1 do {
2       savings = savings * (100 + interest) / 100;
3       System.out.println(savings);
4 } while (savings < 2 * amount);

Other common mistakes that are not syntax errors, but affect the program’s operation:

  • Writing an infinite loop that never stops executing, e.g.:

    static void printMessage(String message){ do System.out.println(message); while (true); }

    static void printMessageA(String message){ do System.out.println(message); while (message == null || message != null); }

    static void printPower(int a, int b){ int res = 1;

    1 do {
    2     res = res * a;
    3     System.out.println(res);
    4 } while (res < b || res >= b);
    

    }

Instead of (correct) - only the third method since the first two are only demonstrations of an infinite loop:

1 static void printPower(int a, int b){
2   int res = 1;
3 
4   do {
5       res = res * a;
6       System.out.println(res);
7   } while (res < b);
8 }
  • Writing an un-executable do-while loop that executes only the first iteration or is always interrupted during the first iteration, e.g.:

    static void printMessage(String message){ do System.out.println(message); while (message == null && message != null); }

    static void printPower(int a, int b){ int res = 1;

    1 do {
    2     res = res * a;
    3     System.out.println(res);
    4 } while (res < b && res >= b);
    

    }

    static void printPowerA(int a, int b){ int res = 1;

    1 do {
    2     res = res * a;
    3     System.out.println(res);
    4 } while (res > b);
    

    }

    static double calculateDoubleAmount(double amount, double interest) { double savings = amount;

    1 do {
    2     savings = savings * (100 + interest) / 100;
    3     System.out.println(savings);
    4     return savings;
    5 } while (savings < 2 * amount);
    6 
    7 return savings;
    

    }

Instead of (correct) - only the third and fourth methods because the first two are only demonstrations of an un-executable loop:

 1 static void printPower(int a, int b){
 2   int res = 1;
 3 
 4   do {
 5       res = res * a;
 6       System.out.println(res);
 7   } while (res < b);
 8 }
 9 
10 static double calculateDoubleAmount(double amount, double interest) {
11   double savings = amount;
12 
13   do {
14       savings = savings * (100 + interest) / 100;
15       System.out.println(savings);
16   } while (savings < 2 * amount);
17 
18   return savings;
19 }