Java:- Everything you Need to Know

Java:- Everything you Need to Know

·

13 min read

Java is a high-level, class-based, object-oriented programming language that is designed to have as few implementation dependencies as possible. It is a general-purpose programming language intended to let programmers write once, run anywhere (WORA), meaning that compiled Java code can run on all platforms that support Java without the need to recompile. Java applications are typically compiled to bytecode that can run on any Java virtual machine (JVM) regardless of the underlying computer architecture. The syntax of Java is similar to C and C++, but has fewer low-level facilities than either of them. The Java runtime provides dynamic capabilities (such as reflection and runtime code modification) that are typically not available in traditional compiled languages.

This is What wikipedia says but lets simplify that now

  • Java is High Level Programming Language

  • Java is distributed(i.e) JVM follows WORA Pattern (Write Once Read Anywhere)

  • What is JVM? compiler? interpreter? JIT? JDK?


  • Java was developed by James Gosling under sun microsystems at early 1990s and later made the version 1 avaliable by 1995

  • Java was originally called oak because of oak trees outside james gosling office


Architecture

  1. What is JDK?

    • JDK stands for Java Development Kit.

    • It's a bundle of software that developers use to develop Java applications.

  2. Components of JDK:

    • JDK includes tools for writing, compiling, and running Java programs.

    • It contains the Java Runtime Environment (JRE), which is needed to run Java applications.

  3. Key Tools:

    • javac: Compiler that translates Java code into bytecode.

    • java: Interpreter that executes bytecode on the Java Virtual Machine (JVM).

    • jar: Tool for creating Java Archive files for packaging applications.

  4. Libraries and APIs:

    • JDK provides a rich set of libraries and APIs for common tasks like networking, I/O, and database access.

    • Developers can use these libraries to build robust and scalable Java applications.

What are Jar Files?

  • JAR files use the ZIP file format for compression, making them efficient for storage and distribution.

  • The compression reduces file size, making downloads faster and saving disk space.


The Java Runtime Environment (JRE) is a software package that provides the necessary runtime environment for executing Java applications. Here's a concise explanation:

  1. Execution Environment:

    • The JRE includes the Java Virtual Machine (JVM), which is responsible for executing Java bytecode.

    • It provides runtime libraries and components required for running Java applications.

  2. Key Components:

    • JVM: Interprets and executes Java bytecode, providing platform independence by abstracting hardware and operating system specifics.

    • Java Class Library: A collection of pre-written Java classes and methods used by Java applications for common tasks like input/output, networking, and data manipulation.

  3. Deployment:

    • Users install the JRE on their systems to run Java applications without needing to install the entire Java Development Kit (JDK).

    • JRE deployment ensures that Java applications can run reliably on various devices and platforms with a compatible JVM


The Java Virtual Machine (JVM) is an essential component of the Java Runtime Environment (JRE) responsible for executing Java bytecode. Here's a brief explanation:

  1. Execution Environment:

    • The JVM provides a platform-independent execution environment for Java bytecode, allowing Java applications to run on any device or operating system with a compatible JVM installed.
  2. Bytecode Execution:

    • When a Java program is compiled, it's translated into platform-neutral bytecode instead of machine code.

    • The JVM interprets this bytecode at runtime, translating it into native machine code instructions that the underlying hardware can execute.

  3. Key Functions:

    • Memory Management: The JVM manages memory allocation and deallocation, including garbage collection to reclaim unused memory.

    • Just-In-Time (JIT) Compilation: In some JVM implementations, bytecode may be dynamically compiled into native machine code for improved performance.

    • Security: The JVM enforces Java's security features, such as sandboxing to restrict potentially harmful operations by untrusted code.

  4. Class Loading:

    • The JVM loads classes as needed during execution, either from local disk or over a network.

    • It dynamically links these classes to form a cohesive program structure.

  5. Optimizations:

    • Modern JVM implementations employ various optimizations, such as method inlining, loop unrolling, and escape analysis, to improve runtime performance.

Let's have a look into diagrams for simplified understanding

  1. When we make a java file our code is converted to a byte code by jdk

  2. that byte is processed by JVM and it uses JIT compiler to convert it to machine code which is then given for hardware for processing


JVM Architecture

  1. Class Loading Subsystem:

    • Responsible for loading classes into memory when they are referenced by a Java program.

    • Includes three main components: Class Loader, Method Area, and Runtime Constant Pool.

  2. Memory Management:

    • Manages memory allocation and deallocation for Java programs.

    • Includes Heap (for object allocation) and Stack (for method execution).

  3. Execution Engine:

    • Executes Java bytecode instructions.

    • Comprises two components: Interpreter (interprets bytecode instructions) and Just-In-Time (JIT) Compiler (compiles frequently executed bytecode into native machine code for improved performance).

  4. Runtime Data Areas:

    • Includes various memory areas used during program execution:

      • Method Area: Stores class structures, method code, static variables, and runtime constant pool.

      • Heap: Stores objects and their instance variables.

      • Java Stack: Stores method invocation frames, including local variables, method parameters, and return addresses.

      • PC Register: Keeps track of the current instruction being executed.

      • Native Method Stack: Stores native method invocation frames.

  5. Native Interface:

    • Allows Java programs to interact with native code written in languages like C and C++.

    • Facilitates platform-specific operations and integration with system libraries.

  6. Native Method Libraries:

    • Provides access to platform-specific functionality and system resources.

    • Includes libraries for tasks like I/O operations, networking, and GUI programming.


Stack vs Heap Memory

STACKHEAP
Access to stack memory is fasterComparitively Slower
storing method execution frames and local variablesdynamic memory allocation to store objects and their instance variables.
Stack memory is managed in a Last In, First Out (LIFO) mannerheap memory is managed using garbage collection algorithms.
Stack memory is limited in size and typically smaller than heap memoryheap memory can dynamically grow and shrink as needed.

Features of Java

  1. Java is Object Oriented

  2. Java is Multithreaded

  3. Java is Distributed

  4. Java is Robust

  5. Java syntax is similar to C and C++

  6. Java Has High Performance


Multi-Threading

Certainly, here's a breakdown according to the sections:

  1. Definition:

    • Multithreading involves the simultaneous execution of multiple threads within a single process.

    • Threads are lightweight processes that share the same memory space and resources, allowing them to run concurrently.

  2. Usage:

    • Multithreading is used to achieve parallelism and improve performance in applications.

    • It is employed in scenarios where tasks can be executed concurrently without dependencies on each other.

  3. Scenarios:

    • GUI Applications: Keep the user interface responsive while performing background tasks like file I/O or network communication.

    • Server Applications: Handle multiple client requests simultaneously to improve throughput and scalability.

    • Performance-Critical Applications: Utilize multiple CPU cores efficiently to enhance processing speed.

  4. Main Function:

    • The primary function of multithreading is to enable efficient utilization of system resources.

    • It enhances application responsiveness by allowing tasks to execute concurrently, reducing idle time and maximizing CPU utilization.


Let's Learn About Threads

Threads are lightweight processes that execute independently within a program, allowing for concurrent execution of tasks.

Thread Lifecycle:

  1. New: The thread is in the new state before it is started.

  2. Runnable: After starting, the thread enters the runnable state, waiting for CPU time to execute.

  3. Blocked: A thread transitions to the blocked state when it's waiting for a resource, such as I/O operation or synchronization lock.

  4. Waiting: The thread moves to the waiting state when it waits indefinitely for another thread's notification.

  5. Timed Waiting: Similar to waiting, but for a specified period.

  6. Terminated: The thread exits the terminated state when it completes its execution or is terminated abruptly.

_________________________________________________________________________________________

Deadlock

A deadlock occurs in a multi-threaded or distributed system when two or more threads or processes are waiting for each other to release resources, resulting in a situation where none of them can proceed. This often leads to a system-wide halt, as the involved threads are unable to progress, and the resources they need are locked by other threads that are also waiting.


class MyThread extends Thread {
    public void run() {
        System.out.println("Thread is running.");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();

        // Start the threads
        thread1.start();
        thread2.start();
    }
}
  • We define a class MyThread that extends the Thread class and overrides its run() method to define the thread's behavior.

  • In the main method, we create two instances of MyThread, thread1, and thread2.

  • We start each thread using the start() method, which internally calls the run() method defined in the MyThread class.

Conditions for Multithreading

  1. Create a class that extends the Thread class or implements the Runnable interface.

  2. Override the run() method in the class to define the code that will run in the thread.

  3. Create an instance of the class.

  4. Call the start() method on the instance to start the thread

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Thread is running.");
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread1 = new Thread(myRunnable);
        Thread thread2 = new Thread(myRunnable);

        // Start the threads
        thread1.start();
        thread2.start();
    }
}

Lambda expressions, introduced in Java 8, provide a concise way to represent anonymous functions or closures. Here's an explanation of lambda expressions starting from basics:

  1. Anonymous Functions:

    • In traditional Java programming, anonymous inner classes were used to define implementations of functional interfaces.

    • These classes were verbose and required a lot of boilerplate code for simple tasks.

  2. Lambda Expression Syntax:

    • Lambda expressions provide a more concise syntax for defining anonymous functions.

    • They consist of parameters, an arrow (->), and a body, which can be an expression or a block of code.

  3. Basic Syntax:

    • (parameters) -> expression

    • (parameters) -> { statements; }

  4. Functional Interfaces:

    • Lambda expressions are most commonly used with functional interfaces, which have a single abstract method.

    • Examples include Runnable, Callable, and Comparator from the Java API.

  5. Example:

     // Traditional anonymous inner class
     Runnable runnable1 = new Runnable() {
         public void run() {
             System.out.println("Hello from anonymous inner class!");
         }
     };
    
     // Lambda expression
     Runnable runnable2 = () -> {
         System.out.println("Hello from lambda expression!");
     };
    
  6. Type Inference:

    • In many cases, the types of parameters in a lambda expression can be inferred by the compiler, making lambda expressions even more concise.

    • However, explicit type declarations can also be used.

  7. Target Typing:

    • Lambda expressions can be used in contexts where a functional interface is expected.

    • The type of the lambda expression is inferred based on the context in which it's used.

  8. Benefits:

    • Lambda expressions make code more readable and maintainable by reducing boilerplate code.

    • They facilitate functional programming paradigms, enabling a more expressive and concise coding style.

  9. Common Use Cases:

    • Lambda expressions are commonly used in Java streams API for data manipulation operations like mapping, filtering, and reducing.

    • They are also used in event handling, concurrency, and other scenarios where a functional approach is beneficial.

  10. Limitations:

    • Lambda expressions are not a replacement for anonymous inner classes in all situations, especially when you need access to instance variables or need to specify an exact type.

In summary, lambda expressions provide a powerful and concise way to represent anonymous functions in Java, enabling more expressive and functional programming styles. They are widely used in modern Java development, especially in contexts like the streams API and event handling.


Exception Handling

Exception handling in Java is a mechanism to deal with runtime errors or exceptional conditions that may occur during the execution of a program. Here's an explanation of exception handling from basics:

  1. Types of Exceptions:

    • In Java, exceptions are classified into two types: checked exceptions and unchecked exceptions.

    • Checked exceptions are checked at compile-time, while unchecked exceptions (also known as runtime exceptions) are not.

  2. try-catch Blocks:

    • The try block is used to enclose the code that may throw an exception.

    • The catch block is used to handle the exception if it occurs.

    • Multiple catch blocks can be used to handle different types of exceptions.

  3. Syntax:

     try {
         // Code that may throw an exception
     } catch (ExceptionType1 e1) {
         // Handle ExceptionType1
     } catch (ExceptionType2 e2) {
         // Handle ExceptionType2
     } finally {
         // Optional: Code that always executes, regardless of whether an exception occurred
     }
    
  4. Throwing Exceptions:

    • You can explicitly throw an exception using the throw keyword.

    • The thrown exception can be an instance of any class that extends Throwable.

  5. Checked Exceptions:

    • Checked exceptions must be either caught or declared in the method's throws clause.

    • Examples include IOException, SQLException, etc.

  6. Unchecked Exceptions:

    • Unchecked exceptions are subclasses of RuntimeException or Error.

    • They do not need to be explicitly caught or declared in the method's throws clause.

    • Examples include NullPointerException, ArrayIndexOutOfBoundsException, etc.

  7. Finally Block:

    • The finally block is used to execute code that should always run, regardless of whether an exception occurred or not.

    • It's commonly used for cleanup tasks like closing resources (e.g., file handles, database connections).

  8. try-with-resources:

    • Introduced in Java 7, the try-with-resources statement automatically closes resources at the end of the block.

    • Resources that implement the AutoCloseable interface can be used within the try-with-resources block.

  9. Custom Exceptions:

    • You can create custom exception classes by extending the Exception class or one of its subclasses.

    • Custom exceptions allow you to define and handle application-specific error conditions.

  10. Best Practices:

    • Handle exceptions at an appropriate level of abstraction.

    • Provide informative error messages and log exceptions for debugging purposes.

    • Use checked exceptions for conditions that callers should handle and unchecked exceptions for programming errors or unexpected conditions.

Exception handling is an essential aspect of Java programming, ensuring robustness and reliability in applications by gracefully handling errors and exceptional conditions.


Throw and Throws

Certainly! In Java, both throw and throws are keywords used in exception handling, but they serve different purposes:

  1. throw Keyword:

    • The throw keyword is used to explicitly throw an exception within a method or block of code.

    • It is typically followed by an instance of an exception class or a subclass of Throwable.

    • When a throw statement is executed, the program control immediately transfers to the nearest enclosing catch block or terminates the method if no suitable catch block is found.

Example:

    public void withdraw(double amount) throws InsufficientFundsException {
        if (balance < amount) {
            throw new InsufficientFundsException("Not enough balance to withdraw.");
        }
        // Withdraw logic here
    }
  1. throws Clause:

    • The throws keyword is used in method declarations to indicate that the method might throw certain exceptions during its execution.

    • It specifies the exceptions that a method can potentially throw but does not handle within the method itself.

    • The caller of the method is responsible for handling or propagating the exceptions declared in the throws clause.

Example:

    public void readFile(String fileName) throws IOException {
        // Code that may throw IOException
    }

In this example, the readFile method declares that it may throw an IOException during its execution. The caller of the readFile method must handle this exception or declare it in its own throws clause.

In summary, throw is used to explicitly throw an exception within a method, while throws is used in method declarations to indicate the exceptions that the method may throw. They work together to facilitate proper exception handling and propagation in Java programs.

Did you find this article valuable?

Support Thirumalai by becoming a sponsor. Any amount is appreciated!