Tricky Java interview questions for 7 years of Experience

AlishaS
Javarevisited
Published in
14 min readAug 4, 2023

Java is a popular and widely used programming language that is used for developing a wide range of applications, from desktop and mobile applications to web applications and enterprise software. With seven years of experience working with Java, you are likely well-versed in the language’s syntax, data structures, and programming concepts. However, when it comes to interviews, even experienced developers can find themselves challenged by tricky questions that test their knowledge and problem-solving skills.

In this article, we will discuss some of the most common tricky Java interview questions that developers with seven years of experience or above may encounter. We will provide detailed explanations and examples for each question. By considering these tricky Java interview questions, you can increase your chances of success and impress potential employers with your advanced Java knowledge and expertise.

1. What is the purpose of the “transient” keyword in Java? And how can you achieve that?

In Java, the transient keyword is used to indicate that a particular field of a class should not be included in the object’s serialized form.

It means that when an object is serialized, its state is converted into a sequence of bytes that can be written to a file or sent over a network.

By marking a field as transient, you are telling Java that it should not include that field’s value when the object is serialized.

There are several reasons why you might want to use the transient keyword. For example, you might have a field that contains a temporary value that does not need to be persisted when the object is serialized. Alternatively, there might have a field that contains sensitive data that should not be included in the object’s serialized form for security reasons.

public class MyClass implements Serializable {
private int myInt;
private transient String myTransientString;
// Constructor, getters, and setters are ignored for simplicity
// Other methods…
}

In this example, the field “myTransientString” is marked as transient, which means that its value will not be included when an instance of MyClass is serialized.

2. Can you explain how “Inheritance” and “Composition” are different? Explain with an example.

Inheritance and composition are two fundamental ways of creating relationships between classes in object-oriented programming. Both approaches enable code reuse and abstraction, but they differ in their implementation and the kinds of relationships they create between classes.

Here is a brief overview of each approach:

  • Inheritance: It is a mechanism in which a new class is created by deriving properties and characteristics from an existing class. The new class, known as a subclass or derived class, inherits the methods and fields of the existing class, known as the superclass or base class. The subclass can also override methods of the superclass to provide its own implementation. Inheritance creates an “is-a” relationship between the superclass and subclass.
  • Composition: It is a mechanism in which a class contains one or more instances of another class as its fields. The contained class is referred to as the component or part class. The class that contains the component class is called the container or whole class. The composition creates a “has-a” relationship between the container class and the component class.

In the diagram, we have two classes: “Vehicle” and “Engine”. The Vehicle class can either use inheritance or composition to incorporate the Engine class.

  • Inheritance example: The Vehicle class extends the Engine class, which means that it inherits all of the fields and methods of the Engine class. This creates an “is-a” relationship between the Vehicle and Engine classes, where the Vehicle “is a” type of Engine.
public class Vehicle extends Engine {
// Fields and methods specific to the Vehicle class
}
  • Composition example: The Vehicle class contains an instance of the Engine class as its field. This creates a “has-a” relationship between the Vehicle and Engine classes, where the Vehicle “has a” Engine.
public class Vehicle {
private Engine engine;
public Vehicle(Engine engine) {
this.engine = engine;
}
// Methods that use the Engine instance
}

In general, inheritance is more appropriate when there is a clear “is-a” relationship between classes, and when the subclass can be seen as a specialized version of the superclass. Composition is more appropriate when there is a “has-a” relationship between classes, and when the container class needs to use or manage one or more instances of another class.

3. Can you explain the difference between a HashSet and a TreeSet in Java? Also, explain how the data is stored internally.

Let’s say we have the following integer data: {7, 3, 9, 4, 1, 8}.

  • For HashSet, the data is stored internally in a hash table. The hash table uses the hashCode() method of each element to determine a unique index where that element should be stored.

In the above example image, the hash table has eight buckets, labeled ‘51’ through ‘56’. Each bucket is a set that contains the elements with hash codes that map to that bucket. For example, the set at index ‘53’ contains the elements 3 and 4, which both have hashcode [197]. The set at index ‘56’ contains the elements 7, 8, and 9, which all have hashcode [195].

  • For TreeSet, the data is stored internally in a red-black tree. The tree is sorted based on the natural ordering of the elements or the ordering defined by a custom comparator passed to the TreeSet constructor. Here’s an example of how the data might be stored in a red-black tree:

In this example, the red-black tree has six nodes, each containing one of the elements {1, 3, 4, 7, 8, 9}. The nodes are colored red or black, with red nodes indicating that a violation of the red-black tree properties has occurred. The elements are stored in sorted order within the tree, with smaller elements on the left and larger elements on the right. For example, element 1 is the smallest element and is stored at the leftmost leaf node, while element 9 is the largest element and is stored at the rightmost leaf node.

4. How do you handle concurrent modifications to a Collection in Java?

Concurrent modifications to a collection in Java can cause a range of issues, such as unexpected behavior, non-deterministic results, or even throwing a ConcurrentModificationException. To handle concurrent modifications to a collection in Java, you can use one of the following approaches:

  • Use synchronized collections: One way to handle concurrent modifications to a collection is to use a synchronized collection. A synchronized collection is a thread-safe collection that ensures only one thread can modify the collection at a time. We can create a synchronized collection by calling the Collections.synchronizedCollection() method, passing in the collection you want to synchronize. For example
List<String> list = new ArrayList<>();
List<String> synchronizedList = Collections.synchronizedList(list);
  • Use concurrent collections: Another way to handle concurrent modifications to a collection is to use a concurrent collection. A concurrent collection is a thread-safe collection that allows multiple threads to modify the collection concurrently without external synchronization. The (java.util.concurrent) package provides a range of concurrent collection classes, such as ConcurrentHashMap, ConcurrentLinkedDeque, and ConcurrentSkipListSet.
  • Use explicit locking: We can also handle concurrent modifications to a collection by using explicit locking. We can use the synchronized keyword or the (java.util.concurrent.locks) package to lock the collection when modifying it. For example
List<String> list = new ArrayList<>();
synchronized(list) {
list.add(“foo”);
}
  • Use iterators properly: When iterating over a collection, you should use the Iterator interface to avoid concurrent modifications. If you modify the collection while iterating over it using an iterator, you will get a ConcurrentModificationException. Instead, you can use the remove() method of the iterator to remove elements from the collection while iterating over it. For example
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if (someCondition) {
iterator.remove(); // safe way to remove an element from the list
}
}

5. How do you implement a deadlock situation in Java?

Deadlock in Java occurs when two or more threads are blocked forever, waiting for each other to release the lock or resource they are holding. Implementing a deadlock situation in Java involves creating a scenario where two or more threads are blocked, waiting for each other, and unable to proceed further. Here is an example of how to create a deadlock in Java:

public class Main {
//Object lock that is required by the thread for execution.
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();

public static void main(String[] args) {

//Creating one thread and its implemented annonymous method.
Thread thread1 = new Thread(() -> {

//Synchronized block that acquires lock on the Object
synchronized (lock1) {
System.out.println(“Thread 1 acquired lock 1”);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}

//Acquiring synchronized block that acquires lock on another
//object for execution.
synchronized (lock2) {
System.out.println(“Thread 1 acquired lock 2”);
}
}
});

//Creating another thread and its implemented annonymous method.
Thread thread2 = new Thread(() -> {

//Synchronized block that acquires lock on the Object
synchronized (lock2) {
System.out.println(“Thread 2 acquired lock 2”);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}

//Acquiring synchronized block that acquires lock on another
//object for execution.
synchronized (lock1) {
System.out.println(“Thread 2 acquired lock 1”);
}
}
});

//Stating both the thread.
thread1.start();
thread2.start();
}
}

In this example, we have two threads, thread1 and thread2, each trying to acquire two locks: lock1 and lock2.

  • Thread1 first acquires lock1, then waits for 100 milliseconds before trying to acquire lock2.
  • At the same time, thread2 acquires lock2 and waits for 100 milliseconds before trying to acquire lock1.

Since both threads are waiting for each other to release the locks they are holding, a deadlock situation is created, and the program will be stuck forever, unable to proceed.

6. Can you explain the difference between a “Reader-Writer” lock and a “ReentrantReadWriteLock” in Java? Which is more flexible to use?

Reader-Writer lock:

A reader-writer lock allows multiple threads to read a shared resource simultaneously but only one thread can write to it at a time. When a thread wants to write to the resource, it must wait for all the readers to finish reading before acquiring the lock.

The reader-writer lock is not reentrant, meaning that a thread holding the lock for reading cannot acquire the lock for writing without releasing the read lock first. Similarly, a thread holding the lock for writing cannot acquire the lock for reading without releasing the write lock first.

ReentrantReadWriteLock:

ReentrantReadWriteLock is a more flexible implementation of a reader-writer lock. It allows multiple threads to acquire the read lock at the same time and also allows a thread holding the read lock to acquire the write lock without releasing the read lock first. This makes it possible for a thread to upgrade a read lock to a write lock.

In addition, the ReentrantReadWriteLock is reentrant, meaning that a thread holding the lock for reading or writing can acquire the lock again without releasing the lock first.

Overall, the ReentrantReadWriteLock provides more flexibility than the Reader-Writer lock, but it is also more complex and can potentially lead to deadlocks if not used properly. It is generally recommended to use the ReentrantReadWriteLock when more fine-grained control is needed over the lock, and the Reader-Writer lock when simplicity is preferred.

7. Imagine a scenario where there are two drives on a computer, each with multiple nested folders, and some of these folders contain some file. Can you write a Java program to find out whether this file exists? How would you minimize the search time for any file?

We can use the concept of Multithreading to search for the file. Here is the java code to implement the same -

import java.io.File;
class FileSearchThread extends Thread {

//File name and File Directory for every thread
private final String fileName;
private final File directory;
//Constuctor
public FileSearchThread(String fileName, File directory) {
this.fileName = fileName;
this.directory = directory;
}

//Run method that calls search method for searching file.
@Override
public void run() {
boolean result = searchFile(fileName, directory);
if(result) {
System.out.println(“File Found. Location — “ + directory.toString());
}
}
private boolean searchFile(String fileName, File directory) {
//Searching in the directory
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
// Recursively search in nested directories
searchFile(fileName, file);
} else if (file.getName().equalsIgnoreCase(fileName)) {
System.out.println(“File found at: “ + file.getAbsolutePath());
return true;
}
}
}
return false;
}
}
public class FileSearch {
public static void main(String[] args) {
String fileNameToSearch = “xyz.txt”;
// Create a separate thread for each drive and folder
for (char drive = ‘C’; drive <= ‘D’; drive++) {
String rootPath = drive + “:\\”;
File[] directories = new File(rootPath).listFiles(File::isDirectory);
if (directories != null) {
for (File directory : directories) {
new FileSearchThread(fileNameToSearch, directory).start();
}
}
}
}
}

In this program, we first define a “FileSearchThread” class that extends Thread and takes a fileName and directory parameter in its constructor. In the run method, we call the searchFile method to recursively search for the file in the given directory and its nested directories. If the file is found, we print its absolute path and return from the method.

In the “FileSearch” class, we create a separate thread for each drive and folder by iterating through the directories of drives C and D. We pass the fileNameToSearch and the current directory to the FileSearchThread constructor and start the thread.

By creating a separate thread for each drive and folder, we can search for the file in parallel and minimize the search time.

8. Can you explain the difference between the “Type 1” and “Type 4” JDBC drivers? Which one is preferred?

JDBC (Java Database Connectivity) drivers are used to connect Java applications to databases. There are several types of JDBC drivers, but the two most commonly used types are Type 1 and Type 4 drivers. Here’s the difference between the two:

Type 1 driver:

It is also known as the JDBC-ODBC bridge driver, the Type 1 driver is a bridge between the JDBC API and ODBC (Open Database Connectivity) API. The Type 1 driver uses the ODBC driver to connect to the database and requires the ODBC driver to be installed on the client machine.

The Type 1 driver is easy to use and can be used with any database that has an ODBC driver available. However, it has performance issues since it adds an extra layer of communication between the Java application and the database.

Type 4 driver:

It is also known as the Pure Java driver, the Type 4 driver is a fully Java-based driver that communicates directly with the database using a native protocol. The Type 4 driver does not require any external libraries or drivers and is the most commonly used driver type in Java applications.

The Type 4 driver provides better performance than the Type 1 driver since it does not add any extra layers of communication between the Java application and the database. It also provides better security and platform independence, as it is not dependent on any external libraries.

The Type 4 driver is preferred over the Type 1 driver for Java applications because it provides better performance, security, and platform independence.

9. How do you implement optimistic locking in JDBC?

Optimistic locking is a technique used to prevent data conflicts in a multi-user environment. In JDBC, optimistic locking can be implemented using the following steps:

  • When a user starts editing a record, retrieve the current version number of the record from the database and store it in a variable.
  • When the user is finished editing the record, update the record in the database with the new values and increment the version number.
  • If the update is successful, commit the transaction. If not, roll back the transaction.
  • Before updating the record in the database, compare the current version number with the version number stored in the variable. If the two values match, update the record. If not, it means that another user has updated the record since the first user retrieved it. In this case, you can either abort the transaction and prompt the user to retrieve the latest version of the record or merge the changes made by both users.

Here’s an example code snippet that implements optimistic locking in JDBC using a ‘PreparedStatement’:

try {
// Retrieve the current version number of the record
PreparedStatement selectStmt = conn.prepareStatement(“SELECT version FROM table WHERE id = ?”);
selectStmt.setInt(1, id);
ResultSet rs = selectStmt.executeQuery();
int currentVersion = 0;
if (rs.next()) {
currentVersion = rs.getInt(1);
}
// Update the record and increment the version number
PreparedStatement updateStmt = conn.prepareStatement(“UPDATE table SET column1 = ?, column2 = ?, version = ? WHERE id = ? AND version = ?”);
updateStmt.setString(1, newValue1);
updateStmt.setString(2, newValue2);
updateStmt.setInt(3, currentVersion + 1);
updateStmt.setInt(4, id);
updateStmt.setInt(5, currentVersion);
int rowsUpdated = updateStmt.executeUpdate();
// Check if the update was successful
if (rowsUpdated == 1) {
conn.commit();
} else {
conn.rollback();
}
} catch (SQLException e) {
conn.rollback();
e.printStackTrace();
}

In this example, we first retrieve the current version number of the record using a SELECT statement. We then update the record using a PreparedStatement that includes the current version number in the WHERE clause to ensure that we only update the record if it hasn’t been modified by another user. Finally, we check the number of rows updated by the UPDATE statement and commit or roll back the transaction accordingly.

10. What is the purpose of the “Exchanger” class in Java? How to use this class? In which scenario it is useful?

The Exchanger class in Java is a synchronization tool that allows two threads to exchange objects in a blocking manner. It provides a simple way to exchange data between two threads in a producer-consumer scenario, where one thread produces data and the other thread consumes it.

The Exchanger class is part of the (java.util.concurrent) package and provides a single method called exchange(). This method blocks until both threads call it, at which point it exchanges the objects provided by both threads.

Here’s an example of how to use the Exchanger class:

import java.util.concurrent.Exchanger;
public class ExchangerExample {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Thread producerThread = new Thread(() -> {
try {
String data = “Hello from producer thread”;
System.out.println(“Producer thread is sending: “ + data);
String receivedData = exchanger.exchange(data);
System.out.println(“Producer thread received: “ + receivedData);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumerThread = new Thread(() -> {
try {
String data = “Hello from consumer thread”;
System.out.println(“Consumer thread is sending: “ + data);
String receivedData = exchanger.exchange(data);
System.out.println(“Consumer thread received: “ + receivedData);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producerThread.start();
consumerThread.start();
}
}

In this example, we create an ‘Exchanger’ object and two threads: a producer thread and a consumer thread. The producer thread sends a string message to the consumer thread using the ‘exchange()’ method. The consumer thread also sends a string message to the producer thread using the same method. Both threads block until the other thread calls ‘exchange()’, at which point the objects are exchanged and each thread prints out the received message.

The Exchanger class is useful in scenarios where two threads need to communicate with each other and exchange data. It simplifies the coordination between threads and can help avoid race conditions and other synchronization issues.

Conclusion

The article on Java tricky interview questions for 7 Years of Experience provides a glimpse into some of the questions that may come up during an interview.

Preparing for these questions can help Java developers enhance their knowledge and skills, thereby increasing their chances of success in an interview.

--

--

AlishaS
Javarevisited

I am enthusiastic about programming, and marketing, and constantly seeking new experiences.