Java

Learning Java opens up a wide array of topics, as it's a versatile programming language commonly used for developing various types of applications, from web applications to mobile apps, desktop software, and even embedded systems.

Variables

Variables are fundamental components of programming languages, including Java. They are used to store data temporarily during the execution of a program. In Java, variables have a specific data type, which determines the type of data they can hold.

Variable Declaration

Before using a variable, you need to declare it. This involves specifying the variable's name and its data type.

Example:

int age; // Declaring an integer variable named 'age'

double salary; // Declaring a double variable named 'salary'

String name; // Declaring a String variable named 'name' 

Variable Initialization

After declaring a variable, you can optionally initialize it, which means assigning it an initial value. Uninitialized variables in Java have a default value depending on their data type (e.g., 0 for numeric types, false for boolean, null for reference types).

Example:

int age = 30; // Initializing the 'age' variable with the value 30

double salary = 50000.50; // Initializing the 'salary' variable with the value 50000.50

String name = "John"; // Initializing the 'name' variable with the value "John" 

Naming Rules

  • Variable names must begin with a letter, dollar sign ($), or underscore (_). 
  • Subsequent characters can be letters, digits, dollar signs, or underscores.
  • Variable names are case-sensitive. Variable names cannot be a Java keyword or reserved word.

Variable Demonstration

Variables play a crucial role in storing and manipulating data in Java programs, making them an essential concept to understand for any Java developer or anyone who wants to learn or program in Java.

Example:

public class VariablesExample {
      public static void main(String[] args) {
          int age = 30; // declaring and initializing an integer variable 'age'
          double salary = 50000.50; // declaring and initializing a double variable 'salary'
          String name = "John"; // declaring and initializing a String variable 'name'

          System.out.println("Name: " + name);
          System.out.println("Age: " + age);
          System.out.println("Salary: $" + salary);
      }
  } 

Output:

Name: John
Age: 30
Salary: $50000.5

Scope

  • The scope of a variable determines where in the program it can be accessed.
  • Java supports local variables, instance variables (also known as fields), and class variables (static fields), each with its own scope rules.

Local Variables

  • Definition: Local variables are declared within a method, constructor, or block of code.
  • Scope: They are accessible only within the block in which they are declared.
  • Lifetime: Their lifetime is limited to the execution of the block in which they are declared.

Example:

public void exampleMethod() {
     int localVar = 10; // localVar is a local variable
     // localVar can only be used within this method
     System.out.println(localVar);
 } 

Instance Variables (Fields)

  • Definition: Instance variables are declared within a class but outside any method, constructor, or block.
  • Scope: They are accessible to all methods, constructors, and blocks in the class.
  • Lifetime: They exist as long as the object to which they belong exists.

Example:

public class MyClass {
     int instanceVar; // instanceVar is an instance variable
     // instanceVar can be accessed by any method in MyClass
 } 

Class Variables (Static Fields)

  • Definition: Class variables are declared with the static keyword within a class but outside any method, constructor, or block.
  • Scope: They are accessible to all instances of the class and can be accessed using the class name.
  • Lifetime: They exist as long as the class is loaded in memory.

Example:

public class MyClass {
     static int classVar; // classVar is a class variable
     // classVar can be accessed using MyClass.classVar
 } 

Here's a summary of the Scope Rules:

  • Local variables take precedence over instance variables and class variables if they share the same name within a smaller scope.
  • Instance variables are specific to each instance of a class.
  • Class variables are shared among all instances of a class.

Primitive Data Types

  • byte: 8-bit signed integer. Range: -128 to 127.
  • short: 16-bit signed integer. Range: -32,768 to 32,767.
  • int: 32-bit signed integer. Range: -2147483648 to 2147483647.
  • long: 64-bit signed integer. Range: -9223372036854775808 to 9223372036854775807.
  • float: 32-bit floating-point number. It's recommended to use float when memory space is a concern.
  • double: 64-bit floating-point number. Java's default choice for decimal values.
  • char: 16-bit Unicode character. Represents a single character within single quotes (e.g., 'A', '7', '$').
  • boolean: Represents true or false values. Used for logical operations.

public class DataTypesExample {
     public static void main(String[] args) {

         // Primitive data types
         byte myByte = 127;
         short myShort = 32000;
         int myInt = 2147483647;
         long myLong = 9223372036854775807L; // Note the 'L' suffix for long literals
         float myFloat = 3.14f; // Note the 'f' suffix for float literals
         double myDouble = 2.71828;
         char myChar = 'A';
         boolean myBoolean = true;

          // Output
         System.out.println("Primitive Data Types:");
         System.out.println("byte: " + myByte);
         System.out.println("short: " + myShort);
         System.out.println("int: " + myInt);
         System.out.println("long: " + myLong);
         System.out.println("float: " + myFloat);
         System.out.println("double: " + myDouble);
         System.out.println("char: " + myChar);
         System.out.println("boolean: " + myBoolean);        
     }
 } 

Output:

Primitive Data Types:
byte: 127
short: 32000
int: 2147483647
long: 9223372036854775807
float: 3.14
double: 2.71828
char: A
boolean: true

Non-Primitive (Reference) Data Types

Objects of Classes: In Java, objects are instances of classes. Classes define the structure and behavior of objects.

Arrays: Arrays are collections of elements of the same type. They can hold primitive data types or objects.

public class DataTypesExample {
     public static void main(String[] args) {        

          // Reference data types
         String myString = "Hello, Java!";
         int[] myArray = {1, 2, 3, 4, 5}; // Array of integers

          // Output      
         System.out.println("nReference Data Types:");
         System.out.println("String: " + myString);
         System.out.print("Array: [");
         for (int i = 0; i < myArray.length; i++) {
             System.out.print(myArray[i]);
             if (i < myArray.length - 1) {
                 System.out.print(", ");
             }
         }
         System.out.println("]");
     }
 } 

Output:

Reference Data Types:
String: Hello, Java!
Array: [1, 2, 3, 4, 5] 

Constants

Constants play a crucial role in Java programming, especially when you want to define values that should remain constant throughout the execution of a program. In Java, constants are declared using the final keyword, which ensures that their values cannot be modified after initialization.

Declaring Constants

public class ConstantsExample {

     // Constant using the 'final' keyword
     public static final int MAX_VALUE = 100;
     public static final double PI = 3.14159;
     public static final String APPLICATION_NAME = "MyApp";
 } 

Using Constants

public class Main {
     public static void main(String[] args) {
         System.out.println("Max value: " + ConstantsExample.MAX_VALUE);
         System.out.println("PI: " + ConstantsExample.PI);
         System.out.println("Application Name: " + ConstantsExample.APPLICATION_NAME);
     }
 } 

Benefits of Constants

  • Readability: Constants improve code readability by giving meaningful names to values.
  • Maintainability: If a value needs to be changed, you only need to modify it in one place (the constant declaration).
  • Preventing Bugs: Constants help prevent accidental modifications of values that should remain constant.
  • Compiler Optimization: Constants declared with final are optimized by the compiler, potentially improving performance.

Best Practices for Constants

  • Use uppercase letters and underscores to name constants (e.g., MAX_VALUE, PI, APPLICATION_NAME).
  • Declare constants at the class level (static) if they are shared among multiple instances or methods.
  • Use final keyword to ensure immutability.
  • Group related constants in appropriate classes or interfaces.

Example of grouping related constants in an Interface

Constants are an essential part of Java programming, providing a way to define and use fixed values throughout your codebase.

public interface MathConstants {
     double PI = 3.14159;
     double E = 2.71828;
 }

  public class Main {
     public static void main(String[] args) {
         System.out.println("PI: " + MathConstants.PI);
         System.out.println("E: " + MathConstants.E);
     }
 } 

Operators

Operators in Java are symbols used to perform operations on variables and values. Java supports a wide range of operators, categorized into different types based on their functionality.

Arithmetic Operators

  • Addition (+): Adds two operands. 
  • Subtraction (-): Subtracts the right operand from the left operand. 
  • Multiplication (*): Multiplies two operands. 
  • Division (/): Divides the left operand by the right operand. 
  • Modulus (%): Returns the remainder of the division of the left operand by the right operand. 
  • Increment (++): Increases the value of a variable by 1. 
  • Decrement (--): Decreases the value of a variable by 1.

public class ArithmeticOperatorsExample {
     public static void main(String[] args) {
         int a = 10;
         int b = 5;

         // Addition
         int sum = a + b;
         System.out.println("Sum: " + sum);

         // Subtraction
         int difference = a - b;
         System.out.println("Difference: " + difference);

         // Multiplication
         int product = a * b;
         System.out.println("Product: " + product);

         // Division
         int quotient = a / b;
         System.out.println("Quotient: " + quotient);

         // Modulus
         int remainder = a % b;
         System.out.println("Remainder: " + remainder);

         // Increment
         a++;
         System.out.println("Incremented a: " + a);

         // Decrement
         b--;
         System.out.println("Decremented b: " + b);
     }
 } 

Relational Operators

  • Equal to (==): Checks if two operands are equal. 
  • Not equal to (!=): Checks if two operands are not equal. 
  • Greater than (>): Checks if the left operand is greater than the right operand. 
  • Less than (<): Checks if the left operand is less than the right operand. 
  • Greater than or equal to (>=): Checks if the left operand is greater than or equal to the right operand. 
  • Less than or equal to (<=): Checks if the left operand is less than or equal to the right operand.

public class RelationalOperatorsExample {
     public static void main(String[] args) {
         int x = 5;
         int y = 10;

         // Equal to
         System.out.println("Is x equal to y? " + (x == y));

         // Not equal to
         System.out.println("Is x not equal to y? " + (x != y));

         // Greater than
         System.out.println("Is x greater than y? " + (x > y));

         // Less than
         System.out.println("Is x less than y? " + (x < y));

         // Greater than or equal to
         System.out.println("Is x greater than or equal to y? " + (x >= y));

         // Less than or equal to
         System.out.println("Is x less than or equal to y? " + (x <= y));
     }
 } 

Logical Operators

  • Logical AND (&&): Returns true if both operands are true. 
  • Logical OR (||): Returns true if either of the operands is true. 
  • Logical NOT (!): Returns true if the operand is false and vice versa.

public class LogicalOperatorsExample {
     public static void main(String[] args) {
         boolean a = true;
         boolean b = false;

         // Logical AND
         System.out.println("a AND b: " + (a && b));

         // Logical OR
         System.out.println("a OR b: " + (a || b));

         // Logical NOT
         System.out.println("NOT a: " + (!a));
     }
 } 

Assignment Operators

  • Assignment (=): Assigns the value of the right operand to the left operand. 
  • Compound Assignment (+=, -=, *=, /=, %=): Performs an arithmetic operation on the variable and assigns the result to the variable.

public class AssignmentOperatorsExample {
     public static void main(String[] args) {
         int x = 10;

         // Assignment
         int y = x;
         System.out.println("y: " + y);

         // Compound Assignment
         x += 5;
         System.out.println("x after compound assignment: " + x);
     }
 } 

Bitwise Operators

  • Bitwise AND (&): Performs a bitwise AND operation. 
  • Bitwise OR (|): Performs a bitwise OR operation. 
  • Bitwise XOR (^): Performs a bitwise XOR (exclusive OR) operation. 
  • Bitwise Complement (~): Flips the bits of its operand. 
  • Left Shift (<<): Shifts the bits of the left operand to the left by the number of positions specified by the right operand. 
  • Right Shift (>>): Shifts the bits of the left operand to the right by the number of positions specified by the right operand. 
  • Unsigned Right Shift (>>>): Shifts the bits of the left operand to the right by the number of positions specified by the right operand, filling the leftmost bits with zeros.

public class BitwiseOperatorsExample {
     public static void main(String[] args) {
         int a = 5; // 101 in binary
         int b = 3; // 011 in binary

         // Bitwise AND
         int bitwiseAnd = a & b; // Result: 001 (1 in decimal)
         System.out.println("Bitwise AND: " + bitwiseAnd);

         // Bitwise OR
         int bitwiseOr = a | b; // Result: 111 (7 in decimal)
         System.out.println("Bitwise OR: " + bitwiseOr);

         // Bitwise XOR
         int bitwiseXor = a ^ b; // Result: 110 (6 in decimal)
         System.out.println("Bitwise XOR: " + bitwiseXor);

         // Bitwise Complement
         int bitwiseComplement = ~a; // Result: 11111111111111111111111111111010 (-6 in decimal)
         System.out.println("Bitwise Complement of a: " + bitwiseComplement);

         // Left Shift
         int leftShift = a << 1; // Result: 1010 (10 in decimal)
         System.out.println("Left Shift of a: " + leftShift);

         // Right Shift
         int rightShift = a >> 1; // Result: 10 (2 in decimal)
         System.out.println("Right Shift of a: " + rightShift);
     }
 } 

Conditional Operator (Ternary Operator)

  • Conditional Operator (?:): Evaluates a boolean expression and returns one of two values based on the result of the evaluation.

public class ConditionalOperatorExample {
     public static void main(String[] args) {
         int x = 5;
         int y = 10;

         // Conditional Operator (Ternary Operator)
         int max = (x > y) ? x : y;
         System.out.println("Max value: " + max);
     }
 } 

Loops

Control flow structures in Java, such as if-else statements and loops, allow you to control the execution of your code based on certain conditions or to repeat blocks of code multiple times.

These control flow structures allow you to make decisions and repeat actions based on certain conditions, making your programs more flexible and powerful.

If-Else Statements

  • The if-else statement is used to execute a block of code if a specified condition is true, and another block of code if the condition is false.

public class IfElseExample {
     public static void main(String[] args) {
         int number = 10;

         // If-else statement
         if (number % 2 == 0) {
             System.out.println("Even number");
         } else {
             System.out.println("Odd number");
         }
     }
 } 

Nested If-Else Statements

  • You can also nest if-else statements to check for multiple conditions.

public class NestedIfElseExample {
     public static void main(String[] args) {
         int age = 20;
         boolean isCitizen = true;

         // Nested if-else statement
         if (age >= 18) {
             if (isCitizen) {
                 System.out.println("You are eligible to vote");
             } else {
                 System.out.println("You are not a citizen");
             }
         } else {
             System.out.println("You are not eligible to vote");
         }
     }
 } 

Switch Statement

  • The switch statement is used to select one of many code blocks to be executed.

public class SwitchExample {
     public static void main(String[] args) {
         int day = 3;
         String dayName;

          // Switch statement
         switch (day) {
             case 1:
                 dayName = "Monday";
                 break;
             case 2:
                 dayName = "Tuesday";
                 break;
             case 3:
                 dayName = "Wednesday";
                 break;
             default:
                 dayName = "Unknown";
         }
          System.out.println("Day: " + dayName);
     }
 } 

For Loop

  • The for loop is used to execute a block of code a specified number of times.

public class ForLoopExample {
     public static void main(String[] args) {

         // For loop to print numbers from 1 to 5
         for (int i = 1; i <= 5; i++) {
             System.out.println(i);
         }
     }
 } 

While Loop

  • The while loop is used to repeatedly execute a block of code as long as a specified condition is true.

public class WhileLoopExample {
     public static void main(String[] args) {
         int i = 1;

          // While loop to print numbers from 1 to 5
         while (i <= 5) {
             System.out.println(i);
             i++;
         }
     }
 } 

Do-While Loop

  • The do-while loop is similar to the while loop, but the block of code is executed at least once, even if the condition is false.

public class DoWhileLoopExample {
     public static void main(String[] args) {
         int i = 1;

          // Do-while loop to print numbers from 1 to 5
         do {
             System.out.println(i);
             i++;
         } while (i <= 5);
     }
 } 

Object Oriented Programming (OOP)

Object-oriented programming (OOP) is a programming paradigm that revolves around the concept of objects, which can contain data (attributes) and code (methods). In Java, OOP is heavily utilized, and it revolves around several key principles.

In Java, these principles of OOP help in writing modular, maintainable, and scalable code. They promote code reusability, extensibility, and organization, making it easier to manage large and complex software projects.       

Classes

  • A class is a blueprint for creating objects. It defines the properties (attributes) and behaviors (methods) that objects of the class will have.

public class Car {

     // Attributes
     String make;
     String model;
     int year;

     // Methods
     public void start() {
         System.out.println("Car started");
     }

      public void stop() {
         System.out.println("Car stopped");
     }
 } 

Objects

  • An object is an instance of a class. It represents a specific entity with its own unique set of properties and behaviors.

public class Main {
     public static void main(String[] args) {

        // Creating objects of the Car class
         Car myCar = new Car();
         myCar.make = "Toyota";
         myCar.model = "Corolla";
         myCar.year = 2020;

          // Calling methods on the object
         myCar.start();
         myCar.stop();
     }
 } 

Inheritance

  • Inheritance is a mechanism in which a new class (subclass or derived class) is created from an existing class (superclass or base class). The subclass inherits the properties and behaviors of the superclass and can also add its own additional features.

// Superclass
 public class Vehicle {
     String make;
     String model;

      public void drive() {
         System.out.println("Vehicle is driving");
     }
 }

// Subclass
 public class Car extends Vehicle {
     int year;

      public void start() {
         System.out.println("Car started");
     }

      public void stop() {
         System.out.println("Car stopped");
     }
 } 

Polymorphism

  • Polymorphism allows objects of different classes to be treated as objects of a common superclass. This allows methods to be called on objects without knowing their specific type at compile time.

// Superclass
 public class Animal {
     public void makeSound() {
         System.out.println("Some sound");
     }
 }

  // Subclasses
 public class Dog extends Animal {
     public void makeSound() {
         System.out.println("Bark");
     }
 }

  // Subclasses
 public class Cat extends Animal {
     public void makeSound() {
         System.out.println("Meow");
     }
 } 

Encapsulation

  • Encapsulation is the bundling of data (attributes) and methods that operate on the data into a single unit (class). It allows for data hiding, where the internal state of an object is hidden from outside access and can only be accessed through public methods.

public class Employee {
     private String name;
     private double salary;

      public String getName() {
         return name;
     }

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

      public double getSalary() {
         return salary;
     }

      public void setSalary(double salary) {
         this.salary = salary;
     }
 } 

Abstraction

Abstraction is a fundamental concept in object-oriented programming that focuses on hiding the implementation details of a system and showing only the essential features of an object. It allows programmers to deal with objects at a higher level of abstraction without worrying about the internal complexities.  

In Java, abstraction is achieved through two main mechanisms (1) Abstract Class, and (2) Interfaces.

Abstract Class

  • An abstract class is a class that cannot be instantiated and may contain abstract methods (methods without a body) that must be implemented by its subclasses. Abstract classes can also contain concrete methods with a body.

// Abstract class
 abstract class Shape {

     // Abstract method (no body)
     public abstract double area();

      // Concrete method
     public void display() {
         System.out.println("This is a shape.");
     }
 }

// Concrete subclass
 class Rectangle extends Shape {
     private double length;
     private double width;

      // Constructor
     public Rectangle(double length, double width) {
         this.length = length;
         this.width = width;
     }

      // Implementation of abstract method
     public double area() {
         return length * width;
     }
 }

// Usage
 public class Main {
     public static void main(String[] args) {
         Rectangle rectangle = new Rectangle(5, 3);
         System.out.println("Area of rectangle: " + rectangle.area());
         rectangle.display();
     }
 }

Interfaces

  • An interface in Java is a reference type that contains only abstract methods (methods without a body), default methods (methods with a body and the default keyword), static methods, and constant variables. Interfaces provide a way to achieve full abstraction in Java.

// Interface
 interface Shape {

     // Abstract method
     double area();

      // Static method
     static void display() {
         System.out.println("This is a shape.");
     }
 }

// Concrete class implementing the interface
 class Rectangle implements Shape {
     private double length;
     private double width;

      // Constructor
     public Rectangle(double length, double width) {
         this.length = length;
         this.width = width;
     }

      // Implementation of abstract method
     public double area() {
         return length * width;
     }
 }

// Usage
 public class Main {
     public static void main(String[] args) {
         Rectangle rectangle = new Rectangle(5, 3);
         System.out.println("Area of rectangle: " + rectangle.area());
         Shape.display();
     }
 }

Benefits of Abstraction

  • Hide Complexity: Abstraction hides the complex implementation details, allowing users to focus on essential functionalities. 
  • Encapsulation: Abstraction and encapsulation go hand in hand, as abstraction allows for defining interfaces without specifying the implementation details. 
  • Code Reusability: Abstraction promotes code reuse by allowing classes to inherit from abstract classes or implement interfaces, enabling polymorphism. 
  • Abstraction is a powerful concept that helps in creating modular, maintainable, and scalable software systems by focusing on essential features and hiding unnecessary details.

Exception Handling

Exception handling in Java allows you to gracefully handle unexpected or exceptional situations that may occur during the execution of a program. These situations, known as exceptions, can arise due to various reasons such as invalid user input, network issues, file I/O errors, etc. Java provides a robust exception handling mechanism to deal with such scenarios.

Exception handling in Java helps in writing robust and reliable code by providing mechanisms to deal with unexpected situations gracefully. It's essential to handle exceptions appropriately to ensure the stability and reliability of your Java applications.

Types of Exceptions

  • Java divides exceptions into two main types (1) Checked Exception, and (2) Unchecked (Runtime) Exception.
  • Checked Exceptions: These are exceptions that must be handled either by catching them using a try-catch block or by declaring them in the method's throws clause. Examples include IOException, SQLException, etc. 
  • Unchecked Exceptions (Runtime Exceptions): These are exceptions that don't need to be explicitly handled. Examples include NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException, etc.

Handling Exceptions: Try-Catch Block

  • The try-catch block is used to catch exceptions and handle them gracefully.

try {
     // Code that may throw an exception
     int result = 10 / 0;
 // This will throw an ArithmeticException
 } catch (ArithmeticException e) {
     // Handle the exception
     System.out.println("An arithmetic exception occurred: " + e.getMessage());
 } 

Handling Exceptions: Multiple Catch Blocks

  • You can have multiple catch blocks to handle different types of exceptions.

try {
     // Code that may throw an exception
     int[] arr = new int[5];
     System.out.println(arr[10]);
 // This will throw an ArrayIndexOutOfBoundsException
 } catch (ArithmeticException e) {
     // Handle arithmetic exception
 } catch (ArrayIndexOutOfBoundsException e) {
     // Handle array index out of bounds exception
 } catch (Exception e) {
     // Handle other types of exceptions
 } 

Handling Exceptions: Finally Blocks

  • The finally block is used to execute code that needs to be executed whether an exception occurs or not (e.g., resource cleanup).

try {
     // Code that may throw an exception
 } catch (Exception e) {
     // Handle the exception
 } finally {
     // Code that always executes, regardless of whether an exception occurs or not
 } 

Throwing Exceptions

  • You can also manually throw exceptions using the throw keyword.

public void withdraw(double amount) throws InsufficientFundsException {
     if (balance < amount) {
         throw new InsufficientFundsException("Insufficient funds in the account");
     }
     // Withdraw the amount
 } 

Custom Exceptions

  • You can create your own custom exception classes by extending the Exception class.

public class CustomException extends Exception {
     public CustomException(String message) {
         super(message);
     }
 } 

Collections Framework

The Java Collections Framework provides a set of interfaces and classes to store, manipulate, and retrieve groups of objects. It offers a wide range of data structures, including lists, sets, and maps, each with its own characteristics and use cases.

The Java Collections Framework is a powerful tool for managing collections of objects efficiently and conveniently. It forms the backbone of many Java applications, providing essential data structures for storing and manipulating data.

Lists

  • Lists in Java represent an ordered collection of elements where each element has an index. The main implementations of the List interface are ArrayList and LinkedList.

Lists: ArrayList

  • Implements a dynamic array that can grow or shrink in size as needed.

List arrayList = new ArrayList<>();
 arrayList.add(1);
 arrayList.add(2);
 arrayList.add(3); 

Lists: LinkedList

  • Implements a doubly-linked list where elements are linked to their previous and next elements.

List linkedList = new LinkedList<>();
 linkedList.add(1);
 linkedList.add(2);
 linkedList.add(3); 

Sets

  • Sets in Java represent a collection of unique elements where duplicates are not allowed. The main implementations of the Set interface are HashSet, TreeSet, and LinkedHashSet.

Sets: HashSet

  • Implements a hash table-based set where elements are stored using their hash code.

Set hashSet = new HashSet<>();
 hashSet.add("apple");
 hashSet.add("banana");
 hashSet.add("apple"); // Duplicate element 

Sets: TreeSet

  • Implements a sorted set using a Red-Black tree, where elements are stored in sorted order.

Set treeSet = new TreeSet<>();
 treeSet.add(3);
 treeSet.add(1);
 treeSet.add(2); 

Sets: LinkedHashSet

  • Implements a hash table-based set with a predictable iteration order (insertion order).

Set linkedHashSet = new LinkedHashSet<>();
 linkedHashSet.add("apple");
 linkedHashSet.add("banana");
 linkedHashSet.add("apple"); // Duplicate element 

Maps: HashMap

  • Maps in Java represent a collection of key-value pairs, where each key is unique. The main implementations of the Map interface are HashMap, TreeMap, and LinkedHashMap.

Maps: HashMap

  • Implements a hash table-based map where key-value pairs are stored using their hash code.

Map hashMap = new HashMap<>();
 hashMap.put("one", 1);
 hashMap.put("two", 2);
 hashMap.put("one", 3); // Overwrites the value for key "one" 

Maps: TreeMap

  • Implements a sorted map using a Red-Black tree, where key-value pairs are stored in sorted order based on the keys.

Map treeMap = new TreeMap<>();
 treeMap.put(3, "C");
 treeMap.put(1, "A");
 treeMap.put(2, "B"); 

Maps: LinkedHashMap

  • Implements a hash table-based map with a predictable iteration order (insertion order).

Map linkedHashMap = new LinkedHashMap<>();
 linkedHashMap.put("one", 1);
 linkedHashMap.put("two", 2);
 linkedHashMap.put("one", 3); // Overwrites the value for key "one" 

Benefits of the Collections Framework

  • Reusable and Efficient: Provides efficient implementations of common data structures, reducing the need for custom implementations. 
  • Type Safety: Utilizes generics to ensure type safety, preventing runtime errors. 
  • Standardized APIs: Offers standardized interfaces and classes, making it easier to work with collections across different libraries and frameworks.

Input/output (I/O) streams

Input/output (I/O) streams in Java provide a mechanism for reading from and writing to various sources and destinations, such as files, network connections, and system input/output devices. Streams are a sequence of data elements transferred serially, typically one at a time.

Java's I/O streams form a powerful and flexible mechanism for handling input and output operations in a variety of scenarios, from reading and writing files to interacting with network sockets and standard input/output.

InputStream and OutputStream

  • InputStream: An abstract class representing a stream of bytes used for reading data. 
  • OutputStream: An abstract class representing a stream of bytes used for writing data.

Reader and Writer

  • Reader: An abstract class representing a stream of characters used for reading text data. 
  • Writer: An abstract class representing a stream of characters used for writing text data.

Common Input/Output Streams: Byte Streams

  • FileInputStream: Reads bytes from a file. 
  • FileOutputStream: Writes bytes to a file. 
  • ByteArrayInputStream: Reads bytes from a byte array. 
  • ByteArrayOutputStream: Writes bytes to a byte array. 
  • BufferedInputStream/BufferedOutputStream: Provides buffering for input/output streams, improving efficiency.

Common Input/Output Streams: Character Streams

  • FileReader: Reads characters from a file. 
  • FileWriter: Writes characters to a file. 
  • BufferedReader/BufferedWriter: Provides buffering for character input/output streams, improving efficiency.

Reading and Writing Data: Reading from a File

try (FileInputStream fis = new FileInputStream("input.txt");
      BufferedInputStream bis = new BufferedInputStream(fis)) {
     int data;     while ((data = bis.read()) != -1) {
         System.out.print((char) data);
     }
 } catch (IOException e) {
     e.printStackTrace();
 } 

Reading and Writing Data: Writing to a File

try (FileOutputStream fos = new FileOutputStream("output.txt");
      BufferedOutputStream bos = new BufferedOutputStream(fos)) {
     String message = "Hello, world!";
     byte[] bytes = message.getBytes();
     bos.write(bytes);
 } catch (IOException e) {
     e.printStackTrace();
 } 

Reading and Writing Data: Reading from System Input (Keyboard)

try (InputStreamReader isr = new InputStreamReader(System.in);
      BufferedReader br = new BufferedReader(isr)) {
     System.out.println("Enter text:");
     String input = br.readLine();
     System.out.println("You entered: " + input);
 } catch (IOException e) {
     e.printStackTrace();
 } 

Reading and Writing Data: Writing to System Output (Console)

try (OutputStreamWriter osw = new OutputStreamWriter(System.out);
      BufferedWriter bw = new BufferedWriter(osw)) {
     bw.write("Hello, world!");
     bw.newLine(); // Write a new line
     bw.flush(); // Flush the buffer
 } catch (IOException e) {
     e.printStackTrace();
 } 

Benefits of Streams

  • Efficiency: Streams efficiently handle large volumes of data by reading or writing data in small chunks. 
  • Abstraction: Streams provide a uniform way to interact with different types of data sources and destinations. 
  • Flexibility: Streams can be chained and manipulated using various decorators to add additional functionalities.