Automation QA Testing Course Content

Java Basic on Operators

 

How to Write Hello World in Java:

Ideally the first step should've been setting up Java on your computer, but I don't want to bore you with downloading and installing a bunch of software right at the beginning. For this example, you'll use https://replit.com/ as your platform.

First, head over to https://replit.com/ and create a new account if you don't already have one. You can use your existing Google/GitHub/Facebook account to login. Once logged in, you'll land on your home. From there, use the Create button under My Repls to create a new repl.



In the Create a Repl modal, choose Java as Template, set a descriptive Title such as HelloWorld and hit the Create Repl button.



A code editor will show up with an integrated terminal as follows:


On the left side is the list of files in this project, in the middle is the code editor, and on the right side is the terminal.

The template comes with some code by default. You can run the code by hitting the Run button. Go ahead and do that, run the program.


If everything goes fine, you'll see the words "Hello world!" printed on the right side. Congratulations, you've successfully run your first Java program.

What’s Going On in the Code?

The hello world program is probably the most basic executable Java program that you can possibly write – and understanding this program is crucial.

class Main {
  public static void main(String[] args) {
    System.out.println("Hello world!");
  }
}
Let's start with the first line:
class Main {
  //...
}

This line creates a Main class. A class groups together a bunch of related code within a single unit.

This is a public class, which means this class is accessible anywhere in the codebase. One Java source file (files with the .java extension) can contain only one top level public class in it.

This top level public class has to be named exactly the same as the source code filename. That's why the file named Main.java contains the main class in this project.

To understand why, click on the three dots in the list of files and click on the Show hidden files option.


This will unveil some new files within the project. Among them is the Main.class file. This is called a bytecode. When you hit the Run button, the Java compiler compiled your code from the Main.java file into this bytecode.

Now, modify the existing Hello World code as follows:

class Main {
  public static void main(String[] args) {
    System.out.println("Hello world!");
  }
}

class NotMain {
  public static void main(String[] args) {
    System.out.println("Not hello world!");
  }
}

As you can see, a new class called NotMain has been added. Go ahead and hit the Run button once more while keeping your eyes on the Files menu.

image-206

A new bytecode named NotMain.class has showed up. This means that for every class you have within your entire codebase, the compiler will create a separate bytecode.

This creates confusion about which class is the entry-point to this program. To solve this issue, Java uses the class that matches the source code file name as the entry-point to this program.

Enough about the class, now let's look at the function inside it:

class Main {
  public static void main(String[] args) {
    System.out.println("Hello world!");
  }
}

The public static void main (String[] args) function is special in Java. If you have experience with languages like C, C++ or Go, you should already know that every program in those languages has a main function. The execution of the program begins from this main function.

In Java, you have to write this function as exactly public static void main (String[] args) otherwise it won't work. In fact, if you change it even a little bit Java will start to scream.

image-207

The return type has changed from void to int and the function now returns 0 at the end. As you can see in the console, it says:

Error: Main method must return a value of type void in class Main, please 
define the main method as:
   public static void main(String[] args)

Listen to that suggestion and revert your program back to how it was before.

class Main {
  public static void main(String[] args) {
    System.out.println("Hello world!");
  }
}

The main method is a public method and the static means, you can call it without instantiating its class.

The void means that the function doesn't return any value and the String[] args means that the function takes an array of strings as an argument. This array holds command line arguments passed to the program during execution.

The System.out.println prints out strings on the terminal. In the example above, "Hello world!" has been passed to the function, so you get Hello world! printed on the terminal.

In Java, every statement ends with a semicolon. Unlike JavaScript or Python, semicolons in Java are mandatory. Leaving one out will cause the compilation to fail.

That's pretty much it for this program. If you didn't understand every aspect of this section word by word, don't worry. Things will become much clearer as you go forward.

For now, remember that the top level public class in a Java source file has to match the file name, and the main function of any Java program has to be defined as public static void main(String[] args

How to Setup Java on Your Computer

First, head over to https://www.oracle.com/java/technologies/downloads/ and download the latest version of the Java SE Development Kit according to the platform you're on:

image-208

Once the download has finished, start the installer and go through the installation process by hitting the Next buttons. Finish it by hitting the Close button on the last page.

image-235

The installation process may vary on macOS and Linux but you should be able to figure it out by yourself.

Once the installation has finished, execute the following command on your terminal:

java --version

# java 18.0.2 2022-07-19
# Java(TM) SE Runtime Environment (build 18.0.2+9-61)
# Java HotSpot(TM) 64-Bit Server VM (build 18.0.2+9-61, mixed mode, sharing)

If it works, you've successfully install Java SE Development Kit on your computer. If you want to use OpenJDK instead, feel free to download Microsoft Build of OpenJDK or Adoptium and go through the installation process.

For the simple example programs that we're going to write in this article, it won't matter which JDK you're using. But in real life, make sure that your JDK version plays nicely with the type of project you're working on.

How to Install a Java IDE on Your Computer

When it comes to Java, IntelliJ IDEA is undeniably the best IDE out there. Even Google uses it as a base for their Android Studio.

The ultimate version of the IDE can cost an individual up-to $149.00 per year. But if you're student, you can get educational licenses for all JetBrains products for free.

There is also the completely free and open-source community edition. This is the one we'll be using throughout the entire book.

Head over to the IntelliJ IDEA download page, and download the community edition for your platform.

image-344

Once the download finishes, use the installer to install IntelliJ IDEA like any other software.

How to Create a New Project on IntelliJ IDEA

public class Main {
    public static void main (String[] args) {
        System.out.println("Hello World!");
    }
}

What are the Primitive Data Types in Java?

At a high level, there are two types of data in Java. There are the "primitives types" and the "non-primitive" or "reference types".

Primitive types store values. For example, int is a primitive type and it stores an integer value.

A reference type, on the other hand, stores the reference to a memory location where a dynamic object is being stored.

There are eight primitive data types in Java.

TYPEEXPLANATION
byte8-bit signed integer within the range of -128 to 127
short16-bit signed integer within the range of -32,768 to 32,767
int32-bit signed integer within the range of -2147483648 to 2147483647
long64-bit signed integer within the range of -9223372036854775808 to 9223372036854775807
floatsingle-precision 32-bit floating point within the range of 1.4E-45 to 3.4028235E38
doubledouble-precision 64-bit floating point within the range of 4.9E-324 to 1.7976931348623157E308
booleanIt can be either true or false
charsingle 16-bit Unicode character within the range of \u0000 (or 0) to \uffff (or 65,535 inclusive)

Yeah yeah I know the table looks scary but don't stress yourself. You don't have to memorize them.

You will not need to think about these ranges very frequently, and even if you do, there are ways to print them out within your Java code.

However, if you do not understand what a bit is, I would recommend this short article to learn about binary.

You've already learned about declaring an integer in the previous section. You can declare a byte, short, and long in the same way.

Declaring a double also works the same way, except you can assign a number with a decimal point instead of an integer:

public class Main {

	public static void main(String[] args) {
		double gpa = 4.8;
		
		System.out.println("My GPA is " + gpa + ".");

	}
}

If you assign an int to the double, such as 4 instead of 4.8, the output will be 4.0 instead of 4, because double will always have a decimal point.

Since double and float are similar, you may think that replacing the double keyword with float will convert this variable to a floating point number – but that's not correct. You'll have to append a f or F after the value:

public class Main {

	public static void main(String[] args) {
		float gpa = 4.8f;
		
		System.out.println("My GPA is " + gpa + ".");

	}
}

This happens because, by default, every number with a decimal point is treated as a double in Java. If you do not append the f, the compiler will think you're trying to assign a double value to a float variable.

boolean data can hold either true or false values.

public class Main {

	public static void main(String[] args) {
		boolean isWeekend = false;
		
		System.out.println(isWeekend); // false

	}
}

As you can imagine, false can be treated as a no and true can be treated as a yes.

Booleans will become much more useful once you've learned about conditional statements. So for now, just remember what they are and what they can hold.

The char type can hold any Unicode character within a certain range.

public class Main {

	public static void main(String[] args) {
		char percentSign = '%';
		
		System.out.println(percentSign); // %

	}
}

In this example, you've saved the percent sign within a char variable and printed it out on the terminal.

You can also use Unicode escape sequences to print out certain symbols.

public class Main {

	public static void main(String[] args) {
		char copyrightSymbol = '\u00A9';
		
		System.out.println(copyrightSymbol); // ©

	}
}

The Unicode escape sequence for the copyright symbol, for example, is \u00A9 and you can find more Unicode escape sequences on this website.

Among these 8 types of data, you'll be working with int, double, boolean, and char majority of the time.

What is Type Conversion or Casting?

public class Main {

	public static void main(String[] args) {
		int number1 = 8;
		double number2 = number1;
		
		System.out.println(number2); // 8.0
	}

}

What are Wrapper Classes in Java?

Wrapper classes can wrap around primitive datatypes and turn them into reference types. Wrapper classes are available for all eight primitive data types.

PRIMITIVE TYPEWRAPPER CLASS
intInteger
longLong
shortShort
byteByte
booleanBoolean
charCharacter
floatFloat
doubleDouble

You can use these wrapper classes as follows:

public class Main {
    public static void main (String[] args) {
        Integer age = 27;
        Double gpa = 4.8;

        System.out.println(age); // 27
        System.out.println(gpa); // 4.8
    }
}

All you have to do is replace the primitive data type with the equivalent wrapper class. These reference types also have methods for extracting the primitive type from them.

For example, age.intValue() will return the age as a primitive integer and the gpa.doubleValue() will return the GPA in a primitive double type.

There are such methods for all eight datatypes. Although you'll use the primitive types most of the time, these wrapper classes will be handy in some scenarios we'll discuss in a later section.

How to Use Operators in Java

Operators in programming are certain symbols that tell the compiler to perform certain operations such as arithmetic, relational, or logical operations.

Although there are six types of operators in Java, I won't talk about bitwise operators here. Discussing bitwise operators in a beginner guide can make it intimidating.

What Are the Arithmetic Operators?

public class Main {

	public static void main(String[] args) {
		double number1 = 8;
		double number2 = 5;
		
		System.out.println(number1 / number2); // 1.6
	}

}

What Are the Assignment Operators?

package operators;

public class Main {

	public static void main(String[] args) {
		double number1 = 10;
		double number2 = 5;
		
		number1 += number2;
		
		System.out.println(number1); // 15
	}

}

What Are the Relational Operators?

public class Main {

	public static void main(String[] args) {
		double number1 = 10;
		double number2 = 5;
		
		System.out.println(number1 == number2); // false
		System.out.println(number1 != number2); // true
		System.out.println(number1 > number2); // true
		System.out.println(number1 < number2); // false
		System.out.println(number1 >= number2); // true
		System.out.println(number1 <= number2); // false
	}

}

What Are the Logical Operators?

public class Main {

	public static void main(String[] args) {
		int age = 20;
		
		System.out.println(age >= 18 && age <= 40); // true
	}

}

What Are the Unary Operators?

public class Main {

	public static void main(String[] args) {
		int score = 95;
		int turns = 11;
		
		score++;
		turns--;
		
		System.out.println(score); // 96
		System.out.println(turns); // 10
	}

}

How to Work with Strings in Java

public class Main {

	public static void main(String[] args) {
		String literalString1 = "abc";
		String literalString2 = "abc";
		
		String objectString1 = new String("abc");
		String objectString2 = new String("abc");
		
		System.out.println(literalString1 == literalString2);
		System.out.println(objectString1 == objectString2);

	}

}

How to Format a String

You've already seen the usage of the + operator to sew strings together or format them in a specific way.

That approach works until you have a lot of additions to a string. It's easy to mess up the placements of the quotation marks.

A better way to format a string is the String.format() method.

public class Main {
	public static void main(String[] args) {
		String name = "Farhan";
		int age = 27;
		
		String formattedString = String.format("My name is %s and I'm %d years old.", name, age);
        
		System.out.println(formattedString);
	}

}

The method takes a string with format specifiers as its first argument and arguments to replace those specifiers as the later arguments.

In the code above, the %s, and %d characters are format specifiers. They're responsible for telling the compiler that this part of the string will be replaced with something.

Then the compiler will replace the %s with the name and the %d with the age. The order of the specifiers needs to match the order of the arguments and the arguments need to match the type of the specifier.

The %s and %d are not random. They are specific for string data and decimal integers. A chart of the commonly used specifiers are as follows:

SPECIFIERDATA TYPE
%b, %BBoolean
%s, %SString
%c, %CUnicode Character
%dDecimal Integer
%fFloating Point Numbers

There is also %o for octal integers, %x or %X for hexadecimal numbers, and %e or %E for scientific notations. But since, we won't discuss them in this book, I've left them out.

Just like the %s and %d specifiers you saw, you can use any of these specifiers for their corresponding data type. And just in case you're wondering, that %f specifier works for both floats and doubles.

How to Get the Length of a String or Check if It's Empty or Not

Checking the length of a string or making sure its not empty before performing some operation is a common task.

Every string object comes with a length() method that returns the length of that string. It's like the length property for arrays.

public class Main {
	public static void main(String[] args) {
		String name = "Farhan";
        
		System.out.println(String.format("Length of this string is: %d.", name.length())); // 6
	}

}

The method returns the length as an integer. So you can freely use it in conjunction with the integer format specifier.

To check if a string is empty or not, you can use the isEmpty() method. Like the length() method, it also comes with every string object.

public class Main {
	public static void main(String[] args) {
		String name = "Farhan";
		
		if (name.isEmpty()) {
			System.out.println("There is no name mentioned here");
		} else {
			System.out.println(String.format("Okay, I'll take care of %s.", name));
		}
	}

}

The method returns a boolean value so you can use it directly in if statements. The program checks if the name is empty or not and prints out different responses based off of that.

How to Split and Join Strings

The split() method can split a string based on a regular expression.

import java.util.Arrays;

public class Main {
	public static void main(String[] args) {
		String name = "Farhan Hasin Chowdhury";

		System.out.println(Arrays.toString(name.split(" ")));
	}

}

The method returns an array of strings. Each string in that array will be a substring from the original string. Here for example, you're breaking the string Farhan Hasin Chowdhury at each space. So the output will be [Farhan, Hasin, Chowdhury].

Just a reminder that arrays are collections of multiple data of the same type.

Since the method takes a regex as argument, you can use regular expressions to perform more complex split operations.

You can also join this array back into a string like this:

public class Main {
	public static void main(String[] args) {
		String name = "Farhan Hasin Chowdhury";
		
		String substrings[] = name.split(" ");
		
		String joinedName = String.join(" ", substrings);

		System.out.println(joinedName); // Farhan Hasin Chowdhury
	}

}

The join() method can also help you in joining multiple strings together outside of an array.

public class Main {
	public static void main(String[] args) {
		System.out.println(String.join(" ", "Farhan", "Hasin", "Chowdhury")); // Farhan Hasin Chowdhury
	}

}

How to Convert a String to Upper or Lowercase

Converting a string to upper or lower case is very straightforward in Java. There are the aptly named toUpperCase() and toLowerCase() methods to perform the tasks:

public class Main {
	public static void main(String[] args) {
		String name = "Farhan Hasin Chowdhury";

		System.out.println(name.toUpperCase()); // FARHAN HASIN CHOWDHURY
		
		System.out.println(name.toLowerCase()); // farhan hasin chowdhury
	}

}

How to Compare Two Strings

Since strings are reference types, you can not compare them using the = operator.

The equals() method checks whether two strings are equal or not and the equalsIgnoreCase() method ignores their casing when comparing.

public class Main {
	public static void main(String[] args) {
		String name = "Farhan Hasin Chowdhury";
		String nameUpperCase = name.toUpperCase();

		System.out.println(name.equals(nameUpperCase)); // false
		
		System.out.println(name.equalsIgnoreCase(nameUpperCase)); // true
	}

}

How to Replace Characters or Substrings in a String

The replace() method can replace characters or entire substrings from a given string.

package strings;

public class Main {
	public static void main(String[] args) {
		String loremIpsumStd = "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.";

		System.out.println(String.format("Standard lorem ipsum text: %s", loremIpsumStd));
		
		String loremIpsumHalfTranslated = loremIpsumStd.replace("Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium", "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system");
		
		System.out.println(String.format("Translated lorem ipsum text: %s", loremIpsumHalfTranslated));
	}

}

Here, the loremIpsumStd string contains a portion of the original lorem ipsum text. Then you're replacing the first line of that string and saving the new string in the loremIpsumHalfTranslated variable.

How to Check If a String Contains a Substring or Not

The contains() method can check whether a given string contains a certain substring or not.

public class Main {
	public static void main(String[] args) {
		String lyric = "Roses are red, violets are blue";

		if (lyric.contains("blue")) {
			System.out.println("The lyric has the word blue in it.");
		} else {
			System.out.println("The lyric doesn't have the word blue in it.");
		}
	}

}

The method returns a boolean value, so you can use the function in any conditional statement.

There were some of the most common string methods. If you'd like to learn about the other ones, feel free to consult the official documentation.

What Are the Different Ways of Inputting and Outputting Data?

import java.util.Scanner;

public class Main {

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		
		System.out.print("What's your name? ");
		String name = scanner.nextLine();
		
		System.out.printf("So %s. How old are you? ", name);
		int age = scanner.nextInt();
		
		System.out.printf("Cool! %d is a good age to start programming.", age);
		
		scanner.close();

	}

}

How to Use Conditional Statements in Java

public class Main {

	public static void main(String[] args) {
		int age = 20;
		
		// if (condition) {...}
		if (age >= 18 && age <= 40) {
			System.out.println("you can use the program");
		}
		
	}

}

What is a switch-case statement?

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("What is the first operand? ");
        int a =scanner.nextInt();

        // consumes the dangling newline character
        scanner.nextLine();

        System.out.print("What is the second operand? ");
        int b = scanner.nextInt();

        // consumes the dangling newline character
        scanner.nextLine();

        System.out.print("What operation would you like to perform? ");
        String operation = scanner.nextLine();

        switch (operation) {
            case "sum":
                System.out.printf("%d + %d = %d", a, b, a+b);
                break;
            case "sub":
                System.out.printf("%d - %d = %d", a, b, a-b);
                break;
            case "mul":
                System.out.printf("%d * %d = %d", a, b, a*b);
                break;
            case "div":
                if (b == 0) {
                    System.out.print("Can't divide by zero!");
                } else {
                    System.out.printf("%d / %d = %d", a, b, a / b);
                }
                break;
            default:
                System.out.printf("Invalid Operation!");
        }

        scanner.close();

    }

}

What is Variable Scope in Java?

public class Main {

	public static void main(String[] args) {
		int age = 20;
		
		if (age >= 18 && age <= 40) {
			// age variable is accessible here
			// booleans are not accessible here

			boolean isSchoolStudent = true;
			boolean isLibraryMember = false;
			
			if (isSchoolStudent || isLibraryMember) {
				// booleans are accessible here
				// age variable is accessible here

				System.out.println("you can use the program");
			}
		} else {
			// age variable is accessible here
			// booleans are not accessible here
            
			System.out.println("you can not use the program");
		}
		
	}

}

What Are Default Values of Variables in Java?

public class Main {
	
	// gets 0 as the value by default
	static int age;

	public static void main(String[] args) {
		System.out.println(age); // 0
	}
}

How to Work with Arrays in Java

public class Main {

	public static void main(String[] args) {
		// <type> <name>[] = new <type>[<length>]
		char vowels[] = new char[5];

	}
}

How to Sort an Array

import java.util.Arrays;

public class Main {

	public static void main(String[] args) {		
		char vowels[] = {'e', 'u', 'o', 'i', 'a'};
		
		Arrays.sort(vowels);
		
		System.out.println("The sorted array: " + Arrays.toString(vowels)); // [a, e, i, o , u]

	}
}

How to Perform Binary Search on an Array

public class Main {

	public static void main(String[] args) {
		char vowels[] = {'a', 'e', 'i', 'o', 'u'};
        
        char key = 'i';
		
		int foundItemIndex = Arrays.binarySearch(vowels, key);
		
		System.out.println("The vowel 'i' is at index: " + foundItemIndex); // 2

	}
}

How to Fill an Array

import java.util.Arrays;

public class Main {

	public static void main(String[] args) {		
		char vowels[] = {'e', 'u', 'o', 'i', 'a'};
		
		Arrays.fill(vowels, 'x');
		
		System.out.println("The filled array: " + Arrays.toString(vowels)); // [x, x, x, x, x]

	}
}

How to Make Copies of an Array

import java.util.Arrays;

public class Main {

	public static void main(String[] args) {
		int oddNumbers[] = {1, 3, 5};
		int copyOfOddNumbers[] = oddNumbers;
		
		Arrays.fill(oddNumbers, 0);
		
		System.out.println("The copied array: " + Arrays.toString(copyOfOddNumbers)); // [0, 0, 0]

	}
}

How to Compare Two Arrays

public class Main {

	public static void main(String[] args) {		
		int oddNumbers1[] = {1, 3, 5, 7, 9, 11, 13, 15};
		int oddNumbers2[] = {1, 3, 5, 7, 9, 11, 13, 15};
		
		System.out.println(oddNumbers1 == oddNumbers2); // false
	}
}

How to Use Loops in Java

If you ever need to repeat a task for a set number of times, you can use a loop. Loops can be of three types: they are for loops, for...each loops, and while loops.

For Loop

public class Main {

	public static void main(String[] args) {
		int fibonacciNumbers[] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55};
		
		for(int index = 0; index < fibonacciNumbers.length; index++) {
			System.out.println(fibonacciNumbers[index]);
		}
	}
}

For-Each Loop

public class Main {

	public static void main(String[] args) {
		int fibonacciNumbers[] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55};
		
		for(int number : fibonacciNumbers) {
			System.out.println(number);
		}
	}
}

While Loop

If you want to execute a bunch of code until a certain condition is met, you can use a while loop.

while-loop-generic

There are no initialization or update steps in a while loop. Whatever happens, happens within the loop body. If you rewrite the program for printing out the multiplication table of 5 using a while loop, it'll be as follows:

public class Main {

	public static void main(String[] args) {
		int number = 5;
		int multiplier = 1;
		
		while (multiplier <= 10) {
			System.out.println(String.format("%d x %d = %d", number, multiplier, number*multiplier));
			
			multiplier++;
		}
	}
}

// 5 x 1 = 5
// 5 x 2 = 10
// 5 x 3 = 15
// 5 x 4 = 20
// 5 x 5 = 25
// 5 x 6 = 30
// 5 x 7 = 35
// 5 x 8 = 40
// 5 x 9 = 45
// 5 x 10 = 50

Although, while loops are not as common as for loops in the real world, learning about them is worth it.

Do-While Loop

The final type of loop you'll learn about is the do-while loop. It kind of reverses the order of the regular while loop – so instead of checking the condition before executing the loop body, you execute the loop body first and then check the condition.

do-while-loop-generic

The multiplication table code implemented using a do-while loop will be as follows:

public class Main {

	public static void main(String[] args) {
		int number = 5;
		int multiplier = 1;
		
		do {
			System.out.println(String.format("%d x %d = %d", number, multiplier, number*multiplier));
			
			multiplier++;
		} while (multiplier <= 10);
	}
}

// 5 x 1 = 5
// 5 x 2 = 10
// 5 x 3 = 15
// 5 x 4 = 20
// 5 x 5 = 25
// 5 x 6 = 30
// 5 x 7 = 35
// 5 x 8 = 40
// 5 x 9 = 45
// 5 x 10 = 50

Do-while loops are very useful when you need to perform some operation until the user gives a specific input. Such as, show a menu until user presses the "x" key.

How to Work with Array Lists in Java

import java.util.ArrayList;

public class Main {
    public static void main (String[] args) {
        ArrayList<Integer> oddNumbers = new ArrayList<>();

        oddNumbers.add(1);
        oddNumbers.add(3);
        oddNumbers.add(5);
        oddNumbers.add(7);
        oddNumbers.add(9);

        System.out.println(oddNumbers.toString()); // [1, 3, 5, 7, 9]
    }
}

How to Add or Remove Multiple Elements

import java.util.ArrayList;

public class Main {
    public static void main (String[] args) {
        ArrayList<Integer> oddNumbers = new ArrayList<>();

        oddNumbers.add(1);
        oddNumbers.add(3);
        oddNumbers.add(5);

        ArrayList<Integer> moreOddNumbers = new ArrayList<>();

        moreOddNumbers.add(7);
        moreOddNumbers.add(9);
        moreOddNumbers.add(11);

        oddNumbers.addAll(moreOddNumbers); // [1, 3, 5, 7, 9, 11]

        System.out.println(oddNumbers.toString());

        oddNumbers.removeAll(moreOddNumbers);

        System.out.println(oddNumbers.toString()); // [1, 3, 5]
    }
}

How to Remove Elements Based on a Condition

The removeIf() method can remove elements from an array list if they meet a certain condition:

import java.util.ArrayList;

public class Main {
    public static void main (String[] args) {
        ArrayList<Integer> numbers = new ArrayList<>();

        for (int i = 0; i <= 10; i++) {
            numbers.add(i);
        }

        System.out.println(numbers.toString()); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

        numbers.removeIf(number -> number % 2 == 1);

        System.out.println(numbers.toString()); // [0, 2, 4, 6, 8, 10]
    }
}

The method takes a lambda expression as a parameter. Lambda expressions are like unnamed methods. They can receive parameters and work with them.

Here, the removeIf() method will loop over the array list and pass each element to the lambda expression as the value of the number variable.

Then the lambda expression will check whether the given number is divisible by 2 or not and return true or false based on that.

If the lambda expression returns true, the removeIf() method will keep the value. Otherwise the value will be deleted.

How to Clone and Compare Array Lists

import java.util.ArrayList;

public class Main {
    public static void main (String[] args) {
        ArrayList<Integer> numbers = new ArrayList<>();

        for (int i = 0; i <= 10; i++) {
            numbers.add(i);
        }

        ArrayList<Integer> numbersCloned = (ArrayList<Integer>)numbers.clone();

        System.out.println(numbersCloned.equals(numbers)); // true
    }
}

How to Check if an Element Is Present or the Array List Is Empty

You can use the contains() method to check if an array list contains a given element or not:

import java.util.ArrayList;

public class Main {
    public static void main (String[] args) {
        ArrayList<Integer> oddNumbers = new ArrayList<>();

        oddNumbers.add(1);
        oddNumbers.add(3);
        oddNumbers.add(5);
        oddNumbers.add(7);
        oddNumbers.add(9);

        System.out.println(oddNumbers.isEmpty()); // false
        System.out.println(oddNumbers.contains(5)); // true
    }
}

If you want to check if an array list is empty or not, just call the isEmpty() method on it and you'll get a boolean in return.

How to Sort an Array List

You can sort an array list in different orders using the sort() method:

import java.util.ArrayList;
import java.util.Comparator;

public class Main {
    public static void main (String[] args) {
        ArrayList<Integer> oddNumbers = new ArrayList<>();

        oddNumbers.add(5);
        oddNumbers.add(7);
        oddNumbers.add(1);
        oddNumbers.add(9);
        oddNumbers.add(3);

        System.out.println(oddNumbers.toString()); [5, 7, 1, 9, 3]

        oddNumbers.sort(Comparator.naturalOrder());

        System.out.println(oddNumbers.toString()); [1, 3, 5, 7, 9]
    }
}

The sort() method takes a comparator as its parameter. A comparator imposes the order of sorting on the array list.

You can sort the array list in reverse order just by changing the passed comparator:

import java.util.ArrayList;
import java.util.Comparator;

public class Main {
    public static void main (String[] args) {
        ArrayList<Integer> oddNumbers = new ArrayList<>();

        oddNumbers.add(5);
        oddNumbers.add(7);
        oddNumbers.add(1);
        oddNumbers.add(9);
        oddNumbers.add(3);

        System.out.println(oddNumbers.toString()); // [5, 7, 1, 9, 3]

        oddNumbers.sort(Comparator.reverseOrder());

        System.out.println(oddNumbers.toString()); // [9, 7, 5, 3, 1]
    }
}

Comparators have other usages as well but those are out of the scope of this book.

How to Keep Common Elements From Two Array Lists

Think of a scenario where you have two array lists. Now you'll have to find out which elements are present in both array lists and remove the rest from the first array list.

import java.util.ArrayList;

public class Main {
    public static void main (String[] args) {
        ArrayList<Integer> oddNumbers = new ArrayList<>();

        oddNumbers.add(1);
        oddNumbers.add(3);
        oddNumbers.add(5);

        ArrayList<Integer> moreOddNumbers = new ArrayList<Integer>();

        moreOddNumbers.add(5);
        moreOddNumbers.add(7);
        moreOddNumbers.add(9);

        oddNumbers.retainAll(moreOddNumbers);

        System.out.println(oddNumbers.toString()); // [5]
    }
}

The retainAll() method can get rid of the uncommon elements from the first array list for you. You'll need to call the method on the array list you want to operate on and pass the second array list as a parameter.

How to Perform an Action on All Elements of an Array List

You've already learned about looping in previous sections. Well, array lists have a forEach() method of their own that takes a lambda expression as parameter and can perform an action on all the elements of the array list.

import java.util.ArrayList;

public class Main {
    public static void main (String[] args) {
        ArrayList<Integer> oddNumbers = new ArrayList<>();

        oddNumbers.add(1);
        oddNumbers.add(3);
        oddNumbers.add(5);
        oddNumbers.add(7);
        oddNumbers.add(9);

        oddNumbers.forEach(number -> {
            number = number * 2;
            System.out.printf("%d ", number); // 2 6 10 14 18
        });

        System.out.println(oddNumbers.toString()); // [1, 3, 5, 7, 9]
    }
}

Last time, the lambda expression you saw was a single line – but they can be bigger. Here, the forEach() method will loop over the array list and pass each element to the lambda expression as the value of the number variable.

The lambda expression will then multiply the supplied value by 2 and print it out on the terminal. However, the original array list will be unchanged.

How to Work With Hash Maps in Java

Hash maps in Java can store elements in key-value pairs. This collection type is comparable to dictionaries in Python and objects in JavaScript.

import java.util.HashMap;

public class Main {
    public static void main (String[] args) {
        HashMap<String, Double> prices = new HashMap<>();

        prices.put("apple", 2.0);
        prices.put("orange", 1.8);
        prices.put("guava", 1.5);
        prices.put("berry", 2.5);
        prices.put("banana", 1.0);

        System.out.printf(prices.toString()); // {orange=1.8, banana=1.0, apple=2.0, berry=2.5, guava=1.5}
    }
}

To create hash maps, you'll first have to import the java.util.HashMap class at the top of your source file.

Then you start by writing HashMap and then inside a pair of less than-greater than signs, you'll write the data type for the key and the value.

Here, the keys will be strings and values will be doubles. After that the assignment operator, followed by new HashMap<>().

You can use the put() method to put a record in the hash map. The method takes the key as the first parameter and its corresponding value as the second parameter.

There is also the putIfAbsent() method that adds the given element only if it already doesn't exist in the hash map.

import java.util.HashMap;

public class Main {
    public static void main (String[] args) {
        HashMap<String, Double> prices = new HashMap<>();

        prices.put("apple", 2.0);
        prices.put("orange", 1.8);
        prices.put("guava", 1.5);
        prices.put("berry", 2.5);
        prices.put("banana", 1.0);

        prices.putIfAbsent("guava", 2.9);

        System.out.println(prices.toString()); // {orange=1.8, banana=1.0, apple=2.0, berry=2.5, guava=1.5}
    }
}

You can use the get() method to bring out a value from the hash map. The method takes the key as its parameter.

import java.util.HashMap;

public class Main {
    public static void main (String[] args) {
        HashMap<String, Double> prices = new HashMap<>();

        prices.put("apple", 2.0);
        prices.put("orange", 1.8);
        prices.put("guava", 1.5);
        prices.put("berry", 2.5);
        prices.put("banana", 1.0);

        System.out.println(prices.get("banana")); // 1.000000
    }
}

There is another variation of the this method. The getOrDefault() method works like get() but if the given key is not found, it'll return a specified default value.

import java.util.HashMap;

public class Main {
    public static void main (String[] args) {
        HashMap<String, Double> prices = new HashMap<>();

        prices.put("apple", 2.0);
        prices.put("orange", 1.8);
        prices.put("guava", 1.5);
        prices.put("berry", 2.5);
        prices.put("banana", 1.0);

        System.out.println(prices.getOrDefault("jackfruit", 0.0)); // 0.0
    }
}

The default value has to match the type of the values in the hash map. You can update a value in a hash map using the replace() method:

import java.util.HashMap;

public class Main {
    public static void main (String[] args) {
        HashMap<String, Double> prices = new HashMap<>();

        prices.put("apple", 2.0);
        prices.put("orange", 1.8);
        prices.put("guava", 1.5);
        prices.put("berry", 2.5);
        prices.put("banana", 1.0);

        prices.replace("berry", 2.8);

        System.out.printf(prices.toString()); // {orange=1.8, banana=1.0, apple=2.0, berry=2.8, guava=1.5}
    }
}

For removing elements from a hash map, you can use the aptly named remove() method:

import java.util.HashMap;

public class Main {
    public static void main (String[] args) {
        HashMap<String, Double> prices = new HashMap<>();

        prices.put("apple", 2.0);
        prices.put("orange", 1.8);
        prices.put("guava", 1.5);
        prices.put("berry", 2.5);
        prices.put("banana", 1.0);

        prices.remove("guava");

        System.out.printf(prices.toString()); // {orange=1.8, banana=1.0, apple=2.0, berry=2.5}
    }
}

If you ever need to know how many entries are there in a hash map, you can do so by using the size() method:

import java.util.HashMap;

public class Main {
    public static void main (String[] args) {
        HashMap<String, Double> prices = new HashMap<>();

        prices.put("apple", 2.0);
        prices.put("orange", 1.8);
        prices.put("guava", 1.5);
        prices.put("berry", 2.5);
        prices.put("banana", 1.0);

        System.out.println(prices.size()); // 5
    }
}

Finally if you want to clear a hash map in Java, you can do so by using the clear() method.

import java.util.HashMap;

public class Main {
    public static void main (String[] args) {
        HashMap<String, Double> prices = new HashMap<>();

        prices.put("apple", 2.0);
        prices.put("orange", 1.8);
        prices.put("guava", 1.5);
        prices.put("berry", 2.5);
        prices.put("banana", 1.0);

        prices.clear();

        System.out.println(prices.toString()); // {}
    }
}

Just like in the array lists, the method doesn't take any argument or return any value.

How to Put in or Replace Multiple Elements in a Hash Map

If you want to put multiple elements into a hash map in a single go, you can do so by using the putAll() method:

import java.util.HashMap;

public class Main {
    public static void main (String[] args) {
        HashMap<String, Double> prices = new HashMap<>();

        prices.put("apple", 2.0);
        prices.put("orange", 1.8);
        prices.put("guava", 1.5);
        prices.put("berry", 2.5);
        prices.put("banana", 1.0);

        HashMap<String, Double> morePrices = new HashMap<>();

        prices.put("jackfruit", 2.9);
        prices.put("pineapple", 1.1);
        prices.put("tomato", 0.8);

        prices.putAll(morePrices);

        System.out.println(prices.toString()); // {orange=1.8, banana=1.0, apple=2.0, berry=2.5, pineapple=1.1, tomato=0.8, guava=1.5, jackfruit=2.9}
    }
}

The method takes another hash map as its parameter and adds its elements to the one the method has been called upon.

You can also use the replaceAll() method to update multiple values in a hash map.

import java.util.HashMap;

public class Main {
    public static void main (String[] args) {
        HashMap<String, Double> prices = new HashMap<>();

        prices.put("apple", 2.0);
        prices.put("orange", 1.8);
        prices.put("guava", 1.5);
        prices.put("berry", 2.5);
        prices.put("banana", 1.0);

        prices.replaceAll((fruit, price) -> price * 2);

        System.out.println(prices.toString()); // {orange=3.6, banana=2.0, apple=4.0, berry=5.0, guava=3.0}
    }
}

The replace all method iterates over the hashmap and passes each key value pair to the lambda expression.

The first parameter to the lambda expression is the key and the second one is the value. Inside the lambda expression, you perform your actions.

How to Check if a Hash Map Contains an Item or if It’s Empty

You can use the methods containsKey() and containsValue() for checking if a hash map contains a value or not.

import java.util.HashMap;

public class Main {
    public static void main (String[] args) {
        HashMap<String, Double> prices = new HashMap<>();

        prices.put("apple", 2.0);
        prices.put("orange", 1.8);
        prices.put("guava", 1.5);
        prices.put("berry", 2.5);
        prices.put("banana", 1.0);

        System.out.println(prices.containsKey("banana")); // true
        System.out.println(prices.containsValue(2.5)); // true
    }
}

Difference between the two methods is that the containsKey() method checks if the given key exists or not and the containsValue() method checks if the given value exists or not.

And if you want to check if a hash map is empty or not, you can do so by using the isEmpty() method:

import java.util.HashMap;

public class Main {
    public static void main (String[] args) {
        HashMap<String, Double> prices = new HashMap<>();

        prices.put("apple", 2.0);
        prices.put("orange", 1.8);
        prices.put("guava", 1.5);
        prices.put("berry", 2.5);
        prices.put("banana", 1.0);

        System.out.println(prices.isEmpty()); // false
    }
}

Since the method returns a boolean value, you can use it within if-else statements.

How to Perform an Action on All Elements of a Hash Map

Like the array lists, hash maps also have their own forEach() method that you can use to loop over the hash map and repeat a certain action over each entry.

import java.util.HashMap;

public class Main {
    public static void main (String[] args) {
        HashMap<String, Double> prices = new HashMap<>();

        prices.put("apple", 2.0);
        prices.put("orange", 1.8);
        prices.put("guava", 1.5);
        prices.put("berry", 2.5);
        prices.put("banana", 1.0);

        System.out.println("prices after discounts");

        prices.forEach((fruit, price) -> {
            System.out.println(fruit + " - " + (price - 0.5));
        });
    }
}

// prices after discounts
// orange - 1.3
// banana - 0.5
// apple - 1.5
// berry - 2.0
// guava - 1.0

The method loops over each entry and passes the key and value to the lambda expression. Inside the lambda expression body, you can do whatever you want.

Classes and Objects in Java

import java.time.LocalDate;

public class Main {
    public static void main (String[] args) {
        User user = new User();

        user.name = "Farhan";
        user.birthDay = LocalDate.parse("1996-07-15");

        System.out.printf("%s was born on %s.", user.name, user.birthDay.toString()); // Farhan was born on 15th July 1996.
    }
}

What is a Method?

import java.time.LocalDate;

public class Main {
    public static void main (String[] args) {
        User user = new User();

        user.name = "Farhan";
        user.birthDay = LocalDate.parse( "1996-07-15");

        System.out.printf("%s is %s years old.", user.name, user.age()); // Farhan a 26 years old.
    }
}

What is Method Overloading?

In Java, multiple methods can have the same name if their parameters are different. This is called method overloading.

One example can be the borrow() method on the User class. Right now, it accepts a single book as its parameter. Let's make an overloaded version which can accept an array of books instead.

import java.time.LocalDate;
import java.time.Period;
import java.util.ArrayList;
import java.util.Arrays;

public class User {
    private String name;
    private LocalDate birthDay;
    private ArrayList<Book> borrowedBooks = new ArrayList<Book>();

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBorrowedBooks() {
        return this.borrowedBooks.toString();
    }

    User (String name, String birthDay) {
        this.name = name;
        this.birthDay = LocalDate.parse(birthDay);
    }

    int age() {
        return Period.between(this.birthDay, LocalDate.now()).getYears();
    }

    void borrow(Book book) {
        borrowedBooks.add(book);
    }

    void borrow(Book[] books) {
        borrowedBooks.addAll(Arrays.asList(books));
    }
}

The return type and name of the new method is identical to the previous one, but this one accepts an array of Book objects instead of a single object.

Let's update the Main.java file to make use of this overloaded method.

public class Main {
    public static void main (String[] args) {
        User user = new User("Farhan", "1996-07-15");

        Book book1 = new Book("Carmilla", new String[]{"Sheridan Le Fanu"});
        Book book2 = new Book("Frankenstein", new String[]{"Mary Shelley"});
        Book book3 = new Book("Dracula", new String[]{"Bram Stoker"});

        user.borrow(new Book[]{book1, book2});

        user.borrow(book3);

        System.out.printf("%s has borrowed these books: %s", user.getName(), user.getBorrowedBooks());
    }
}

As you can see, the borrow() method now accepts an array of books or a single book object without any issue.

What are Constructors in Java?

import java.util.ArrayList;
import java.util.Arrays;

public class Book {
    public String title;
    public ArrayList<String> authors = new ArrayList<String>();

    Book(String title, String[] authors) {
        this.title = title;
        this.authors = new ArrayList<String>(Arrays.asList(authors));
    }

    public String toString() {
        return String.format("%s by %s", this.title, this.authors.toString());
    }
}

What Are Access Modifiers in Java?

import java.util.ArrayList;
import java.util.Arrays;

public class Book {
    private String title;
    private ArrayList<String> authors = new ArrayList<String>();

    Book(String title, String[] authors) {
        this.title = title;
        this.authors = new ArrayList<String>(Arrays.asList(authors));
    }

    public String toString() {
        return String.format("%s by %s", this.title, this.authors.toString());
    }
}

What Are the Getter and Setter Methods in Java?

Getters and setters are public methods in classes used to read and write private properties.

import java.time.LocalDate;
import java.time.Period;
import java.util.ArrayList;

public class User {
    private String name;
    private LocalDate birthDay;
    private ArrayList<Book> borrowedBooks = new ArrayList<Book>();

    public String getName() {
        return this.name;
    }

    public String getBirthDay() {
        return this.birthDay.toString();
    }

    public String getBorrowedBooks() {
        return this.borrowedBooks.toString();
    }

    User (String name, String birthDay) {
        this.name = name;
        this.birthDay = LocalDate.parse(birthDay);
    }

    int age() {
        return Period.between(this.birthDay, LocalDate.now()).getYears();
    }

    void borrow(Book book) {
        borrowedBooks.add(book);
    }
}

The getName() and getBorrowedBooks() are responsible for returning the value of the name and borrowedBooks variables.

You never actually access the birthday variable out of the age() method, so a getter is not necessary.

Since the type of the borrowedBooks variable is not a concern of the Main class, the getter makes sure to return the value in the proper format.

Now update the code in the Main.java file to make use of these methods:

public class Main {
    public static void main (String[] args) {
        User user = new User("Farhan", "1996-07-15");

        Book book = new Book("Carmilla", new String[]{"Sheridan Le Fanu"});

        user.borrow(book);

        System.out.printf("%s has borrowed these books: %s", user.getName(), user.getBorrowedBooks());
    }
}

Excellent. It has become even cleaner and easier to read. Like getters, there are setters for writing values to the private properties.

For example, you may want to allow the user to change their name or birthday. The borrow() method already works as a setter for the borrowedBooks array list.

import java.time.LocalDate;
import java.time.Period;
import java.util.ArrayList;

public class User {
    private String name;
    private LocalDate birthDay;
    private ArrayList<Book> borrowedBooks = new ArrayList<Book>();

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    public void setBirthDay(String birthDay) {
        this.birthDay = LocalDate.parse(birthDay);
    }

    public String getBorrowedBooks() {
        return this.borrowedBooks.toString();
    }

    User (String name, String birthDay) {
        this.name = name;
        this.birthDay = LocalDate.parse(birthDay);
    }

    int age() {
        return Period.between(this.birthDay, LocalDate.now()).getYears();
    }

    void borrow(Book book) {
        borrowedBooks.add(book);
    }
}

Now you can call the setName() method with whatever name you want to set to the user. Similarly, the setBirthDay() method can set the birthday.

You can implement some getters and setters for the Book class as well.

import java.util.ArrayList;
import java.util.Arrays;

public class Book {
    private String title;
    private ArrayList<String> authors = new ArrayList<String>();

    public String getTitle() {
        return this.title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthors() {
        return this.authors.toString();
    }

    public void setTitle(String[] authors) {
        this.authors = new ArrayList<String>(Arrays.asList(authors));
    }
    
    Book(String title, String[] authors) {
        this.title = title;
        this.authors = new ArrayList<String>(Arrays.asList(authors));
    }

    public String toString() {
        return String.format("%s by %s", this.title, this.authors.toString());
    }
}

Now you can not access those properties directly. Instead you'll have to use one of the getters or setters.

What is Inheritance in Java?

Inheritance is another big feature of object oriented programming. Imagine you have three kinds of books. The regular ones, e-books, and audio books.

Although they have similarities such as title and author, they also have some differences. For example, the regular books and e-books have page count whereas audio books have run time. The e-books also have format such as PDF or EPUB.

So using the same class for all three of them is not an option. That doesn't mean you'll have to create three separate classes with minor differences, though. You can just create separate classes for e-books and audio books and make them inherit the properties and methods from the Book class.

Let's begin by adding the page count in the Book class:

import java.util.ArrayList;
import java.util.Arrays;

public class Book {
    private String title;
    private int pageCount;
    private ArrayList<String> authors = new ArrayList<String>();

    Book(String title, int pageCount, String[] authors) {
        this.title = title;
        this.pageCount = pageCount;
        this.authors = new ArrayList<String>(Arrays.asList(authors));
    }

    public String length() {
        return String.format("%s is %d pages long.", this.title, this.pageCount);
    }

    public String toString() {
        return String.format("%s by %s", this.title, this.authors.toString());
    }
}

Since you're not going to use getters and setters in these examples, cleaning up seemed like a good idea. The length() method returns the length of the book as a string.

Now create a new Java class named AudioBook and put the following code in it:

public class AudioBook extends Book{
    private int runTime;

    AudioBook(String title, String[] authors, int runTime) {
        super(title, 0, authors);

        this.runTime = runTime;
    }
}

The extends keyword lets the compiler know that this class is a subclass of the Book class. This means that this class inherits all the properties and methods from the parent class.

Inside the AudioBook constructor method, you set the run time for the audio book which is fine – but you'll also have to manually call the constructor of the parent class.

The super keyword in Java refers to the parent class, so super(title, 0, authors) essentially calls the parent constructor method with the necessary parameters.

Since the audio books don't have any pages, setting the page count to zero can be an easy solution.

Or you can create an overloaded version of the Book constructor method that doesn't require the page count.

Next, create another Java class named Ebook with the following code:

public class Ebook extends Book{
    private String format;

    Ebook(String title, int pageCount, String[] authors, String format) {
        super(title, pageCount, authors);

        this.format = format;
    }
}

This class is largely identical to the Book class except the fact that it has a format property.

public class Main {
    public static void main (String[] args) {
        Book book = new Book("Carmilla", 200, new String[]{"Sheridan Le Fanu"});
        Ebook ebook = new Ebook("Frankenstein", 220, new String[]{"Mary Shelley"}, "EPUB");
        AudioBook audioBook = new AudioBook("Dracula", new String[]{"Bram Stoker"}, 160);

        System.out.println(book.toString()); // Carmilla by [Sheridan Le Fanu]
        System.out.println(ebook.toString()); // Frankenstein by [Mary Shelley]
        System.out.println(audioBook.toString()); // Dracula by [Bram Stoker]
    }
}

So far everything is working fine. But do you remember the length() method you wrote inside the Book class? It'll work for the regular books but will break in the e-books.

That's because the page count property is marked as private and no other class except Book will be able to access it. The title is also a private property.

Open the Book.java file and mark the title and pageCount properties as protected.

import java.util.ArrayList;
import java.util.Arrays;

public class Book {
    protected String title;
    protected int pageCount;
    private ArrayList<String> authors = new ArrayList<String>();

    Book(String title, int pageCount, String[] authors) {
        this.title = title;
        this.pageCount = pageCount;
        this.authors = new ArrayList<String>(Arrays.asList(authors));
    }

    public String length() {
        return String.format("%s is %d pages long.", this.title, this.pageCount);
    }

    public String toString() {
        return String.format("%s by %s", this.title, this.authors.toString());
    }
}

This'll make them accessible from the subclasses. The audio books have another problem with the length() method.

Audio books don't have a page count. They have run times and this difference will break the length method.

One way to solve this problem is by overriding the length() method.

How to Override a Method in Java

As the name suggests, overriding means cancelling the effect of a method by replacing it with something else.

public class AudioBook extends Book{
    private int runTime;

    AudioBook(String title, String[] authors, int runTime) {
        super(title, 0, authors);

        this.runTime = runTime;
    }

    @Override
    public String length() {
        return String.format("%s is %d minutes long.", this.title, this.runTime);
    }
}

You override a method from the parent class by rewriting the method in the subclass. The @Override keyword is an annotation. Annotations in Java are metadata.

It's not mandatory to annotate the method like this. But if you do, the compiler will know that the annotated method overrides a parent method and will make sure you're following all the rules of overriding.

For example, if you make a mistake in the method name and it doesn't match any method from the parent, the compiler will let you know that the method is not overriding anything.

public class Main {
    public static void main (String[] args) {
        AudioBook audioBook = new AudioBook("Dracula", new String[]{"Bram Stoker"}, 160);

        System.out.println(audioBook.length()); // Dracula is 160 minutes long.
    }
}