What are parallel streams and what are their potential pitfalls?
A parallel stream is a feature of the Java 8 Stream API that allows stream operations to be executed concurrently on multiple threads. It is designed to leverage multi-core processors to speed up processing of large datasets. You can create a parallel stream by calling the .parallelStream()
method on a collection or by converting an existing stream using .parallel()
.
Potential Pitfalls:
findFirst()
, may be slower on a parallel stream. If ordering is not important, using findAny()
is more performant with parallel streams.ForkJoinPool
. If you have multiple parts of your application running long-running parallel stream operations, they can starve each other of threads, leading to poor performance across the application.Parallel streams are a powerful tool but should be used judiciously, primarily for performance-critical operations on large datasets where the processing of each element is independent.
What is a `ReentrantLock` and how is it different from a `synchronized` block?
ReentrantLock
is a class from the java.util.concurrent.locks
package that provides a more flexible and powerful alternative to the `synchronized` keyword for locking.
Key Differences and Features of `ReentrantLock`:
lock()
to acquire the lock and unlock()
to release it. The `unlock()` call is typically placed in a finally
block to ensure it always executes.ReentrantLock lock = new ReentrantLock();
...
lock.lock();
try {
// access shared resource
} finally {
lock.unlock();
}
In summary, use `synchronized` for simple cases. Use `ReentrantLock` when you need its advanced features like timed or interruptible lock waits, or a fairness policy.
What is the difference between a `CountDownLatch` and a `CyclicBarrier`?
Both are synchronization aids from the `java.util.concurrent` package used to make threads wait for certain conditions, but they serve different purposes.
CountDownLatch
:
await()
method. Threads that perform the operations call the countDown()
method when they finish.CyclicBarrier
:
await()
method when it reaches the barrier.What is a `Semaphore`?
A Semaphore
is a synchronization aid that controls access to a shared resource by maintaining a set of permits.
acquire()
method.release()
method, allowing another waiting thread to acquire it.A semaphore initialized with one permit (a 'binary semaphore') acts like a simple mutex lock. A semaphore initialized with multiple permits can be used to control access to a pool of finite resources, such as database connections or worker threads.
Analogy: A semaphore is like a bouncer at a club with a limited capacity. The number of permits is the club's capacity. To enter, you need a permit. When you leave, you give the permit back.
What are sealed classes and interfaces in Java?
Sealed Classes and Interfaces, standardized in Java 17, provide a mechanism to restrict which other classes or interfaces may extend or implement them.
Before sealed classes, if you made a class `public`, any other class could extend it. If you made it `final`, no class could extend it. Sealed classes give you a middle ground, allowing you to explicitly declare which classes are permitted to be direct subclasses.
You use the sealed
modifier on the superclass declaration and the permits
clause to list the allowed subclasses. The permitted subclasses must themselves be either final
, sealed
, or non-sealed
.
final
: The class cannot be extended further.sealed
: The class can only be extended by the classes it permits.non-sealed
: The class can be extended by any other class, returning to the pre-sealed behavior.// The Shape class can only be extended by Circle, Square, and Rectangle
public abstract sealed class Shape permits Circle, Square, Rectangle {
// ...
}
public final class Circle extends Shape { /* ... */ }
public final class Square extends Shape { /* ... */ }
public non-sealed class Rectangle extends Shape { /* ... */ }
This feature is very useful for domain modeling and works well with modern `switch` expressions to ensure all cases are handled.
Explain Pattern Matching for `instanceof`.
Pattern Matching for `instanceof`, standardized in Java 16, is a feature that simplifies the common pattern of checking an object's type and then casting it.
Before Java 16:
You had to perform three steps: test the type with `instanceof`, declare a new variable, and then explicitly cast the object.
if (obj instanceof String) {
String s = (String) obj;
// now use s
System.out.println(s.toUpperCase());
}
With Pattern Matching in Java 16+:
You can test, declare, and bind the variable in a single step. The new variable (`s`) is only in scope and assigned if the `instanceof` check is `true`.
if (obj instanceof String s) {
// s is automatically available here as a String
System.out.println(s.toUpperCase());
}
This reduces boilerplate, makes the code more concise and readable, and reduces the chance of `ClassCastException` errors.
What is a covariant return type?
A covariant return type is a feature in Java (since Java 5) that allows a subclass to override a method and change its return type to a more specific type (a subtype of the original return type).
Before Java 5, an overriding method had to have the exact same return type as the overridden method in the superclass.
Example:
class Animal {}
class Dog extends Animal {}
class AnimalFactory {
// Superclass method returns an Animal
public Animal createAnimal() {
return new Animal();
}
}
class DogFactory extends AnimalFactory {
// Overriding method has a more specific return type (Dog)
@Override
public Dog createAnimal() {
return new Dog();
}
}
// Usage
DogFactory df = new DogFactory();
Dog myDog = df.createAnimal(); // No cast needed!
This feature eliminates the need for the client to cast the result, making the code cleaner and more type-safe. The `clone()` method is another classic example where covariant return types are useful.
What is method hiding?
Method hiding occurs when a subclass defines a static method with the same signature (name and parameters) as a static method in its superclass.
This is different from method overriding. Method overriding applies to instance methods and is resolved at runtime (runtime polymorphism). Method hiding applies to static methods and is resolved at compile time.
The version of the static method that gets called depends on the type of the reference variable, not the type of the object it points to.
class Parent {
public static void show() {
System.out.println("Parent's static show()");
}
}
class Child extends Parent {
public static void show() {
System.out.println("Child's static show()");
}
}
// Main method
Parent p = new Child();
p.show(); // Prints "Parent's static show()"
In the example, even though `p` refers to a `Child` object, the call `p.show()` invokes the parent's static method because the reference type is `Parent`. The child's static method 'hides' the parent's method, it does not override it.
Can you override a private or static method in Java?
No, you cannot override a `private` or `static` method in Java.
How can you break a Singleton pattern?
While the Singleton pattern is designed to restrict instantiation, it can be broken using a few advanced techniques.
MySingleton instanceOne = MySingleton.getInstance();
MySingleton instanceTwo = null;
Constructor[] constructors = MySingleton.class.getDeclaredConstructors();
for (Constructor constructor : constructors) {
constructor.setAccessible(true); // Make the private constructor accessible
instanceTwo = (MySingleton) constructor.newInstance();
break;
}
// instanceOne and instanceTwo will be different objects.
Fix: Modify the private constructor to throw an exception if an instance already exists.
Fix: Implement the `readResolve()` method in the Singleton class and make it return the existing instance.
protected Object readResolve() {
return getInstance();
}
Fix: Override the `clone()` method and make it throw a `CloneNotSupportedException` or return the existing instance.