Java interview prep: 15 Java interview questions

Not all interviews will focus on algorithms and data structures — often times an interview will focus solely on the language or technology that you have claimed to be an expert in. In interviews like these, there usually aren’t any “gotcha” questions, instead they require you to draw on memory and your experience with the language — in other words, they test your knowledge of the programming language.

However, it can be easy to forget all the ins and outs of a language like Java, because simply put, we don’t deal with questions like “What kind of memory does the JVM manage?” and “Describe polymorphism with an example.” on a daily basis.

This post outlines some popular questions asked in a Java interview. Because Java specific questions can vary so much, this post acts as a guide to get you thinking about the different types of questions you can expect and what topics you should prepare for. If you’d like to see a complete guide to mastering the Java language, you can check out The Definitive Java Interview Handbook”.

Today we’ll look at interview questions and answers related to:

  • The Java ecosystem
  • Java Classes
  • Interfaces
  • Inheritance
  • Multi-threading
  • Memory management
  • Collections
  • Exception handling
  • Serialization
  • Singleton

Let’s get started!

Q1: What is meant by Java being platform independent?

Java works on the principle of write once and run anywhere. Once a Java program is written, it is compiled into what is known as byte code, which can then be run on any Java Virtual Machine or JVM for short.

Mac OS, Windows OS, Linux

Compilation to bytecode is the magic behind Java's interoperability. Different operating systems and hardware architectures have JVMs custom designed for themselves and all JVMs can run the same bytecode. Therefore, if you write a Java program on Linux, it will run seamlessly on a JVM designed for Windows operating system, making code agnostic to the underlying hardware and OS.

Q2: Explain the concepts of JRE, JDK, and JVM

  • JRE (Java Runtime Environment) includes the Java Virtual Machine and the standard Java APIs (core classes and supporting files.). The JRE contains just enough to execute a Java application, but not enough to compile it.
  • JDK (Java Development Kit) is the JRE plus the Java compiler, and a set of other tools to compile and debug code. JRE consists of Java platform libraries, Java Virtual Machine (JVM), Java Plugin and Java Web Start to run Java applications. JRE as a stand-alone does not contain compilers and debugging tools. If you need to develop Java programs you need the full Java SDK. The JRE is not enough for program development. Only the full Java SDK contains the Java compiler which turns your .java source files into bytecode .class files.
  • JVM (Java Virtual Machine) is an implementation of a specification, detailing the behavior expected of a JVM. Any implementation that conforms to the JVM specification should be able to run code compiled into Java bytecode irrespective of the language in which the code was originally written. In the Java programming language, all source code is first written in plain text files ending with the .java extension. Those source files are then compiled into .class files by the javac compiler. A .class file does not contain code that is native to your processor; it instead contains bytecodes — the machine language of the Java Virtual Machine. The java launcher tool then runs your application with an instance of the Java Virtual Machine.

Q3: How would you mark an entity package private in Java?

There's no explicit modifier for package private. In the absence of any modifier the class or member variables are package private. A member marked package private is only visible within its own package. Consider the class below.

// a class can be accessed by other classes within the same
// package but not outside of it.

class IamPackagePrivateClass {

   int IamPackagePrivate;
   private int IamPrivate;

   
   public IamPackagePrivate(int a, int b) {
      this.IamPackagePrivate = a;
      this.IamPrivate = b;
   }

}

Package private is a slightly wider form of private. One nice thing about package-private is that you can use it to give access to methods you would otherwise consider private to unit test classes. So, if you use helper classes which have no other use but to help your public classes do something clients need, it makes sense to make them package private as you want to keep things as simple as possible for users of the library.

Q4: Why should you avoid the finalize() method in the Object class? What are some alternatives?

The Object class provides a callback method, finalize(), that may be invoked on an object when it becomes garbage. Object's implementation of finalize() does nothing — you can override finalize() to do cleanup, such as freeing up resources.

The finalize() method may be called automatically by the system, but when it is called, or even if it is called, is uncertain. Therefore, you should not rely on this method to do your cleanup for you. For example, if you don't close file descriptors in your code after performing I/O and you expect finalize() to close them for you, you may run out of file descriptors.

Here are some alternatives:

  • The try-with-resources idiom can be used to clean up objects. This requires implementing the AutoCloseable interface.
  • Using a PhantomReference to perform cleanup when an object is garbage collected
  • Using Cleaner class to perform cleanup actions.
  • Implement a close() method, which does the cleanup and document that the method be called.

Q5: Can you change the contents of a final array as shown in the code snippet below?

final int[] array = new int[5];
array[0] = 1;

It may appear counterintuitive, but we can actually change the contents of the array even though it is marked as final. The array variable points to a particular start location in the memory where the contents of the array are placed. The location or the memory address can't be changed. For instance, the following code will not compile:

final int[] array = new int [5]
array = new int[10];

However, the following code will work.

public class FinalArrayExample {
  final int[] array = new int[5];
  
  // allowed
  void changeArrayContents(int i, int val) {
    array[i] = val;
  }
  
  // not allowed and will not compile
  /*
  
  void changeArray() {
    array = new int [10]
    
  }*/

}
  

Q6: Explain the difference between an interface and an abstract class? When should you use one or the other?

An abstract class can't be instantiated, but it can be subclassed. An abstract class usually contains abstract and non-abstract methods that subclasses are forced to provide an implementation for.

An interface is a completely "abstract class" that is used to group related methods with empty bodies.

Following are four main differences between abstract classes and interfaces:

  • An abstract class can have final variables, static variables, or class member variables whereas an interface can only have variables that are final and static by default.
  • An abstract class can have static, abstract, or non-abstract methods. An interface can have static, abstract, or default methods.
  • Members of an abstract class can have varying visibility of private, protected, or public. Whereas, in an interface all methods and constants are public.
  • A class can only extend another class, but it can implement multiple interfaces. Similarly, an interface can extend multiple interfaces. An interface never implements a class or an interface.

Use an abstract class when subclasses share state or use common functionality. Or you require to declare non-static, non-final fields or need access modifiers other than public.

Use an interface if you expect unrelated classes would implement your interface. For example, the interfaces Comparable and Cloneable are implemented by many unrelated classes. Interfaces are also used in instances where multiple inheritance of type is desired.

Q7: What is polymorphism? Can you give an example?

Polymorphism is the ability in programming to present the same interface for differing underlying forms or data types. Polymorphism is when you can treat an object as a generic version of something, but when you access it, the code determines which exact type it is and calls the associated code. What this means is that polymorphism allows your  code to work  with   different classes without needing to know which class it’s using.

Polymorphism is used to make applications more modular and extensible. Instead of messy conditional statements describing different courses of action, you create interchangeable objects that you select based on your needs. That is the basic goal of polymorphism.

The classic example of polymorphism is a Shape class. We derive Circle, Triangle, and Rectangle classes from the parent class Shape, which exposes an abstract method draw(). The derived classes provide their custom implementations for the draw() method. Now it is very easy to render the different types of shapes all contained within the same array by calling the draw() method on each object. This saves us from creating separate draw methods for each shape e.g. drawTriangle(), drawCircle() etc.

Q8: Can the main method be overloaded?

Yes, the main method, which is a static method, can be overloaded. But only public static void main(String[] args) will be used when your class is launched by the JVM even if you specify one or two command-line arguments. However, programmatically one can invoke the overloaded versions of the main method.

Q9: How can you pass multiple arguments to a method on each invocation call?

We can pass variable number of arguments to a method using varargs feature. Below is an example of passing multiple arguments of the same type to a method.

public void childrenNames(string... names) {
   for(int i= 0; i < names.length; i++)
   system.out.println(names[i]);

}
  • The type name is followed by three dots, a space, and then the variable name.
  • The varargs variable is treated like an array.
  • The varargs variable must appear at the last in the method signature.
  • As a consequence of the above, there can only be a single varargs in a method signature.

The above method can be invoked as follows:
Invoking Varargs Method

childrenNames();
childrenNames("Jane");
childrenNames("Jane", "Tom", "Peter");

Q10: Can a semaphore act as a mutex?

A semaphore can potentially act as a mutex if the number of permits it can give out is set to 1. However, the most important difference between the two is that in the case of a mutex, the same thread must call acquire and subsequent release on the mutex whereas in the case of a binary semaphore, different threads can call acquire and release on the semaphore.

This leads us to the concept of "ownership". A mutex is owned by the thread acquiring it, till the point, it releases it, whereas for a semaphore there's no notion of ownership.

Need a refresher on multithreading? Check out this article "Java Multithreading and Concurrency: What to know to crack a senior engineering interview".

Q11: Explain the Externalizable interface

The Serializable interface gets us automatic serialization capability for objects of our class. On the other hand the Externalizable interface provides a way to implement a custom serialization mechanism. A class that implements the Externalizable interface is responsible to save and restore the contents of its own instances.

The Externalizable interface extends the Serializable interface and provides two methods to serialize and deserialize an object, writeExternal() and readExternal().

Q12: If a code block throws more than one exception, how can it be handled?

Multiple types of exceptions thrown by a snippet of code can be handled by multiple catch block clauses followed by the try block. An example snippet of exception handling appears below:

void process(int val)  {
   try {
        if (val == 1)
            //checked exception
            throw new FileNotFoundException();
       
        if (val == 2)
            // runtime exception
            throw new NullPointerExxception();
        
        if (val == 3)
            // error exception
            throw new StackOverflowError
            
   } catch (RuntimeException re) {
            // catches all unchecked  exceptions
            
   } catch (Exception e) {
            // catches all checked exceptions
            
   } catch (Error err) {
            // catches all errors
   
   }
   
}

Q13: If you were to use a set, how would you determine between a HashSet and a TreeSet?

Initially, you may want to use HashSet as it will give you a better time complexity, but it makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time.

So if you are wanting to maintain the order it’s best to use a TreeSet as it stores keys in ascending order rather than in their insertion order. It's not thread safe. However, keep in mind that TreeSet is not thread safe whereas a HashSet is.

Q14: What are a few ways you can improve the memory footprint of a Java application?

Here are three key steps you can take to improve the memory footprint:

  • Limiting the scope of local variables. Each time the top scope from the stack is popped up, the references from that scope are lost, and this could make objects eligible for garbage collection.
  • Explicitly set variable references to null when not needed. This will make objects eligible for garbage collection.
  • Avoid finalizers. They slow down program performance and do not guarantee anything.

Q15: What is the best way to implement a singleton class?

The best way to implement a singleton as per Josh Bloch is to use an enum type for the singleton. Because Java ensures that only a single instance of an enum is ever created, the singleton class implemented via enums is safe from reflection and serialization attacks.

class Demonstration {
    public static void main( String args[] ) {
        Superman superman = Superman.INSTANCE;
        superman.fly();
    }
}

enum Superman {
    INSTANCE;

    private final String name = "Clark Kent";
    private String residence = "USA";

    public void fly() {
        System.out.println("I am flyyyyinggggg ...");
    }
}

Gaining a mastery

There has been a lot covered in this post about the Java programming language, ranging from the Java ecosystem (question1) to multi-threading (question 10) and exceptions (question 12). These are the types of Java interview questions you can expect. It’s best to use the material outlined above as a guideline for topics you’ll want to study and the types of questions you can expect.

However, the material here just scratches the surface. There are many more concepts to revisit or explore like object-oriented programming, static variables, and method overloading. If you’d like to take a deep dive into Java and explore hundreds of more questions about the topics mentioned above then “The Definitive Java Interview Handbook” is going to be a great resource to get you refreshed on the core java principles — everything from the basics to more advanced functionality.

Happy learning!

Show Comments