Hello everyone, ladies and gentlemen, software engineers! Let's talk about interview questions. About what you need to prepare for and what you need to know. This is a great time to review or to study these points for the first time. Top 50 job interview questions and answers for Java Core. Part 1 - 1 I ended up with a rather extensive collection of frequently asked questions about OOP, Java syntax, Java exceptions, collections, and multithreading, which I will split into several parts for convenience. It's difficult to cover everything at once, but I hope this material will provide a good foundation for those preparing to find their first job as a programmer. For the best understanding and retention, I advise combing through other sources as well. You can get a deeper grasp of a concept by approaching it from several different angles. Important: We will only be talking about Java before version 8. All innovations that came in versions 9, 10, 11, 12, and 13 will not be considered here. Any ideas/comments on how to improve the answers are welcome. Enjoy your reading. Let's go!

Java interview: questions about OOP

1. What are the characteristics of Java?

Answer:
  1. OOP concepts:

    1. object orientation
    2. inheritance
    3. encapsulation
    4. polymorphism
    5. abstraction
  2. Cross-platform: A Java program can be run on any platform without any changes. Of course, this requires an installed JVM (Java virtual machine).

  3. High performance: The Just-In-Time (JIT) compiler makes high performance possible. The JIT compiler converts the bytecode to machine code and then the JVM starts execution.

  4. Multithreading: The JVM creates a thread of execution called the main thread. A programmer can create multiple threads by deriving from the Thread class or implementing the Runnable interface.

2. What is inheritance?

Inheritance means that one class can inherit another class (using the extends keyword). This means you can reuse code from the class you inherit. The existing class is known as the superclass and the newly created class is the subclass. People also say use the terms parent and child.
public class Animal {
   private int age;
}

public class Dog extends Animal {

}
where Animal is the parent and Dog is the child.

3. What is encapsulation?

This question is often asked in interviews for Java developer positions. Encapsulation is hiding the implementation by using access modifiers, getters, and setters. This is done in order to prevent external access wherever developers think it is necessary. A simple example from real life is the car. We have no direct access to the operation of the engine. All we need to do is put the key into the ignition and turn on the engine. The processes that take place under the hood are none of our business. Moreover, if we were to interfere in the engine's activity, it could lead to an unpredictable situation, possibly damaging the car and resulting in bodily harm. Exactly the same thing happens in programming. This is described well on Wikipedia. There is also an article about encapsulation on CodeGym.

4. What is polymorphism?

Polymorphism is a program's ability to treat objects with the same interface in the same way, without information about the object's specific type. As the saying goes, "one interface — many implementations". With polymorphism, you can combine and use different types of objects based on shared behaviors. For example, we have an Animal class that has two descendants: Dog and Cat. The generic Animal class has a behavior shared by all, the ability to make a sound. We use polymorphic capabilities when we need to gather everything that inherits the Animal class and execute the "make sound" method. Here's how it looks:
List<Animal> animals = Arrays.asList(new Cat(), new Dog(), new Cat());
animals.forEach(animal -> animal.makeSound());
In other words, polymorphism is helpful. And this also applies to polymorphic (overloaded) methods. How to use polymorphism

Interview questions about Java syntax

5. What is a constructor in Java?

Constructors have the following characteristics:
  1. When a new object is created, the program uses the appropriate constructor to create it.
  2. A constructor is like a method. Its distinctive features lie in the fact that there is no return value (including void) and that its name is the same as the name of the class.
  3. If no constructor is created explicitly, an empty constructor is created automatically.
  4. A constructor can be overridden.
  5. If you declare a constructor with parameters but also need one without parameters, then you must create it separately, since it will not be created automatically.

6. Which two classes do not inherit Object?

Do not be fooled by trick questions — there are no such classes. All classes inherit the Object class either directly or through ancestors!

7. What's a local variable?

This is another popular interview question for Java developers. A local variable is a variable that is defined inside a method and exists as long as the method is being executed. As soon as execution ends, the local variable ceases to exist. Here is a program that uses a local variable named helloMessage in the main() method:
public static void main(String[] args) {
   String helloMessage;
   helloMessage = "Hello, World!";
   System.out.println(helloMessage);
}

8. What's an instance variable?

An instance variable is a variable that is declared inside a class. It exists as long as an object exists. For example, we have a Bee class, which has two instance variables — nectarLoad and maxNectarLoad:
public class Bee {

   /**
    * Current nectar load
    */
   private double nectarLoad;

   /**
    * Maximum nectar that can the bee can collect.
    */
   private double maxNectarLoad = 20.0;

  ...
}

9. What are access modifiers?

Access modifiers are a mechanism for customizing access to classes, methods, and variables. The following modifiers exist, listed in order of increasing access:
  1. private — This access modifier is used on methods, fields and constructors. Access is limited to the class in which they are declared.
  2. package-private (default) — This is the default access level for classes. Access is limited to the specific package in which a class, method, variable, or constructor is declared.
  3. protected — This access modifier offers the same access level as package-private with the addition of access for classes that inherit a class with the protected modifier.
  4. public — This access level is also used for classes. This access level means there is full access throughout the application.
Top 50 job interview questions and answers for Java Core. Part 1 - 2

10. What is method overriding?

We override methods when a child class wants to change the behavior of its parent class. If we also need to do what is in the parent method, we can use super.methodName() in the child, which will execute the parent method. We can add our additional logic after that. Requirements that must be observed:
  • the method signature must be the same
  • the return value must be the same

11. What are method signatures?

Top 50 job interview questions and answers for Java Core. Part 1 - 3A method signature is the combination of the method name and the arguments the method takes. The method signature is a method's unique identifier when overloading methods.

12. What is method overloading?

Method overloading is a feature of polymorphism in which we change the method signature to create multiple methods that perform the same action:
  • the same name
  • different arguments
  • there can be different return types
For example, the ArrayList class's add() method can be overloaded, allowing us to add in a different ways depending on the input arguments:
  • add(Object o) — This method simply adds an object
  • add(int index, Object o) — This method adds an object at a specific index
  • add(Collection<Object> c) — This method adds a list of objects
  • add(int index, Collection<Object> c) — This method adds a list of objects starting from a specific index.

13. What's an interface?

Java does not support multiple inheritance. To overcome this limitation, interfaces were added int he form that we know and love ;) For a long time, interfaces had only methods without any implementation. In the context of this answer, let's talk about them. For example:

public interface Animal {
   void makeSound();
   void eat();
   void sleep();
}
Some details follow from this:
  • All methods in an interface are public and abstract
  • All variables are public static final
  • Classes do not inherit interfaces (i.e. we don't use the extends keyword). Instead, classes implement them (i.e. we use the implements keyword). Moreover, you can implement as many interfaces as you want.
  • Classes that implement an interface must provide an implementation of all the methods that are in the interface.
Like this:
public class Cat implements Animal {
   public void makeSound() {
       // Method implementation
   }

   public void eat() {
       // Implementation
   }

   public void sleep() {
       // Implementation
   }
}

14. What is a default method in an interface?

Now let's talk about default methods. What are they for? Who are they for? These methods were added to serve "both hands". What am I talking about? Well, on the one hand, there was a need to add new functionality: lambdas and the Stream API. On the other hand, it was necessary to retain what Java is famous for — backwards compatibility. To do this, interfaces needed some new ready-made solutions. This is how default methods came to us. A default method is an implemented method in an interface, marked with the default keyword. For example, the well-known stream() method in the Collection interface. Believe me, this interface is not as simple as it seems. Or also the equally famous forEach() method in the Iterable interface. It also didn't exist until the default methods were added. By the way, you can also read about it on CodeGym here.

15. How then do we inherit two identical default methods?

The previous answer about what a default method is begs another question. If you can implement methods in interfaces, then theoretically you can implement two interfaces with the same method. How do we do that? Here are two different interfaces with the same method:
interface A {
   default void foo() {
       System.out.println("Foo A");
   }
}

interface B {
   default void foo() {
       System.out.println("Foo B");
   }
}
And we have a class that implements these two interfaces. But just how do we choose a specific method in interface A or B? The following special construct allows this: A.super.foo():
public class C implements A, B {
   public void fooA() {
       A.super.foo();
   }

   public void fooB() {
       B.super.foo();
   }
}
Thus, the fooA() method will use the default foo() method of the A interface, while the fooB() method will use the foo() method of the B interface.

16. What are abstract methods and classes?

In Java, abstract is a reserved word. It is used to denote abstract classes and methods. First, we need definitions. An abstract method is a method that is declared using the abstract keyword without an implementation in an abstract class. That is, this is a method as in an interface, but with the addition of a keyword, for example:
public abstract void foo();
An abstract class is a class also marked with the abstract keyword:
public abstract class A {

}
An abstract class has several features:
  • you cannot create an object of an abstract class
  • it can have abstract methods
  • it may also not have abstract methods
Abstract classes are needed for abstraction (sorry for the tautology) that has a set of common behaviors and states (that is, methods and variables). Real life is full of examples. Everything around us. "Animal", "Car", "Geometric figure", and so on.

17. What's the difference between String, StringBuilder and StringBuffer?

String values are stored in a constant string pool. As soon as a string is created, it appears in this pool. And you cannot delete it. For example:
String name = "book";
The variable will point to the constant string pool Top 50 job interview questions and answers for Java Core. Part 1 - 4Setting the name variable to a different value, we have:
name = "pen";
The constant string pool looks like this: Top 50 job interview questions and answers for Java Core. Part 1 - 5In other words, both values remain there. String Buffer:
  • String values are stored in a stack. If a value is changed, then the new value will replace the old one.
  • String Buffer is synchronized and is therefore thread safe.
  • Due to thread safety, its performance is poor.
Example:
StringBuffer name = “book”;
Top 50 job interview questions and answers for Java Core. Part 1 - 6As soon as the value of the name variable changes, the value in the stack changes: Top 50 job interview questions and answers for Java Core. Part 1 - 7StringBuilder is exactly the same as StringBuffer, only it is not thread safe. As a result, it is noticeably faster than StringBuffer.

18. What's the difference between an abstract class and an interface?

Abstract class:
  • Abstract classes have a default constructor. It is called every time a descendant of the abstract class is created.
  • They can include both abstract methods and non-abstract ones. In general, an abstract class doesn't have to have abstract methods.
  • A class that inherits an abstract one must implement only abstract methods.
  • An abstract class can have instance variables (see Question #5).
Interface:
  • An interface has no constructor and cannot be initialized.
  • Only abstract methods can be added (except for default methods).
  • Classes that implement the interface must implement all methods (except for default methods).
  • Interfaces can only have constants.

19. Why is accessing an element in an array O(1)?

This question was literally asked in my last interview. As I learned later, the purpose of this question is to see how a person thinks. Clearly, there is little practical value in this knowledge. Merely knowing it is enough. First, we need to clarify that O(1) is notation for the time complexity of a "constant time" algorithm. In other words, this designation indicates the fastest execution time. To answer this question, we need to consider what we know about arrays. To create an int array, we must write the following:
int[] intArray = new int[100];
Several conclusions can be drawn from this syntax:
  1. When an array is declared, its type is known. If the type is known, then the size of each cell in the array is known.
  2. The size of the whole array is known.
It follows therefore that to understand which cell to write to, we just need to calculate which area of memory to write to. For a computer, this is easy peasy. The computer knows where the allocated memory starts, the number of elements, and the size of each cell. All this means that the place to write will be equal to the starting place of the array + the size of each cell multiplied by the index.

So how do we arrive at O(1) when accessing objects in an ArrayList?

This question immediately follows the previous one. The truth is when working with an array that holds primitives, we know in advance (at the time of creation) the size of the element type. But what do we do if we have this sort of inheritance hierarchy and Top 50 job interview questions and answers for Java Core. Part 1 - 8we want to create a collection for elements of type A and add different implementations (B, C, and D):
List<A> list = new ArrayList();
list.add(new B());
list.add(new C());
list.add(new D());
list.add(new B());
In this situation, how do we calculate the size of each cell? After all, each object will be different, possibly with different additional fields. What to do? Here the question is posed in a way that is meant to confuse you. We know that the collection doesn't directly store objects. It only stores references to objects. And all references have the same size, and it is known. As a result, we calculate addresses here in the same way as in the previous question.

21. Autoboxing and unboxing

Historical background: autoboxing and unboxing are some of the main innovations in JDK 5. Autoboxing is the process of automatic conversion from a primitive type to a corresponding wrapper class. Unboxing is the exact opposite of autoboxing. It is the process of converting a wrapper class into a primitive. But if the value of a wrapper is null, then a NullPointerException will be thrown during unboxing.

Primitives and their corresponding wrappers

Primitive Wrapper class
boolean Boolean
int Integer
byte Byte
char Character
float Float
long Long
short Short
double Double

// Autoboxing happens:

  • when assigning a primitive to a reference to a wrapper class:

    BEFORE Java 5:

    // Manual boxing (the way it was BEFORE Java 5).
    public void boxingBeforeJava5() {
       Boolean booleanBox = new Boolean(true);
       Integer intBox = new Integer(3);
       // And so on for other types
    }
    
    After Java 5:
    // Automatic boxing (the way it became in Java 5).
    public void boxingJava5() {
       Boolean booleanBox = true;
       Integer intBox = 3;
       // And so on for other types
    }
  • when a primitive is passed as an argument to a method that expects a wrapper:

    public void exampleOfAutoboxing() {
       long age = 3;
       setAge(age);
    }
    
    public void setAge(Long age) {
       this.age = age;
    }

// Unboxing happens:

  • when we assign an instance of a wrapper class to a primitive variable:

    // BEFORE Java 5:
    int intValue = new Integer(4).intValue();
    double doubleValue = new Double(2.3).doubleValue();
    char c = new Character((char) 3).charValue();
    boolean b = Boolean.TRUE.booleanValue();
    
    // And after JDK 5:
    int intValue = new Integer(4);
    double doubleValue = new Double(2.3);
    char c = new Character((char) 3);
    boolean b = Boolean.TRUE;
  • During arithmetic operations. The operations apply only to primitive types, so unboxing to primitives is necessary.

    // BEFORE Java 5:
    Integer integerBox1 = new Integer(1);
    Integer integerBox2 = new Integer(2);
    
    // A comparison used to require this:
    integerBox1.intValue() > integerBox2.intValue()
    
    // In Java 5
    integerBox1 > integerBox2
  • when passing an instance of a wrapper class to a method that takes the corresponding primitive:

    public void exampleOfAutoboxing() {
       Long age = new Long(3);
       setAge(age);
    }
    
    public void setAge(long age) {
       this.age = age;
    }

22. What is the final keyword and where is it used?

The final keyword can be used on variables, methods, and classes.
  1. The value of a final variable cannot be changed after it is initialized.
  2. A final class is sterile :) It cannot have children.
  3. A final method cannot be overridden by a descendant.
We've covered the high-level stuff. Now lets dive deeper.

Final variables

Java gives us two ways to declare a variable and assign a value to it:
  1. You can declare a variable and initialize it later.
  2. You can declare a variable and assign a value right away.
Here's an example that demonstrates these uses of final variables:
public class FinalExample {

   // A static final variable that is immediately initialized:
   final static String FINAL_EXAMPLE_NAME = "I'm likely the final one";

   // A final variable that is not initialized, but will only work if you
   // initialize it in the constructor:
   final long creationTime;

   public FinalExample() {
       this.creationTime = System.currentTimeMillis();
   }

   public static void main(String[] args) {
       FinalExample finalExample = new FinalExample();
       System.out.println(finalExample.creationTime);

       // The final FinalExample.FINAL_EXAMPLE_NAME field cannot be accessed
//    FinalExample.FINAL_EXAMPLE_NAME = "Not you're not!";

       // The final Config.creationTime field cannot be accessed
//    finalExample.creationTime = 1L;
   }
}

Can a final variable be considered a constant?

Since we cannot assign new values to final variables, it seems that these are constant variables. But only at first glance: If the variable's data type is immutable, then, yes, it is a constant. But if the data type is mutable, that is, changeable, then it will be possible to use methods and variables to change the value of the object referenced by a final variable. Because of this, it cannot be called a constant. The following example shows that some final variables are truly constants, while others are not, since they can be changed.
public class FinalExample {

   // Immutable final variables
   final static String FINAL_EXAMPLE_NAME = "I'm likely the final one";
   final static Integer FINAL_EXAMPLE_COUNT  = 10;

   // Mutable final variables
   final List<String> addresses = new ArrayList();
   final StringBuilder finalStringBuilder = new StringBuilder("Constant?");
}

Local final variables

When a final variable is created within a method, it is called a local final variable:
public class FinalExample {

   public static void main(String[] args) {
       // You can do this
       final int minAgeForDriveCar = 18;

       // Or you can do this, in a for-each loop:
       for (final String arg : args) {
           System.out.println(arg);
       }
   }

}
We can use the final keyword in an enhanced for loop, because a new variable is created after each iteration of the loop. Keep in mind that this does not apply to a normal for loop, so we will get a compile-time error.
// The final local j variable cannot be assigned
for (final int i = 0; i < args.length; i ++) {
   System.out.println(args[i]);
}

Final class

A class declared as final cannot be extended. Putting it more simply, no other class can inherit it. An excellent example of a final class in the JDK is String. The first step to creating an immutable class is to mark it as final, thus preventing it from being extended:
public final class FinalExample {
}

// Compilation error!
class WantsToInheritFinalClass extends FinalExample {
}

Final methods

When a method is marked final, it is called a final method (makes sense, right?). A final method cannot be overridden in a child class. Incidentally, the Object class's wait() and notify() methods are final, so we don't have the ability to override them.
public class FinalExample {
   public final String generateAddress() {
       return "Some address";
   }
}

class ChildOfFinalExample extends FinalExample {

   // Compilation error!
   @Override
   public String generateAddress() {
       return "My OWN Address";
   }
}

How and where to use final in Java

  • Use the final keyword to define some class-level constants;
  • Create final variables for objects that you don't want to be changed. For example, object-specific properties that we can use for logging purposes.
  • If you do not want a class to be extended, then mark it as final.
  • If you need to create an immutable class, you need to make it final.
  • If you want a method's implementation to not change in its descendants, then mark the method as final. This is very important to be sure that the implementation does not change.

23. What are mutable and immutable types?

Mutable

Mutable objects are objects whose state and variables can be changed after creation. Examples of mutable classes include StringBuilder and StringBuffer. Example:
public class MutableExample {

   private String address;

   public MutableExample(String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   // This setter can change the name field
   public void setAddress(String address) {
       this.address = address;
   }

   public static void main(String[] args) {

       MutableExample obj = new MutableExample("First address");
       System.out.println(obj.getAddress());

       // We are updating the name field, so this is a mutable object
       obj.setAddress("Updated address");
       System.out.println(obj.getAddress());
   }
}

Immutable

Immutable objects are objects whose state and variables cannot be changed after the object is created. A great key for a HashMap, don't you think? :) For example, String, Integer, Double, and so on. Example:
// We'll make this class final so no one can change it
public final class ImmutableExample {

   private String address;

   ImmutableExample(String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   // We remove the setter

   public static void main(String[] args) {

       ImmutableExample obj = new ImmutableExample("Old address");
       System.out.println(obj.getAddress());

       // There is no way to change this field, so it is an immutable object
       // obj.setName("new address");
       // System.out.println(obj.getName());

   }
}
In the next part, we consider questions and answers about collections. My profile on GitHub Top 50 job interview questions and answers for Java Core. Part 2