Java interview: questions about OOP
1. What are the characteristics of Java?
Answer:OOP concepts:
- object orientation
- inheritance
- encapsulation
- polymorphism
- abstraction
Cross-platform: A Java program can be run on any platform without any changes. Of course, this requires an installed JVM (Java virtual machine).
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.
- 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 theRunnable
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 thesuperclass
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 polymorphismInterview questions about Java syntax
5. What is a constructor in Java?
Constructors have the following characteristics:- When a new object is created, the program uses the appropriate constructor to create it.
- 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.
- If no constructor is created explicitly, an empty constructor is created automatically.
- A constructor can be overridden.
- 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:private
— This access modifier is used on methods, fields and constructors. Access is limited to the class in which they are declared.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.protected
— This access modifier offers the same access level aspackage-private
with the addition of access for classes that inherit a class with theprotected
modifier.public
— This access level is also used for classes. This access level means there is full access throughout the application.
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?
A 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
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 objectadd(int index, Object o)
— This method adds an object at a specific indexadd(Collection<Object> c)
— This method adds a list of objectsadd(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.
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 thedefault
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
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
Setting the name variable to a different value, we have:
name = "pen";
The constant string pool looks like this:
In 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.
StringBuffer name = “book”;
As soon as the value of the name variable changes, the value in the stack changes:
StringBuilder 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).
- 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 anint
array, we must write the following:
int[] intArray = new int[100];
Several conclusions can be drawn from this syntax:
- 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.
- The size of the whole array is known.
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 we 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 isnull
, 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?
Thefinal
keyword can be used on variables, methods, and classes.
- The value of a final variable cannot be changed after it is initialized.
- A final class is sterile :) It cannot have children.
- A final method cannot be overridden by a descendant.
Final variables
Java gives us two ways to declare a variable and assign a value to it:- You can declare a variable and initialize it later.
- You can declare a variable and assign a value right away.
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 isimmutable
, 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 afinal
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 asfinal
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
GO TO FULL VERSION