×
Community Blog Interview Questions We've Learned Over the Years: Java Basics

Interview Questions We've Learned Over the Years: Java Basics

This article covers the basics you should know about Java developer interviews.

By Taosu

1

Java Basics

Three major object-oriented features

Encapsulation, inheritance, and polymorphism

Encapsulation: a procedure of extracting product features as objects and configuring private attributes for the objects while providing methods for external access to the attributes.

Inheritance: a procedure of creating child classes that reuse the attributes or features of parent classes and add new data domains or features to achieve multiple implementations.

Polymorphism: achieved through inheritance (multiple child classes rewriting the same method) or interface implementation and overriding.

1. Differences between Java and C++

C++ supports multiple inheritance and the concept of pointer, and in C++ memory is managed by developers. Java uses single inheritance and allows developers to implement multiple inheritance using interfaces. Java does not provide pointers for direct memory access. This improves program memory safety. In addition, Java virtual machines (JVMs) support automatic memory management so that developers do not have to manually release idle memory.

2. Working Principles of Polymorphism

The underlying implementation of polymorphism is the dynamic binding mechanism that binds a method call to a function only at runtime.

Static and dynamic polymorphism:

In static polymorphism, which is also known as static dispatch, the decision about which function to execute is made at compile-time. This type of polymorphism is used in method overloading.

In dynamic polymorphism, which is also known as dynamic dispatch, the decision about which function to execute is made at runtime. This type of polymorphism is used in method overriding (rewriting) and interface implementation.

Implementation of Polymorphism

Java virtual machine (JVM) stacks store stack frames. A new stack frame is created each time a method is executed. The stack frame is used to store the local variable table, operand stack, dynamic linking, and return address. To implement polymorphism is actually to implement dynamic method dispatch. Assume that a child class overrides the methods in the parent class. When polymorphism is used to call the methods, it is first determined that the class actually called is the child class and then the methods in the child class are searched. This process is the essence of method overriding.

3. Keywords: static and final

static: This keyword is used to modify attributes and methods.

static attribute:

A static attribute is a class-level attribute that is shared across all objects of a class. A static attribute is loaded (only once) when the class is loaded. Static attributes can be accessed directly using the class name, without creating an object of a class.

static method:

A static method is loaded each time the class is loaded. Static methods can be called directly using the class name, without creating an object of a class. In static methods, only static members can be called and the "this" keyword cannot be used.

final: This keyword is mainly used to modify variables, methods, and classes.

final variable:

If a final variable is of a basic data type, the value of the variable cannot be modified after initialization. If a final variable is a reference variable, the variable cannot be pointed to another object after initialization.

final method:

A method declared as final cannot be overridden by a child class. All private methods are implicitly final.

final class:

A final class cannot be inherited. All methods in a final class are implicitly final. Another way to prevent a class from being inherited is to configure a private constructor. However, this does not apply to inner classes.

4. Abstract Classes and Interfaces

Abstract class: a class that is declared with the "abstract" keyword and includes abstract methods. Abstract classes cannot be instantiated or declared final, because they can be inherited by child classes.

Interface: a blueprint used to implement a class. It is a collection of abstract methods and supports multiple inheritance. Methods defined in an interface are public abstract methods by default.

Similarities:

① Both abstract classes and interfaces cannot be instantiated.

② Both abstract classes and interfaces can include abstract methods, which must be overwritten by child or implementation classes.

Differences:

① Abstract classes can declare constructors, while interfaces cannot.

② Abstract classes can contain non-abstract methods. Interfaces can only contain public abstract methods, although since Java 8, interfaces can also have non-abstract methods.

③ Abstract classes support only single inheritance while interfaces can support multiple inheritance.

④ Abstract classes can contain various types of variables, while interfaces can only contain public static and final variables.

Scenarios for using abstract classes:

Use an abstract class when you want to define the common behavior for all child classes (regardless of how each child class performs the behavior) and include default methods and instance variables in a class.

Scenarios for using interfaces:

Use an interface when you want to define the common behavior for all implementation classes (regardless of how each implementation class performs the behavior). The features implemented by different implementation classes may be unrelated to each other.

5. Generic Types and Type Erasure

Generic types:

Generic types are essentially parameterized types. These parameterized types can be used to create classes, interfaces, and methods, referred to as generic classes, generic interfaces, and generic methods, respectively.

Type erasure:

Generic types in Java are pseudo-generic types. When these generic types are used, type parameters are added. During the type erasure, the compiler replaces the type parameters in generics and generates bytecode.

For example, all List types will become Lists after the compilation. The JVM can only identify the List type and cannot detect the extra generic type information.

Other types of elements can be added by using reflection.

6. Principle and Use Scenarios of Reflection in Java

Reflection in Java:

Reflection is an API that is used to query all attributes and methods of a class and call any method in the class.

Principle of reflection:

Reflection works by obtaining the bytecode of a class in Java and mapping the methods, variables, and constructors in the bytecode to the corresponding Method, Filed, and Constructor classes.

Obtain an instance of a class:

1. Class name.class (a bytecode)
2. Class.forName(String className); Construct a Class object based on the fully qualified name of a class.
3. Each object contains a getClass() method: obj.getClass(). The method returns the real type of the object.

Scenarios:

Development of a general-purpose framework: The most important application of reflection is to develop general-purpose frameworks. Many frameworks, such as Spring, are configurable. For example, JavaBeans and Filters can be configured by using XML files. To ensure the versatility of a framework, dynamic object or class loading and method calling based on configuration files at runtime should be implemented for the framework.

Dynamic proxies: In aspect-oriented programming (AOP), dynamic proxies are usually used to intercept specific methods. Dynamic proxies can be implemented by using reflection.

JDK contains the default dynamic proxies in Spring and the use of JDK requires implementing interfaces.

Code Generation Library (CGLIB) is built on top of ASM and can be used to generate serialized byte streams. CGLIB is configurable and has poor performance.

Custom annotations: Annotations themselves serve only as markups. You can use annotations in conjunction with the reflection mechanism so that the annotation interpreter can be invoked based on annotations to perform actions.

7. Exception System in Java

2

The Throwable class is the superclass of all errors and exceptions in the Java language. Its subclasses are Error and Exception.

Error:

Errors refer to the internal errors and resource exhaustion errors of the system at runtime in Java. Applications do not throw such errors. If such an error occurs, inform users immediately and make every effort to terminate the program safely.

Exceptions are divided into two categories: Runtime Exceptions and Checked Exceptions.

Programming errors fall into three categories: syntax errors, logic errors, and runtime errors.

Syntax errors (also known as compilation errors) are errors that occur during compilation and are detected by the compiler.

Logic errors refer to program execution results that do not meet expectations. Such errors can be located and their causes can be identified through debugging.

Runtime errors are errors that cause abnormal program interruption. Runtime errors must be processed using exception handling.

RuntimeExceptions are runtime exceptions. Such exceptions should be logically avoided in program code.

Examples of RumtimeExceptions include NullPointerException and ClassCastException.

CheckedExceptions are checked exceptions. These exceptions can be caught and handled by the program using try-catch blocks.

Examples of CheckedExceptions include IOException, SQLException, and NotFoundException.

Data Structures

3

1. ArrayList and LinkedList

ArrayList:

ArrayLists are implemented based on arrays and support fast random access to elements. ArrayLists are suitable for random search and traversal, but not for insertion or deletion. (Mention the use of ArrayLists in actual scenarios.)

The default capacity of a newly created ArrayList is 10. When the capacity is exceeded, a new array is created with 50% more capacity, and the contents of the old one are copied to the new array. However, to insert elements into or remove elements from the middle of an ArrayList, you need to copy the array and move data, which incurs high costs.

LinkedList:

LinkedLists are implemented based on doubly linked lists and are suitable for dynamic insertion and deletion of data.LinkedLists provide methods that are not defined in the List interface to manage table header and footer elements, which can be used as stacks, queues, and bidirectional queues. (For example, the JDK official recommendation is to use LinkedList-based Deque to manage stacks.)

Differences between ArrayList and LinkedList:

Neither ArrayLists nor LinkedLists are thread-safe. ArrayLists are suitable for data search and LinkedLists are suitable for data additions and deletions.

Implement thread safety:

You can use native vectors or the Collections.synchronizedList(List list) method to return a thread-safe ArrayList.

We recommend that you use the CopyOnWriteArrayList class in the java.util.concurrent package.

Vectors use the "synchronize" keyword at the underlying layer to ensure thread safety but with poor efficiency.

CopyOnWriteArrayList uses a copy-on-write method to implement locking during data writes but not during data reads.

2. Fail-fast and Fail-safe in List Traversal

Use traversal with a for loop to delete elements in a list

for(int i=0; i < list.size(); i++){
   if(list.get(i) == 5) 
       list.remove(i);
}

Use iterative traversal with the list.remove(i) method to delete elements in a list

Iterator<Integer> it = list.iterator();
while(it.hasNext()){
    Integer value = it.next();
    if(value == 5){
        list.remove(value);
    }
}

Use traversal with a foreach loop to delete elements in a list

for(Integer i:list){
    if(i==3) list.remove(i);
}

fail-fast:

An exception is thrown immediately after it occurs and the program is terminated.

In most cases, traversal of elements in a collection requires using an iterator. If the structure (modCount) of the collection is changed during traversal, the ConcurrentModificationException is thrown to terminate the traversal. This is what we call the fail-fast mechanism.

fail-safe:

For a collection container that uses the fail-safe mechanism, the traversal process does not directly access the data in the collection. Instead, it first copies the data to a new collection and then traverses the new collection. During the traversal process, changes made to the original collection cannot be detected by the iterator, so that the ConcurrentModificationException does not occur.

Disadvantage:

Despite the advantage of preventing the ConcurrentModificationException, the iterator cannot access the modified data. Simply put, the iterator traverses only the copy of the collection created at the beginning of the traversal, and the modifications to the data in the original collection during the traversal are unknown to the iterator.

Scenario: All containers in the java.util.concurrent package adopts the fail-safe mechanism and can be concurrently used and modified in multiple threads.

3. About HashMap

Key points: data structure, capacity expansion, procedure of searching data using the put method, hash functions, the 2^N capacity mechanism, differences between JDK 1.7 and JDK 1.8

Reference: https://www.jianshu.com/p/9fe4cb316c05

Data structure:

HashMap uses arrays, linked lists, and red-black trees as underlying data structures, and uses hash mapping to store key-value pairs.

Capacity expansion:

The default load factor is 0.75, meaning that if the number of elements in an array exceeds 75% of the array length, capacity expansion is triggered.

[1] A new array twice the length of the original array is created.

[2] JDK 1.7 uses entry rehashing, and JDK 1.8 uses high-order operation.

Procedure of searching data using the put method:

4

  1. Determine whether an array is empty; if so, initialize it.
  2. If the array is not empty, calculate the hash value of the key and use the (n-1) & hash formula to calculate the index that indicates where the value should be stored in the array.
  3. Check whether the table[index] contains data. If there is no data, construct a node and store it in the table[index].
  4. If the table[index] contains data, a hash conflict has occurred (two keys on the node have the same hash value). Continue to check whether the keys are equal. If they are, replace the original value with the new value.
  5. If the two keys are not equal, check whether the current node is a tree node. If it is, create a tree node and insert it into the red-black tree.
  6. If it is not a red-black tree, create a regular node and add it to the linked list. Determine whether the length of the linked list is greater than 8 and, if it is, convert the linked list into a red-black tree.
  7. Determine whether the current number of nodes is greater than the threshold. If it is, expand the array capacity to twice the original size.

Hash function:

A hash function first calculates (by using the prime factor 31 for loop accumulation) the hashcode of a key, which is a 32-bit value, and then uses the XOR operation on the first 16 bits and the last 16 bits. This function is also known as the disturbing function and uses tail insertion to minimize hash collisions.

2^N capacity mechanism:

Use the modulus operator to obtain the remainder of the length of an array. The remainder is the index that indicates where the value should be stored in the array. This index is calculated based on the formula: (n-1) & hash, where n represents the length of the array. This makes modulo operation convenient when elements are added to or deleted from the array, when elements in the array are modified, or when the capacity of the array is expanded.

Differences between JDK 1.7 and JDK 1.8:

JDK 1.7 HashMap:

At the bottom layer, JDK 1.7 HashMap combines arrays and linked lists to form hash linked lists. When two or more keys are mapped to the same index, if the key values are equal, the value is directly overridden; otherwise, separate chaining is used to solve the conflict. During capacity expansion and inversing where inconsistent sequences exist, if head insertion is used, an infinite loop occurs, resulting in 100% CPU utilization.

JDK 1.8 HashMap:

JDK 1.8 HashMap uses arrays, linked lists, and red-black trees as underlying data structures. If the length of a linked list is greater than the threshold (the default threshold is 8 based on the Poisson distribution) and the length of the array is greater than 64, the linked list is converted into a red-black tree to shorten the search time. This solves the notorious problem of URL parameter DoS attacks in Apache Tomcat.

4. ConcurrentHashMap

Both ConcurrentHashMap and Hashtable offer thread safety. Hashtable is one of the oldest collection classes in Java and provides key-value storage and retrieval APIs. It uses the synchronized keyword for modification, with low efficiency. ConcurrentHashMap is implemented based on segment locks and provides higher efficiency than Hashtable does.

Underlying implementation of ConcurrentHashMap:

In JDK 1.7, the underlying implementation of ConcurrentHashMap is based on segmented arrays and linked lists. ConcurrentHashMap divides the map into segments (16 by default), and applies a separate lock to each segment. Multiple threads can access data in different segments concurrently, thus minimizing lock contention and so maximizing performance.

5

In JDK 1.8, ConcurrentHashMap uses the same data structures as HashMap, which are arrays, linked lists, and red-black trees. Instead of dividing the map into segments, ConcurrentHashMap directly uses node arrays, linked lists, and red-black trees for implementation, and uses the synchronized keyword and compare-and-swap (CAS) to ensure thread safety.

5. Serialization and Deserialization

Serialization is the process of converting the state of an object into a byte stream that can be used to generate identical objects. Object serialization is an implementation of object persistence, which converts the properties and methods of an object into a serialized form for storage and transmission. Deserialization is the process of reconstructing an object based on this serialized form.

Serialization: the process of converting a Java object into a sequence of bytes.

Deserialization: the process of converting a sequence of bytes into a Java object.

Advantages:

  • Data persistence is achieved. Serialization can be used to permanently store data on disks (usually in files). An example is the Redis database.
  • Serialization can be used to achieve remote communication, that is, to transmit byte sequences of objects over networks. An example is Google's Protocol Buffers.

Scenarios of deserialization failures:

If the serialVersionUID is inconsistent, deserialization fails.

6. String

A string stores data as an array, which is modified by the final keyword. Therefore, strings are immutable.

StringBuffer is a peer class of String and provides a synchronization lock for methods, offering thread safety and slightly lower efficiency than StringBuilder.

Design Patterns and Principles

1. Singleton pattern

The Singleton pattern defines a class that has only one instance and provides a global point of access to it. For example, this pattern is used for the singleton pool in Spring's level-1 cache.

Advantages:

Unique access: suitable for scenarios involving unique serialization or the default Bean scope of Spring.

Improved performance: suitable for scenarios involving frequent instance creation and destruction and for time-consuming and resource-intensive scenarios, such as connection pools and thread pools.

Disadvantage: not suitable for stateful applications that involve data changes.

Implementation methods:

Eager initialization: thread-safe and fast.

Lazy initialization: double-checked locking, with the first check to reduce lock overhead and the second check to prevent duplication, using the volatile keyword to prevent incomplete instantiation caused by reordering.

Static block initialization: thread-safe and high utilization.

Enum: recommended by Effective Java, resistant to reflection attacks.

2. Factory Pattern

The Factory pattern provides an interface for creating objects in a parent class but allows child classes to alter the type of objects that will be created.

Advantage: You can avoid tight coupling between the creator and the concrete products. By using configuration files, you can introduce new variants of products without breaking existing client code.

Disadvantage: The code may become more complicated because adding new products requires creating new subclasses.

3. Abstract Factory Pattern

The Abstract Factory pattern defines an interface for creating families of related (or dependent) objects but without specifying their concrete child classes.

Advantage: You can set constraints on the product family by maintaining a class.

Disadvantage: Almost all factory classes need to be modified if a new product is added to the product family.

Interview Questions

Constructor

Constructors can be overloaded. There is a default constructor in a class only if no constructor is explicitly declared in the class.

Constructors are used to create new objects and do not return values.

Initialization Block

Static initialization blocks have the highest priority and are executed first, before non-static initialization blocks.

Static initialization blocks are the first to be executed when a class is loaded for the first time, before the main method is executed.

This

The keyword this represents a reference to the current object, the object on which the attribute or method is called.

The keyword this cannot be used in static methods. A static method does not depend on a reference to a specific object of the class.

Difference between override and overload

Overloading means having two or more methods in a class with the same name and different signatures.

Overriding occurs when a child class has the same method in both name and signature as the parent class.

Object class methods

The toString() method is a pointer by default and needs to be overridden in most cases.

The equals() method compares two objects. Its default function is the same as the == operator.

The hashCode() method returns a hash code value for the object. Objects that are equal (according to their equals()) must return the same hash code. If the equals() method is overloaded, the hashCode() method must also be overloaded.

The finalize() method is called just before deleting or destroying the object that is eligible for garbage collection. This method is empty by default and needs to be overridden in child classes.

The clone() method performs deep copy and requires the class to implement the Cloneable interface.

The getClass() method returns the class object of "this" object and is used to get the actual runtime class of the object. It can also be used to get metadata of this class, including its name and method.

The wait() and notify() methods are used to notify and wake up waiting threads.

Basic Data Types and Wrapper Classes

Basic Data Type Storage Capacity Valid Values Default Value
byte 8-bit signed number -27 to 27-1 0
short 16-bit signed number -215 to 215-1 0
int 32-bit signed number -231 to 231-1 0
long 64-bit signed number -263 to 263-1 0L
float 32-bit, IEEE 754 compliant Negative numbers from -3.402823e+38 to -1.401298e-45 and positive numbers from 1.401298e-45 to 3.402823e+38 0.0f
double 64-bit, IEEE 754 compliant Negative numbers from -1.797693e+308 to -4.9000000e-324 and positive numbers from 4.9000000e-324 to 1.797693e+308 0.0d
char 16-bit 0 to 216-1 'u0000'
boolean 1-bit true and false false
Type Valid Range
Byte, Short, Integer, Long [-128, 127]
Character [0, 127]
Boolean [false, true]


Disclaimer: The views expressed herein are for reference only and don't necessarily represent the official views of Alibaba Cloud.

0 1 0
Share on

Alibaba Cloud Community

987 posts | 239 followers

You may also like

Comments