Monday 24 June 2024

Important and frequently asked advanced java questions in interview

1. What are Java Streams and how do you use them?

 

Java Streams are a part of the Java Collections Framework introduced in Java 8. They allow for functional-style operations on sequences of elements such as map, filter, and reduce. Streams can be created from collections, arrays, or I/O resources and are used to process data in a declarative way.

Example:

```java

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

List<String> filteredNames = names.stream()

                                  .filter(name -> name.startsWith("A"))

                                  .collect(Collectors.toList());

System.out.println(filteredNames); // Output: [Alice]

```

Tip: Use streams for efficient and readable data processing, especially with large datasets.

 

 

2. Explain the concept of CompletableFuture in Java.

 

`CompletableFuture` is a class introduced in Java 8 for asynchronous programming. It represents a future result of an asynchronous computation. `CompletableFuture` can be used to build complex asynchronous pipelines, handle exceptions, and combine multiple async operations.

Example:

```java

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {

    return 50 + 20;

});

future.thenAccept(result -> System.out.println("Result: " + result));

```

Tip: Use `CompletableFuture` for non-blocking, asynchronous tasks to improve performance in concurrent applications.

 

 

3. What are the differences between HashMap and ConcurrentHashMap?

 

- HashMap: It is not thread-safe and cannot be shared between threads without proper synchronization. Operations on a `HashMap` are faster in single-threaded environments.

- ConcurrentHashMap: It is thread-safe and allows concurrent read and write operations. It achieves thread safety through a technique called lock stripping, which divides the map into segments.

 

Example:

```java

Map<String, Integer> map = new ConcurrentHashMap<>();

map.put("one", 1);

map.put("two", 2);

System.out.println(map.get("one"));

```

 

Tip: Use `ConcurrentHashMap` for thread-safe operations when multiple threads are accessing the map concurrently.

 

 

4. How does the Java Memory Model handle concurrency?

 

The Java Memory Model (JMM) defines how threads interact through memory and what behaviors are allowed in concurrent execution. It specifies rules for:

- Visibility: Ensuring changes made by one thread to shared variables are visible to other threads.

- Atomicity: Ensuring that certain operations are performed as a single unit of execution.

- Ordering: Preventing unexpected reordering of code instructions.

 

Example of `volatile` for visibility:

```java

public class VolatileExample {

    private volatile boolean flag = true;

 

    public void stop() {

        flag = false;

    }

 

    public void run() {

        while (flag) {

            // Do something

        }

    }

}

```

 

Tip:. Use `volatile` for variables that are accessed by multiple threads but not modified in a synchronized block.


 

5. What are the different types of class loaders in Java?

 

 Java has several class loaders, each responsible for loading classes into the JVM:

- Bootstrap ClassLoader: Loads core Java classes (e.g., `java.lang.*`).

- Extension ClassLoader: Loads classes from the Java extensions directory (`jre/lib/ext`).

- System/Application ClassLoader: Loads classes from the classpath (`-cp` or `CLASSPATH` environment variable).

- Custom ClassLoader: User-defined class loaders to load classes in a customized way.

 

Example of a custom class loader:

```java

public class MyClassLoader extends ClassLoader {

    @Override

    public Class<?> findClass(String name) throws ClassNotFoundException {

        byte[] b = loadClassFromFile(name);

        return defineClass(name, b, 0, b.length);

    }

 

    private byte[] loadClassFromFile(String fileName) {

        // Load the class file into a byte array

    }

}

```

Tip: Use custom class loaders for dynamic class loading and to implement specific security policies.

 

 

 6. What is the difference between Callable and Runnable?

Runnable: Represents a task that can be executed by a thread. It does not return a result and cannot throw checked exceptions.

- Callable: Similar to `Runnable` but can return a result and throw checked exceptions.

 

Example:

```java

// Runnable example

Runnable task = () -> System.out.println("Task executed");

new Thread(task).start();

 

// Callable example

Callable<Integer> task = () -> {

    return 123;

};

Future<Integer> future = Executors.newSingleThreadExecutor().submit(task);

System.out.println(future.get());

```

Tip: Use `Callable` when you need a task to return a result or throw an exception.

 

 

 7. Explain the Fork/Join Framework in Java.

The Fork/Join framework is designed for parallel processing and is part of the `java.util.concurrent` package. It helps in breaking a large task into smaller subtasks and then combining the results. It uses a work-stealing algorithm to optimize the performance.

 

Example:

```java

public class FibonacciTask extends RecursiveTask<Integer> {

    private final int n;

 

    public FibonacciTask(int n) {

        this.n = n;

    }

 

    @Override

    protected Integer compute() {

        if (n <= 1) return n;

        FibonacciTask f1 = new FibonacciTask(n - 1);

        FibonacciTask f2 = new FibonacciTask(n - 2);

        f1.fork();

        return f2.compute() + f1.join();

    }

 

    public static void main(String[] args) {

        ForkJoinPool pool = new ForkJoinPool();

        FibonacciTask task = new FibonacciTask(10);

        System.out.println(pool.invoke(task));

    }

}

```

 

Tip: Use the Fork/Join framework for tasks that can be recursively divided into smaller subtasks.

 


 8. What is Java's try-with-resources statement?

 

The try-with-resources statement ensures that each resource is closed at the end of the statement. It is used for managing resources such as files, sockets, and database connections that need to be closed after being used.

 

Example:

```java

try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {

    String line;

    while ((line = br.readLine()) != null) {

        System.out.println(line);

    }

} catch (IOException e) {

    e.printStackTrace();

}

```

 

Tip: Use try-with-resources to automatically close resources and reduce boilerplate code.

 

 

 9. Explain the difference between `synchronized` and `ReentrantLock`.

synchronized: A keyword that provides a simple way to lock a method or a block of code. It is intrinsic and cannot be interrupted.

ReentrantLock: A class from `java.util.concurrent.locks` that provides more flexible locking mechanisms. It supports lock polling, timed lock waits, and interruptible lock waits.

 

Example:

```java

ReentrantLock lock = new ReentrantLock();

lock.lock();

try {

    // critical section

} finally {

    lock.unlock();

}

```

 

Tip: Use `ReentrantLock` for advanced locking features like fairness, tryLock, and interruptibility.

 

 

 10. What are Phantom References in Java?

 

 Phantom references are one of the types of references in Java, represented by the `PhantomReference` class. They are used to determine when an object is no longer reachable and is about to be collected by the garbage collector. Unlike soft or weak references, they do not prevent their referents from being collected.

 

Example:

```java

ReferenceQueue<Object> refQueue = new ReferenceQueue<>();

PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), refQueue);

 

// Check if the object is about to be collected

System.gc();

Reference<?> ref = refQueue.poll();

if (ref != null) {

    System.out.println("Object is about to be collected");

}

```

 

Tip: Use phantom references for scheduling pre-mortem cleanup actions before the object is collected by the GC.