An introductory guide into Java
- Section 1 - Java Programme Structure
- Section 2 - Variables
- Section 3 - Operators
- Section 4 - Control Flow
- Section 5 - Methods
- Section 6 - Strings
- Section 7 - OOP
- Section 8 - Arrays
All Java files are compiled into class files so that they can be run. The programme name is declared following the class keyword and all of the code that defines the class is contained within curly braces. The class names should always begin with an uppercase letter as shown below:
class Hello
{
}
Within the curly the main method should be defined. This is the starting point for all Java programmes. The keywords will be defined later in the guide. But the method is defined below:
class Hello
{
public static void main (String [] args){ }
}
To print out a text output, the "System.out.println()" function is used:
class Hello
{
public static void main (String [] args){
System.out.println("Hello World")
}
}
When programming in any language it is good practice to add comments to explain each section. This makes the code more easily underrstood by other and yourself when revisiting code.
Single Line comments can be added using the following syntax.
class Main
{
public static void main (String [] args){
// This is a comment
}
}
Single Line comments can be added using the following syntax.
class Main
{
public static void main (String [] args){
/* This is a
Multi Line
Comment
*/
}
}
A variable is a container that stores a value. Variables are created by specifying the type of data being stored and then a name for that variable.
Data Type | Description | Example |
---|---|---|
char | A single Unicode character. Written between single quotes | 'a' or '/u002B' |
String | Any number of Unicode characters | "My String" |
byte | Integer number ranging from -128 to 127 | 12 |
short | Integer number ranging from -32,768 to 32,767 | 2000 or 2_000 |
int | An integer number, ranging from -2.14 billion to 2.14 billion | 20000 or 20_000 |
long | An integer number, exceeding +/- 2.14 billion | 20000 or 20_000 |
float | A floating-point number, with a decimal point. Must have an 'f' suffix to ensure it's treated as a float | 3.141f |
double | Extremely long floating point number. Should have 'd' suffix to ensure readability | 3.14159265d |
boolean | A logical value if either true or false | true or false |
Variables can be defined as shown below:
class Main
{
public static void main (String [] args){
// Underscores can be used for large numbers for increased readability
int myValue = 10_000
System.out.println(myValue)
// Prints 10000
}
}
The "final" keyword is a modifier that can be used to prevent any changes to the values initially assigned to them. This is useful when storing a fixed value in a program to avoid it being accidentally altered. The convention is to name constants in all uppercase characters to distinguish them from regular variables. See below:
class Main
{
public static void main (String [] args){
final double PI = 3.14159265359d;
final double EULER = 2.71828;
}
}
To convert the result of an expression from one variable type to another, it must be cast. This is done by putting the data type you wish to cast to in brackets beside the expression.
class Main
{
public static void main (String [] args){
byte myMinByteValue = Byte.MIN_VALUE;
/*
Expression below defaults to an int. Must be cast to byte to work correctly.
byte myNewByteValue = (myMinByteValue / 2); Required Type byte, provided int.
*/
byte myNewByteValue = (byte) (myMinByteValue / 2);
System.out.println(myNewByteValue)
// Prints -64
}
}
Examples on the mentioned data types can be found below:
Operators are used to perform operations on variables and their are four types:
- Arithmetic
- Assignment
- Comparison
- Logical
Used to create expressions in Java programmes and perform common mathematical operations
Operator | Name | Description |
---|---|---|
+ | Addition | Adds two values. Also used to concatenate String values |
- | Subtraction | Subtracts two values |
* | Multiplication | Multiplies two values |
/ | Division | Divides one value by another |
% | Modulus | Returns the remainder of a division operation |
++ | Increment | Increases the value of a variable by 1 |
-- | Decrement | Decreases the value of a variable by 1 |
Used to assign the result of an expression. There are also shorthand versions of longer equivalent expressions:
Operator | Example | Equivalent |
---|---|---|
= | a = b | a = b |
+= | a += b | a = a + b |
-= | a -= b | a = a - b |
*= | a *= b | a = a * b |
/= | a /= b | a = a / b |
%= | a %= b | a = a % b |
Comparisson operators are used to compare two values in an expression and return a boolean value of true or false.
Operator | Comparison |
---|---|
== | Equality |
!= | Inequality |
> | Greater than |
>= | Greater than, or equal to |
< | Less than |
<= | Less than, or equal to |
Logical operators are used to combine multiple expressions that each return a boolean value, into a complex expression that returns a single boolean value.
Operator | Operation |
---|---|
&& | Logical AND |
! | Logical NOT |
The Tenary Operator evaluates an expression for a true or false value and returns one of the given operands depending on the result. It's syntax looks like this:
( boolean-expression ) ? if-true-return-this : if-false-return-this;
An example can be seen below:
class Main
{
public static void main (String [] args){
int age = 21;
boolean isAdult = (age > 18) ? true : false;
// isAdult resolves to true
}
}
The 'if' statement performs a conditional test to evaluate an expression for a boolean value. The statements following the expression will only be executed when the expression is true. The if statement syntax cam be seen below:
if (test-expression) code-to-be-executed-when-true:
An Example can be seen below:
class Main
{
public static void main (String [] args){
if (2 > 1){
System.out.println("Five is greater than one")
}
}
}
The 'else' statement is used in conjunction with the "if" keyword to create "if else" statements. This creates alternative branches for a program to pusue according to the evaluation of the tested expression. This enables an alternative statement to be executed when the first statement fails:
class Main
{
public static void main (String [] args){
int hours = 11;
if (hours <= 12){
System.print.ln("It is morning")
}
else {
System.print.ln("It is no longer morning")
}
}
}
Else If Statements The 'else if' operator used to specify a new condition to test, if the first condition is false.
class Main
{
public static void main (String [] args){
int hours = 11;
if (hours <= 12){
System.print.ln("Good morning")
} else if (hours < 18){
System.print.ln("Good afternoon")
} else {
System.print.ln("Good evening")
}
}
}
A 'switch' statement is used to specify many alternative blocks of code to be executed. It evaluates a statement once and compares the output of the expression against each 'case'. If the output matches a case then its corresponding code block will be run. An example can be seen below:
class Main
{
public static void main (String [] args){
int day = 2;
switch (day) {
case 1:
System.out.println("Monday");
break; // used to signify end of case
case 2:
System.out.println("Tuesday"); // print Tuesday as the day number is 2
break;
case 3:
System.out.println("Wednesday");
break;
case 4:
System.out.println("Thursday");
break;
case 5:
System.out.println("Friday");
break;
case 6:
System.out.println("Saturday");
break;
case 7:
System.out.println("Sunday");
break;
default: // triggers when the input doesn't match any case
System.out.println("Sorry that's not a valid input");
}
}
}
A more detailed example on operators & conditional statements can be found here
A loop is a block of code that repeatedly executes the statement it contains until a tested condition is met.
The most frequently used loop structure is the "for" loop which has the syntax below:
for ( initialiser; test-expression; updater ) { statements-to-be-executed-on-each-iteration; }
The for loop must contain three parameters to control the loop
- Initialiser - assigns an initial value to a counter variable, which will track the number of iterations.
- Test expression - evaluated at the start of each iteration. When the evaluation returns true, the iteration proceeds. Else the loop is terminated
- Updater - changes the current value of the counter variable, started by the initialiser. Typically this will use i++ fir counting upm or i-- for counting down.
class Main
{
public static void main (String [] args){
for (int i = 1; i < 11; i++){
System.out.println(i);
}
// Prints out 1 - 10
}
}
An alternative to the for loop is the "while" loop, which has the syntax below:
while( test-expression ) { statements-to-be-executed-on-each-iteration; }
Unlike the "for" loop the while loop does not contain an initialiser or updater. This means the test expression must evaluate a value that gets changed in the loop statements as the loop proceeds. Otherwise an infinite loop will be created. See below:
public class Main {
public static void main(String[] args) {
int i = 1;
while(i < 11){
System.out.println(i);
i++;
}
}
}
A variation of the "while" loop is the "do while" loop. This is created with the following syntax:
do { statements-to-be-executed-on-each-iteration; } while (test-expression);
Like the "for" and "while" loops, a "do while" loop repeatedly executes the statemetns until the test-expression returns false. However, unlike the "while" loop. The test statement is only evaluatied at the end of each iteration. Therefore the loop is always executed at least once.
public class Main {
public static void main(String[] args) {
int count = 1;
do {
System.out.println("Value is :" + count);
count++;
} while (count < 11);
// Prints out 1 - 10. Always prints out initial value.
}
}
The "break" keyword can be used to prematurely terminate a loop once a specified condition is met. When a loop in a nested loop is broken out of it proceeds to the next iteration of its outer loop.
public class Main {
public static void main(String[] args) {
int count = 0;
while (count < 10){
System.out.println(count);
count++;
if (count == 4){
System.out.println("Breaking out of loop");
break;
}
}
}
}
The "continue" keyword can be used to skip a single iteration of a loop when a specified condition is met.
public class Main {
public static void main(String[] args) {
int count = 0;
while (count < 11){
count++;
if (count % 2 != 0) {
continue;
}
System.out.println(count);
// Prints even numbers
}
}
}
Programmes are typically split into seperate methods to create modules of code that each perform set tasks and can be called repeatedly throughout the programme as required.
After the main method, more methods can be declared inside the curly brackets that define the class definition. Each new method must be given a new name. The syntax is shown below:
public static void secondMethod ( method-arguments ){ code-to-be-executed }
When defining a method you may wish for it to return a value. This can be done with the third keyword in the method declaration. "void" means the function does not return anything. However this can be substituted with any data type. For instance the method "calculateScore" below returns an integer.
public class Main {
public static void main(String[] args) {
boolean gameOver = true;
int score = 10, levelCompleted = 2, bonus = 100;
int highScore = calculateScore(gameOver, score, levelCompleted, bonus);
}
public static int calculateScore(boolean gameOver, int score, int levelCompleted, int bonus){
if (gameOver){
int finalScore = score +(levelCompleted * bonus);
return finalScore;
}
return -1;
}
A class may contain multiple methods of the same name provided they have different a different number of arguments, or arguments of different data types. This is known as overloading. An example can be seen below:
public class Main {
public static void main(String[] args) {
print(7);
print(7, 3);
print("Hello World");
}
public static void print(int value){
System.out.println("Printing..." + value);
}
public static void print(int value1, int value2){
System.out.println("Printing..." + value1 + " " + value2);
}
public static void print(String value){
System.out.println("Printing..." + value);
}
}
Strings are objects that have over 60 methods available. The string is a sequence of characters, meaning its characters are ordered and indexed. The index starts at zero. The table below shows the indices above each character for the String "Hello World"
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
Character | H | e | l | l | o | W | o | r | l | d |
Characters can be identified through there position. "H" is at index 0 and "W" is at index 6. The length of the string is 11, but its last index is 10.
String methods can be split into three categories:
- String inspection methods - Provide info on Strings such as length & isEmpty
- Method for comparing string values - Usually return a boolean values e.g. false if string are not equal
- String Manipulation Methods - Transform one string value into another
Method | Description |
---|---|
length | Returns the number of characters in the String |
charAt | Returns the character at the index passed as an argument |
indexOf/lastIndexOf | Returns an integer, representing the index in the sequence where the String of character passed can be located in the String |
isEmpty | Returns true if the length is zero |
isBlank | Returns true length is zero OR the string only contains whitespace characters (added in JDK 11) |
Examples can be seen below:
public class Main {
public static void main(String[] args) {
printInformation("Hello World");
printInformation("");
printInformation("\t \n");
String helloWorld = "Hello World";
System.out.printf("Index of r = %d %n", helloWorld.indexOf("r")); // prints 8
System.out.printf("Index of World = %d %n", helloWorld.indexOf("World")); // prints 6
System.out.printf("Index of l = %d %n", helloWorld.indexOf("l")); // prints 2
System.out.printf("Index of l = %d %n", helloWorld.lastIndexOf("l")); // prints 9
// Find second l
System.out.printf("Index of l = %d %n", helloWorld.indexOf("l",3)); // prints 3
System.out.printf("Index of l = %d %n", helloWorld.lastIndexOf("l",8)); // prints 3
}
public static void printInformation(String string) {
int length = string.length();
System.out.printf("Length = %d %n", length); // Prints length of String
if (string.isEmpty()) {
System.out.println("String is Empty"); //returns if length is zero and no blank space
return;
}
if (string.isBlank()){
System.out.println("String is Blank"); // returns if length is zero with blank space
return;
}
System.out.printf("First char = %c %n", string.charAt(0)); //%c character format specifier, prints first character
System.out.printf("Last char = %c %n", string.charAt(length - 1)); // prints last character
}
}
Method | Description |
---|---|
contentEquals | Returns a boolean if the String's value is equal to the value of the argument passed. This method allows for arguments other than String, for any type that is a character sequence. |
equals | Returns a boolean if the String's value is equal to the value of the argument passed |
equalsIgnoreCase | Returns a boolean if the String's value is equal (ignoring case), to the value of the argument passed. |
contains | Returns a boolean if the String contains the argument passed. |
endsWith/startsWith | These return a boolean, and are much like the "contains" method, but more specific to the placement of the String |
regionMatches | Returns a boolean, if defined sub-regions are matched. |
public class Main {
public static void main(String[] args) {
String helloWorld = "Hello World";
String helloWorldLower = helloWorld.toLowerCase(); //hello world
if (helloWorld.equals(helloWorldLower)) {
System.out.println("Values match exactly"); // Doesn't match exactly - no output
}
if (helloWorld.equalsIgnoreCase(helloWorldLower)) {
System.out.println("Values match ignoring case"); // Prints value
}
if (helloWorld.contentEquals("Hello World")) {
System.out.println("Values match exactly"); // Prints values match exactly
}
if (helloWorld.startsWith("Hello")) {
System.out.println("String starts with Hello"); // Prints
}
if (helloWorld.endsWith("World")) {
System.out.println("String ends with World"); // Prints
}
if (helloWorld.contains("World")) {
System.out.println("String contains World"); // Prints
}
}
}
There are two types of String manipulation methods, the first set, don't change the underlying meaning of the text value, but perform some kind of clean up
Method | Description |
---|---|
indent | This method was added in JDK 15, adds or removes spaces from the beginning of lines in multi-line text |
strip/ stripLeading / stripTrailing/ trim | The difference between the strip and trim method is that strip() supports a larger set of white space characters. It and the corresponding stripLeading and stripTrailing methods were added in JDK 11. |
toLowerCase/ toUpperCase | Returns a new String, either in a lower case or upper case |
The second set transform the String value and return a String with a different meaning:
Method | Description |
---|---|
concat | Similar to '+' operator for strings, it concatenates text to the String and returns a new String as the result. |
join | Allows multiple strings to be concatenated together in a single method, specifying a delimiter |
repeat | Returns the String repeated by the number of times specified in the argument. |
replace/ replaceAll/ replaceFirst | These methods replace characters or substrings in the String, returning a new String with replacements made. |
substring/ subSequence | These return a part of the String, its range defined by the start and end of the index specified |
public class StringMethods {
public static void main(String[] args) {
String birthDate = "12/11/1994";
int startingIndex = birthDate.indexOf("1994");
System.out.printf("Starting Index = %d %n", startingIndex); // Prints 6
System.out.printf("Birth Year = %s %n", birthDate.substring(startingIndex)); // Prints 1994
System.out.printf("Month = %s %n", birthDate.substring(3,5)); // Prints 11
String newDate = String.join("/", "25", "7", "1991");
System.out.printf("New Date = %s %n", newDate); // Prints 25/7/1991
newDate = "25"; // Can also be done with concat, but is very inefficiently
newDate = newDate.concat("/");
newDate = newDate.concat("7");
newDate = newDate.concat("/");
newDate = newDate.concat("1991");
System.out.printf("New Date = %s %n", newDate); // Prints 25/7/1991
// Concat using method chaining
newDate = "25".concat("/").concat("7").concat("/").concat("1991");
System.out.printf("New Date = %s %n", newDate); // Prints 25/7/1991
System.out.printf("New Date = %s %n",newDate.replace("/", "-")); // replaces '/' with '-'
System.out.printf("New Date = %s %n",newDate.replace("7", "07")); // replaces '7' with '07'
System.out.printf("New Date = %s %n",newDate.replaceFirst("/", "-")); // replaces first '/' with '-'
System.out.printf("New Date = %s %n",newDate.replaceAll("/", "---")); // replaces all '/' with '---'
System.out.println("ABC\n".repeat(3));
System.out.println("-".repeat(20));
}
}
Java provides a mutable class that lets us change its text value. The most efficient way to create regular string is to assign a literal to a String object.
There are four ways to create a new StringBuilder object, using the new keyword.
- Pass a String
- Pass no arguments
- Pass an integer value
- Pass another type of character sequence (like StringBuilder)
An example can be seen below:
public class Main {
public static void main(String[] args) {
String helloWorld = "Hello" + " World";
// StringBuilder helloWorldBuilder = "Hello" + "World"; Does not compile
StringBuilder helloWorldBuilder = new StringBuilder("Hello" + " World");
printInformation(helloWorld); //Prints Hello World
printInformation(helloWorldBuilder); //Prints Hello World
helloWorld.concat(" and Goodbye"); // returned value not reassigned to new variable
helloWorldBuilder.append(" and Goodbye"); // appended value added to original string
printInformation(helloWorld); //Prints Hello World
printInformation(helloWorldBuilder); //Prints Hello World and Goodbye
}
public static void printInformation(String string) {
System.out.printf("String = %s %n", string);
System.out.printf("Length = %d %n", string.length());
}
public static void printInformation(StringBuilder builder) {
System.out.printf("String = %s %n", builder);
System.out.printf("Length = %d %n", builder.length());
}
}
String methods create a new object in memory and return a reference to this new object. StringBuilder methods return a StringBuilder referenfce, but it's really a self reference.
The StringBuilder calss has many similar methods to Strings. But there are also methods unique to the StringBuilder class:
Method | Description |
---|---|
delete/ deleteCharAt | Deletes a substring using indices to specify a range, or delete a single character at an index |
insert | Inserts text at a specified position |
reverse | Reverses the order of the characters in the sequence |
setLength | Can be used to truncate the sequence, or include null sequences to "fill out" the sequence to that length |
public class Main {
public static void main(String[] args) {
StringBuilder builderPlus = new StringBuilder("Hello World");
builderPlus.append(" and Goodbye");
builderPlus.deleteCharAt(16). insert(16, "g");
System.out.println(builderPlus); // Hello World and goodbye
builderPlus.replace(16,17, "G");
System.out.println(builderPlus); // Hello World and Goodbye
builderPlus.reverse().setLength(7);
System.out.println(builderPlus); // Prints eybdooG
}
public static void printInformation(String string) {
System.out.printf("String = %s %n", string);
System.out.printf("Length = %d %n", string.length());
}
public static void printInformation(StringBuilder builder) {
System.out.printf("String = %s %n", builder);
System.out.printf("Length = %d %n", builder.length());
System.out.printf("Capacity = %d %n", builder.capacity());
}
}
System.out.printf can be used to format the output of the string. The example below uses the format specifier "%d" to insert variables into the printed line.
public class Main {
public static void main(String[] args) {
int age = 35;
System.out.printf("Your age is %d\n", age);
int yearOfBirth = 2023 - age;
System.out.printf("Age = %d, Birth year = %d", age, yearOfBirth);
}
}
At their most complex, format specifiers take the following form.
%[argument_index$][flags][width][.precision]conversion
They start with a percent sign, end with a conversion symbol, and have lots of options inbetween.
The conversion type "d" is used for decimal integer values. Some common types are shown below
Conversion | Argument Category | Description |
---|---|---|
'b', 'B' | general | If the argument (arg) is null, then the return is false. If arg is a boolean, then the result is the string returned by String.valueOf(arg). Otherwise the result is "true" |
'd', 'D' | floating point | The result is formatted as a decimal integer (short, int & long) |
'e', 'E' | floating point | The result is formatted as a decimal number in computerised scientific notation |
'f', 'F' | floating point | The result is formatted as a decimal number (floating point numbers, floats & doubles) |
'n, 'N' | line separator | The result is the platform-specific line separator (preferred) |
't', 'T' | date/time | Prefix for date and time conversion characters |
Using the example above, the "f" type can be used to output floating point numbers. The default precision is 6 d.p.
public class Main {
public static void main(String[] args) {
int age = 35;
System.out.printf("Your age is %f\n", (float) age); // Outputs 35.000000 (default precision)
}
}
However, the precision can be modified by inputing (.precision) inbetween the % and the type as shown below.
public class Main {
public static void main(String[] args) {
int age = 35;
System.out.printf("Your age is %.2f\n", (float) age); // Outputs 35.00 (2 d.p)
}
}
The width of the output can be adjusted by inserting the desired width inbewteen the "%" and the type as shown below:
Without Formatting:
public class Main {
public static void main(String[] args) {
for (int i = 1; i < 100_000; i *= 10 ) {
System.out.printf("Printing %d %n", i);
}
}
}
Output:
Printing 1 Printing 10 Printing 100 Printing 1000 Printing 10000
With Formatting
public class Main {
public static void main(String[] args) {
for (int i = 1; i < 100_000; i *= 10 ) {
System.out.printf("Printing %6d %n", i); // Width of largest value (1000000) is 6
}
}
}
Output:
Printing 1 Printing 10 Printing 100 Printing 1000 Printing 10000
In Java 15 String formatting was added so the string itself can be directly formatted using two different methods as shown below:
public class Main {
public static void main(String[] args) {
int age = 35
String formattedString = String.format("Your age is %d", age);
System.out.println(formattedString); // Your age is 35
}
}
public class Main {
public static void main(String[] args) {
int age = 35
String formattedString = "Your age is %d".formatted(age)
System.out.println(formattedString); // Your age is 35
}
}
Some common escape sequences are shown below:
Escape Sequence | Description |
---|---|
\t | Insert a tab character |
\n | Insert a new line character |
\" | Insert a double quote character |
\\ | Insert a backslash character |
Incorporating these characters and formatting with them can look quite complicated:
public class Main {
public static void main(String[] args) {
String bulleted = "Print a Bulleted List:\n" +
"\t\u2022 First Point\n" +
"\t\t\u2022 Second Point\n";
System.out.println(bulleted);
}
}
Output:
Print a Bulleted List:
- First Point
- Second Point
However, this can be easily replicated using a text block in Java 15.
public class Main {
public static void main(String[] args) {
String textBlock = """
Print a Bulleted List:
\u2022 First Point
\u2022 Second Point""";
System.out.println(textBlock);
}
}
Print a Bulleted List:
- First Point
- Second Point
Java uses OOP (Onject Orientated Programming) which is a technique to model objects that contain both data and code. The building block of Java that leads to Object-Oriented programming is a 'class' which describes the data (fields) and behaviour (methods) that are relevant to the object being described. Class members can be a field, method or any dependent element. If a field is static, there is only one copy in memory, and this value is associated with the class itself. If a field is not static, it is an instance field, and each object may have a different value stored for this field. A class is like a blueprint for an object and when an object is created, it inherits all the variables and functions from the class.
For example: in real life, a car is an object. The car has attributes, such as weight and color, and methods, such as drive and brake.
A class member. OOP has several advantages over procedural programming:
- OOP is faster and easier to execute
- OOP provides a clear structure for the programs
- OOP helps to prevent code repition and makes the code easier to maintain, modify and debug
- OOP makes it possible to create full reusable applications with less code and shorter development time
To create a class in Java, The syntax is to use the 'class' keyword with the class name having a capital letter, as shown below:
public class Car {
}
Classes can be organised into logical groupings called packages. You declare a package name in the class using the package statements. If no package is declared, the class implicitly belongs to the default package.
Encapsulation in OOP has two meanings. One is bundling behaviour and attributes into a single object. The other is the practice of hiding fields and methods from public access. Usually the meaning is the latter and the method of doing this is using access modifiers.
There are many access modifiers that control where the class can be accessed from
Access Keyword | Description |
---|---|
When no modifier is specified, package access is granted and the class is accessible to classes in the same package | |
public | any other class in any package can access this class |
private | No other class can access this member |
Class fields are variables that are specific to the class and defined in the code block as opposed to inside a method. When an object is created from the class, the values assigned to these fields represent the state of the object. Unlike local variables, class variables should have some type of access modifier declared for it. If not Java will declare the default (package private) implicitly. Below is an example of a car class with its field all having private access:
public class Car {
private String make;
private String model;
private String colour;
private int doors;
private boolean convertible;
}
Class fields can be given default values as shown below:
public class Car {
private String make = "BMW";
private String model = "Model 3";
private String colour = "Grey";
private int doors = 2;
private boolean convertible = false;
}
This means that every object that gets instantiated will have these as default values.
Class methods are first defined using the access modifier, return type and user given name. This is then followed by parenthesis in which you'd specify the type and name of any method arguments and then enclose the method code within curly brackets. The method below prints out the details from the car class defined above:
public void describeCar(){
System.out.println(doors + "-Door " +
colour + " " +
make + " " +
model + " " +
(convertible ? "Convertable" : " "));
}
The access modifier is public, which means it can be called from other classes such as "Main" and the return type is void. Which means it doesn't return anything.
A setter is a method on a class that sets the value of a private field. A getter is a method on a class that retrieves the value of a private field.
The purpose of these methods is to control, and protect, access to private fields.
An example to get and set the "make" field of the car class is shown below:
public String getMake(){
return make;
}
public void setMake(String make) {
this.make = make;
}
Note - In IntelliJ alt+insert can be used to quickly create setters & getters
Setters also allow validation. The example below only allows 3 OEM's to be accepted in the setMake method, if the argument passed in is anything else. The make is set to "Unsupported"
public void setMake(String make) {
if (make == null){
make = "Unknown";
}
String lowercaseMake = make.toLowerCase();
switch (lowercaseMake){
case "mazda":
case "mitsubishi":
case "toyota":
this.make = make;
break;
default:
this.make = "Unsupported";
}
}
In the earlier example. The "this" keyword was used used in the setter method. The this keyword refers to the instance that was created when the object was instantiated. So "this" is a special reference name for the object or instance, which it can use to describe itself. and it can be used to access fields on the class.
A class is just a template. To create an object (instance) of a class, you need a constructor. This is a special type of code block that has a specific name and parameters just like a method. It has the same name as the class itself and doesn't return any values. You never include a return value from a constructor, not even void. You can, and should also specify the access modifier, to control who should be able to create new instances of the class.
public class Car{ // Class declaration
public Car(){ // Constructor declaration
}
}
If no constructor is declared. The Java implicitly creates a constructor. However, if a class contains any other constructor declarations, then a default constructor is NOT implicitly declared.
Car car = new Car(); // Default constructor
car.desribeCar();
From here you can call the setters discussed previously to assign the fields of the car class.
public static void main(String[] args) {
Car firstCar = new Car();
firstCar.setMake("Mazda");
firstCar.setModel("3");
firstCar.setDoors(4);
firstCar.setColour("Black");
firstCar.setConvertible(false);
}
Instead of setting all of the fields individually. The constructor can be used to set all the fields by passing them in as arguments to the constructor:
public Car(String make, String model, String colour, int doors, boolean convertible) {
this.make = make;
this.model = model;
this.colour = colour;
this.doors = doors;
this.convertible = convertible;
}
Constructor overloading is declaring multiple constructors, with differeent formal parameters. The number of parameters can be different between constructors. Or if the number of parameters is the same between constructors, their types of order of the types much differ.
public Car(String make, String model, String colour, int doors, boolean convertible) {
this.make = make;
this.model = model;
this.colour = colour;
this.doors = doors;
this.convertible = convertible;
}
public Car(String make, String model, String colour, int doors) { // Overloaded constructor
this.make = make;
this.model = model;
this.colour = colour;
this.doors = doors;
this.convertible = false;
}
Constructor chaining is when one constructor explictly calls another overloaded constructor. You can only call a constructor from another constructor. You must use the "this()" keyword to execute another constructor, passing it arguments if required. "this" must be the first executable statement, if it's used from another constructor.
public Car(){
this("Jaguar","I-Pace","Grey", 4, false);
System.out.println("Empty constructor called, default values used");
}
public Car(String make, String model, String colour, int doors, boolean convertible) {
this.make = make;
this.model = model;
this.colour = colour;
this.doors = doors;
this.convertible = convertible;
System.out.println("Constructor with parameters called");
}
The result of the code above would be:
- Constructor with parameters called
- Empty constructor called, default values used
A static variable is declared using the keyword 'static'. Every instance of the class share the same static variable. Therefore - if a change is made to that variable, all other instances of that class will see the effect of the change. It is common practice to use the class name and not a reference variable to access a static variable. This makes it clear that the variable is shared with the class and not stored within the instance.
class Dog {
static String genus = "Canis";
void printData() {
Dog d = new Dog();
System.out.println(d.genus); // Confusing
System.out.println(Dog.genus); // Clearer
}
}
Also an instance isn't required to exist to access the value of a static variable.
class Dog {
static String genus = "Canis";
}
class Main {
public static void main(String [] args){
System.out.orintln(Dog.genus); // No instance required to access static variable
}
}
They can be used for:
- Storing counters
- Generating unique ids
- Storing a constant value that doesn't change, like PI for example.
- Creating and controlling access to a shared resource (database/log file)
Static methods are declared using the 'static' modifier. Static methods cannot access instance methods and instance variable directly. They're usually used for operations that don't require any data from an instance of the class (from 'this').
Therefore inside a static method - you cannot used the 'this' keyword.
Whenever you see a method that doesn;t use instance variables, that method should probably be declared as a static method. For example, 'main' is a static method, and it's called by the Java virtual matchine when it starts the Java application.
class Calculator {
public static void printSum (int a, int b) {
System.out.println("sum = " + (a + b));
}
}
public class Main {
public static void main (String [] args) {
Calculator.printSum(5, 10); // Does not require an instance
printHello();
}
public static void printHello(){
System.out.println("Hello");
}
}
Instance variables are also known as fields, or member variables and belong to a specific instance of a class. Each instance has it's own copy of an instance variable that can have a different value.
Instance methods belong to an instance of a class. To use an instance method, an instance must be created from the class, usually by using the new keyword.
Instance methods can access both static/instance variables and static/instance methods directly.
class Dog {
public void bark() {
System.out.println("woof");
}
}
class Main {
public static void main(String [] args){
Dog rex = new Dog(); // Create instance
rex.bark(); // Call instance method
}
}
Inheritance is a way to reuse code. It is a way to organise classes into a parent-child hierachy, which lets the child inherit, fields and methods from its parent.
The most generic (base class), starts at the top of the hierachy. Every class below it is a subclass. A parent can have multiple children. However, in Java a child can only have one parent. But it will also inherit from it's parent class's parent etc.
Below is an example of an animal class:
public class Animal {
private String type;
private String size;
private double weight;
public Animal() {
}
public Animal(String type, String size, double weight) {
this.type = type;
this.size = size;
this.weight = weight;
}
@Override
public String toString() {
return "Animal{" +
"type='" + type + '\'' +
", size='" + size + '\'' +
", weight=" + weight +
'}';
}
public void move(String speed){
System.out.println(type + " moves " + speed);
}
public void makeNoise(){
System.out.println(type + " makes some kind of noise ");
}
}
super() is a lot like this(). It's a way to call a constructor on the super class, directly from the sub classes constructor. Like this(), it has to be the first statement of the constructor. Because of that rule, this() and super() can never be called from the same constructor. If a explicit call to super() is not made, then Java does it for you using super's default constructor. If the super class does not have a default constructor, then you must explicitly call super() in all of your constructors, passing the right arguments to that constructor.
A Dog class has been created, as a dog "IS A" animal, it will inherit animal attributes (type, size and weight) as well as the animal methods. The Dog class can also be specialised with its own fields and behaviour. To make one class inherit from another, the "extends" keyword is used. This class calls the default constructor of the animal class and doesn't set values to any field methods. Therefore, type & size will be "null" and weight will be 0.0.
public class Dog extends Animal {
public Dog(){
super();
}
}
However, you can pass values into the Dog constructor to instantiate these values. The updated Dog class below now has two fields specific to it (ear & tail shape). As well as this it uses a ternary operator to pass the size into the super constructor and the toString() method calls the super.toString() method to print out all information.
public class Dog extends Animal {
private String earShape;
private String tailShape;
public Dog(String type, double weight){
this(type,weight, "Perky", "Curled");
}
public Dog(String type, double weight, String earShape, String tailShape) {
super(type, weight < 15 ? "small": (weight < 35 ? "medium" : "large"), weight);
this.earShape = earShape;
this.tailShape = tailShape;
}
@Override
public String toString() {
return "Dog{" +
"earShape='" + earShape + '\'' +
", tailShape='" + tailShape + '\'' +
"} " + super.toString();
}
}
Overriding a method is when you create a method on a subclass, which has the same signature as a method on a super class. This enables the child class to show different behaviour for that method.
See the example below where the makeNoise() method is overriden in the dog class and now has no output.
class Dog {
public void makeNoise() {
}
}
Another way to ensure a method is overriden is with alt + insert in intelliJ. This includes the @Override symbol that tells you the method is overriding the parent method.
class Dog {
@Override
public void makeNoise() {
}
}
An overriden class can do one of three things
- it can implement completely different behaviour, overriding the behaviour of the parent.
- It can simply call the parents class method, which is somewhat redundant to do.
- It can call the parent class's method, including other code to run, so it can extend the functionality.
Composition is another component of OOP. The difference between inheritance and composition is that inheritance defines an 'IS A' relationship. Whereas composition defines a 'HAS A' relationship. Composition allows the modelling of parts which make up a greater whole. The example below models a PC which is product comprised of a monitor, motherboard and case.
public class Product {
private String model;
private String manufacturer;
private int width;
private int height;
private int depth;
public Product(String model, String manufacturer) {
this.model = model;
this.manufacturer = manufacturer;
}
}
class PC extends Product {
private ComputerCase computerCase;
private Monitor monitor;
private Motherboard motherboard;
public PC(String model, String manufacturer, ComputerCase computerCase, Monitor monitor, Motherboard motherboard) {
super(model, manufacturer);
this.computerCase = computerCase;
this.monitor = monitor;
this.motherboard = motherboard;
}
public ComputerCase getComputerCase() {
return computerCase;
}
public Monitor getMonitor() {
return monitor;
}
public Motherboard getMotherboard() {
return motherboard;
}
}
class Monitor extends Product {
private int size;
private String resolution;
public Monitor(String model, String manufacturer) {
super(model, manufacturer);
}
public Monitor(String model, String manufacturer, int size, String resolution) {
super(model, manufacturer);
this.size = size;
this.resolution = resolution;
}
public void drawPixelAt(int x, int y, String colour){
System.out.println(String.format("Drawing pixel at %d, %d in colour %s ", x,y, colour));
}
}
class Motherboard extends Product {
private int ramSlots;
private int cardSlots;
private String bios;
public Motherboard(String model, String manufacturer) {
super(model, manufacturer);
}
public Motherboard(String model, String manufacturer, int ramSlots, int cardSlots, String bios) {
super(model, manufacturer);
this.ramSlots = ramSlots;
this.cardSlots = cardSlots;
this.bios = bios;
}
public void loadProgram(String programName){
System.out.println(String.format("Program %d is now loading...", programName));
}
}
class ComputerCase extends Product {
private String powerSupply;
public ComputerCase(String model, String manufacturer) {
super(model, manufacturer);
}
public ComputerCase(String model, String manufacturer, String powerSupply) {
super(model, manufacturer);
this.powerSupply = powerSupply;
}
public void pressPowerButton(){
System.out.println("Power button pressed");
}
}
As a general rule composition should be looked at first when designing programs in Java:
- Composition is more flexible, parts can be added or removed. These changes are less likely to have a downstream effect.
- Composition provides functional reuse outside of the class hierachy, meaning classes can share attributes & behaviour, by having similar components, instead of inheriting functionality from a parent or base class.
- Java's inheritance breaks encapsulation, because subclasses may need direct access to a parent's state or behaviour.
On the other hand:
- Inheritance is less flexible
- Adding or removing a class from a class hierachy may impact all subclasses from that point.
- A new subclass may not need all the functionality or attributes of its parent class.
In Java, encapsulation means hiding things, by making them private or inaccessible.
Reasons being hiding things:
- Makes the interface simpler, we may want to hide unnecessary details
- Protects the integrity of data on an object, we may hide or restrict access to some of the data and operations
- To decouple the published interface from the internal details of the class, we may hide actual names and types of class members.
The interface here referres to the class members that can be accessed by calling code. Everything else is interal or private. An application programming interface, or API, is the public contract, that tells others how to use the class.
To see why encapsulation is important, see the example below that does not use it.
class Game {
public static void main(String[] args) {
Player player = new Player();
player.name = "John"; // Can access fields directly
player.health = 20;
player.weapon = "Sword";
int damage = 10;
player.loseHealth(damage);
System.out.printf("Remaining health = %d%n", player.healthRemaining()); // Remaining health = 10
player.health = 200;
player.loseHealth(11);
System.out.printf("Remaining health = %d%n", player.healthRemaining()); // Remaining health = 189 exceeding max of 100
}
}
public class Player {
public String name; // Public access modifier
public int health;
public String weapon;
public void loseHealth(int damage) {
health -= damage;
if (health <= 0) {
System.out.println("Player knocked out of game");
}
}
public int healthRemaining() {
return health;
}
public void restoreHealth(int extraHealth) {
health += extraHealth;
if (health > 100) {
System.out.println("Player restored to 100%");
health = 100;
}
}
}
Allowing direct access to data on an object, can potentially bypass checks and additional processing that the class has in place to manage the data. It also prevents renaming or ammending variable names. For example:
public class Player {
public String fullName; // Member field updated from name to fullName
public int health;
public String weapon;
}
This would break the Game class that can no longer find those fields.
An encapsulated version can be seen below:
class Game {
public static void main(String[] args) {
EnhancedPlayer enhancedPlayer = new EnhancedPlayer("John");
System.out.printf("Initial health is %d %n", enhancedPlayer.getHealthPercentage()); //
EnhancedPlayer enhancedPlayer2 = new EnhancedPlayer("Pete", 200, "Sword");
System.out.printf("Initial health is %d %n", enhancedPlayer2.getHealthPercentage()); // Maximum health is still 100
}
}
public class EnhancedPlayer {
private String name;
private int healthPercentage;
private String weapon;
public EnhancedPlayer(String name) {
this(name, 100, "Sword");
}
public EnhancedPlayer(String name, int health, String weapon) {
this.name = name;
if (health <= 0 ) {
this.healthPercentage = 1;
} else if (health > 100) {
this.healthPercentage = 100;
} else {
this.healthPercentage = health;
}
this.weapon = weapon;
}
public void loseHealth(int damage){
this.healthPercentage -= damage;
if (this.healthPercentage <=0){
System.out.println("Player knocked out");
// Reduce number of lives remaining
}
}
public int getHealthPercentage(){
return this.healthPercentage;
}
}
The main benefit of encapsulation is you're not affecting any other code. Its acts as a black box
To create an encapsulated class:
- Create constructors for object initialisation, which enforces that only objects with valid data will get created.
- Use the private access modifier for your fields.
- Use setter and getter methods sparingly, and only as needed.
- Use access modifiers that are not private, only for the methods that the calling code needs to use.
Polymophism means many forms. This allows the writing of code to call a method, but at runtime, the method's behaviour can be different for different objects. This means the behaviour is dependent on the runtime type of the object which may be different from the declared type in the code.
The declared type has to have some kind of relationship to the runtime type. Inheritance is one way to establish this relationship. However, there are other ways.
class Cinema {
public static void main(String[] args) {
Movie movie1 = new Adventure("Star Wars"); // Adventure is a type of movie
movie1.watchMovie(); // Calls Adventure watch movie method
Movie movie2 = new Comedy("Space Balls"); // Comedy is a type of movie
movie2.watchMovie(); // Calls Comedy watch movie method
Movie movie3 = Movie.getMovie("Adventure", "Iron Man"); // creates adventure type movie
movie3.watchMovie();
}
}
public class Movie {
private String title;
public Movie(String title) {
this.title = title;
}
public void watchMovie() {
String instanceType = this.getClass().getSimpleName();
System.out.printf("%s is a %s film %n", title, instanceType);
}
public static Movie getMovie(String type, String title) { // returns new movie type depending on type
switch (type.toUpperCase().charAt(0)) {
case 'A':
return new Adventure(title);
case 'C':
return new Comedy(title);
default:
return new Movie(title);
}
}
}
class Adventure extends Movie {
public Adventure(String title) {
super(title);
}
@Override
public void watchMovie() {
super.watchMovie();
System.out.printf(".. %s%n".repeat(3),
"Pleasant Scene",
"Scary Music",
"Something Bad Happens");
System.out.println();
}
}
class Comedy extends Movie {
public Comedy(String title) {
super(title);
}
@Override
public void watchMovie() {
super.watchMovie();
System.out.printf(".. %s%n".repeat(3),
"Something funny happens",
"Something even funnier happens",
"Happy Ending");
System.out.println();
}
}
The Scanner class can be used to get input from the user.
import java.util.Scanner;
class Cinema {
public static void main(String[] args) {
Scanner s = new Scanner(System.in); //Get user input
while (true) {
System.out.print("Enter Type (A for Adventure or C for Comedy, " +
"or Q to quit:");
String type = s.nextLine();
if ("Qq".contains(type)) { //checks if 'Q' or 'q' are entered
break;
}
System.out.print("Enter Movie Title: ");
String title = s.nextLine();
Movie movie = Movie.getMovie(type, title);
movie.watchMovie();
}
}
}
Polymorphism enables you to write generic code, based on the base class.
Arrays are a data structure that allows the storage and manipulation of multiple values of the same type. Elements in an Array are 0 indexed.
When you declare an array, first the type of elements in the array must be specified, followed by square brackets which is the key for Java to identify the variable as an Array. The square brackets can also be after the variable name:
int [] integers;
String [] names;
double accountBalances [];
One way to instantiate an Array is with the new keyword and a pair of square brackets:
int [] integers = new int [10]; // Array with a size of 10 elements
The size of an array, once created, is fixed. Elements can not be added or deleted. However, the existing elements in the array can be reassigned. An Array instantiation does not have a set of parentheses, meaning data cannot be passed to a constructor for an array.
An Array initialiser makes instantiating and intialising a small Array easier:
int [] integers = new int []{1, 2, 3, 4, 5}; // Array with 1, 2, 3, 4 & 5 as its elements
Because the values are specified, the length of the array can be determined. Therefore, they don't need to be declared in the square brackets. In fact the "new int[]" does not need to be specified. However, this is only the case during instantiation.
int [] integers = {1, 2, 3, 4, 5}; // Array with 1, 2, 3, 4 & 5 as its elements
An example of Array initilisation and instantiations can be seen below:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int [] integers = new int [10]; // Integer Array with a size of 10 and values of 0
integers[5] = 50; // sets 6th element to 50
double [] doubles = new double [10]; // Double Array with size of 10 and values of 0.0
doubles[2] = 3.5; // Third element set to 3.5
System.out.println(Arrays.toString(integers)); // Prints out Array values [0, 0, 0, 0, 0, 50, 0, 0, 0, 0]
System.out.println(Arrays.toString(doubles)); // [0.0, 0.0, 3.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
int [] integers1 = new int []{1, 2, 3, 4, 5}; // Array with 1, 2, 3, 4 & 5 as its elements
/*
Compiler Error - Array initialiser is not allowed here
int [] integers2;
integers2 = {2, 4, 6, 8, 10};
*/
int [] integers2;
integers2 = new int []{1,2,3,4,5}; // Initialiser can be used with new int keywords
int [] integers3 = {2, 4, 6, 8, 10}; // Array with 2, 4, 6, 8 & 10 as its elements
for (int i = 0; i < integers2.length; i++){
System.out.print(integers2[i] + " "); // Prints out Array values
}
System.out.println();
System.out.println(Arrays.toString(integers1)); // [1, 2, 3, 4, 5]
System.out.println(Arrays.toString(integers3)); // [2, 4, 6, 8, 10]
System.out.printf("First Element= %d %n", integers1[0]); // Prints first element
System.out.printf("Length of Array = %d %n", integers1.length); // Prints Length of Array
System.out.printf("Last Element = %d %n", integers1[(integers1.length - 1)]); // Prints Last Element of Array
}
}
Array is a special class in Java which like all other classes inherits from java.lang.Object.
The enhanced for loop/ the For Each loop. Was designed to walk through elements in an Array or any kind of collection. It processes one element at a time, from the first to the last. The syntax can be seen below:
for (declaration : collection) {
// block of statements
}
It's important to notice the seperator between components is a colon and not a semi-colon. The enhanced for loops also only has two components vs the three using the basic for loop. See below for an example:
int [] integers1 = new int []{1, 2, 3, 4, 5}; // Array with 1, 2, 3, 4 & 5 as its elements
for (int element: integers1) {
System.out.print(element + " ");
}
Arrays are used to manage many items of the same type. Some common behaviour for Arrays would be sorting, initialising values, copying the Array and finding an element. For an array, this bejaviour isn't on the Array instance itself, but with a helper class java.util.Arrays. Some examples on sorting, filling and copying can be seen below:
import java.util.Arrays;
import java.util.Random;
public class Main {
public static void main(String[] args) {
int[] firstArray = getRandomArray(10);
System.out.println("firstArray = " + Arrays.toString(firstArray));
Arrays.sort(firstArray); // Sorts in place
System.out.println("firstArray sorted = " + Arrays.toString(firstArray));
int[] secondArray = new int[10];
System.out.println("secondArray = " + Arrays.toString(secondArray));
Arrays.fill(secondArray, 5); // Fills Array with specified value
System.out.println("secondArray filled with 5's= " + Arrays.toString(secondArray));
int[] thirdArray = getRandomArray(10);
System.out.println("thirdArray = " + Arrays.toString(thirdArray));
int[] fourthArray = Arrays.copyOf(thirdArray, thirdArray.length); //Copy entire Array
System.out.println("fourthArray = " + Arrays.toString(thirdArray));
Arrays.sort(fourthArray);
System.out.println("thirdArray = " + Arrays.toString(thirdArray));
System.out.println("fourthArray sorted= " + Arrays.toString(fourthArray));
int[] smallerArray = Arrays.copyOf(thirdArray,5);
System.out.println("smallerArray= " + Arrays.toString(smallerArray)); // Only copied first 5 values
int[] largerArray = Arrays.copyOf(thirdArray,15);
System.out.println("largerArray= " + Arrays.toString(largerArray)); // Adds 5 extra elements
}
public static int[] getRandomArray (int length) { //Returns Array with random values
Random random = new Random();
int[] newInt = new int[length];
for (int i = 0; i < length; i++) {
newInt[i] = random.nextInt(100);
}
return newInt;
}
}
There are different algorithms for searching and matching elements in lists. Java includes many of the common search techniques such as binarySearch on many of its collections. However:
- The array has to be sorted
- If there are duplicate values in the array, there is no guarentee which one it'll match on.
- Elements must be comparable. Trying to compare instances of different types, may lead to errors and invalid results.
The binarySearch method returns:
- The position of a match if found
- A -1 when no match was found
- If the array has duplicates and you need to find the first element, other methods should be used.
An example can be found below:
import java.util.Arrays;
import java.util.Random;
public class Main {
public static void main(String[] args) {
String[] sArray = {"Able", "Jane", "Mark", "Ralph", "David"};
Arrays.sort(sArray);
System.out.println(Arrays.toString(sArray));
if (Arrays.binarySearch(sArray, "Mark") >= 0) {
System.out.println("Found Mark");
}
}
public static int[] getRandomArray(int length) {
Random random = new Random();
int [] newArray = new int[length];
for (int i = 0; i < length; i++) {
newArray[i] = random.nextInt(100);
}
return newArray;
}
}
Two Arrays are considered equal if both Arrays have the same number of elements and all elements in the same position are equal. An example can be seen below:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] s1 = {1, 2, 3, 4, 5};
int[] s2 = {1, 2, 3, 4, 5};
int[] s3 = {5, 4, 3, 2, 1};
int[] s4 = {1, 2, 3, 4, 5, 0};
if (Arrays.equals(s1, s2)) {
System.out.println("Arrays are equal"); // Prints as - Elements are the same
} else {
System.out.println("Arrays are not equal");
}
if (Arrays.equals(s1, s3)) {
System.out.println("Arrays are equal");
} else {
System.out.println("Arrays are not equal"); // Prints as - Elements are not in the same order
}
if (Arrays.equals(s1, s4)) {
System.out.println("Arrays are equal");
} else {
System.out.println("Arrays are not equal"); // Prints as - Elements are not the same length
}
}
}
WHen an object is assigned to a variable, the variable becomes a reference to that object. This is true of Arrays. However, if the Array contains objects. Then every Array element is also a reference. One way to know if it's a reference type is the 'new' operator as that creates a new object in memory.
Examples can be seen below:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] myIntArray = new int[5];
int[] anotherArray = myIntArray; // Holds the same reference address
System.out.printf("myIntArray = %s %n", Arrays.toString(myIntArray)); // myIntArray = [0, 0, 0, 0, 0]
System.out.printf("anotherArray = %s %n", Arrays.toString(myIntArray)); // anotherArray = [0, 0, 0, 0, 0]
anotherArray[0] = 1;
System.out.printf("myIntArray after change = %s %n", Arrays.toString(myIntArray)); // myIntArray = [1, 0, 0, 0, 0]
System.out.printf("anotherArray after change = %s %n", Arrays.toString(myIntArray)); // anotherArray = [1, 0, 0, 0, 0]
modifyArray(myIntArray); // Creates another reference
System.out.printf("myIntArray after second change = %s %n", Arrays.toString(myIntArray)); // myIntArray = [1, 2, 0, 0, 0]
System.out.printf("anotherArray after second change = %s %n", Arrays.toString(myIntArray)); // anotherArray = [1, 2, 0, 0, 0]
}
private static void modifyArray(int[] array) {
array[1] = 2;
}
}
The brackets in an Array method signnature can be replaced by three periods. This is a special designation for Java, that means it will take zero or many fields as arguments for the method and create an array to process them in the method.
public class Main {
public static void main(String... args) {
System.out.println("Hello world!");
String [] splitStrings = "Hello World Again".split(" ");
printText(splitStrings);
System.out.println("_".repeat(20));
printText("Hello");
System.out.println("_".repeat(20));
printText("Hello", "World", "Again");
System.out.println("_".repeat(20));
printText();
String[] sArray = {"first", "second", "third", "fourth", "fifth"};
System.out.println(String.join(",", sArray));
}
private static void printText(String... textList){
for (String t: textList){
System.out.println(t);
}
}
}
There can only be one variable argument in a method. The variable argument must be the last argument.
Nested Arrays occur when an Array element is an Array. A two dimensional array can be thought of as a table or matrix of values with rows and columns. An Array initialiser can be used for this:
public class Main {
public static void main(String[] args) {
int[][] array = { // Multiline 2D Array
{1, 2, 3},
{11, 12, 13},
{21, 22, 23}
};
int[][] array2 = {{1, 2, 3}, {11, 12, 13}, {21, 22, 23}}; // single line 2D Array
}
}
Multi-Dimensional arrays do not have to be uniform matrixes and the nested Arrays can be different sized
int[][] array = {{1, 2}, {11, 12, 13}, {21, 22, 23, 24, 25}};
Each element is an array of integers.
You can also initialise a multi-dimensional Array and define the size of the nested Arrays:
int[][] array = new int [3][3]; // [outer][inner]
The statement above says that the Array contains three nested arrays and each array will have three ints.
You can also not specify the size of the nested arrays and only specify the outer array size:
int[][] array = new int [3][];
There are many ways to declare a two-dimensional array:
int[][] array; // Most common and clearest way
int[] array [];
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[][] array2 = new int[4][4];
System.out.println(Arrays.toString(array2)); // Prints string references
System.out.println("arrays2.length = " + array2.length);
for (int[] outer: array2){
System.out.println(Arrays.toString(outer)); // Prints element values
}
for (int i = 0; i < array2.length; i++) { // Prints individual element values
var innerArray = array2[i];
for (int j = 0; j < innerArray.length; j++) {
System.out.print(array2[i][j] + " ");
}
System.out.println();
}
System.out.println();
for (var outer: array2) { // Same as line above but with enhanced for loop
for (var element: outer) {
System.out.print(element + " ");
}
System.out.println();
}
for (int i = 0; i < array2.length; i++) { // Prints individual element values
var innerArray = array2[i];
for (int j = 0; j < innerArray.length; j++) {
array2[i][j] = (i * 10) + (j + 1); //Assigns values
}
}
System.out.println();
System.out.println(Arrays.deepToString(array2)); // Prints whole Array
}
}
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[][] array = new int[4][4];
for (int i = 0; i < array.length; i++) {
var outer = array[i];
for (int j = 0; j < outer.length; j++) {
array[i][j] = (i * 10) + (j + 1);
}
}
System.out.println(Arrays.deepToString(array));
/*
array[1] = {10, 20, 30}; // Array initialiser cannot be used here
System.out.println(Arrays.deepToString(array));
*/
array[1] = new int[] {10, 20, 30}; //Replaces second Array element to [10, 20, 30]
System.out.println(Arrays.deepToString(array));
Object[] anyArray = new Object[3];
System.out.println(Arrays.toString(anyArray)); // [null, null, null]
anyArray[0] = new String[] {"a", "b", "c"};
System.out.println(Arrays.deepToString(anyArray)); // [[a, b, c], null, null]
anyArray[1] = new String[][] {
{"1", "2"},
{"3", "4", "5"},
{"6", "7", "8", "9"}
};
System.out.println(Arrays.deepToString(anyArray)); // [[a, b, c], [[1, 2], [3, 4, 5], [6, 7, 8, 9]], null]
anyArray[2] = new int[2][2][2];
System.out.println(Arrays.deepToString(anyArray)); // [[a, b, c], [[1, 2], [3, 4, 5], [6, 7, 8, 9]], [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]]
for (Object element: anyArray) {
System.out.printf("Element type = %s %n", element.getClass().getSimpleName());
System.out.printf("Element toString() = %s %n", element);
System.out.println(Arrays.deepToString((Object[]) element )); //Element casted to object Array type
}
}
}
There are other data structures that allow the modification of the number of elements in an Array as well as many other improvements.
An Array is mutable and the values in an Array can be set or changed. However, the size of the Array cannot be changed. Java has several other classes where items can be add, removed and the container can be resized. These classes are said to implement a Lists behaviour. List is a special type in Java called an Interface. Interfaces describe a set of method signitures that all List classes are expected to have.
The ArrayList is a class that maintains an array in memory, that's actually bigger than what is needeed is most cases. It keeps track of the capacity - the actual size of the array. But it also keeps track of the elements that have been assigned or set- the size of the ArrayList. As elements are added to the ArrayList, its capacity needs to grow, this all happens behind the scenes, which is why the ArrayList is resizable.
import java.util.ArrayList;
import java.util.Arrays;
record GroceryItem(String name, String type, int count) {
public GroceryItem(String name) {
this(name,"DAIRY",1);
}
@Override
public String toString(){
return String.format("%d %s in %s", count, name.toUpperCase(), type);
}
}
public class Main {
public static void main(String[] args) {
GroceryItem[] groceryArray = new GroceryItem[3];
groceryArray[0] = new GroceryItem("milk");
groceryArray[1] = new GroceryItem("apples","PRODUCE",6);
groceryArray[2] = new GroceryItem("oranges", "PRODUCE", 5);
System.out.println(Arrays.toString(groceryArray));
ArrayList<GroceryItem> groceryList = new ArrayList<>();
groceryList.add(new GroceryItem("Butter"));
groceryList.add(new GroceryItem("Milk"));
groceryList.add(new GroceryItem("oranges", "PRODUCE", 5));
groceryList.add(0, new GroceryItem("apples", "PRODUCE", 6)); // add new element at specified index
System.out.println(groceryList);
groceryList.set(0, new GroceryItem("Bananas", "PRODUCE", 6)); // replace element at specified index
System.out.println(groceryList);
groceryList.remove(1); // Remove element at specified index
System.out.println(groceryList);
}
}
Adding, Modifying, Accessing & Converting ArrayLists
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class MoreLists {
public static void main(String[] args) {
String[] items = {"apples", "bananas", "milk", "eggs"};
List<String> list = List.of(items); // Transforms Array into List
System.out.println(list);
System.out.println(list.getClass().getName()); //Immutable List - java.util.ImmutableCollections$ListN
// list.add("yogurt"); Does not work as class is immutable
ArrayList<String> groceries = new ArrayList<>(list);
groceries.add("yogurt");
System.out.println(groceries);
ArrayList<String> newList = new ArrayList<>(
List.of("pickles", "mustard", "cheese")); //Adds list elements to ArrayList
System.out.println(newList);
groceries.addAll(newList); // Add other ArrayList elements
System.out.println(groceries);
System.out.println("Third item = " + groceries.get(2)); //Access ArrayList Elements
if (groceries.contains("mustard")) {
System.out.println("List contains mustard");
}
groceries.add("yogurt");
System.out.println("first = " + groceries.indexOf("yogurt"));
System.out.println("last = " + groceries.lastIndexOf("yogurt"));
System.out.println("Grocery List = " + groceries);
groceries.remove(1);
System.out.println("Grocery List = " + groceries); // 2nd element removed ("bananas")
groceries.remove("yogurt");
System.out.println("Grocery List = " + groceries); // first "yogurt" removes
groceries.add("apples");
groceries.removeAll(List.of("apples", "eggs"));
System.out.println("Grocery List = " + groceries); // All apples and eggs removed
groceries.retainAll(List.of("apples", "milk", "mustard", "cheese")); // Only keeps elements in the List within the ArrayList
System.out.println(groceries);
groceries.clear(); // Empties ArrayList
System.out.println(groceries);
System.out.println("isEmpty = " + groceries.isEmpty());
groceries.addAll(List.of("apples", "milk", "mustard", "cheese"));
groceries.addAll(Arrays.asList("eggs", "pickles", "mustard", "ham"));
System.out.println(groceries);
groceries.sort(Comparator.naturalOrder()); // orders Strings alphabetically and number in size order
System.out.println(groceries);
groceries.sort(Comparator.reverseOrder()); // Reverses order
System.out.println(groceries);
var groceryArray = groceries.toArray(new String [groceries.size()]); // Converts ArrayList into Array
System.out.println(Arrays.toString(groceryArray));
var groceryList = Arrays.asList(groceries); // Converts ArrayList into List
System.out.println(groceryList);
}
}
A multi-dimensional ArrayList simply has a type, which in itself is an ArrayList and then instantiating each element as an ArrayList.
import java.util.ArrayList;
public class MultiDimensionalArrayList {
public static void main(String[] args) {
int size = 3;
ArrayList<ArrayList<Integer>> coordinates = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
coordinates.add(new ArrayList<>());
}
coordinates.get(0).add(1);
coordinates.get(1).add(2);
coordinates.get(2).add(0);
coordinates.get(1).add(0);
coordinates.get(2).add(1);
coordinates.get(0).add(2);
System.out.println(coordinates); // [[1, 2], [2, 0], [0, 1]]
}
}
Whereas an ArrayList is implemented on top of an Array, a LinkedList is a doubly linked list. Both implement all of List's methods, but the LinkedList also implements the Queue and Stack methods aswell.
A queue is a FIFO (First-in, First-Out) data collection. To remove an item, the queue is polled to get the first element in line. To add an item, it's offered onto the queue and sent to the back of the line. Single-ended queues always process elements from the start of the queue. However, a double-ended queue allows access to bother the start and end of the queue. A LinkedList can be used as a double ended queue.
A Stack is a LIFO (Last-in, First-Out) Data collection. When an item is added, it is pushed onto the stack. To get an item, the top item is removed (popped) from the stack. A LinkedList can also be used as a Stack.
Examples on how to add and remove elements can be seen below:
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
LinkedList<String> placesToVisit = new LinkedList<>();
placesToVisit.add("Birmingham");
placesToVisit.add(0, "London");
System.out.println(placesToVisit);
addMoreElements(placesToVisit);
System.out.println(placesToVisit);
removeElements(placesToVisit);
System.out.println(placesToVisit);
}
private static void addMoreElements (LinkedList<String> list) {
list.addFirst("Leamington Spa");
list.addLast("Brighton");
// Queue methods
list.offer("Sheffield"); // Adds the specified element as the tail (last element) of this list.
list.offerFirst("Manchester"); // Inserts the specified element at the front of this list.
list.offerLast("Blackpool"); // Adds the specified element as the tail (last element) of this list.
// Stack methods
list.push("Coventry"); // Pushes an element onto the stack represented by this list. In other words, inserts the element at the front of this list.
}
private static void removeElements(LinkedList<String> list) {
list.remove(4); // Removes Birmingham
list.remove("London");
System.out.println(list);
String s1 = list.remove(); // Removes first element
System.out.println(s1 + " was removed");
String s2 = list.removeFirst(); // Removes first element
System.out.println(s2 + " was removed");
String s3 = list.removeLast(); // Removes last element
System.out.println(s3 + " was removed");
// Queue/Deque poll methods
String p1 = list.poll(); //Removes first element
System.out.println(p1 + " was removed");
String p2 = list.pollFirst(); //Removes first element
System.out.println(p2 + " was removed");
String p3 = list.pollLast(); //Removes last element
System.out.println(p3 + " was removed");
// Stack methods
list.push("London"); // Adds element to queue
list.push("Leamington Spa");
list.push("Rugby");
System.out.println(list);
String p4 = list.pop(); //Removes first element
System.out.println(p4 + " was removed");
}
}
Methods to access LinkedList elements can be seen below:
import java.util.LinkedList;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
LinkedList<String> placesToVisit = new LinkedList<>();
placesToVisit.add("Birmingham");
placesToVisit.add(0, "London");
System.out.println(placesToVisit);
addMoreElements(placesToVisit);
System.out.println(placesToVisit);
placesToVisit.add("London");
gettingElements(placesToVisit);
printItinerary3(placesToVisit);
}
private static void gettingElements(LinkedList<String> list) {
System.out.println("Retrieved Element = " + list.get(4));
System.out.println("First Element = " + list.getFirst());
System.out.println("Last Element = " + list.getLast());
System.out.println("Leamington Spa is at position " + list.indexOf("Leamington Spa"));
System.out.println("London is at position " + list.lastIndexOf("London"));
// Queue retrieval method
System.out.println("Element from element() = " + list.element());
// Stack retrieval methods
System.out.println("Element from peek() = " + list.peek());
System.out.println("Element from peekFirst() = " + list.peekFirst());
System.out.println("Element from peekLast() = " + list.peekLast());
}
// Using for loop
public static void printItinerary(LinkedList<String> list) {
System.out.println("Trip starts at: " + list.getFirst());
for (int i = 1; i < list.size(); i++) {
System.out.println("--> From: " + list.get(i - 1) + " to " + list.get(i));
}
System.out.println("Trip ends at: " + list.getLast());
}
// Using enhanced for loop
public static void printItinerary2(LinkedList<String> list) {
System.out.println("Trip starts at: " + list.getFirst());
String previousTown = list.getFirst();
for (String town: list) {
System.out.println("--> From: " + previousTown + " to " + town); //Prints first elements to first element
previousTown = town;
}
System.out.println("Trip ends at: " + list.getLast());
}
// Using iterator
public static void printItinerary3(LinkedList<String> list) {
System.out.println("Trip starts at: " + list.getFirst());
String previousTown = list.getFirst();
ListIterator<String> iterator = list.listIterator(1);
while (iterator.hasNext()) {
var town = iterator.next();
System.out.println("--> From: " + previousTown + " to " + town); //Prints first elements to first element
previousTown = town;
}
System.out.println("Trip ends at: " + list.getLast());
}
}
An Iterator is an object used to loop through collections.There are two types of iterators, the Iterator and the ListIterator. An Iterator can call the next() method to get the next element in the list. It can also used the hasNext() method to check if any elements remain to be processed.
When an Iterator is created, it's position is before the first element. The first call to the next() method gets the first element, and moves the cursor position between the first and second elements.
import java.util.LinkedList;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
LinkedList<String> placesToVisit = new LinkedList<>();
placesToVisit.add("Birmingham");
placesToVisit.add(0, "London");
System.out.println(placesToVisit);
placesToVisit.add("London");
testIterator(placesToVisit);
}
private static void testIterator(LinkedList<String> list) {
var iterator = list.iterator();
while (iterator.hasNext()) {
/*
if (iterator.next().equals("London")) { // Concurrent Modification Exception - cannot modify the list while looping through it
list.remove();
}
*/
if (iterator.next().equals("London")) { // Removes if element is equal to London while looping
iterator.remove();
}
}
System.out.println(list);
}
}
An Iterator is forwards only, and only supports the remove() method. A ListIterator can be used to go both forwards and backwards, and in addition to the remove method, also supports the add() and set() methods. The method above has been rewritten with a listIterator.
private static void testIterator(LinkedList<String> list) {
var iterator = list.listIterator();
while (iterator.hasNext()) {
/*
if (iterator.next().equals("London")) { // Concurrent Modification Exception - cannot modify the list while looping through it
list.remove();
}
*/
if (iterator.next().equals("London")) { // Removes if element is equal to London while looping
iterator.add("Kent"); // listIterator can add and set elements
}
}
while (iterator.hasNext()) {
System.out.println(iterator.next()); // Doesn't print anything as iterator.hasNext() = false.
}
while (iterator.hasPrevious()) {
System.out.println(iterator.previous()); // Traverse the list backwards
}
System.out.println(list);
var iterator2 = list.listIterator(3); // Places iterator between index 2 & 3
System.out.println("Iterator 2 element = " + iterator2.next()); // prints index 3
var iterator3 = list.listIterator(3); // Places iterator between index 2 & 3
System.out.println("Iterator 3 element = " + iterator3.previous()); // print index 2
}
Classes like the ArrayList or LinkedList, do not support primitive data types, as the collection type. Therefore, the following cannot be done:
LinkedList<int> integers = new LinkedList<>(); // Does not compile
However, Java gives wrapper classes for each primitive type. A primitive going to a wrapper is called boxing, and a wrapper to a primitive is called unboxing. This can be done with ease in Java.
Each wrapper has a static overloaded factor method, valueOf() which takes a primite, and returns an instance of the wrapper class.
Integer boxedInt = Integer.valueOf(15); // returns Integer instance
Another way of boxing is by creating a new instance of the wrapper class using the new keyword. The primitive value is then passed to the constructor.
Integer boxedInt = new Integer(15); returns Integer instance
If this is done in IntelliJ, with a Java version greater than JDK-9, it will tell you this is deprecated code.
- Deprecated code means it's older and may not be supported in the future.
- The static factory valueof() is generally a better choice as it is likely to yield significantly better space and time performance.
However, primitives rarely need to be boxed, as Java supports something called autoboxing.
####### Autoboxing
A primitive can be assigned to a wrapper variable as shown below:
Integer boxedInt = 15;
This is actually preferred to manually boxing.
Every wrapper class supports a method to return the primitive value it contains. This is called unboxing
int unboxedInt = boxedInt.intValue();
Calling this function is called manual unboxing. However, like boxing it is unnecessary to do this manually.
int unboxedInt = boxedInteger;
Autoboxing means that the primitive and wrapper class can be used interchangeably.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
Integer[] wrapperArray = new Integer[5]; // Array of Integer wrapper class
wrapperArray[0] = 50;
System.out.println(Arrays.toString(wrapperArray)); // [50, null, null, null, null]
System.out.println(wrapperArray[0].getClass().getName()); // Class type java.lang.Integer
Character[] characterArray = {'a', 'b', 'c', 'd'};
System.out.println(Arrays.toString(characterArray)); // [a, b, c, d]
System.out.println(characterArray[0].getClass().getName()); // java.lang.Character
var ourList = getList(1, 2, 3, 4, 5); // ArrayList<Integer>
System.out.println(ourList);
var ourList2 = List.of(1,2,3,4,5); // List<Integer>
System.out.println(ourList2);
}
private static ArrayList<Integer> getList (int... varargs) {
ArrayList<Integer> aList = new ArrayList<>();
for (int i: varargs) {
aList.add(i);
}
return aList;
}
private static int returnAnInt(Integer i) { // Unboxes Integer to int return type
return i;
}
private static Integer returnAnInteger(int i) { // Boxes and returns an Integer
return i;
}
}
The enum type is Java's type to support enumeration. Java describes the enum type as a "special data type that contains predefined constants". An enum functions as an Array with known elements, that are not changeable and each element can be referred to by a constant name, instead of index position.
An enum is it's simplest form, is described like a class, where the keyword 'enum', replaces the keyword 'class'. The UpperCamelCase is the preferred style. The identifiers are seperated by commas and by convension (constants) all uppercase labels. An example of an enum is shown below:
public enum DaysOfTheWeek {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
A enum is ordered, by the way the constants are declared. In the example above, SUNDAY is the first day of the week and SATURDAY is the last.
Other examples could include:
- Months in the year
- Compass Directions
Using the enum DaysOfTheWeek defined above, example use cases are shown below:
import java.util.Random;
public class Main {
public static void main(String[] args) {
DayOfTheWeek weekDay = DayOfTheWeek.TUES;
System.out.println("weekDay = " + weekDay);
for (int i = 0; i < 10; i++) {
weekDay = getRandomDay();
System.out.printf("Name is %s, Ordinal Value = %d%n", weekDay.name(), weekDay.ordinal()); // ordinal synonymous with index
if (weekDay == DayOfTheWeek.FRI) {
System.out.println("Found a Friday!!!");
}
}
}
public static DayOfTheWeek getRandomDay(){
int randomInteger = new Random().nextInt(7);
var allDays = DayOfTheWeek.values();
return allDays[randomInteger];
}
}
import java.util.Random;
public class Main {
public static void main(String[] args) {
DayOfTheWeek weekDay = DayOfTheWeek.TUES;
System.out.println("weekDay = " + weekDay);
for (int i = 0; i < 10; i++) {
weekDay = getRandomDay();
switchDayOfWeek(weekDay);
}
}
public static DayOfTheWeek getRandomDay(){
int randomInteger = new Random().nextInt(7);
var allDays = DayOfTheWeek.values();
return allDays[randomInteger];
}
public static void switchDayOfWeek (DayOfTheWeek weekDay) {
int weekDayInteger = weekDay.ordinal() + 1; // Puts weekDay through range of 1-7 instead of 0-6
switch(weekDay) {
case WED -> System.out.println("Wednesday is Day " + weekDayInteger);
case SAT -> System.out.println("Saturday is Day " + weekDayInteger);
default -> System.out.println(weekDay.name().charAt(0) + weekDay.name()
.substring(1).toLowerCase() + "day is Day " +
weekDayInteger);
}
}
}
class BurgerShop {
public static void main(String[]args){
for (Topping topping: Topping.values()) {
System.out.println(topping.name() + " : " + topping.getPrice());
}
}
}
public enum Topping {
MUSTARD,
PICKLES,
BACON,
CHEDDAR,
TOMATO;
public double getPrice() {
return switch (this) {
case BACON -> 1.5;
case CHEDDAR -> 1.0;
default -> 0.5;
};
}
}
Below is an example Theatre class that holds an ArrayList of Seats and allows them to be reserved or cancelled.
import java.util.ArrayList;
import java.util.List;
class Reservations {
public static void main(String[] args) {
Theatre theatre = new Theatre("Milfield Theatre", 10, 12);
// theatre.getSeats();
if (theatre.reserveSeat("H11")) {
System.out.println("Please pay");
} else {
System.out.println("Sorry, seat is taken");
}
if (theatre.reserveSeat("H11")) {
System.out.println("Please pay");
} else {
System.out.println("Sorry, seat is taken");
}
}
}
public class Theatre {
private final String theatreName;
private List<Seat> seats = new ArrayList<>();
public Theatre(String theatreName, int numRows, int seatsPerRow) {
this.theatreName = theatreName;
int lastRow = 'A' + (numRows - 1);
for (char row = 'A'; row <= lastRow; row++) {
for (int seatNum = 1; seatNum <= seatsPerRow; seatNum++) {
Seat seat = new Seat(row + String.format("%02d", seatNum)); // Pads rows so A1 would be written A01
seats.add(seat);
}
}
}
public String getTheatreName() {
return theatreName;
}
public boolean reserveSeat(String seatNumber) {
Seat requestedSeat = null;
for (Seat seat: seats) {
if (seat.getSeatNumber().equals(seatNumber)) {
requestedSeat = seat;
break;
}
}
if (requestedSeat == null) {
System.out.println("There is no seat " + seatNumber);
return false;
}
return requestedSeat.reserve();
}
public void getSeats() {
for (Seat seat: seats) {
System.out.println(seat.getSeatNumber());
}
}
private class Seat {
private final String seatNumber;
private boolean reserved = false;
public Seat(String seatNumber) {
this.seatNumber = seatNumber;
}
public boolean reserve() {
if (!this.reserved) {
this.reserved = true;
System.out.println("Seat " + seatNumber + " reserved");
return true;
} else {
return false;
}
}
public boolean cancel() {
if (this.reserved) {
this.reserved = false;
System.out.println("Reservation of seat " + seatNumber + " cancelled");
return true;
} else {
return false;
}
}
public String getSeatNumber() {
return seatNumber;
}
}
}
The Collections interface contains Lists, Sets, Queues and Deques. The Collections interface comes with a built in binarySearch function. However, for this to work the data collection must be sorted and comparable. Therefore the Seat class is made to implement the Comparable Interface. and The compareTo() function overriden to compare seats by seatNumber. This significantly increases the search speed.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class Reservations {
public static void main(String[] args) {
Theatre theatre = new Theatre("Milfield Theatre", 10, 12);
// theatre.getSeats();
if (theatre.reserveSeat("H11")) {
System.out.println("Please pay");
} else {
System.out.println("Sorry, seat is taken");
}
if (theatre.reserveSeat("H11")) {
System.out.println("Please pay");
} else {
System.out.println("Sorry, seat is taken");
}
}
}
public class Theatre {
private final String theatreName;
private List<Seat> seats = new ArrayList<>();
public Theatre(String theatreName, int numRows, int seatsPerRow) {
this.theatreName = theatreName;
int lastRow = 'A' + (numRows - 1);
for (char row = 'A'; row <= lastRow; row++) {
for (int seatNum = 1; seatNum <= seatsPerRow; seatNum++) {
Seat seat = new Seat(row + String.format("%02d", seatNum)); // Pads rows so A1 would be written A01
seats.add(seat);
}
}
}
public String getTheatreName() {
return theatreName;
}
public boolean reserveSeat(String seatNumber) {
Seat requestedSeat = new Seat(seatNumber);
int foundSeat = Collections.binarySearch(seats, requestedSeat, null); // binarySearch function implemented
if (foundSeat >= 0) {
return seats.get(foundSeat).reserve();
} else {
System.out.println("There is no seat " + seatNumber);
return false;
}
// for (Seat seat: seats) {
// System.out.println(".");
// if (seat.getSeatNumber().equals(seatNumber)) {
// requestedSeat = seat;
// break;
// }
// }
// if (requestedSeat == null) {
// System.out.println("There is no seat " + seatNumber);
// return false;
// }
// return requestedSeat.reserve();
}
public void getSeats() {
for (Seat seat: seats) {
System.out.println(seat.getSeatNumber());
}
}
private class Seat implements Comparable<Seat>{
private final String seatNumber;
private boolean reserved = false;
public Seat(String seatNumber) {
this.seatNumber = seatNumber;
}
@Override
public int compareTo(Seat seat) {
return this.seatNumber.compareToIgnoreCase(seat.getSeatNumber()); //Allows sorting and binarySearch
}
public boolean reserve() {
if (!this.reserved) {
this.reserved = true;
System.out.println("Seat " + seatNumber + " reserved");
return true;
} else {
return false;
}
}
public boolean cancel() {
if (this.reserved) {
this.reserved = false;
System.out.println("Reservation of seat " + seatNumber + " cancelled");
return true;
} else {
return false;
}
}
public String getSeatNumber() {
return seatNumber;
}
}
}
You can reverse, copy and get the Minimum & Maximum elements in a list based on the compareTo() method
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
Theatre theatre = new Theatre("Milfield Theatre", 10, 12);
//
List<Theatre.Seat> seatCopy = new ArrayList<>(theatre.seats); // Shallow Copy copies reference not a seperate list
printList(seatCopy);
seatCopy.get(1).reserve(); // Reserves Seat A02
if (theatre.reserveSeat("A02")) {
System.out.println("Please pay for A02");
} else {
System.out.println("Seat already reserved"); // Prints statement as seat was reserved in seatCopy
}
Collections.reverse(seatCopy);
System.out.println("Printing seatCopy reversed");
printList(seatCopy); // prints backwards
System.out.println("Printing theatre.seats");
printList(theatre.seats); //prints forwards
Collections.shuffle(seatCopy); // prints in psuedo random order
System.out.println("Printing seatCopy shuffled");
Theatre.Seat minSeat = Collections.min(seatCopy);
Theatre.Seat maxSeat = Collections.max(seatCopy);
System.out.println("Minimum seat number is " + minSeat.getSeatNumber());
System.out.println("Maximum seat number is " + maxSeat.getSeatNumber());
}
public static void printList(List<Theatre.Seat> list) {
for(Theatre.Seat seat : list) {
System.out.print(" " + seat.getSeatNumber());
}
System.out.println();
System.out.println("=".repeat(20));
}
}