Top

Java Programming

51.

What is Type Erasure?

Ans:

Type Erasure is the process by which the Java compiler removes generic type information during compilation. Generic information is present only at compile time for type-checking purposes; it is not available at runtime.

During type erasure, the compiler:

  1. Replaces all generic type parameters in generic types with their bounds or with `Object` if the type parameter is unbounded. So, `List<String>` becomes just `List`.
  2. Inserts type casts where necessary to preserve type safety.
  3. Generates bridge methods to maintain polymorphism in extended generic types.

The main reason for type erasure was to ensure backward compatibility. It allowed generic code to be compatible with older, non-generic legacy code and run on older JVMs that had no knowledge of generics.

A consequence of type erasure is that you cannot do things like `new T()` or `if (obj instanceof List<String>)` at runtime, because the type `T` or `String` has been erased.

52.

What are wildcards in Generics? Explain `? extends T` and `? super T`.

Ans:

Wildcards (`?`) in generics represent an unknown type. They are used to increase the flexibility of methods that work with generic types.

Upper Bounded Wildcard (`? extends T`):

  • This wildcard restricts the unknown type to be a specific type `T` or a subtype of `T`.
  • It is used when you want a method to read elements from a generic structure. You can safely get elements out of the structure as type `T`, but you cannot put new elements into it (except for `null`), because you don't know the exact subtype.
  • This follows the PECS principle: Producer Extends, Consumer Super. Use `extends` when the structure is a 'producer' (you only get values from it).
// This method can accept a List of Number, or a List of Integer, or a List of Double.
public void processNumbers(List<? extends Number> list) {
    for (Number num : list) {
        // Safe to read as Number
        // ...
    }
    // list.add(new Integer(5)); // COMPILE ERROR!
}

Lower Bounded Wildcard (`? super T`):

  • This wildcard restricts the unknown type to be a specific type `T` or a supertype of `T`.
  • It is used when you want a method to add elements to a generic structure. You can safely add elements of type `T` (or its subtypes) into the structure, but when you read elements from it, you can only be sure that they are of type `Object`.
  • Following PECS, use `super` when the structure is a 'consumer' (you only put values into it).
// This method can accept a List of Integer, or a List of Number, or a List of Object.
public void addIntegers(List<? super Integer> list) {
    list.add(new Integer(5)); // Safe to add an Integer
    // Object obj = list.get(0); // Can only read as Object
}

53.

What are new features in Java 9?

Ans:

Java 9 introduced several significant features. Some of the most important ones are:

  • The Java Platform Module System (Project Jigsaw): This is the flagship feature. It allows you to divide a large application into smaller, manageable, and reusable modules. It improves security and performance by allowing you to bundle only the necessary parts of the JDK with your application.
  • JShell (The Java REPL): Java 9 introduced an interactive Read-Eval-Print-Loop tool, allowing developers to quickly test snippets of code without having to write a full class with a `main` method.
  • Factory Methods for Immutable Collections: Convenient static factory methods like `List.of()`, `Set.of()`, and `Map.of()` were added to easily create unmodifiable collections.
  • Private Methods in Interfaces: In addition to default and static methods, interfaces can now have private methods to share common code between default methods within the interface itself, without exposing that implementation logic.
  • Enhancements to the Stream API: New methods like `takeWhile`, `dropWhile`, and `ofNullable` were added.
  • Enhancements to `Optional`: New methods like `ifPresentOrElse` and `stream` were added.

54.

What is the `var` keyword introduced in Java 10?

Ans:

The `var` keyword, introduced in Java 10, allows you to declare a local variable without explicitly specifying its type. This is called Local-Variable Type Inference.

The compiler infers the type of the variable from the type of the initializer on the right-hand side of the assignment.

Before Java 10:

Map<String, List<Integer>> myMap = new HashMap<>();

With Java 10 `var`:

var myMap = new HashMap<String, List<Integer>>();

Important Restrictions:

  • `var` can only be used for local variables inside a method or code block.
  • It cannot be used for member variables (fields), method parameters, or method return types.
  • The variable must be initialized at the time of declaration, as the compiler needs the initializer to infer the type. `var x;` is not allowed.
  • You cannot initialize it to `null`, as the type cannot be inferred from `null`.

It is important to note that this is purely a compiler feature (syntactic sugar) to reduce boilerplate code. The variable is still strongly typed; its type is fixed at compile time and cannot be changed.

55.

What is the Reflection API?

Ans:

The Reflection API is a feature in Java that allows an application to examine or modify the runtime behavior of applications running in the JVM. It provides the ability to inspect classes, interfaces, fields, and methods at runtime, without knowing their names at compile time.

Using reflection, you can:

  • Get information about a class, such as its name, modifiers, and superclass.
  • Get information about a class's constructors, methods, and fields.
  • Create an instance of a class whose name is not known until runtime.
  • Get and set the value of an object's field, even if it is private.
  • Invoke a method on an object, even if it is private.

It is a very powerful tool but should be used with caution because:

  • It breaks encapsulation: It can access private members, which violates the principle of encapsulation.
  • It has performance overhead: Operations using reflection are significantly slower than direct method calls.
  • It is less secure and less maintainable: It makes the code harder to understand and can lead to errors that are only detectable at runtime.

Reflection is commonly used in frameworks like Spring and Hibernate for dependency injection and object-relational mapping.

56.

How do you create a custom exception?

Ans:

You can create a custom exception in Java by extending one of the existing exception classes.

  • If you want to create a checked exception, you should extend the java.lang.Exception class.
  • If you want to create an unchecked exception, you should extend the java.lang.RuntimeException class.

It is a common practice to provide at least two constructors in your custom exception class:

  1. A default constructor with no arguments.
  2. A constructor that takes a `String` message, which it passes to the superclass constructor.

Example of a Custom Checked Exception:

// 1. Create the custom exception class
public class InvalidAgeException extends Exception {
    // 2. Provide constructors
    public InvalidAgeException() {
        super();
    }

    public InvalidAgeException(String message) {
        super(message);
    }
}

// 3. Use the custom exception
public class AgeValidator {
    public void validate(int age) throws InvalidAgeException {
        if (age < 18) {
            throw new InvalidAgeException("User is not old enough.");
        }
    }
}

57.

What is the difference between `java.io` and `java.nio`?

Ans:

java.io (IO) and `java.nio` (NIO - New I/O) are two different APIs for handling input/output operations in Java.

FeatureJava IO (`java.io`)Java NIO (`java.nio`)
I/O ModelStream-oriented. It reads or writes data one byte at a time sequentially.Buffer-oriented. Data is first read into a buffer, from which it is then processed.
BlockingBlocking I/O. The thread that makes a `read()` or `write()` call is blocked until some data is available or the data is fully written.Non-blocking I/O. A thread can request to read data from a channel and get whatever is currently available, or nothing at all if no data is available. The thread is not blocked and can go on to do other things.
Channels and SelectorsDoes not have the concept of channels or selectors.Uses Channels as a medium for I/O operations and Selectors to monitor multiple channels for I/O events (like data available for reading) with a single thread.
PerformanceGenerally slower for high-volume network applications due to its blocking nature.Generally faster and more scalable for I/O-intensive operations, especially in networking, because a single thread can manage multiple connections.

NIO is more complex to use than IO, but it is the preferred choice for building high-performance, scalable servers.

58.

What is the Builder Design Pattern?

Ans:

The Builder pattern is a creational design pattern used to construct a complex object step by step. It separates the construction of a complex object from its representation so that the same construction process can create different representations.

It is particularly useful when an object has many constructor parameters, some of which may be optional. Instead of creating many complex constructors (a 'telescoping constructor' anti-pattern), you use a builder.

Steps to Implement:

  1. Create a `static` nested class called `Builder` inside the main class.
  2. The main class should have a private constructor that takes the `Builder` as an argument.
  3. The `Builder` class has fields that correspond to the fields of the main class.
  4. The `Builder` class has methods to set these fields (e.g., `setName()`, `setAge()`), and each method returns the `Builder` instance itself to allow for method chaining.
  5. The `Builder` class has a final `build()` method that creates and returns an instance of the main class by calling its private constructor.
public class User {
    private final String firstName; // required
    private final String lastName;  // required
    private final int age;          // optional
    private final String phone;     // optional

    private User(UserBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.phone = builder.phone;
    }

    public static class UserBuilder {
        private final String firstName;
        private final String lastName;
        private int age;
        private String phone;

        public UserBuilder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }

        public UserBuilder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}

// Usage:
User user = new User.UserBuilder("John", "Doe").age(30).phone("123-456").build();

59.

What is a record in Java?

Ans:

Records are a new feature introduced as a preview in Java 14 and standardized in Java 16. They provide a compact syntax for declaring classes that are transparent holders for immutable data. They are a special kind of class.

Before records, if you wanted to create a simple data carrier class (like a `Point` with x and y coordinates), you had to write a lot of boilerplate code: private final fields, a constructor, accessor methods (`getters`), and implementations for `hashCode()`, `equals()`, and `toString()`.

With records, you can declare all of this in a single line.

Example using a record:

public record Point(int x, int y) {}

When you declare a record, the Java compiler automatically generates:

  • A `private final` field for each component (`x` and `y`).
  • A canonical constructor that takes all components as arguments.
  • Public accessor methods for each component (e.g., `x()` and `y()`).
  • Implementations of `equals()`, `hashCode()`, and `toString()`.

Records are implicitly `final` and cannot extend any other class. They are designed to significantly reduce boilerplate code for simple data aggregate classes.

60.

What is the difference between `map` and `flatMap` in the Stream API?

Ans:

Both `map` and `flatMap` are intermediate operations in the Java Stream API that apply a function to the elements of a stream. The key difference is in how they handle the result of the function.

  • map(Function<T, R>): This operation transforms each element of a stream from type `T` to type `R`. If you have a stream of objects and the mapping function returns a stream for each object, the result will be a stream of streams (e.g., Stream<Stream<R>>). It performs a one-to-one transformation.
  • flatMap(Function<T, Stream<R>>): This operation is a combination of a `map` and a `flatten` operation. It transforms each element into a stream of other objects, and then it flattens all these generated streams into a single, flat stream (Stream<R>>). It performs a one-to-many transformation.

Example: Given a list of words, get a list of all unique characters.

List<String> words = Arrays.asList("Hello", "World");

// Using map: results in Stream<Stream<String>>, which is not what we want.
List<Stream<String>> resultWithMap = words.stream()
    .map(word -> Arrays.stream(word.split("")))
    .collect(Collectors.toList());

// Using flatMap: correctly results in Stream<String>.
List<String> uniqueChars = words.stream()
    .map(word -> word.split("")) // Stream<String[]>
    .flatMap(Arrays::stream)      // Flattens Stream<String[]> to Stream<String>
    .distinct()
    .collect(Collectors.toList());
// Result: [H, e, l, o, W, r, d]

In short, use `flatMap` when you need to transform one element into multiple elements and want them all in a single, flat stream.

Loading…
Tags: Java Programming Interview Questions and Answers || Java Programming Sort Questions and Answers || Java Programming Detailed Questions and Answers || Java Programming Tutorial