Intended for saving solutions for tests , exercises and assignments as part of an introductory course to computer science in the Java language 😎
- Link to Mega
- Lint to [Drive ] ( https://drive.google.com/drive/folders/15saERJfCxzcg_EJF3H0cMsJ9g-HHERY2)
-
Introduction And Basics Of The Language
-
Object Oriented Programming
-
Flow Control (conditional sentences and loops)
-
Arrays
-
Extending Object-Oriented-Programming - inheritance, static methods and variables, loading of methods, cases, polymorphism and interfaces
-
Complications
-
Search And Sort Algorithms
-
Recursion
-
Linked Lists
-
Stacks And Queues
-
Trees And Binary Trees
-
Computational
+@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @+
@@ o o @@
@@ | | @@
@@ _L_L_ @@
@@ ❮\/__-__\/❯ Programming isn't about what you know @@
@@ ❮(|~o.o~|)❯ It's about what you can figure out @@
@@ ❮/ \`-'/ \❯ @@
@@ _/`U'\_ @@
@@ ( . . ) .----------------------------. @@
@@ / / \ \ | while( ! (succed=try() ) ) | @@
@@ \ | , | / '----------------------------' @@
@@ \|=====|/ @@
@@ |_.^._| @@
@@ | |"| | @@
@@ ( ) ( ) Testing leads to failure @@
@@ |_| |_| and failure leads to understanding @@
@@ _.-' _j L_ '-._ @@
@@(___.' '.___) @@
+@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @+
- 1. TABLE OF CONTENTS
- 2. PRIMITIVE VARIABLES TYPES
- 3. TAKING INPUT
- 4. ARITHMETIC
- 5. CASTING
- 6. RELATIONAL AND LOGICAL EXPRESSIONS
- 7. CONDITIONAL STATEMENTS
- 8. LOOPS
- 9. ARRAYS
- 10. SORTING ALGORITHMS
- 11. SEARCHING ALGORITHMS
- 12. MATH CLASS
- 13. STRING CLASS
- 14. STRING BUFFER CLASS
- 15. METHODS
- 16. RANDOM NUMBERS
- 17. RECURSION
- 18. READ AND WRITE TO FILE
- 19. LINKED LIST
- 20. STACKS
- 21. TREE
- 22. ALIASING
- 23. STATIC KEYWORD
- 24. JAVADOC
- 25. ACCESS MODIFIERS
- 26. OBJECT-ORIENTED-PROGRAMMING
- 27. ENUM
- 28. CONSTANT
- 29. EXCEPTION HANDLING
- 30. OBJECT CLASS
- 31. TIME COMPLEXITY
- 32. SPCAE COMPLEXITY
- 33. QUEUES
- 34. GENERICS
- 35. UNARY OPERATOR
- 36. HASHMAP
- 37. HASHTABLE
- 38. HASHSET
- 39. POSTFIX AND PREFIX
- 40. TERNARY OPERATOR
- 41. ABSTRACT CLASS
- 42. INTERFACE
- 43. ANONYMOUS INNER CLASS
- 44. OPTIONAL
- 45. LAMBDA EXPRESSION
// headers
//*******************************************************
// Exam in July 2022B (91)
// Semester 2022B
// Solutions to the test
// Author: liron mizrahi
//*******************************************************
//*******************************************************
// IntList.java
// the class reprsents IntList
// Author: liron mizrahi
//*******************************************************
// API
/**
* method return true if the arr values shift by 1
* @param: intp[ arr1, int[] arr2
* @return: boolean
*/
In Java, variables are used to store and manipulate data. There are several types of variables, each with its own characteristics and uses.
Definition | Example | Size | Range |
---|---|---|---|
char | 'b', 'B', '9', '&' | 1 byte | -2^7...2^7-1 (-128...127) |
int | -5, 9, 8214 | 4 bytes | -2^31...2^31-1 |
long | -3, 77, 8234 | 8 bytes | -2^63...2^63-1 |
short | -1, 3, 1456 | 2 bytes | -2^15...2^15-1 |
byte | -2, 8, 42 | 1 byte | -2^7...2^7-1 |
double | 5.22, -89, 1.65 | 8 bytes | -1.710^308 to 1.710^308 |
float | 44.22, -89, 8.6 | 4 bytes | -3.410^38 to 3.410^38 |
boolean | true / false | 1 byte | true / false |
public class ExamplesOfBasicVariablesTypes
{
public static void main(String[] args)
{
// define char
char c = 'k';
// define int
int num = 5;
// define long
long num1 = 765;
// define sort
sort num2 = 6;
// define byte
byte num3 = 32;
// define double
double num4 = 326;
// define float
float num5 = 89;
// define boolean
boolean bool = true;
}// end of method main
}// end of class ExamplesOfBasicVariablesTypes
in this table we use:
Scanner scan = new Scanner(System.in)
In Java, there are several ways to take input from a user.
Here the Scanner method:
(Remember to use the Scanner class you need to import it using import java.util.Scanner; It should be used at the very beginning of your java file before any other code written. So, you can use the methods in the table below after importing the class.)
Data Type | Format |
---|---|
int | in.nextInt() |
short | in.nextShort() |
long | in.nextLong() |
char | in.next().charAt(0) |
float | in.nextFloat() |
double | in.nextDouble() |
byte | in.nextByte() |
boolean | in.nextBoolean() |
String | in.next() |
A string across an entire line including spaces | in.nextLine() |
import java.util.Scanner;
public class InputExample
{
public static void main(String[] args)
{
// make a Scanner object
Scanner scan = new Scanner(System.in);
// Prompt user to enter an integer
System.out.print("Enter an integer: ");
int num1 = scan.nextInt();
// Prompt user to enter a short
System.out.print("Enter a short: ");
short num2 = scan.nextShort();
// Prompt user to enter a long
System.out.print("Enter a long: ");
long num3 = scan.nextLong();
// Prompt user to enter a character
System.out.print("Enter a character: ");
char ch = scan.next().charAt(0);
// Prompt user to enter a float
System.out.print("Enter a float: ");
float num4 = scan.nextFloat();
// Prompt user to enter a double
System.out.print("Enter a double: ");
double num5 = scan.nextDouble();
// Prompt user to enter a byte
System.out.print("Enter a byte: ");
byte num6 = scan.nextByte();
// Prompt user to enter a boolean
System.out.print("Enter a boolean: ");
boolean bool = scan.nextBoolean();
System.out.println("You entered: " + num1 + ", " + num2 + ", " + num3 + ", " + ch + ", " + num4 + ", " + num5 + ", " + num6 + ", " + bool);
}// end of method main
}// end of class InputExample
In Java, arithmetic operations are used to perform mathematical calculations on variables.
Operations | Symbol |
---|---|
Addition | + |
Subtraction | - |
Multiplication | * |
Division | / |
Modulus | % |
public class Arithmetic
{
public static void main(String[] args)
{
int a = 5;
int b = 2;
// addition
int c = a + b;
System.out.println("Addition: " + a + " + " + b + " = " + c);
// subtraction
c = a - b;
System.out.println("Subtraction: " + a + " - " + b + " = " + c);
// multiplication
c = a * b;
System.out.println("Multiplication: " + a + " * " + b + " = " + c);
// division
c = a / b;
System.out.println("Division: " + a + " / " + b + " = " + c);
// modulus
c = a % b;
System.out.println("Modulus: " + a + " % " + b + " = " + c);
}// end of method main
}// end of class Arithmetic
Shorthand Expressions | Symbol |
---|---|
Addition Assignment | += |
Subtraction Assignment | -= |
Multiplication Assignment | *= |
Division Assignment | /= |
Modulus Assignment | %= |
public class ShorthandExpressions
{
public static void main(String[] args)
{
int x = 10;
int y = 5;
// addition assignment
x += y;
System.out.println("Addition Assignment: x += y : " + x);
// subtraction assignment
x -= y;
System.out.println("Subtraction Assignment: x -= y : " + x);
// multiplication assignment
x *= y;
System.out.println("Multiplication Assignment: x *= y : " + x);
// division assignment
x /= y;
System.out.println("Division Assignment: x /= y : " + x);
// modulus assignment
x %= y;
System.out.println("Modulus Assignment: x %= y : " + x);
}// end of method main
}// end of class ShorthandExpressions
In Java, casting is the process of converting one data type to another.
Type | Description |
---|---|
Implicit casting | Also known as automatic casting, it occurs when a smaller type is converted to a larger type without the need for explicit casting. |
Explicit casting | Also known as manual casting, it occurs when a larger type is converted to a smaller type. |
public class Casting
{
public static void main(String[] args)
{
// Implicit casting
int a = 10;
long b = a; // automatic casting from int to long
// Explicit casting
double c = 2.12;
int d = (int)c; // explicit casting from double to int
System.out.println("Implicit casting: int to long : " + b);
System.out.println("Explicit casting: double to int : " + d);
}// end of method main
}// end of class Casting
In Java, relational and logical expressions are used to make comparisons and control the flow of a program.
Logical And Relational Expressions | Symbol | Description |
---|---|---|
AND | && | Returns true if both operands are true |
OR | || | Returns true if one or both operands are true |
NOT | ! | Returns true if the operand is false and false if the operand is true |
Less than | < | Returns true if the left operand is less than the right operand |
Less than or equal to | <= | Returns true if the left operand is less than or equal to the right operand |
Greater than | > | Returns true if the left operand is greater than the right operand |
Greater than or equal to | >= | Returns true if the left operand is greater than or equal to the right operand |
Not equal to | != | Returns true if the operands are not equal |
Equal to | == | Returns true if the operands are equal |
public class RelationalAndLogicalExpressions
{
public static void main(String[] args)
{
int a = 13;
int b = 5;
// relational greater than
if (a > b)
{
System.out.println("a is greater than b");
}
// relational less than
if (a < b)
{
System.out.println("a is less than b");
}
// relational greater than or equal to
if (a >= b)
{
System.out.println("a is greater than or equal to b");
}
// relational less than or equal to
if (a <= b)
{
System.out.println("a is less than or equal to b");
}
// relational equal to
if (a == b)
{
System.out.println("a is equal to b");
}
// relational not equal to
if (a != b)
{
System.out.println("a is not equal to b");
}
// logical AND
if (a > b && a < 20)
{
System.out.println("a is greater than b and less than 20");
}
// logical OR
if (a > b || a < 20)
{
System.out.println("a is either greater than b or less than 20");
}
// logical NOT
if (!(a == b))
{
System.out.println("a is not equal to b");
}
}// end of method main
}// end of class RelationalAndLogicalExpressions
In Java, conditional statements are used to control the flow of a program based on certain conditions. The most basic and commonly used conditional statement is the if statement.
The if statement allows you to execute a block of code only if a certain condition is true. The basic syntax for an if statement is as follows:
Syntax | Description |
---|---|
if (condition) { // code to be executed if the condition is true } |
Executes a block of code if the specified condition is true |
if (condition) { // code to be executed if the condition is true } else { // code to be executed if the condition is false } |
Executes a block of code if the specified condition is true, and another block of code if the condition is false |
if (condition1) { // code to be executed if condition1 is true } else if (condition2) { // code to be executed if condition2 is true } else { // code to be executed if both conditions are false } |
Executes a block of code for the first true condition, and another block of code if none of the conditions are true |
public class IfElseExample
{
public static void main(String[] args)
{
int age = 40;
// if statement
if (age >= 18)
{
System.out.println("You are an adult.");
}
// if-else statement
if (age >= 21)
{
System.out.println("You can drink alcohol.");
}
else
{
System.out.println("You cannot drink alcohol.");
}
// if-if-else-else statement
if (age >= 65)
{
System.out.println("You are a senior citizen.");
}
else if (age >= 18)
{
System.out.println("You are an adult.");
}
else
{
System.out.println("You are a minor.");
}
}// end of method main
}// end of class IfElseExample
A switch statement in Java is used to execute different code based on the value of an expression. The expression is evaluated, and the corresponding branch of the switch statement is executed.
public class WeekDayChecker
{
public static void main(String[] args)
{
int day = 2;
// switch statement to check the day of the week
switch(day)
{
case 1:
System.out.println("Monday");
break;
case 2:
System.out.println("Tuesday");
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:
System.out.println("Invalid day of the week");
break;
}
}// end of method main
}// end of class WeekDayChecker
Java provides several types of loops for different use cases:
Loop Type | Description | Best use |
---|---|---|
for | Iterating through a range of values or an array | Counting loops |
while | Executing a block of code as long as a certain condition is true | Conditional loops |
do-while | Executing a block of code at least once before the condition is checked | Loop should always execute at least one time |
Additionally, Java provides two keywords for controlling the flow of loops:
Keyword | Description |
---|---|
break | Break out of a loop early |
continue | Skip an iteration of a loop |
A for loop is used for iterating through a range of values or an array. It consists of three parts:
- initialization
- termination condition
- increment/decrement.
public class ForLoopExample
{
public static void main(String[] args)
{
// for loop to print the numbers from 0 to 10
for (int i = 0; i < 11; i++)
{
System.out.println(i);
}// end of for loop
}// end of method main
}// end of class ForLoopExample
While loop in Java consists of two parts: initialization and termination condition
Initialization : set the initial value for the loop variable before the start of the loop.
Termination condition: the condition that must be met for the loop to continue, if the condition is true the code inside the loop is executed, otherwise the loop is terminated.
It is important to be sure that the termination condition will be false at some point, otherwise the loop will keep running forever, this is called an infinite loop.
public class WhileLoopExample
{
public static void main(String[] args)
{
int i = 0; // initialization
// while loop to iterate through numbers 0 to 10
while (i < 11) // termination condition
{
System.out.println(i);
i++; // increment
}// end of while loop
}// end of method main
}// end of class WhileLoopExample
Do-while loop in Java consists of two parts: initialization and termination condition
Initialization : set the initial value for the loop variable before the start of the loop.
Termination condition: the condition that must be met for the loop to continue, if the condition is true the code inside the loop is executed, otherwise the loop is terminated.
The main difference between a while loop and a do-while loop is that the code inside a do-while loop is executed at least once before the termination condition is checked.
It's important to be sure that the termination condition will be false at some point, otherwise the loop will keep running forever, this is called an infinite loop.
public class DoWhileLoopExample
{
public static void main(String[] args)
{
int counter = 0;
do
{
System.out.println(counter);
counter++;
} while (counter < 10);
}// end of method main
}// end of class DoWhileLoopExample
Concept | Description |
---|---|
Array | A container object that holds a fixed number of values of a single type. |
Creating an Array | Use the keyword "new" followed by the data type of the array and the size of the array in brackets. |
Accessing Array Elements | Elements can be accessed using their index, which starts at 0. |
Modifying Array Elements | Elements can be modified by assigning a new value to a specific index. |
Array Methods | Java provides various methods in the Arrays class for sorting, searching, and manipulating arrays. |
class OneDimensionalArrayExample
{
public static void main(String[] args)
{
// Declare a one-dimensional array of integers
int[] myArray = new int[5];
// Assign values to the elements of the array
myArray[0] = 1;
myArray[1] = 2;
myArray[2] = 3;
myArray[3] = 4;
myArray[4] = 5;
// the array
// - - - - -
// |1|2|3|4|5|
// - - - - -
// Print out the third element of the array
System.out.println("The third element of the array is: " + myArray[2]);
}// end of method main
}// end of method OneDimensionalArrayExample
Key Point | Description |
---|---|
Size | The size of a two-dimensional array is the number of rows multiplied by the number of columns |
Usage | Two-dimensional arrays are typically used to store and manipulate data in a tabular format |
Creation | They can be created using the new keyword and specifying the number of rows and columns |
Accessing Elements | Elements in a two-dimensional array can be accessed using the array[row][column] notation |
Iteration | They can be iterated using nested loops to access each element individually |
Pass as a parameter | They can be passed as a parameter to a method just like a one-dimensional array |
Applications | Two-dimensional arrays are commonly used in many applications such as image processing, game development, and scientific computing. |
public class TwoDimensionalArrayExample
{
public static void main(String[] args)
{
// create a 2D array with 3 rows and 3 columns
// and initialize it with values
int[][] arr = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
System.out.println("The original array:");
//printing the original array using two for loops
for (int i = 0; i < arr.length; i++)
{
for (int j = 0; j < arr[i].length; j++)
{
System.out.print(arr[i][j] + " ");
}// end of nested loop
System.out.println();
}// end of first loop
//modifying the array
arr[1][2] = 8;
System.out.println("\nThe modified array:");
//printing the modified array
for (int i = 0; i < arr.length; i++)
{
for (int j = 0; j < arr[i].length; j++)
{
System.out.print(arr[i][j] + " ");
}// end of nested loop
System.out.println();
}// end of first loop
}// end of method main
}// TwoDimensionalArrayExample
1 | 2 | 3 |
---|---|---|
4 | 5 | 8 |
7 | 8 | 9 |
Sorting algorithms are used to order a collection of items in a specific way.
summary of some common sorting algorithms:
Algorithm | Best Case | Worst Case | Average Case | Space Complexity |
---|---|---|---|---|
bubble sort | O(n) | O(n^2) | O(n^2) | O(1) |
insertion sort | O(n) | O(n^2) | O(n^2) | O(1) |
selection sort | O(n^2) | O(n^2) | O(n^2) | O(1) |
merge sort | O(n log n) | O(n log n) | O(n log n) | O(n) |
quick sort | O(n log n) | O(n^2) | O(n log n) | O(log n) |
heap sort | O(n log n) | O(n log n) | O(n log n) | O(1) |
Bubble sort is a simple sorting algorithm that repeatedly compares adjacent elements and swaps them if they are in the wrong order. It repeatedly passes through the list, comparing elements and swapping them as needed, until the list is sorted.
Time Complexity:
Best Case: O(n) when the list is already sorted, no swapping will be done.
Worst Case: O(n^2) when the list is reverse sorted, each element will be compared n times before getting to its correct position.
Average Case: O(n^2)
Space Complexity: O(1) as it only uses a single additional memory space to keep track of the last swap.
Stability: Stable, it preserves the relative order of elements with equal values.
In-Place: Yes, it doesn't require extra memory to perform the sort.
class BubbleSort
{
public static void main(String[] args)
{
// the array before sorting
int[] arr = {5, 3, 8, 6, 2, 1, 9, 4, 7};
// print the array before sorting
System.out.println("Original Array: " + Arrays.toString(arr));
// sort the array
bubbleSort(arr);
// print the array after sorting
System.out.println("Sorted Array: " + Arrays.toString(arr));
}// end of method main
/**
* sort the array using bubble sort method
* @param array - the original array
* @return None
*/
public static void bubbleSort(int[] arr)
{
int temp;
for (int i = 0; i < arr.length - 1; i++)
{
for (int j = 1; j < arr.length - i; j++)
{
if (arr[j - 1] > arr[j])
{
temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
}// end of if
}// end of nested for loops
}// end of for loop
}// end of method bubbleSort
}// end of class BubbleSort
Insertion sort is a simple sorting algorithm that builds up the final sorted list one item at a time, by inserting each new item into its correct position in the already sorted portion of the list.
Time Complexity:
Best Case: O(n) when the list is already sorted, each element will be inserted in the first position.
Worst Case: O(n^2) when the list is reverse sorted, each element will be compared n times before getting to its correct position.
Average Case: O(n^2)
Space Complexity: O(1) as it only uses a single additional memory space to keep track of the current element and its position.
Stability: Stable, it preserves the relative order of elements with equal values.
In-Place: Yes, it doesn't require extra memory to perform the sort.
class InsertionSort
{
public static void main(String[] args)
{
// the array before sorting
int[] arr = {5, 3, 8, 6, 2, 1, 9, 4, 7};
// print the array before sorting
System.out.println("Original Array: " + Arrays.toString(arr));
// sort the array
insertionSort(arr);
// print the array after sorting
System.out.println("Sorted Array: " + Arrays.toString(arr));
}// end of method main
/**
* sort the array using insertion sort sort method
* @param array - the original array
* @return None
*/
public static void insertionSort(int[] arr)
{
int key, j;
for (int i = 1; i < arr.length; i++)
{
key = arr[i];
j = i - 1;
while (j >= 0 && arr[j] > key)
{
arr[j + 1] = arr[j];
j = j - 1;
}// end of while loop
arr[j + 1] = key;
}// end of foor loop
}// end of method insertionSort
}// end of class InsertionSort
election sort is a simple sorting algorithm that repeatedly selects the smallest (or largest) element from the unsorted portion of the list and moves it to the sorted portion.
It repeatedly finds the minimum element from the unsorted part and moves it to the end of the sorted array.
Time Complexity:
Best Case: O(n^2) when the list is already sorted or reverse sorted.
Worst Case: O(n^2) when the list is reverse sorted.
Average Case: O(n^2)
Space Complexity: O(1) as it only uses a single additional memory space to keep track of the current minimum element and its position.
Stability: Unstable, it doesn't preserves the relative order of elements with equal values.
In-Place: Yes, it doesn't require extra memory to perform the sort.
public class SelectionSort
{
public static void main(String[] args)
{
int[] arr = {5, 3, 6, 2, 10};
// Print the original array
System.out.print("Original Array: ");
for (int i : arr)
{
System.out.print(i + " ");
}// end of for loop
// Sort the array using selection sort
selectionSort(arr);
// Print the sorted array
System.out.print("\nSorted Array: ");
for (int i : arr)
{
System.out.print(i + " ");
}// end of for loop
}// end of method main
/**
* sort the array using selection sort method
* @param array - the original array
* @return None
*/
public static void selectionSort(int[] arr)
{
// Loop through the array
for (int i = 0; i < arr.length - 1; i++)
{
// Find the index of the minimum element
int minIndex = i;
for (int j = i + 1; j < arr.length; j++)
{
if (arr[j] < arr[minIndex])
{
minIndex = j;
}// end of if
}// end of for loop
// Swap the minimum element with the current element
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}// end of for loop
}// end of method selectionSort
}// end of class SelectionSort
Merge sort is a divide-and-conquer sorting algorithm that repeatedly divides an array or list into two halves until each half contains only one element, and then combines them back together in a sorted order.
The algorithm begins by dividing the array into two equal halves.
It then recursively sorts each half by calling the merge sort function on each half.
Once both halves are sorted, the merge function is called to merge the two sorted halves back together, in a sorted order.
The merge function compares the first element of each half and adds the smaller element to a new array. It then continues this process until one half is exhausted and adds the remaining elements of the other half.
The new array is then returned as the sorted version of the original array.
The merge sort algorithm has a time complexity of O(n log n), making it more efficient than other sorting algorithms such as bubble sort or insertion sort. It is a stable sort, meaning that it preserves the relative order of elements with equal keys. It also requires O(n) extra space to perform the sorting.
public class MergeSort
{
public static void main(String[] args)
{
int[] array = {5, 1, 9, 3, 7, 6, 8, 2, 4};
mergeSort(array);
for (int i : array)
{
System.out.print(i + " ");
}// end of for
}// end of main method
/**
* Sorts an array using the merge sort algorithm
* @param: array - the array to be sorted
* @return: None
*/
public static void mergeSort(int[] array)
{
if (array.length > 1)
{
// Split the array into left and right halves
int[] left = leftHalf(array);
int[] right = rightHalf(array);
// Recursively sort the left and right halves
mergeSort(left);
mergeSort(right);
// Merge the sorted left and right halves back together
merge(array, left, right);
}
}// end of mergeSort method
/**
* Returns the left half of an array
* @param array - the original array
* @return array - the left half of the array
*/
public static int[] leftHalf(int[] array)
{
int size1 = array.length / 2;
int[] left = new int[size1];
for (int i = 0; i < size1; i++)
{
left[i] = array[i];
}
return left;
}// end of leftHalf method
/**
* Returns the right half of an array
* @param array - the original array
* @return array - the right half of the array
*/
public static int[] rightHalf(int[] array)
{
int size1 = array.length / 2;
int size2 = array.length - size1;
int[] right = new int[size2];
for (int i = 0; i < size2; i++) {
right[i] = array[i + size1];
}
return right;
}// end of method rightHalf
/**
* Merges two sorted arrays into a single sorted array
* @param result - the final sorted array, left - the left half of the array, right - the right half of the array
@ @return None
*/
public static void merge(int[] result, int[] left, int[] right)
{
int i1 = 0;
int i2 = 0;
for (int i = 0; i < result.length; i++)
{
if (i2 >= right.length || (i1 < left.length && left[i1] <= right[i2]))
{
result[i] = left[i1];
i1++;
}
else
{
result[i] = right[i2];
i2++;
}
}// end of for loop
}// end of merge method
}// end of class MergeSort
Quick Sort is a sorting algorithm that uses the divide-and-conquer strategy to sort an array or a list of elements.
The basic idea behind the algorithm is to partition the array into two sub-arrays, one containing elements that are less than a chosen pivot element, and the other containing elements that are greater than the pivot.
The pivot element can be chosen in different ways, but a common approach is to select the last element of the array as the pivot.
Once the array is partitioned, the pivot element is in its final position in the sorted array. The algorithm then recursively sorts the left and right sub-arrays.
public class QuickSort
{
public static void main(String[] args)
{
int[] array = {5, 1, 9, 3, 7, 6, 8, 2, 4};
sort(array);
for (int i : array)
{
System.out.print(i + " ");
}
}// end of main method
/**
* Sorts an array using the quick sort algorithm
* @param array - the array to be sorted
* @return none
*/
public static void sort(int[] array)
{
sort(array, 0, array.length - 1);
}// end of sort method
/**
* Sorts an array within a given range using the quick sort algorithm
* @param array - the array to be sorted, int - low - the lower bound of the range to be sorted int - high - the upper bound of the range to be sorted
* @return None
*/
private static void sort(int[] array, int low, int high)
{
if (low < high)
{
// Choose pivot and partition the array
int pivotIndex = partition(array, low, high);
// Recursively sort the left and right partitions
sort(array, low, pivotIndex);
sort(array, pivotIndex + 1, high);
}
}// end of sort method
/**
* Partitions an array within a given range and returns the pivot index
* @param array - the array to be partitioned, int - low - the lower bound of the range to be partitioned, int high - the upper bound of the range to be partitioned
* @return the pivot index
*/
private static int partition(int[] array, int low, int high)
{
int pivot = array[high];
int i = low - 1;
for (int j = low; j < high; j++)
{
if (array[j] <= pivot)
{
i++;
// Swap array[i] and array[j]
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
// Swap array[i + 1] and array[high]
int temp = array[i + 1];
array[i + 1] = array[high];
array[high] = temp;
return i + 1;
}// end of partition method
}// end of QuickSort class
Heap sort is a comparison-based sorting algorithm that is based on the binary heap data structure.
In Java, it can be implemented by first creating a max heap out of the input array, and then repeatedly extracting the maximum element from the heap and placing it at the end of the sorted array.
The process continues until the heap is empty, resulting in a sorted array in ascending order. It has a time complexity of O(n log n) and requires O(1) extra space.
class HeapSort
{
// Heap sort method
public static void heapSort(int[] arr)
{
int n = arr.length;
// Build heap (rearrange array)
for (int i = n / 2 - 1; i >= 0; i--)
{
heapify(arr, n, i);
}
// One by one extract an element from heap
for (int i=n-1; i>=0; i--)
{
// Move current root to end
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// call max heapify on the reduced heap
heapify(arr, i, 0);
}
}// end of method heapSort
// To heapify a subtree rooted with node i which is
// an index in arr[]. n is size of heap
static void heapify(int[] arr, int n, int i)
{
int largest = i; // Initialize largest as root
int l = 2*i + 1; // left = 2*i + 1
int r = 2*i + 2; // right = 2*i + 2
// If left child is larger than root
if (l < n && arr[l] > arr[largest])
{
largest = l;
}
// If right child is larger than largest so far
if (r < n && arr[r] > arr[largest])
{
largest = r;
}
// If largest is not root
if (largest != i)
{
int swap = arr[i];
arr[i] = arr[largest];
arr[largest] = swap;
// Recursively heapify the affected sub-tree
heapify(arr, n, largest);
}
}// end of heapify method
public static void main(String args[])
{
int[] arr = {12, 11, 13, 5, 6, 7};
heapSort(arr);
System.out.println("Sorted array is:");
for (int i=0; i<arr.length; ++i)
{
System.out.print(arr[i]+" ");
}
}// end of main method
}// end of class HeapSort
Searching algorithms are a set of methods used to locate specific data within a larger data set.
The most common types of searching algorithms include linear search, binary search, jump search, interpolation search, depth-first search, and breadth-first search.
Algorithm | Time Complexity | Space Complexity |
---|---|---|
Linear Search | O(n) | O(1) |
Binary Search | O(log n) | O(1) |
Jump Search | O(√n) | O(1) |
Interpolation Search | O(log log n) | O(1) |
Depth First Search | O(V + E) | O(V) |
Breadth First Search | O(V + E) | O(V) |
inear search is the simplest search algorithm and involves iterating through each element of the data set until the desired element is found.
The time complexity for this algorithm is O(n), where n is the number of elements in the data set.
public class LinearSearch
{
/**
* Method to implement Linear Search algorithm to find a specific element in an array.
* If the element doesn't exist in the array, the method returns -1.
* @param array - array, int x - the number we want to find
* @return int
*/
public static int linearSearch(int[] arr, int x)
{
for (int i = 0; i < arr.length; i++)
{
if (arr[i] == x)
{
return i;
}
}
return -1;
}// end of linearSearch method
public static void main(String[] args)
{
int[] arr = {2, 3, 4, 10, 40};
int x = 10;
int result = linearSearch(arr, x);
if (result == -1)
{
System.out.println("Element not present in array.");
}
else
{
System.out.println("Element found at index: " + result);
}
}// end of main method
}// end of class LinearSearch
Binary search is a more efficient search algorithm that works by repeatedly dividing the search interval in half.
The time complexity for this algorithm is O(log n), which makes it more efficient than linear search for large data sets.
public class BinarySearch
{
/**
* Method to implement Binary Search algorithm to find a specific element in an array.
* If the element doesn't exist in the array, the method returns -1.
* @param array, int x
* @return int
*/
public static int binarySearch(int[] arr, int x)
{
int left = 0;
int right = arr.length - 1;
while (left <= right)
{
int mid = left + (right - left) / 2;
if (arr[mid] == x)
{
return mid;
}
else if (arr[mid] < x)
{
left = mid + 1;
}
else
{
right = mid - 1;
}
}
return -1;
}// end of method binarySearch
public static void main(String[] args)
{
int[] arr = {2, 3, 4, 10, 40};
int x = 10;
int result = binarySearch(arr, x);
if (result == -1)
{
System.out.println("Element not present in array.");
}
else
{
System.out.println("Element found at index: " + result);
}
}// end of main method
}// end of class BinarySearch
Jump search is an algorithm that works by jumping a fixed number of elements at a time instead of iterating through each element. The time complexity for this algorithm is O(√n).
public class JumpSearch
{
/**
* Method to implement Jump Search algorithm to find a specific element in an array.
* If the element doesn't exist in the array, the method returns -1.
* @param array - the array to search through, int x - the element to search for
* @return int - the index of the element if found, -1 if not found
*/
public static int jumpSearch(int[] array, int x)
{
int n = array.length;
int step = (int) Math.sqrt(n);
int prev = 0;
while (array[Math.min(step, n) - 1] < x)
{
prev = step;
step += (int) Math.sqrt(n);
if (prev >= n)
{
return -1;
}
}
while (array[prev] < x)
{
prev++;
if (prev == Math.min(step, n))
{
return -1;
}
}
if (array[prev] == x)
{
return prev;
}
return -1;
}// end of method jumpSearch
public static void main(String[] args)
{
int[] array = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144};
int x = 55;
int index = jumpSearch(array, x);
if (index != -1)
{
System.out.println("Element found at index: " + index);
}
else
{
System.out.println("Element not found in array.");
}
}// end of main method
}// end of class JumpSearch
Interpolation search is a search algorithm for sorted arrays.
It works by making an educated guess about the position of the element in the array, based on the value of the element and the range of values in the array.
The algorithm then repeatedly narrows down the search space by determining if the guessed position is too high or too low, and then updating the guess based on this information.
The algorithm terminates when the element is found or when the search space has been narrowed down to a single element that is not the target.
Interpolation search is faster than a typical binary search when the elements in the array are uniformly distributed.
public class InterpolationSearch
{
/**
* Method to implement Interpolation Search algorithm to find a specific element in an array.
* If the element doesn't exist in the array, the method returns -1.
* @param array - the array to search through, int x - the element to search for
* @return int - the index of the element if found, -1 if not found
*/
public static int interpolationSearch(int[] array, int x)
{
int low = 0;
int high = array.length - 1;
while (low <= high && x >= array[low] && x <= array[high])
{
int pos = low + (((high - low) / (array[high] - array[low])) * (x - array[low]));
if (array[pos] == x)
{
return pos;
}
if (array[pos] < x)
{
low = pos + 1;
}
else
{
high = pos - 1;
}
}
return -1;
}// end of method interpolationSearch
public static void main(String[] args)
{
int[] array = {10, 12, 13, 16, 18, 19, 20, 21, 22, 23, 24, 33, 35, 42, 47};
int x = 18;
int index = interpolationSearch(array, x);
if (index != -1)
{
System.out.println("Element found at index: " + index);
}
else
{
System.out.println("Element not found in array.");
}
}// end of main method
}// end of InterpolationSearch class
Depth First Search (DFS) is a graph traversal algorithm that explores as far as possible along each branch before backtracking.
It starts at a specific vertex (or source node) and visits each vertex in the graph by following each edge as far as possible before backtracking.
DFS can be implemented using a stack data structure, where the vertices that have yet to be visited are pushed onto the stack, and the last vertex added to the stack is the next vertex to be visited.
The algorithm terminates when all vertices have been visited or when a specific goal vertex is found. DFS can be used to find the connected components of a graph, solve puzzles and games, and find the paths between two vertices in a graph.
import java.util.LinkedList;
import java.util.Queue;
public class DepthFirstSearch
{
static class TreeNode
{
int val;
TreeNode left;
TreeNode right;
TreeNode(int val)
{
this.val = val;
this.left = null;
this.right = null;
}
}// end of class TreeNode
/**
* Method to implement Depth First Search algorithm to find a specific element in a tree.
* If the element doesn't exist in the tree, the method returns false.
* @param root - the root of the tree, int x - the element to search for
* @return boolean - true if the element is found, false if not found
*/
public static boolean dfs(TreeNode root, int x)
{
if (root == null)
{
return false;
}
if (root.val == x)
{
return true;
}
boolean left = dfs(root.left, x);
boolean right = dfs(root.right, x);
return left || right;
}// end of method dfs
public static void main(String[] args)
{
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
int x = 4;
boolean found = dfs(root, x);
if (found)
{
System.out.println("Element found in tree.");
}
else
{
System.out.println("Element not found in tree.");
}
}// end of method main
}// end of class DepthFirstSearch
Breadth First Search (BFS) is a graph traversal algorithm that visits all the vertices of a graph in breadth-first order, meaning it visits all the vertices at a given depth level before moving on to the next level.
It starts at a specific vertex (or source node) and visits all its neighboring vertices first, before visiting the vertices at the next level.
BFS can be implemented using a queue data structure, where the vertices to be visited are added to the queue, and the first vertex in the queue is the next vertex to be visited.
The algorithm terminates when all vertices have been visited or when a specific goal vertex is found. BFS can be used to find the shortest path between two vertices in a graph, find the connected components of a graph and solve puzzles and games.
import java.util.LinkedList;
import java.util.Queue;
public class BreadthFirstSearch
{
/**
* method find a specific element in a tree using breadth first search
* if the element is not found, the method returns -1
* @param Node root - the root of the tree, int x - the element to search for
* @return int - the value of the element if found, -1 if not found
*/
public static int bfs(Node root, int x)
{
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty())
{
Node current = queue.poll();
if (current.value == x)
{
return current.value;
}
if (current.left != null)
{
queue.add(current.left);
}
if (current.right != null)
{
queue.add(current.right);
}
}
return -1;
}// end of method bfs
public static void main(String[] args)
{
// create a sample tree for demonstration
Node root = new Node(1);
root.left = new Node(2);
root.right = new Node(3);
root.left.left = new Node(4);
root.left.right = new Node(5);
int elementToFind = 4;
int result = bfs(root, elementToFind);
System.out.println("Value of element " + elementToFind + ": " + result);
}// end of main method
}// end of class BreadthFirstSearch
class Node
{
int value;
Node left;
Node right;
public Node(int value)
{
this.value = value;
left = null;
right = null;
}// end of method Node
}// end of class Node
The java.lang.Math class contains a variety of mathematical functions and constants. Some of the most commonly used methods include:
Function | Description |
---|---|
Math.abs(x) | Returns the absolute value of a given number. |
Math.ceil(x) | Returns the smallest integer greater than or equal to a given number. |
Math.floor(x) | Returns the largest integer less than or equal to a given number. |
Math.max(x, y) | Returns the larger of two given numbers. |
Math.min(x, y) | Returns the smaller of two given numbers. |
Math.pow(x, y) | Returns x raised to the power of y. |
Math.sqrt(x) | Returns the square root of a given number. |
Math.random() | Returns a random number between 0 and 1. |
Math.round(x) | Returns the closest integer to a given number. |
Math.sin(x) | Returns the sine of a given angle in radians. |
Math.cos(x) | Returns the cosine of a given angle in radians. |
Math.tan(x) | Returns the tangent of a given angle in radians. |
Math.asin(x) | Returns the arcsine of a given value in radians. |
Math.acos(x) | Returns the arccosine of a given value in radians. |
Math.atan(x) | Returns the arctangent of a given value in radians. |
Math.log(x) | Returns the natural logarithm of a given number. |
Math.log10(x) | Returns the base-10 logarithm of a given number. |
Math.E | The base of the natural logarithms (approximately 2.718). |
Math.PI | The ratio of the circumference of a circle to its diameter (approximately 3.14159). |
All of these functions are static, which means that you can call them directly on the Math class, without needing to create an instance of the class.
class MathClassExample
{
public static void main(String[] args)
{
// Using Math.abs()
int number = -5;
int absoluteValue = Math.abs(number);
System.out.println("Absolute value of " + number + " is " + absoluteValue);
// Using Math.ceil()
double decimal = 4.3;
double ceilValue = Math.ceil(decimal);
System.out.println("Smallest integer greater than or equal to " + decimal + " is " + ceilValue);
// Using Math.floor()
decimal = 5.8;
double floorValue = Math.floor(decimal);
System.out.println("Largest integer less than or equal to " + decimal + " is " + floorValue);
// Using Math.max()
int x = 10;
int y = 15;
int maxValue = Math.max(x, y);
System.out.println("Larger of " + x + " and " + y + " is " + maxValue);
// Using Math.min()
x = 20;
y = 25;
int minValue = Math.min(x, y);
System.out.println("Smaller of " + x + " and " + y + " is " + minValue);
// Using Math.pow()
x = 2;
y = 3;
double powerValue = Math.pow(x, y);
System.out.println(x + " raised to the power of " + y + " is " + powerValue);
}// end of main method
}// end of class MathClassExample
The String class in Java is a built-in class that represents a sequence of characters. It is one of the most widely used classes in Java, and it is used to represent text. Some of the key features of the String class include:
Immutable: Once a String object is created, its value cannot be changed. Any operation that modifies the value of a String will create a new String object.
Concatenation: The + operator can be used to concatenate two String objects. The concat() method can also be used to concatenate String objects.
Comparison: The equals() method can be used to compare the values of two String objects. The compareTo() method can be used to compare the lexicographic order of two String objects.
Searching: The indexOf() method can be used to find the index of a specific character or substring within a String. The lastIndexOf() method can be used to find the last occurrence of a specific character or substring within a String.
Substring: The substring() method can be used to extract a portion of a String.
Manipulation: The toLowerCase(), toUpperCase(), trim() method can be used to manipulate the string.
Split: The split() method can be used to split a String into an array of substrings based on a specified delimiter.
Formatting: The format() method can be used to format String in a specific way.
Regular expression: String class has matches() method that can be used to check if the string matches a specific regular expression.
These are some of the most common and useful methods of the String class. It also has many other methods that provide additional functionality.
Method | Description |
---|---|
char charAt(int index) | Returns the char value at the specified index |
int compareTo(String anotherString) | Compares two strings lexicographically |
int compareToIgnoreCase(String str) | Compares two strings lexicographically, ignoring case differences |
String concat(String str) | Concatenates the specified string to the end of this string |
boolean contains(CharSequence s) | Returns true if and only if this string contains the specified sequence of char values |
static String copyValueOf(char[] data) | Equivalent to valueOf(char[]) |
static String copyValueOf(char[] data, int offset, int count) | Equivalent to valueOf(char[], int, int) |
boolean endsWith(String suffix) | Tests if this string ends with the specified suffix |
boolean equals(Object anObject) | Compares this string to the specified object |
boolean equalsIgnoreCase(String anotherString) | Compares this String to another String, ignoring case considerations |
void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) | Copies characters from this string into the destination character array |
int indexOf(int ch) | Returns the index within this string of the first occurrence of the specified character |
int indexOf(int ch, int fromIndex) | Returns the index within this string of the first occurrence of the specified character, starting the search at the specified index |
int indexOf(String str) | Returns the index within this string of the first occurrence of the specified substring |
int indexOf(String str, int fromIndex) | Returns the index within this string of the first occurrence of the specified substring, starting at the specified index |
boolean isEmpty() | Returns true if, and only if, length() is 0 |
int lastIndexOf(int ch) | Returns the index within this string of the last occurrence of the specified character |
int lastIndexOf(int ch, int fromIndex) | Returns the index within this string of the last occurrence of the specified character, searching backward starting at the specified index |
int lastIndexOf(String str) | Returns the index within this string of the last occurrence of the specified substring |
int lastIndexOf(String str, int fromIndex) | Returns the index within this string of the last occurrence of the specified substring, searching backward starting at the specified index |
int length() | Returns the length of this string |
String replace(char oldChar, char newChar) | Returns a string resulting from replacing all occurrences of oldChar in this string with newChar |
String replace(CharSequence target, CharSequence replacement) | Replaces each substring of this string that matches the literal target sequence with the specified literal replacement sequence |
String replaceAll(String regex, String replacement) | Replaces each substring of this string that matches the given regular expression with the given replacement |
String replaceFirst(String regex, String replacement) | Replaces the first substring of this string that matches the given regular expression with the given replacement |
String[] split(String regex) | Splits this string around matches of the given regular expression |
String[] split(String regex, int limit) | Splits this string around matches of the given regular expression |
boolean startsWith(String prefix) | Tests if this string starts with the specified prefix. |
boolean startsWith(String prefix, int toffset) | Tests if the substring of this string beginning at the specified index starts with the specified prefix |
CharSequence subSequence(int beginIndex, int endIndex) | Returns a character sequence that is a subsequence of this sequenc |
String substring(int beginIndex) | Returns a string that is a substring of this string |
char[] toCharArray() | Converts this string to a new character array |
String toLowerCase() | Converts all of the characters in this String to lower case using the rules of the default locale |
String toLowerCase(Locale locale) | Converts all of the characters in this String to lower case using the rules of the given Locale |
String toUpperCase() | Converts all of the characters in this String to upper case using the rules of the default locale |
String toUpperCase(Locale locale) | Converts all of the characters in this String to upper case using the rules of the given Locale |
String trim() | Returns a string whose value is this string, with any leading and trailing whitespace removed |
StringBuffer is a class in Java that provides a mutable sequence of characters.
It is similar to the String class, but it can be modified after it is created. StringBuffer is thread-safe, meaning that multiple threads can access a single StringBuffer object without causing any problems.
Some common methods of the StringBuffer class include append, insert, and reverse. Additionally, StringBuffer has a capacity that can be increased if necessary to prevent reallocation of memory while concatenation.
Method | Description |
---|---|
append(Object obj) |
Appends the string representation of the Object argument. |
charAt(int index) |
Returns the char value in this sequence at the specified index. |
delete(int start,int end) |
Removes the characters in a substring of this sequence. |
deleteCharAt(int index) |
Removes the char at the specified position in this sequence. |
getChars(int srcBegin,int srcEnd,char[] dst,int dstBegin) |
Characters are copied from this sequence into the destination character array dst. |
equals(StringBuffer sb) |
Compares this object against the specified object. |
indexOf(String str) |
Returns the index within this string of the first occurrence of the specified substring. |
indexOf(String str,int fromIndex) |
Returns the index within this string of the first occurrence of the specified substring, starting at the specified index. |
insert(int offset,String str) |
Inserts the string into this character sequence. |
lastIndexOf(String str,int fromIndex) |
Returns the index within this string of the last occurrence of the specified substring. |
length() |
Returns the length (character count). |
replace(int start,int end,String str) |
Replaces the characters in a substring of this sequence with characters in the specified String. |
reverse() |
Causes this character sequence to be replaced by the reverse of the sequence. |
setCharAt(int index,char ch) |
The character at the specified index is set to ch. |
subSequence(int start,int end) |
Returns a new character sequence that is a subsequence of this sequence. |
substring(int start,int end) |
Returns a new String that contains a subsequence of characters currently contained in this sequence. |
trimToSize() |
Attempts to reduce storage used for the character sequence. |
In Java, methods are blocks of code that perform a specific task. They are used to encapsulate functionality and can be called multiple times within a program.
A method that has a return type of void, which means it does not return any value.
A method that has a return type and a method name, which means it returns a value of the specified type.
A method can be marked as static, which means it can be called without creating an instance of the class.
A method can take in parameters of different types and names, for example methodName(parameterType parameterName, parameterType parameterName) A method can be marked as final, which means it cannot be overridden by subclasses. A method can be marked as abstract, which means it has no implementation and must be implemented by subclasses.
It's worth noting that the return type can be any valid Java data type, including primitives, objects, and arrays. Also, the naming convention of methods are camelCase and the first letter is lowercase.
Method | Description |
---|---|
void returnType() |
A method that has a return type of void, which means it does not return any value. |
returnType methodName() |
A method that has a return type and a method name, which means it returns a value of the specified type. |
static returnType methodName() |
A method that is marked as static, which means it can be called without creating an instance of the class. |
methodName(parameterType parameterName) |
A method that takes in a parameter of the specified type and name. |
methodName(parameterType parameterName, parameterType parameterName) |
A method that takes in multiple parameters of different types and names. |
final returnType methodName() |
A method that is marked as final, which means it cannot be overridden by subclasses. |
abstract returnType methodName() |
A method that is marked as abstract, which means it has no implementation and must be implemented by subclasses. |
public class MethodExample
{
public static void main(String[] args)
{
printMessage();
int result = add(5, 10);
System.out.println("Result of addition: " + result);
double quotient = divide(20, 10);
System.out.println("Result of division: " + quotient);
int product = multiply(5, 10);
System.out.println("Result of multiplication: " + product);
}// end of main method
/**
* method print message
* @param None
* @return None
*/
public static void printMessage()
{
System.out.println("Hello, this is a message from the print method.");
}// end of method printMessage
/**
* method add two numbers
* @param int a, int b
* @return int
*/
public static int add(int a, int b)
{
return a + b;
}// end of method add
/**
* method divide two numbers
* @param double a, double b
* @return double
*/
public static double divide(double a, double b)
{
return a / b;
}// end of method divide
/**
* method multiply two numbers
* @param int a, int b
* @return int
*/
public static int multiply(int a, int b)
{
return a * b;
}// end of method multiply
}// end of class MethodExample
In Java, there are several ways to generate random numbers.
The most commonly used method is through the use of the "Random" class, which is part of the Java standard library.
This class provides methods for generating various types of random numbers, such as integers, doubles, and booleans. Additionally, the class also provides methods for generating random numbers within a specified range.
import java.util.Random;
public class RandomNumbers
{
public static void main(String[] args)
{
// Creating an object of the Random class
Random rand = new Random();
// Generating a random integer between 0 and 9 (inclusive)
int randomInt = rand.nextInt(10);
System.out.println("Random Integer: " + randomInt);
// Generating a random float between 0 and 1 (inclusive)
float randomFloat = rand.nextFloat();
System.out.println("Random Float: " + randomFloat);
// Generating a random double between 0 and 1 (inclusive)
double randomDouble = rand.nextDouble();
System.out.println("Random Double: " + randomDouble);
}// end of method main
}// end of class RandomNumbers
Recursion in Java is a programming technique where a method calls itself in order to solve a problem. The method has a base case, which is a simple and straightforward problem that can be solved without recursion, and a recursive case, which breaks down the problem into simpler subproblems that can be solved using the same method.
In order for a recursive method to terminate, it must have a base case that is reached at some point. If the base case is not reached, the method will continue calling itself indefinitely, resulting in a stack overflow error.
A common example of recursion is the factorial function, which calculates the factorial of a given number. This can be computed using a loop, but it can also be computed using recursion.
Recursive methods can also be more efficient than their iterative counterparts, as they can take advantage of the computer's stack memory to store temporary data.
However, recursion can also consume more memory and processing power, so it's important to use it judiciously and be mindful of the problem's complexities.
It's also important to be mindful of the problem's complexities when using recursion and that some problems can be solved more efficiently with an iterative approach.
public class Recursion
{
public static void main(String[] args)
{
// Call the recursive method
int result = factorial(5);
System.out.println("Factorial of 5: " + result);
}// end of method main
// Recursive method to calculate the factorial of a given number
public static int factorial(int n)
{
// Base case: if n is 0 or 1, return 1
if (n == 0 || n == 1)
{
return 1;
}
// Recursive case: return n * factorial(n-1)
else
{
return n * factorial(n-1);
}
}// end of method factorial
}// end of class Recursion
Reading and writing to a file in Java can be done using the File and Scanner classes for reading, and the FileWriter and PrintWriter classes for writing.
To read a file, you can first create a File object that represents the file you want to read, and then create a Scanner object that reads the contents of the file. You can then use the nextLine() method of the Scanner class to read each line of the file.
To write a file, you can first create a FileWriter object that represents the file you want to write to, and then create a PrintWriter object that writes to the file. You can then use the println() method of the PrintWriter class to write to the file.
It's important to handle the exception when reading and writing to a file in case the file doesn't exist or there's a problem with the file path.
When reading and writing to a file, it's also important to close the file when you're finished. This can be done by calling the close() method on the Scanner, FileWriter, and PrintWriter objects.
It's also worth noting that java.nio package offers another set of classes to perform file reading and writing operations such as Files, Paths and BufferedReader which offer better performance and flexibility over the traditional IO package.
import java.io.*;
public class WorkWithFile
{
public static void main(String[] args)
{
// File to read from
File file = new File("example.txt");
// File to write to
File outputFile = new File("output.txt");
try
{
// Reading from file
BufferedReader br = new BufferedReader(new FileReader(file));
String line;
while ((line = br.readLine()) != null)
{
System.out.println(line);
}
br.close();
// Writing to file
PrintWriter writer = new PrintWriter(new FileWriter(outputFile));
writer.println("Writing to file example");
writer.close();
}
catch (IOException e
{
System.out.println("An error occurred.");
e.printStackTrace();
}
}// end of main method
}// end of class WorkWithFile
A linked list is a data structure that consists of a sequence of elements, each containing a reference (or "link") to its next element. In Java, the LinkedList class is a commonly used implementation of a linked list.
One of the main advantages of a linked list over an array is that elements can be easily inserted or removed from the middle of the list. With an array, these operations would require shifting all of the elements after the insertion or removal point.
The LinkedList class in Java provides a variety of methods for working with the list, such as adding and removing elements, retrieving elements at specific positions, and searching for elements. It also implements the List interface, which means it has all the common methods that a list should have like add(), get(), remove(), size(), clear(), etc.
The LinkedList class uses a doubly-linked list implementation, which means that each element has a reference to both the next and the previous element in the list. This allows for efficient traversal in both directions.
It's worth noting that LinkedList is less efficient than an ArrayList when it comes to random access of elements (i.e. by index) since it needs to traverse the list from the head to the element's index.
It's also worth noting that linked lists require more memory than arrays, as each element requires a reference to the next element in addition to the element's value.
import java.io.*;
// Java program to implement
// a Singly Linked List
public class LinkedList
{
Node head; // head of list
// Linked list Node.
// This inner class is made static
// so that main() can access it
static class Node
{
int data;
Node next;
// Constructor
Node(int d)
{
data = d;
next = null;
} // end of method Node
} // end of class Node
// Method to insert a new node
public static LinkedList insert(LinkedList list, int data)
{
// Create a new node with given data
Node new_node = new Node(data);
// If the Linked List is empty,
// then make the new node as head
if (list.head == null)
{
list.head = new_node;
}
else
{
// Else traverse till the last node
// and insert the new_node there
Node last = list.head;
while (last.next != null)
{
last = last.next;
}
// Insert the new_node at last node
last.next = new_node;
}
// Return the list by head
return list;
} // end of method insert
// Method to print the LinkedList.
public static void printList(LinkedList list)
{
Node currNode = list.head;
System.out.print("LinkedList: ");
// Traverse through the LinkedList
while (currNode != null)
{
// Print the data at current node
System.out.print(currNode.data + " ");
// Go to next node
currNode = currNode.next;
}
} // end of method printList
// Method to delete a node in the LinkedList by KEY
public static LinkedList deleteByKey(LinkedList list,int key)
{
// Store head node
Node currNode = list.head, prev = null;
if (currNode != null && currNode.data == key)
{
list.head = currNode.next; // Changed head
// Display the message
System.out.println(key + " found and deleted");
// Return the updated List
return list;
}
while (currNode != null && currNode.data != key)
{
// If currNode does not hold key
// continue to next node
prev = currNode;
currNode = currNode.next;
}
// If the key was present, it should be at currNode
// Therefore the currNode shall not be null
if (currNode != null)
{
// Since the key is at currNode
// Unlink currNode from linked list
prev.next = currNode.next;
System.out.println(key + " found and deleted");
}
if (currNode == null)
{
// Display the message
System.out.println(key + " not found");
}
// return the List
return list;
}// end of method deleteByKey
// Method to delete a node in the LinkedList by POSITION
public static LinkedList deleteAtPosition(LinkedList list, int index)
{
// Store head node
Node currNode = list.head, prev = null;
if (index == 0 && currNode != null)
{
list.head = currNode.next; // Changed head
// Display the message
System.out.println(
index + " position element deleted");
// Return the updated List
return list;
}
while (currNode != null)
{
if (counter == index)
{
// Since the currNode is the required
// position Unlink currNode from linked list
prev.next = currNode.next;
// Display the message
System.out.println(
index + " position element deleted");
break;
}
else
{
// If current position is not the index
// continue to next node
prev = currNode;
currNode = currNode.next;
counter++;
}
}
// In this case, the currNode should be null
if (currNode == null)
{
// Display the message
System.out.println(index + " position element not found");
}
// return the List
return list;
}// end of method deleteAtPosition
public static void main(String[] args)
{
/* Start with the empty list. */
LinkedList list = new LinkedList();
// Insert the values
list = insert(list, 1);
list = insert(list, 2);
list = insert(list, 3);
list = insert(list, 4);
list = insert(list, 5);
list = insert(list, 6);
list = insert(list, 7);
list = insert(list, 8);
// Print the LinkedList
printList(list);
} // end of main method
}// end of class LinkedList
A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle, meaning that the last element added to the stack will be the first one to be removed. In Java, the Stack class is a commonly used implementation of a stack.
The Stack class provides a variety of methods for working with the stack, such as pushing and popping elements, retrieving the top element, checking if the stack is empty, and determining the current size of the stack. The most important methods of the stack are push(), which adds an element to the top of the stack, pop(), which removes and returns the top element of the stack, and peek(), which returns the top element of the stack without removing it.
It's worth noting that the Stack class is considered as legacy class and it's not recommended to use it, instead of that it's recommended to use the Deque interface and it's implementations, such as ArrayDeque and LinkedList.
It's also worth noting that stack can be implemented using an array, a linked list, or any other data structure. The choice of implementation will depend on the specific requirements of the application and the trade-offs in terms of performance, memory usage, and ease of implementation.
import java.util.Deque;
import java.util.ArrayDeque;
public class StackExample
{
public static void main(String[] args)
{
// Creating a stack using the ArrayDeque class
Deque<Integer> stack = new ArrayDeque<>();
// Adding elements to the stack
stack.push(1);
stack.push(2);
stack.push(3);
// Retrieving and removing the top element
int top = stack.pop();
System.out.println("Top element: " + top);
// Retrieving the top element without removing it
top = stack.peek();
System.out.println("Top element: " + top);
// Checking if the stack is empty
boolean isEmpty = stack.isEmpty();
System.out.println("Is the stack empty? " + isEmpty);
// Determining the size of the stack
int size = stack.size();
System.out.println("Size of the stack: " + size);
}// end of method main
}// end of class StackExample
A tree is a non-linear data structure that consists of a set of nodes, where each node can have one or more child nodes. In Java, the Tree interface and the DefaultMutableTreeNode class are commonly used to implement a tree.
Trees can be used to represent hierarchical relationships, such as file systems, family trees, or decision-making processes. Each node in the tree represents an element, and the relationship between nodes represents a parent-child relationship.
The Tree interface provides methods for working with the tree structure, such as adding, removing, and retrieving nodes, as well as traversing the tree in various ways, such as breadth-first or depth-first. The DefaultMutableTreeNode class is a concrete implementation of the Tree interface that can be used to create a tree and add nodes to it.
In Java, there are different types of tree data structures, such as binary trees, where each node can have at most two children, and binary search trees, where the left child node has a value less than the parent node and the right child node has a value greater than the parent node.
class BinaryTree
{
Node root;
public void add(int data)
{
root = addRecursive(root, data);
}// end of method add
private Node addRecursive(Node current, int data)
{
if (current == null)
{
return new Node(data);
}
if (data < current.data)
{
current.left = addRecursive(current.left, data);
}
else if (data > current.data)
{
current.right = addRecursive(current.right, data);
}
else
{
return current;
}
return current;
}// end of method addRecursive
public boolean containsNode(int data)
{
return containsNodeRecursive(root, data);
}// end of method containsNode
private boolean containsNodeRecursive(Node current, int data)
{
if (current == null)
{
return false;
}
if (data == current.data)
{
return true;
}
return data < current.data ? containsNodeRecursive(current.left, data) : containsNodeRecursive(current.right, data);
}// end of method containsNodeRecursive
public void delete(int data)
{
root = deleteRecursive(root, data);
}// end of delete method
private Node deleteRecursive(Node current, int data)
{
if (current == null)
{
return null;
}
if (data == current.data)
{
if (current.left == null && current.right == null)
{
return null;
}
if (current.right == null)
{
return current.left;
}
if (current.left == null)
{
return current.right;
}
int smallestValue = findSmallestValue(current.right);
current.data = smallestValue;
current.right = deleteRecursive(current.right, smallestValue);
return current;
}
if (data < current.data)
{
current.left = deleteRecursive(current.left, data);
return current;
}
current.right = deleteRecursive(current.right, data);
return current;
}// end of deleteRecursive methdo
private int findSmallestValue(Node root)
{
return root.left == null ? root.data : findSmallestValue(root.left);
}// end of method findSmallestValue
public void traverseInOrder(Node node)
{
if (node != null)
{
traverseInOrder(node.left);
System.out.print(" " + node.data);
traverseInOrder(node.right);
}
}// end of method traverseInOrder
}// end of class BinaryTree
In Java, aliasing occurs when two or more references refer to the same object in memory. This can lead to unexpected behavior, particularly when the object is modified through one reference and the change is not reflected through the other references. To avoid aliasing, it is best practice to use only one reference to an object, and to create new objects when necessary.
One of the most common ways to avoid aliasing in Java is to use the clone() method. The clone() method creates a new object that is an exact copy of the original object. The new object is completely independent of the original object and any changes made to the new object will not affect the original object.
Another way to avoid aliasing is to use immutable objects. Immutable objects are objects whose state cannot be modified after they are created. Examples of immutable objects in Java are the String, Integer, and BigDecimal classes.
In addition, it is important to use proper encapsulation techniques to limit the scope of references and make it clear which objects are being referred to. This can be done through the use of private fields and getter/setter methods.
In summary, aliasing can be avoided in Java by using the clone() method, immutable objects, and proper encapsulation techniques. These techniques help ensure that changes made to an object through one reference do not affect other references and prevent unexpected behavior.
The static keyword in Java is used to indicate that a member (field or method) belongs to a class rather than an instance of the class. This means that the member is shared by all instances of the class and can be accessed without creating an instance of the class.
Here are some key points about the static keyword in Java:
Static fields and methods can be accessed directly through the class name, without the need to create an instance of the class.
Static fields and methods are initialized only once, when the class is loaded by the JVM.
Static methods cannot access non-static fields and methods, as they do not have access to an instance of the class.
Static fields can be accessed by both static and non-static methods.
Static block is used to initialize the static variable, it gets executed only once when the class is loaded in the memory.
It is commonly used for utility classes, factory classes, and for constants.
In summary, the static keyword in Java is used to indicate that a member belongs to a class rather than an instance of the class. It allows fields and methods to be accessed directly through the class name, without the need to create an instance of the class and allows shared resources across all instances of a class.
It is commonly used for utility classes, factory classes, and for constants.
class StaticKeyword
{
static int staticInt = 0; //static variable
public static void staticMethod()
{
staticInt++;
System.out.println("This is a static method. staticInt = " + staticInt);
}// end of method staticMethod
}// end of method StaticKeyword
JavaDoc is a tool that is used to generate documentation for Java classes, methods, and fields. It uses special comments in the source code, called Javadoc comments, that begin with /** and end with */, to extract documentation. The documentation is then presented in the form of HTML pages that can be easily viewed in a web browser.
JavaDoc uses a set of tags, such as @param and @return, to specify different types of information about the class, method, or field being documented. The generated documentation includes a summary of the class, method, or field, as well as details about its parameters, return type, and exceptions.
JavaDoc is typically used to document the public API of a Java library or application, so that other developers can understand how to use the library or application.
Here are some key points about JavaDoc:
It is a documentation tool for generating API documentation in HTML format from Java source code
It uses special comments starting with /** and ending with */ in the source code
It uses tags to specify different types of information about the class, method, or field being documented
It is typically used to document the public API of a Java library or application
It is integrated with most of the modern IDEs like Eclipse, IntelliJ, Netbeans etc.
In summary, JavaDoc is a tool that generates documentation for Java classes, methods, and fields using special comments in the source code. It uses tags to specify
different types of information about the class, method, or field being documented and it is typically used to document the public API of a Java library or application.
/**
* This class demonstrates the use of JavaDoc
*
* @author liron mizrhai
*/
class JavaDoc
{
/**
* The main method that runs the program
* @param args Command line arguments
* @return None
*/
public static void main(String[] args)
{
System.out.println("Hello, this is a JavaDoc example!");
}// end of method main
}// end of class JavaDoc
In Java, access modifiers are keywords used to specify the level of access to a class, method, or variable. The four access modifiers in Java are:
public: A public class, method, or variable can be accessed from anywhere in the program.
protected: A protected method or variable can be accessed within the same package or by a subclass in a different package. default (no keyword): A default class, method, or variable can be accessed within the same package.
private: A private method or variable can be accessed only within the same class. Here are some key points about access modifiers in Java:
Access modifiers are used to restrict the level of access to a class, method, or variable. The most restrictive access modifier is private, followed by default, protected, and then public.
The default access level for a class, method, or variable is package-private. Classes and interfaces can only be declared public or default.
Inner classes can be declared with any access modifier. If a class is declared public, it must be saved in a file with the same name as the class and the file should be in the root of the source code directory.
In summary, Access modifiers in Java are keywords that specify the level of access to a class, method, or variable. There are four access modifiers: public, protected, default (no keyword), and private. Public members have the least restrictions, while private members have the most.
The default access level for a class, method, or variable is package-private. Access modifiers are used to restrict the level of access to a class, method, or variable and can be used to ensure encapsulation and information hiding.
Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code that manipulates that data. Java is an object-oriented programming language, which means that it supports OOP concepts such as classes, objects, inheritance, and polymorphism.
A class is a blueprint for creating objects (a particular data structure), providing initial values for state (member variables or attributes), and implementations of behavior (member functions or methods). An object is an instance of a class and can be created using the "new" keyword.
Inheritance is the mechanism of deriving a new class from an existing one. The new class is known as the derived class or the child class and the class from which the new class is derived is known as the base class or the parent class. This allows for code reuse and a hierarchical class structure.
Polymorphism is the ability of an object to take on many forms. In Java, polymorphism is achieved through method overloading and method overriding. Method overloading allows a class to have multiple methods with the same name but different parameters. Method overriding allows a derived class to provide a specific implementation of a method that is already provided by its base class.
Overall, OOP in Java allows for the creation of reusable and modular code, and a clear structure for organizing and manipulating data.
public class Opp
{
// state (attributes)
private String name;
private int age;
// constructor
public Opp(String name, int age)
{
this.name = name;
this.age = age;
}// end of method Opp
// behavior (methods) setters and getters
public void setName(String name)
{
this.name = name;
}// end of method setName
public String getName()
{
return name;
}// end of method getName
public void setAge(int age)
{
this.age = age;
}// end of method setAge
public int getAge() {
return age;
}
public void displayInfo()
{
System.out.println("Name: " + name);
System.out.println("Age: " + age);
}// end of method displayInfo
public static void main(String[] args)
{
Opp obj = new Opp("John Doe", 30); // creating an object
obj.displayInfo(); // calling displayInfo() method
}// end of main method
}// end of class Opp
This is a simple example of a class named Opp that demonstrates the use of object-oriented programming concepts in Java. The class has two private member variables name and age which are the state of the object. The class has a constructor Opp(String name, int age) that allows the user to set the values of the member variables.
The class also contains several methods, such as setName(), getName(), setAge(), getAge() and displayInfo() that allow the user to manipulate the state of the object, as well as to display the values of the member variables.
In the main method, an object of the Opp class is created and passed the value of name and age, then the displayInfo() method is called to display the value of name and age.
This example demonstrates how OOP concepts can be used to create a class that encapsulates the state and behavior of an object, making it easy to create and manipulate objects of that class.
Inheritance is a mechanism in object-oriented programming that allows a new class to inherit the properties and methods of an existing class. This allows for code reuse and a hierarchical class structure in which a derived class can inherit the characteristics of a base class.
In Java, a class can be derived from another class using the extends keyword. The derived class is also known as a subclass or child class, and the class from which it is derived is known as the superclass or parent class.
For example, imagine that you have a base class called Animal that has properties like name, age, weight, and methods like eat(), sleep() and move(). And you want to create a new class called Cat which have all the properties and methods of Animal class and also some additional properties and methods like color and sound(). You can create a class Cat by using extends keyword like this:
public class Cat extends Animals
{
private String color;
public void sound()
{
System.out.println("Meow!");
}// end of method sound
}// end of class Cat thet extends Animals
In summary, inheritance in Java allows for code reuse, a hierarchical class structure, and polymorphism. It enables the creation of new classes that inherit the properties and methods of existing classes, making it easier to create and manipulate objects of those classes.
Polymorphism is a concept in object-oriented programming that allows an object to take on many forms. It allows objects of different classes to be treated as objects of a common superclass, enabling them to be used interchangeably. In Java, polymorphism is achieved through method overloading and method overriding.
Method overloading allows a class to have multiple methods with the same name but different parameters. For example, a class might have two methods with the same name, print(), but one takes an int parameter and the other takes a String parameter.
When the print() method is called with an int parameter, the version of the method that takes an int parameter will be called, and when it's called with a String parameter, the version that takes a String parameter will be called.
Method overriding allows a derived class to provide a specific implementation of a method that is already provided by its base class. This allows objects of the derived class to be used in place of objects of the base class, since they have the same interface.
For example, if a base class Animal has a method sound() that prints "generic sound", a derived class Cat can override the sound() method to print "Meow!" instead.
In summary, polymorphism in Java allows objects of different classes to be treated as objects of a common superclass and to be used interchangeably. It is achieved through method overloading, which allows a class to have multiple methods with the same name but different parameters, and method overriding, which allows a derived class to provide a specific implementation of a method from its base class. This enables the creation of more flexible and reusable code.
class Shape
{
public void draw()
{
System.out.println("Drawing a shape");
}// end of draw method
}// end of class Shape
class Rectangle extends Shape
{
@Override
public void draw()
{
System.out.println("Drawing a rectangle");
}// end of draw method
}// end of class Rectangle extends Shape
class Circle extends Shape
{
@Override
public void draw()
{
System.out.println("Drawing a circle");
}// end of draw method
}// end of class Circle that extends Shape
public class PolymorphismExample
{
public static void main(String[] args)
{
Shape shape1 = new Shape();
Shape shape2 = new Rectangle();
Shape shape3 = new Circle();
shape1.draw(); // "Drawing a shape"
shape2.draw(); // "Drawing a rectangle"
shape3.draw(); // "Drawing a circle"
}// end of method main
}// end of method PolymorphismExample
An enumeration is a special type of class in Java that defines a set of predefined values. Enumerations, also known as enums, are used to represent a fixed set of constants, such as the days of the week or the suits in a deck of cards.
An enum is declared using the enum keyword, followed by the name of the enumeration and a list of enumeration constants enclosed in curly braces. For example:
enum Day
{
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
The enumeration constants are automatically given a unique integer value, starting from 0, and can be accessed using the name of the enumeration. For example, Day.MONDAY refers to the enumeration constant MONDAY in the Day enumeration.
Enumerations are useful in situations where a variable can only take on a fixed set of values, as they provide type safety and make the code more readable.
They also have some additional properties such as ordinal() which returns the index of the enumeration constant, name() which returns the name of the enumeration constant, and compareTo() which compares the enumeration constants based on their ordinal values.
In summary, enums in Java are special types of classes that define a set of predefined constants. They are useful in situations where a variable can only take on a fixed set of values, as they provide type safety and make the code more readable.
They also have additional properties such as ordinal(), name() and compareTo() which provide more functionality to work with the enumeration constants.
In Java, a constant is a variable whose value cannot be modified after it is assigned. Constants are often used to represent fixed values that are used throughout a program, such as mathematical constants (e.g. pi), physical constants (e.g. the speed of light), or application-specific constants (e.g. the number of pixels in an image).
There are two ways to create constants in Java:
Using the final keyword: A variable can be declared as final by using the final
final int DAYS_IN_WEEK = 7;
Using static final keyword: A variable can be declared as static final by using both static and final keyword. Once a static final variable is assigned a value, it cannot be reassigned. For example:
static final double PI = 3.14159;
It is also a common convention to use all uppercase letters and underscores to separate words when naming constants, to make them easy to recognize.
In summary, constants are variables whose value cannot be modified after it is assigned. They are used to represent fixed values that are used throughout a program. Java provides two ways to create constants: using the final keyword and using static final keyword.
Constants are commonly named with all uppercase letters and underscores to separate words, to make them easy to recognize.
public class Constant
{
public static final double PI = 3.14159; // constant
public static final int DAYS_IN_WEEK = 7; // constant
public static final String NAME = "John Doe"; // constant
public static void main(String[] args)
{
double radius = 5;
double area = PI * radius * radius;
System.out.println("Area of circle with radius " + radius + " is " + area);
System.out.println("Number of days in a week is " + DAYS_IN_WEEK);
System.out.println("Name is " + NAME);
}// end of main method
}// end of class Constant
Exception handling is a mechanism in Java that allows a program to handle and recover from errors, known as exceptions, that occur during the execution of the program. Exceptions are events that occur during the execution of a program that disrupt the normal flow of instructions.
When an exception occurs, the program generates an object of the class Throwable or one of its subclasses, known as an exception object.
Java provides a built-in mechanism for exception handling, which includes the try-catch statement and the throw statement. The try-catch statement is used to enclose a block of code that might throw an exception. The catch block is used to handle the exception and provide an appropriate response. For example:
try
{
// code that might throw an exception
}
catch (ExceptionType e)
{
// code to handle the exception
}
The throw statement is used to explicitly throw an exception. It can be used to signal that an abnormal condition has occurred and that the program cannot continue. For example:
if (input < 0)
{
throw new IllegalArgumentException("Input cannot be negative");
}
Java also provides a finally block which is used to execute some code regardless of whether an exception is thrown or not. It is typically used to release resources such as file handles or network connections that were acquired in the try block.
In summary, exception handling in Java is a mechanism that allows a program to handle and recover from errors that occur during the execution of the program. It includes the try-catch statement, the throw statement and the finally block.
The try-catch statement is used to enclose a block of code that might throw an exception, the catch block is used to handle the exception and provide an appropriate response, the throw statement is used to explicitly throw an exception and the finally block is used to execute some code regardless of whether an exception is thrown or not, typically used to release resources.
In Java, a custom exception is a user-defined exception class that inherits from one of the exception classes provided by the Java standard library. Custom exceptions are used to handle specific error conditions that are not covered by the built-in exception classes. They can be used to provide a more informative error message or to handle the error condition in a specific way.
To create a custom exception, you need to create a new class that inherits from one of the exception classes provided by the Java standard library, such as Exception, RuntimeException, or IOException. For example, you can create a custom exception class called InvalidAgeException that inherits from Exception:
public class InvalidAgeException extends Exception
{
public InvalidAgeException(String message)
{
super(message);
}// end of method InvalidAgeException
}// end of class InvalidAgeException that extends Exception
Then you can throw the custom exception in your code when a specific error condition occurs, such as an invalid age is passed as an argument:
if (age < 0)
{
throw new InvalidAgeException("Age cannot be negative");
}
And you can catch the custom exception and handle it accordingly:
try
{
// code that might throw InvalidAgeException
}
catch (InvalidAgeException e)
{
}
The Object class is the root class of all classes in Java. It is the superclass of all other classes and provides a basic set of methods and fields that are inherited by all other classes. The Object class is located in the java.lang package, which is automatically imported into all Java programs, so you don't need to import it explicitly.
The Object class provides several important methods such as:
- equals(): compares the current object with another object for equality.
- hashCode(): returns a hash code value for the object.
- toString(): returns a string representation of the object.
- getClass(): returns the runtime class of an object.
- finalize() : method that is invoked before an object is garbage collected.
The Object class also provides a basic implementation of the clone() method, which creates a new object that is a copy of the current object. However, it's a protected method, so it can be overridden by subclasses to provide a more specific implementation.
In summary, the Object class is the root class of all classes in Java. It provides a basic set of methods and fields that are inherited by all other classes, including the equals(), hashCode(), toString(), getClass() and finalize() methods. It also provides a basic implementation of the clone() method which creates a new object that is a copy of the current object.
class ObjectClass
{
public static void main(String[] args)
{
// creating two objects of ObjectClass
ObjectClass obj1 = new ObjectClass();
ObjectClass obj2 = new ObjectClass();
// using the equals method of Object class to compare the two objects
System.out.println("obj1 and obj2 are equal: " + obj1.equals(obj2));
// using the toString method of Object class to return a string representation of the object
System.out.println("String representation of obj1: " + obj1.toString());
// using the getClass method of Object class to return the runtime class of an object
System.out.println("Class of obj1: " + obj1.getClass());
}// end of main method
}// end of class ObjectClass
Time complexity in Java refers to the amount of time it takes for an algorithm or program to run, usually measured in terms of the size of the input. The most common time complexities in Java are:
-
O(1) or constant time, which means the runtime does not depend on the size of the input
-
O(log n) or logarithmic time, which means the runtime increases logarithmically with the size of the input
-
O(n) or linear time, which means the runtime is directly proportional to the size of the input
-
O(n log n) which means the runtime increases by n log n with the size of the input
-
O(n^2) or quadratic time, which means the runtime increases exponentially with the size of the input.
When analyzing and comparing algorithms, it's important to consider the time complexity and choose the one that has the best performance for the specific problem and input size.
class TimeComplexity
{
public static void main(String[] args)
{
int[] arr = {1, 2, 3, 4, 5};
// O(1) example
System.out.println(arr[2]); // constant time, no matter how big the array is, it will always take the same time to access the 3rd element
// O(n) example
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " "); // linear time, the amount of time it takes will increase with the size of the input
}
// O(n^2) example
for (int i = 0; i < arr.length; i++)
{
for (int j = 0; j < arr.length; j++)
{
System.out.print(arr[i] + " " + arr[j] + " "); // quadratic time, the amount of time it takes will increase exponentially with the size of the input
}
}
}// end of main method
}// end of class TimeComplexity
Space complexity in Java refers to the amount of memory used by an algorithm or program, usually measured in terms of the size of the input. The most common space complexities in Java are:
-
O(1) or constant space, which means the memory usage does not depend on the size of the input
-
O(n) or linear space, which means the memory usage is directly proportional to the size of the input
-
O(n^2) or quadratic space, which means the memory usage increases exponentially with the size of the input.
When analyzing and comparing algorithms, it's important to consider the space complexity and choose the one that has the best performance for the specific problem and input size. In some situations, using less memory may be more important than using less time, and vice versa.
It's also worth noting that in addition to the memory usage of the algorithm itself, the memory usage of any data structures used by the algorithm, such as arrays and lists, should also be taken into account.
It's important to be aware of the space complexity of an algorithm to prevent running out of memory or using too much memory, especially when working with large datasets or when using limited resources such as mobile devices.
class SpaceComplexity
{
public static void main(String[] args)
{
int[] arr = {1, 2, 3, 4, 5};
// O(1) example
int constant = 3; // constant space, the amount of memory used does not depend on the size of the input
// O(n) example
int[] linear = arr.clone(); // linear space, the amount of memory used is directly proportional to the size of the input
// O(n^2) example
int[][] quadratic = new int[arr.length][arr.length]; // quadratic space, the amount of memory used increases exponentially with the size of the input
for (int i = 0; i < arr.length; i++)
{
for (int j = 0; j < arr.length; j++)
{
quadratic[i][j] = arr[i] * arr[j];
}
}
}// end of main method
}// end of class SpaceComplexity
A queue is a linear data structure in Java that follows the First-In-First-Out (FIFO) principle. It is a collection of elements that are added to the back of the queue and removed from the front of the queue.
The main operations performed on a queue are enqueue (add an element to the back of the queue) and dequeue (remove an element from the front of the queue).
Java provides a built-in implementation of a queue in the form of the Queue interface and several classes that implement it such as LinkedList, PriorityQueue, ArrayDeque which are all part of the Java Collection Framework.
The LinkedList class, for example, provides a add() method for adding an element to the back of the queue and a remove() method for removing an element from the front of the queue. PriorityQueue is a priority queue, elements are dequeued in order of priority. ArrayDeque is a resizable array implementation of the Deque interface, it's faster than the linked list implementation.
Queues are used in a variety of applications, such as:
-
simulating real-world scenarios where items are added and removed in a first-in-first-out order, such as a line of customers at a store
-
implementing algorithms that require breadth-first traversal, such as breadth-first search
-
coordination between multiple threads or processes, such as a task queue for a worker thread
It's worth noting that, Queues are not thread-safe, which means they are not safe to use in a multi-threaded environment without additional synchronization. Java provides thread-safe implementations of queues in the form of ConcurrentLinkedQueue and BlockingQueue classes.
import java.util.LinkedList;
import java.util.Queue;
class QueuesExample
{
public static void main(String[] args)
{
Queue<Integer> queue = new LinkedList<>();
// Enqueue elements
queue.add(1);
queue.add(2);
queue.add(3);
// Dequeue elements
System.out.println(queue.remove()); // output: 1
System.out.println(queue.remove()); // output: 2
// Check the front element without dequeueing it
System.out.println(queue.peek()); // output: 3
// Check if the queue is empty
System.out.println(queue.isEmpty()); // output: false
// Dequeue all remaining elements
while (!queue.isEmpty())
{
System.out.print(queue.remove() + " "); // output: 3
}
}// end of main method
}// end of class QueuesExample
Generics in Java is a feature that allows developers to write reusable, type-safe code. With generics, developers can define a class, interface, or method that can work with multiple types of data, rather than being restricted to a single type. This allows for increased code reusability and eliminates the need for explicit type casting.
To use generics, developers use angle brackets < > to define a type parameter, which is a placeholder for a specific type that will be provided when the class, interface, or method is instantiated or invoked. For example, a generic class for a stack might be defined as Stack, where T is the type parameter.
Generics are typically used in collection classes such as List, Set, and Queue to specify the type of objects that they can hold. For example, a List is a list of integers, and a List is a list of strings.
In addition to classes and interfaces, generics can also be used in method declarations. A generic method is a method that has a type parameter, which can be used as the type of its return value or as the type of one or more of its parameters.
For example, a generic method that swaps the positions of two elements in an array could be defined as follows:
public static <T> void swap(T[] array, int i, int j)
{
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}// end of method swap
Here, the type parameter T is used to specify the type of the elements in the array, and the method can be used with arrays of any type.
It's worth mentioning that, Generics were introduced in Java SE 5, it provides a way for creating a single class, interface or method that works with multiple types of data. It eliminates the need for explicit type casting and allows for increased code reusability, type-safety and stronger type checking.
A unary operator in Java is an operator that performs an operation on a single operand. The operand can be a variable, a constant, or an expression. There are several types of unary operators in Java:
Arithmetic unary operators: These operators perform arithmetic operations on a single operand. The most common arithmetic unary operators are + and -. For example, -x negates the value of x and +x returns the value of x.
Increment and decrement operators: These operators increment or decrement the value of a variable by 1. The increment operator (++) adds 1 to the variable, and the decrement operator (--) subtracts 1 from the variable. The increment and decrement operators can be used in both prefix and postfix notation.
Logical unary operators: These operators perform logical operations on a single operand. The most common logical unary operator is the negation operator (!). For example, !true returns false.
Bitwise unary operator: These operators perform bitwise operations on a single operand, the most common bitwise unary operator is the bitwise complement operator (~) which inverts all the bits of the operand.
class UnaryOperator
{
public static void main(String[] args)
{
int x = 5;
int y = -10;
// Arithmetic unary operator
System.out.println(-x); // output: -5
System.out.println(+y); // output: -10
// Increment and decrement operator
int z = 0;
System.out.println(++z); // output: 1
System.out.println(z++); // output: 1
System.out.println(z); // output: 2
// Logical unary operator
boolean flag = true;
System.out.println(!flag); // output: false
// Bitwise unary operator
int a = 10;
System.out.println(~a); // output: -11
// Typecasting unary operator
double pi = 3.14;
int intPi = (int) pi;
System.out.println(intPi); // output: 3
}// end of method main
}// end of class UnaryOperator
Typecasting unary operator: The unary operator () can be used to cast the operand to a different type.
It's worth noting that, the unary operator can be used before or after the operand, depending on the operator, the position of the operator can affect the value of the expression. The increment and decrement operators, for example, behave differently when used in prefix notation (++x) versus postfix notation (x++).
A HashMap in Java is a data structure that stores key-value pairs and uses a hash function to map keys to their corresponding values.
It allows for fast lookups, insertions, and deletions of elements by key. It is implemented using an array and a linked list, where each element in the array is a linked list of key-value pairs that share the same hash code.
The HashMap is part of the Java Collections Framework and is not thread-safe by default, but it can be made thread-safe by using the java.util.Collections.synchronizedMap() method.
import java.util.HashMap;
public class HashMapExample
{
public static void main(String[] args)
{
// Create a new HashMap object
HashMap<String, Integer> map = new HashMap<>();
// Add key-value pairs to the map
map.put("Apple", 1);
map.put("Banana", 2);
map.put("Cherry", 3);
// Print the entire map
System.out.println("Original map: " + map);
// Get the value for a specific key
int value = map.get("Banana");
System.out.println("Value for key 'Banana': " + value);
// Check if a key is in the map
boolean containsKey = map.containsKey("Apple");
System.out.println("Map contains key 'Apple': " + containsKey);
// Check if a value is in the map
boolean containsValue = map.containsValue(3);
System.out.println("Map contains value 3: " + containsValue);
// Remove a key-value pair from the map
map.remove("Cherry");
System.out.println("Map after removing key 'Cherry': " + map);
// Clear the entire map
map.clear();
System.out.println("Map after clear: " + map);
}// end of method main
}// end of class HashMapExample
the output:
-
Original map: {Apple=1, Banana=2, Cherry=3}
-
Value for key 'Banana': 2
-
Map contains key 'Apple': true
-
Map contains value 3: true
-
Map after removing key 'Cherry': {Apple=1, Banana=2}
-
Map after clear: {}
A Hashtable in Java is a data structure that stores key-value pairs and uses a hash function to map keys to their corresponding values.
It is similar to a HashMap, but it is synchronized, meaning that only one thread can access the Hashtable at a time. This makes Hashtable a thread-safe alternative to HashMap.
The Hashtable class is part of the original Java version 1.0, and it does not allow null keys or null values. The method used to access elements in a Hashtable is Enumeration.
import java.util.Enumeration;
import java.util.Hashtable;
public class HashTableExample
{
public static void main(String[] args)
{
// Create a new HashTable object
Hashtable<String, Integer> table = new Hashtable<>();
// Add key-value pairs to the table
table.put("Apple", 1);
table.put("Banana", 2);
table.put("Cherry", 3);
// Print the entire table
System.out.println("Original table: " + table);
// Get the value for a specific key
int value = table.get("Banana");
System.out.println("Value for key 'Banana': " + value);
// Check if a key is in the table
boolean containsKey = table.containsKey("Apple");
System.out.println("Table contains key 'Apple': " + containsKey);
// Check if a value is in the table
boolean containsValue = table.containsValue(3);
System.out.println("Table contains value 3: " + containsValue);
// Remove a key-value pair from the table
table.remove("Cherry");
System.out.println("Table after removing key 'Cherry': " + table);
// Iterating over the key-value pairs
Enumeration<String> keys = table.keys();
while (keys.hasMoreElements())
{
String key = keys.nextElement();
Integer val = table.get(key);
System.out.println("Key: " + key + " Value: " + val);
}// end of while
}// end of method main
}// end of class HashTableExample
output :
-
Original table: {Apple=1, Banana=2, Cherry=3}
-
Value for key 'Banana': 2
-
Table contains key 'Apple': true
-
Table contains value 3: true
-
Table after removing key 'Cherry': {Apple=1, Banana=2}
-
Key: Apple Value: 1
-
Key: Banana Value: 2
A HashSet in Java is a collection that stores unique elements in no particular order. It is implemented using a hash table, which provides fast access to elements for operations such as adding, removing, and checking if an element is contained in the set.
HashSet does not guarantee any specific order of the elements.
It is useful for cases where you need to quickly check if an element is present in a set, or remove an element from a set. HashSet does not allow duplicate elements.
import java.util.HashSet;
public class HashSetExample
{
public static void main(String[] args)
{
// Create a new HashSet
HashSet<String> set = new HashSet<>();
// Add elements to the HashSet
set.add("Apple");
set.add("Banana");
set.add("Cherry");
// Attempt to add a duplicate element
set.add("Apple");
// Check the size of the HashSet
System.out.println("Size of HashSet: " + set.size()); // Output: Size of HashSet: 3
// Check if an element is present in the HashSet
System.out.println("Is Orange in HashSet: " + set.contains("Orange")); // Output: Is Orange in HashSet: false
// Remove an element from the HashSet
set.remove("Banana");
// Iterate through the HashSet and print the elements
for (String fruit : set)
{
System.out.println(fruit);
}
}// end of main method
}// end of class HashSetExample
In Java, the postfix and prefix operators are used to increment or decrement a variable by a certain amount.
The postfix operator (e.g. i++) increments the variable after it has been used in the expression, while the prefix operator (e.g. ++i) increments the variable before it has been used in the expression.
For example, consider the following code:
int i = 5;
int j = i++;
In this case, the value of j will be set to 5, and then the value of i will be incremented to 6.
On the other hand, consider the following code:
int i = 5;
int j = ++i;
In this case, the value of i will be incremented to 6, and then the value of j will be set to 6.
In summary, postfix and prefix operators in Java are used to increment or decrement a variable before or after it is used in the expression.
The prefix operator is more efficient than postfix operator and the choice of which one to use depends on the specific use case and the desired behavior of the program.
The ternary operator in Java is a shorthand way of writing an if-else statement. It has the following syntax:
condition ? expression1 : expression2
The ternary operator takes a condition as its first parameter, followed by a question mark (?), and then two expressions separated by a colon (:). If the condition is true, the operator returns the first expression; otherwise, it returns the second expression.
Here's an example:
int x = 5;
int y = 10;
int max = (x > y) ? x : y;
In this example, the ternary operator compares the values of x and y, and assigns the larger value to the max variable. This is equivalent to the following if-else statement:
int max;
if (x > y)
{
max = x;
}
else
{
max = y;
}
In short, the Ternary operator is a shorthand way of writing if-else statements and it's much more concise. It's useful when you have to return a value based on a boolean condition and it's easy to read when the expressions are simple and short.
An abstract class in Java is a class that cannot be instantiated but can have abstract and concrete methods. An abstract method is a method that has a method signature but no implementation. Concrete methods are methods that have a method signature and an implementation.
An abstract class can also have constructors and fields, but it cannot be used to create objects. It is meant to be subclassed, with the subclasses providing the implementation for the abstract methods.
A class that contains at least one abstract method must be declared as abstract. Subclasses of an abstract class must provide an implementation for all of its abstract methods.
Here's an example of an abstract class:
abstract class Shape
{
// Fields
private String color;
// Constructor
public Shape(String color)
{
this.color = color;
}
// Abstract method
public abstract double getArea();
// Concrete method
public String getColor()
{
return color;
}
}// end of abstract class Shape
In this example, the Shape class is an abstract class that cannot be instantiated. It has a constructor, a field, an abstract method (getArea) which does not have implementation and a concrete method (getColor) which have implementation.
Subclasses such as Circle and Rectangle would need to provide an implementation for the getArea method.
In summary, an abstract class is a class that cannot be instantiated and contains abstract and concrete methods, fields and constructors. The abstract methods are meant to be overridden by subclasses and provide a common interface for all subclasses.
In Java, an interface is a collection of abstract methods (methods without a body) and constant fields (static final variables). It defines a contract that classes must adhere to, specifying a set of methods that a class must implement.
A class can implement multiple interfaces. When a class implements an interface, it must provide an implementation for all of the interface's methods.
Here is an example of an interface:
interface Shape
{
double getArea();
double getPerimeter();
}// end of interface Shape
In this example, the Shape interface defines two abstract methods, getArea and getPerimeter, that classes implementing this interface must provide an implementation for.
Here's an example of a class that implements the Shape interface:
class Circle implements Shape
{
private double radius;
public Circle(double radius)
{
this.radius = radius;
}// end of method Circle
public double getArea()
{
return Math.PI * radius * radius;
}// end of method getArea
public double getPerimeter()
{
return 2 * Math.PI * radius;
}// end of method getPerimeter
}// end of class Circle
An anonymous inner class in Java is a class without a name that is defined and instantiated in a single expression. It is typically used as an implementation of a functional interface, such as a listener or callback, and is defined and instantiated at the point of use.
Anonymous inner classes are useful for creating small, one-time-use classes, and can be more concise than defining a named inner class.
public class AnonymousInnerClass
{
public static void main(String[] args)
{
// Create an instance of an anonymous inner class that implements Runnable
Runnable runnable = new Runnable()
{
@Override
public void run()
{
System.out.println("Running from anonymous inner class");
}
};
// Run the anonymous inner class
new Thread(runnable).start();
}// end of method main
}// end of class AnonymousInnerClass
In Java, Optional is a container object which is used to contain a value that may be null or non-null. The purpose of the Optional class is to provide a type-level solution for representing optional values instead of using null references.
It has several methods to check the presence of a value, to retrieve a value, or to perform an action on the value if it's present.
It is introduced in Java 8, and it is a part of the java.util package. Optional class can be used as an alternative to null checks and provide a way to handle null values in a more elegant way.
You can create an Optional instance by calling the of() or ofNullable() method. Once an Optional instance is created, you can use the isPresent() method to check if it contains a value, and the get() method to retrieve the value.
You can also use the orElse() method to provide a default value if the Optional is empty or use orElseGet() and orElseThrow() to handle the case where the Optional is empty.
import java.util.Optional;
public class OptionalExample
{
public static void main(String[] args)
{
// Create an Optional instance that contains a value
Optional<String> optionalString = Optional.of("Hello, Optional!");
// Use the isPresent() method to check if the Optional contains a value
if (optionalString.isPresent())
{
// Use the get() method to retrieve the value from the Optional
System.out.println(optionalString.get());
}
// Create an Optional instance that does not contain a value
Optional<String> emptyOptional = Optional.empty();
// Use the orElse() method to provide a default value if the Optional is empty
System.out.println(emptyOptional.orElse("Default value"));
// Use the orElseGet() method to provide a default value using a Supplier
System.out.println(emptyOptional.orElseGet(() -> "Default value from supplier"));
// Use the orElseThrow() method to throw an exception if the Optional is empty
try
{
emptyOptional.orElseThrow(IllegalArgumentException::new);
}
catch (Exception e)
{
System.out.println("Caught exception: " + e.getMessage());
}
// Use the ifPresent() method to perform an action if the Optional contains a value
optionalString.ifPresent(System.out::println);
}// end of method main
}// end of class OptionalExample
Lambda expressions are a feature introduced in Java 8 that allow for functional programming and more concise code. They are often used to implement functional interfaces, which are interfaces with a single abstract method.
The basic syntax of a lambda expression is as follows:
(parameters) -> {body}
For example:
(int x, int y) -> x + y
- Lambda expressions can be used to replace anonymous inner classes in situations where a functional interface is expected.
- Lambda expressions can be used to sort collections using the sort() method, by passing a comparator as an argument.
- Lambda expressions can be used to pass behavior as an argument to a method, such as with the forEach() method of the Stream API.
- Lambda expressions can be used to filter collections using the filter() method of the Stream API.
- Lambda expressions can be used to transform collections using the map() method of the Stream API.
- Lambda expressions can be used to perform aggregate operations on collections using the reduce() method of the Stream API.
Example:
List<Person> people = new ArrayList<>();
people.add(new Person("Bob", 25));
people.add(new Person("Alice", 30));
people.add(new Person("Charlie", 20));
people.sort((p1, p2) -> p1.name.compareTo(p2.name));
In the above example, we are using lambda expression to sort the people list based on name of the person.
In general, lambda expressions provide a more concise and readable way to express behavior in Java and are often used in functional programming.