... Java Fundamentals

Overview

In this section I explore the basic fundamentals of Java.

The goal is to use the Java programming language to build software systems, cloud solutions and/or any systems that solve real world problems

I start from the very basics and build up to more complex projects.

Below is what is covered in the guide, with links directing to the specific sections.





Standard Input & output

Output

We can print to the standard output using the System.out.print() or System.out.println() command.

				
					System.out.print("Hello world");
					System.out.println("This is another string");
				
				

Input

To get the input from a user, we use the Scanner class.

				
					Scanner scanner = new Scanner(System.in);
					System.out.println("Enter your string input:");
					string_in = scanner.nextLine();
					System.out.println(string_in);
				
				


Data Types

Overview

In Java there are two classes of data types:

  1. Primitive data types ie int, char, bool
  2. Non-primitive/Reference data types ie string, array, class

Below is a list of primitive data types

				
					int x = 5; // This is an integer
					byte w = 2; // This is a byte
					short y = 100; // This is a short
					long z = 10000000; // This is a long

					float a = 12.34f; // This is a float. A float ends with an 'f' character
					double b = 12345.6789d; // This is a double, it ends with a 'd' character

					boolean bool = true; // This is a boolean

					char character = 'a'; // This is a character
				
				

Type Casting

Reference Data types

Reference data types have access to useful methods, which give them more functionalities:

Some Reference data types in Java are:

  • Strings
  • Arrays

Some methods that can be used with strings are as shown below:

				
				String test = "test";
		        test.toUpperCase();
		        test.toLowerCase()
		        test.length();
		        test.concat();
				
				

Wrapper Class

Wrapper classes are used to convert primitive data types to reference data types, which then allows them to access nethods for extra operations, or to be used in collections such as ArrayLists. The downside of using wrapper classes for reference data types is that they are slower compared to primitive data types, as they have extra steps to access data.

The conversion of promitive data types to reference classes is known as Autoboxing, while the inverse is known as unboxing.

Primitive data types can be used as reference data types as below:

				
				Integer i = 2; // for int
		        Long l = 123451234L; // for long
		        Character c = 'c'; // for char
		        Boolean b = true; // for boolean
		        Float f = 1.231F; // for float
		        Double d = 12.123412;  // for double

		        // You can now access the methods as below 
		        i.equals(23);
		        l.hashCode();
		        c.toString();
		        b.booleanValue();
		        f.byteValue();
		        d.isInfinite();
				
				

If using Intellij IDE, you can see a list of methods for a wrapper class on entering the variable name followed buy a dot, ie in the example below, b.

Final Keyword

final is a Java keyword that is applied as a constant, and can be applied to:

  • variable
  • methods
  • classes

On variables, the final keyword is used where a value is assigned to a variable once, after which the value can not be re-assigned. A very common case is in the use of scientific values such as Pi (3.142), Earth gravity (9.8).

				
				final String Pi = 3.142;
				
				

On Methods, the final keyword is used where one does not want it to be overriden eg when defined in a class.

				
				public final String testFunction(){
			    	return "This is a test function";
				}
				
				

On classes, the final keyword is used where one does not want a class to be inherited. You can therefore not create sub-classes from classes that are final.

				
				public final class testClass {
				    public void testMethod() {
				        System.out.println("test method");
				    }
				}
				
				

Operators

Arithmetic Operators

Arithmetic operators are used for mathematic operations such as addition, subtraction etc. Below is a list of the common arithmetic operations

Operator Name
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulus
++ Increment
-- Decrement

Assignment Operators

As the name suggests, the assignment operators are used to assign values to variables. Below is an example of the common assignment operators

= x = 2
+ x += 5 Addition
- x -= 5 Subtraction
* x *= 5 Multiplication
/ x /= 5 Division
% x %= 5 Modulus

Comparison Operators

Again as the name suggests, comparison operators are used to compare two variables or values. They return a boolean value ie, true or false

== Equal to
!= Not equal to
< Less than
> Greater than
<= Less than or equal to
>= Greater than or equal to

Logical Operators

These operators check the logic between variables or values

&& AND
|| OR
! NOT


Conditionals

If Statements

if statements are used to execute a block of code if a certain condition is met.

If statements in java are written as below:

				
				if (condition) {
				    // statement 1
				} else {
				    // statement 2
				}


				
				

Example if an if statement:

				
				int x = 3, y = 5;
				if (x > y) {
	    			System.out.println("(If) x is bigger than y");
				} else {
	    			System.out.println("(if) y is bigger than x");
				}
				
				

Switch Statements

A switch statement is used to test a variable against a list of values. Switch statements can be used instead of if statements if we have many comparison statements.

Switch statements are written as below:

				
				switch (variable){
				    case comparison-1 :
				        // statement 
				        // statement
				        break
				    case comparison-2 : 
				        // statement 
				        break
				    default : 
				        // default statement if none of the comparisons are equal to the variable
				        break;
				}
				
				
Below is an example:
				
				int number = (int) (Math.random() * 40);
				System.out.println("Number : " + number);
				switch (number) {
				    case 1:
				        System.out.println("one");
				        break;
				    case 2:
				        System.out.println("two");
				        break;
				    case 3:
				        System.out.println("three");
				        break;
				    default:
				        System.out.println("none");
				        break;
				}
				
				


Loops

While Loops

A while loop executes a block of code continuously as long as a certain condition is met.

A while loop is written as:

				
				while (condition) {
				    // statement
				    // statement
				}
				
				

we also have a do-while loop, which does what the while loop does. The only difference is that the do-while loop will execute the code block at least once before running the comparison/condition.

				
				do {
				    // statement
				    // statement
				} while (condition)
				
				

A good example for implementing a while is a prompt that asks for an input, and will keep asking for an input if none is given. This is expressed programmatically as below:

				
				public static void main(String[] args) {
			    	Scanner input = new Scanner(System.in);
			    	System.out.println("Please enter your username to continue:");
			    	String cli_input = input.nextLine();
			        
			        // while loop
			    	while (cli_input.isBlank()){
			       	System.out.println("Please enter your username to continue:");
			       	cli_input = input.nextLine();
			    	}
			    	System.out.println("Welcome to the program " + cli_input);
				}
				
				

We can also implement the above code with a do-while loop as below.

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

				    do {
				       System.out.println("Please enter your username to continue:");
				       cli_input = input.nextLine();
				    } while (cli_input.isBlank());
				    System.out.println("Welcome to the program " + cli_input);
				}
				
				

For Loops

for-loops are statements that executes a block of code over a set number of times.

The basic structure of a for-loop is:

				
				for (start-index ; end-index ; increment){
				    // statement
				}

				// eg

				for (int i = 0 ; i <= 10 ; i++){
				    // statement
				}
				
				

As an example, we can print out a string a set number of times as shown below, which increments in the first example, and decrements in the next example.

				
				// incrementing from 0 to 20
				for (int i = 0 ; i <=20 ; i++){
				    System.out.println(i);
				}

				// decrementing from 20 to 0 in intervals of 2
				for (int i = 20 ; i >=0 ; i-=2){
		    		System.out.println(i);
				}
				
				

We can also have a loop within a loop, which is known as a nested loop



Methods

Methods are code blocks that are executed when called or invoked. This is useful as you do not have to repeat a block of code that is repetitive.

				
				void testMethod(){
					System.out.println("This is a test method");
				}
				
				


Recursion



Arrays

An arrays is a variable that is used to store multiple values.


A normal array would be declared as:
int number = 7;
While an array would be declared as:
int[] numbers = {1, 2, 3, 4};


We can alternatively declare an array by:
String letters = new String[90];
if we want to put in the values of the array later.
We can then fill in values in the array by:
letters[0] = "a";
letters[1] = "b";
letters[2] = "c";


Arrays can be accessed by the index of the value, where the first value has an index of 0. The first value will then be accessed by:
numbers[0];
which can be printed out to the standard output as
System.out.println(numbers[0]);


We can also loop through all the values of an array by using a for-loop or a for-each loop.

				
				public static void main(String[] args) {
				    // declare an array "numbers"
				    int[] numbers = new int[5];

				    // fill in the values in the array
				    numbers[0] = 121;
				    numbers[1] = 39;
				    numbers[2] = 345;

				    // loop through the array with a forloop
				    for (int i = 0 ; i < numbers.length ; i++){
				        System.out.println("The number in index:" + i + " is -> " + numbers[i]);
				    }

				    // loop through an array with a for-each loop
				    for (int i : numbers){
			        	System.out.println(i);
			    	}
				}

				/*
					Result:

					The number in index:0 is -> 121
					The number in index:1 is -> 39
					The number in index:2 is -> 345
					The number in index:3 is -> 0
					The number in index:4 is -> 0

					121
					39
					345
					0
					0
				*/
				
				

2D/Multidimensional Arrays

2D arrays are basically arrays within arrays.

Considering a normal array is defined as:

				
				int[] numbers = new int[4];
				
				

A 2D array is defined as:

				
				int[][] numbers = new int[3][5];
				
				

A good way to remember the structure of 2D arrays is to consider the structure of a table, ie
int[][] => int[rows][columns]
Therefore : int[][] numbers = new int[3][5]; would have 3 rows and 5 columns

We can then loop through a 2D array using two for loops. Consider a 2D array:

				
				// we instantiate a 3 row, 2 column array
				String countries[][] = new String[3][2];

				// then enter the data into the array via the index
				countries[0][0] = "Kenya";
				countries[0][1] = "Ethiopia";
				countries[0][0] = "Nigeria";
				countries[0][1] = "Ghana";
				countries[0][0] = "South Africa";
				countries[0][1] = "Zambia";

				// we can use a for loop to loop through the data as below

				for (int i = 0 ; i < countries.length ; i ++){
				    System.out.println();
				    for (int j = 0 ; j < countries[i].length ; j ++) {
				        System.out.println(countries[i][j]);
				    }
				}

				// or use a for-each loop to run through all the data
				for ( String[] x : countries){
				    System.out.println();
				    for ( String y : x){
				        System.out.println(y);
				    }
				}

				
				


Array List

Array lists are arrays that are resizeable after compilation. In comparison to regular arrays, they do not take primitive data types, and only take in Reference data types. In the event that one wants to use primitive data types, they would have to be converted to reference data types using wrapper classes.

Below is the general operations of ArrayLists:

				
				// create a String array list
				ArrayList< String > cars = new ArrayList< String >();

				// add entries to the array list
				cars.add("Audi");
				cars.add("Honda");
				cars.add("Toyota");

				// loop through an array list with a for loop
				for (int i = 0 ; i < cars.size() ; i++){
				    System.out.println(cars.get(i));
				}

				// loop through an arraylist with a foreach loop
				for (String i : cars){
					System.out.println(i);
				}

				/*
					Output from the for loop:

					Audi
					Honda
					Toyota
				*/

				// edit the content of an arrayList
				cars.set(2, "Range Rover");		// replaces "Toyota"

				// print out the array list
				System.out.println(cars);

				/*
					Output from the for loop:
					
					Audi
					Honda
					Range Rover
				*/

				// remove an entry from the arrayList
				cars.remove(1);		// remove the entry on index 1, (Honda)

				// print out the array list
				System.out.println(cars);

				/*
					Output from the for loop:
					
					Audi
					Range Rover
				*/

				// clear the entire arrayList (removes all entries)
				cars.clear();
				
				

2D ArrayLists

Just like 2D Arrays, 2D ArrayLists are lists within lists.

Let's take the example of arrayLists of different countries from different continents. We first create the arrayLists of the countries in each continent, then add the seperate arraylists into another arraylist.

				
				// create an arraylist for africa and add countries
				ArrayList< String > Africa = new ArrayList< String >();
				Africa.add("Kenya");
				Africa.add("Uganda");
				Africa.add("Ethiopia");

				// create an arrayList for asia and add countries
				ArrayList< String > Asia = new ArrayList< String >();
				Asia.add("India");
				Asia.add("Indonesia");
				Asia.add("Malaysia");
				Asia.add("Thailand");

				// create an arrayList for europa and add countries
				ArrayList< String > SouthAmerica = new ArrayList< String >();
				SouthAmerica.add("Brazil");
				SouthAmerica.add("Colombia");
				SouthAmerica.add("Peru");

				// create a 2D arrayList
				ArrayList< ArrayList< String > > countries = new ArrayList< >();
				// add the arraylists to the 2d arraylists
				countries.add(Africa);
				countries.add(Asia);
				countries.add(SouthAmerica);

				System.out.println(countries);

				/*
				*   Result: 
				*   [[Kenya, Uganda, Ethiopia], [India, Indonesia, Malaysia, Thailand], [Brazil, Colombia, Peru]]
					 * */
				
				

We can then loop through the arraylist with a for loop as below

				
				for (int i = 0 ; i < countries.size() ; i ++){
				    for (int j = 0 ; j < countries.get(i).size() ; j++){
				        System.out.println(countries.get(i).get(j));
				    }
				}
				
				

Or with a for-each loop as:

				
				for (ArrayList i : countries){
				    for (String j : i){
				        System.out.println(j);
				    }
				}
				
				


OOP

Overview

Object oriented programming (OOP) is the concept of using Objects and Classes, where classes are blueprints of classes, and classes are instances of objects.

Objects in Java would therefore be programatic representations of real life items/entities eg people, houses, cars, countries etc. This therefore means that each item would have attributes and behaviours/actions (methods) ie in the case of a car, it would have attributes such as color, engine, number of doors etc, while also having behaviours such as driving, braking etc.

We can therefore represent the car described above as below programatically:

					
					public class Car {

					    // attributes
					    String name;
					    String engine;
					    int cc;

					    // constructor
					    public Car(String name, String engine, int cc){
					        this.name = name;
					        this.engine = engine;
					        this.cc = cc;
					    }

					    // Getters
					    public String getName(){
					        return name;
					    }
					    public String getEngine(){
					        return engine;
					    }
					    public int getCc(){
					        return cc;
					    }

					    // Setters
					    public void setName(String name){
					        this.name = name;
					    }
					    public void setEngine(String engine){
					        this.engine = engine;
					    }
					    public void setCc(int cc){
					        this.cc = cc;
					    }

					    public void startCar(){
					    	System.out.println("starting the engine of " + name)
						}
					}

					
					

The Car class can then be instantiated and called as below:

					
					
					    public static void main(String[] args) {
					        // instantiate the class by creating an object
					        Car honda = new Car("Honda", "v8", 2000);
					        
					        // getting the attributes
					        System.out.println(honda.getName());
					        System.out.println(honda.getEngine());
					        System.out.println(honda.getCc());

					        // setting the attributes
					        honda.setName("Honda CRV");
					        honda.setEngine("v8 Hybrid");
					        honda.setCc(2500);

					        // getting the attributes aging to see the changes made by the setters
					        System.out.println(honda.getName());
					        System.out.println(honda.getEngine());
					        System.out.println(honda.getCc());

					        // calling a method
					        honda.startCar();
					    }
					
					
					

Constructors

A constructor is a special method that is used to create new objects. So as the name suggests, they construct new objects.

If we had a class Planet...

					
					public class Planet {
					    String name;
					    int moons;
					}
					
					

We would instantiate it (creating an object), as below:

Planet earth = new Planet();

From the above, Planet(); is actually calling a Planet constructor method, which creates a new earth object.

As we can see, we do not have a method defined in the Planet class. In this case Java has created the constructor method for you, and is usually defined as the default null args constructor. Therefore Java has created an object that is empty.

This means that after creation of an object earth, if we try earth.name we get a null result.

					
					System.out.println(earth.name); // result shall be null
					System.out.println(earth.moons); // result shall be null
					
					

We can now create a custom constructor in the class as below:

					
					public class Planet {
					    String name;
					    int moons;
					    
					    public Planet(){
					        
					    }
					}
					
					

A constructor is like an ordinary method, but the name will always match the class name, i.e the Planet class has the Planet() constructor. Also note that constructors have no return types (no int, string or void)

We can move a step further and now create default values of the objects we create from classes. Now with the Planet class, we can create an object with custom values as below:

					
					public class Planet {
					    String name;
					    int moons;
					    
					    public Planet(String name, int moons){
					        this.name = name;
					        this.moons = moons;
					    }
					}
					
					

or

					
					public class Planet {
					    String name;
					    int moons;
					    
					    public Planet(String newName, int newMoons){
					        name = newName;
					        moons = newMoons;
					    }
					}
					
					

Let's take another example of Anime. An anime would have a name, genre, subtitles and episodes. We would also have methods such as watching, setSubtitles and removeSubtitles. With the above attributes and methods, we would have the class represented as below:

					
					public class Anime {
						// attributes

					    String name;
					    String genre;
					    String subtitles;
					    int episodes;


					    // methods + getters + setters

					    public void watching(boolean status){
					        if (status == true){
					            System.out.println("Currently watching the " + name + " anime.");
					        } else {
					            System.out.println("Currently NOT watching the " + name + " anime. On a break");
					        }
					    }

					    public void setSubtitles(String language){
					        subtitles = language;
					        System.out.println("The subtitles are now set to: " + language);
					    }

					    public void removeSubtitles(){
					        subtitles = "none";
					    }

					    public String getSubtitles(){
					        if (subtitles == "none"){
					            return "There are no subtitles set for the anime: " + name;
					        } else {
					            return "The subtitles for the anime " + name + " is : " + subtitles + " language";
					        }
					    }
					}
					
					

On creating an object from the class as below, we get an unexpected result:

					
					public static void main(String[] args) {
				        Anime bleach = new Anime();
				        bleach.watching(true);
				    }

				    /*
				    	output : Currently watching the null anime.
				    */
					
					

This is where we see the importance of constructors. The above result should have been "Currently watching bleach anime", but we get null instead of bleach

Creating an object from the above example, we use Anime bleach = new Anime();. In this case Anime() is our constructor (default constructor). However we have not passed any parameters, hence getting the null result on creating the object (instantiating the class).

We can bypass this challenge by using the setters when we create the object. however this can be a challenge if we have many attributes to work with.

With constructors we can set default values for our objects while we create them. This is illustreted below with the Anime class:

					
						public class Anime {
						    String name;
						    String genre;
						    String subtitles;
						    int episodes;

						    // class constructor
						    Anime( String name, String genre, String subtitles, int episodes){
						        this.name = name;
						        this.genre = genre;
						        this.subtitles = subtitles;
						        this.episodes = episodes;
						    }

						    public void watching(boolean status){
						        if (status == true){
						            System.out.println("Currently watching the " + name + " anime.");
						        } else {
						            System.out.println("Currently NOT watching the " + name + " anime. On a break");
						        }
						    }

						    public void setSubtitles(String language){
						        subtitles = language;
						        System.out.println("The subtitles are now set to: " + language);
						    }

						    public void removeSubtitles(){
						        subtitles = "none";
						    }

						    public String getSubtitles(){
						        if (subtitles == "none"){
						            return "There are no subtitles set for the anime: " + name;
						        } else {
						            return "The subtitles for the anime " + name + " is : " + subtitles + " language";
						        }
						    }
						}
					
					

with the constructor being:

					
					Anime( String name, String genre, String subtitles, int episodes){
				        this.name = name;
				        this.genre = genre;
				        this.subtitles = subtitles;
				        this.episodes = episodes;
				    }
					
					

Now so far we have learnt what constructors are and how to use them. Something extra to note, is that once we create a constructor with default parameters, we have to pass the required parameters when creating the object. However, even after having a constructor with the default values, we can still instantiate the class without any default values by having an empty constructor, or have a constructor with selected parameters. This concept is called constructor overloading.

					
					

					class Smartphone {
					    String companyName;
					    String processor;

					    public Smartphone(){
					    }

					    public Smartphone(tring companyName){
					    	this.companyName = companyName;
					    }

					    public Smartphone(String companyName, String processor){
					        this.companyName = companyName;
					        this.processor = processor;
					    }

					    public void details(){
					        System.out.println("The phone details are: company name: " + companyName +
					                ", processor: " + processor);
					    }
					}

					public class Test {
					    public static void main(String[] args) {
					        Smartphone nokia = new Smartphone();
					        nokia.details(); // result: The phone details are: company name: null, processpr: null

					        Smartphone iphone = new Smartphone("Apple", "Snap dragon");
        					iphone.details(); // result: The phone details are: company name: Apple, processpr: Snap dragon
					    }
					}
					
					

The above code will return The phone details are: company name: null, processpr: null for the nokia object, since we have created the object without any parameters.

The iphone object will however have no null values as we passed parameters while we creating the object.

Access Modifiers

Access modifiers are meant to control access of data from different files, classes, packages etc.

We have the following access modifiers:

  • Public: Acessible by all classes anf files.
  • Private: Only accessible within a class.
  • Default: Accessible within the same package.
  • Protected: Accessible within the same package and child-class (inheritance)

We can summarise the above as the below:

Modifier Class Package child-class Global
Public
Protected
Default
Private

toString() Method

The toString() is a method that returns the textual representation/description of an object, which by default is the location of the object in memory.

Every object can call the toString method, because all classes in Java extend another class called Object (which in turn has the toString method. In addition, one can also override the method to create a custom string.

					
					// the toString method as definded in the Object class
					public String toString() {
				        return getClass().getName() + "@" + Integer.toHexString(hashCode());
				    }
					
					

To use the method, one can append the toString() statement to a method.

					
					Animal lion = new Animal();

					System.out.println(lion.toString);

					// alternatively 
					System.out.println(lion);   // this will give the same result as the above
					
					

To override the default toString() method, put in a custom method in the class as below.

					
					public String toString(){
				        return "this is the default toString method";
				    } 
					
					

Abstraction, Inheritance, Polymorphism & Abstraction

Abstraction, Inheritance, Polymorphism and Abstraction are deemed to be the four main pillars in object oriented programming. I cover aspects of these four pillars in This section




© Copyright Kifaru Codes. All Rights Reserved.

Designed by KifaruCodes