10 Common Questions about Java Fundamentals (Part 2)
Tags: java, interview
Published: 2023-10-11 01:59:25

After every interview I participate in, I always save the technical questions as notes, and re-read them to remember knowledge. So today, I want to share them so everyone can read and best prepare for their interview.

## 6. Garbare Collection
In Java, the garbage collector is responsible for identifying and freeing unused objects in the heap. The garbage collector runs in the background, periodically checking the heap for objects that are no longer being used. When it finds an object that is no longer being referenced by any other objects or variables, it marks the object as eligible for garbage collection. The garbage collector then frees up the memory occupied by the object, making it available for future use.

Java’s garbage collector uses a mark-and-sweep algorithm to identify and free unused objects. During the mark phase, the garbage collector traverses the object graph, starting from the roots (such as static variables and method parameters), marking all objects that are still being used. During the sweep phase, the garbage collector frees up the memory occupied by objects that were not marked during the mark phase.

There are several different garbage collectors available in Java, each with its own strengths and weaknesses. The most commonly used garbage collector is the ParallelGC collector, which is designed for use on machines with multiple processors.

In summary, the garbage collector in Java is responsible for freeing up memory occupied by unused objects in the heap. It uses a mark-and-sweep algorithm to identify and free unused objects, and there are several different garbage collectors available with different strengths and weaknesses.

### Why GC is very important in Java?
GC can have unpredictable effects on Java application performance. When GC activity occurs a lot, it adds a lot of load to the CPU and slows down the application processing, resulting in slow transaction execution and ultimately affecting the user experience.

Excessive GC activity can be caused by a memory leak in the Java application, or by the programmer not allocating enough memory for the JVM. Usually a sign of GC being overworked is a high CPU usage of the JVM.

To have the Java application achieve optimal performance, we must monitor the GC activity of the JVM. For good performance, GC should run at low frequency only.

### How to enabled GC?
Enable garbage collection logs to observe how the GC execute in your application. You can enable GC logging by adding command-line flags when starting the JVM

## 7. Collections in Java
In Java, Collections is a class that provides various utility methods for working with collections of objects, such as `List`, `Set`, and `Map`.

The `contains()` method is a method defined in the Collection interface, which is implemented by several collection classes in Java, such as ArrayList and HashSet.

The `contains()` method is used to check whether a collection contains a specific element. For example:

```Java
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");

boolean containsBob = names.contains("Bob"); // true
boolean containsDave = names.contains("Dave"); // false
```

In the example above, the `contains()` method is used to check whether the names list contains the elements "Bob" and "Dave".

The method returns true if the collection contains the specified element, and false otherwise.

In summary, the `contains()` method is a method defined in the Collection interface in Java, and it is used to check whether a collection contains a specific element.

### 7.1. List vs Set
In Java, both List and Set are interfaces that are part of the Java Collections Framework, which provides a set of classes and interfaces to work with collections of objects. Both List and Set are used to store collections of elements, but they have some key differences:

|#         |      List     |  Set |
|----------|:-------------:|------:|
| **Duplicates** | Allows duplicate elements. You can have multiple occurrences of the same element in a List | Does not allow duplicate elements. Each element in a Set must be unique |
| **Ordering** | Maintains the order of elements as they are inserted. You can access elements by their index (position) in the List | Does not guarantee the order of elements. The elements in a Set may be stored in a particular order, but that order may not necessarily be the order in which they were inserted |
| **Accessing elements** | Allows access to elements using their index. You can get an element by its position in the List (e.g., `list.get(index))` | Does not provide direct access to elements by index, as the elements are not ordered. To access elements in a Set, you usually use iteration or specific methods like `contains()` |
| **Implementations** | Common implementations of the List interface are `ArrayList`, `LinkedList`, and `Vector` | Common implementations of the Set interface are HashSet, TreeSet, and LinkedHashSet |
| **Use cases** | Use a List when you need a collection that allows duplicates and needs to maintain the insertion order of elements. Lists are useful for scenarios where you frequently access elements by their index or need to perform positional operations like adding or removing elements at specific positions | Use a Set when you want to ensure that each element in the collection is unique, and you don't care about the order of elements. Sets are suitable for scenarios where you need to check for the presence of an element efficiently or ensure that there are no duplicates in the collection |

### 7.2. ArrayList vs LinkedList

**`ArrayList`** and **`LinkedList`** are both implementations of the **`List`** interface in Java. They are used to store collections of elements, but they have different underlying data structures and performance characteristics, which make them suitable for different use cases:

| # | ArrayList | LinkedList |
|----------|:-------------:|------:|
| **Underlying Data Structure** | Uses a dynamic array to store elements. It internally maintains an array that is dynamically resized as elements are added or removed | Uses a doubly-linked list to store elements. Each element (node) in the list holds a reference to the previous and next elements 
| **Performance** | **Fast access by index:** Retrieving an element by its index is very fast in ArrayList because it directly accesses the underlying array. **Slower insertion/deletion:** Insertions and deletions in the middle of the list are slower because elements need to be shifted to accommodate the new element or close the gap left by the deleted element. **Good for random access:** Ideal when you frequently need to access elements by their index | **Fast insertion/deletion**: Insertions and deletions in a LinkedList are fast because you only need to update the references of neighboring nodes. **Slower access by index:** To access an element by index, the LinkedList has to traverse the list from the beginning (or end) until it reaches the desired index, making it slower for random access. **Good for frequent insertions/deletions:** Ideal when you frequently need to add or remove elements from the list | 
| **Memory Overhead** | Typically consumes less memory than LinkedList because it stores elements in a single array, which has less overhead per element than the nodes in a linked list | Consumes more memory than ArrayList because each element is stored as a separate node, requiring additional memory for node references |
| **Iterating Performance** | Iterating through elements using a regular for loop is efficient due to the fast access by index | Iterating through elements is slightly slower than in ArrayList because it involves following the node references |
| **Use Cases** | Use ArrayList when you need fast random access to elements by index and when the number of insertions and deletions is relatively low compared to element access | 	Use LinkedList when you frequently need to perform insertions and deletions, especially in the middle of the list, and when you don't need fast random access | 

## 7.3. List vs Map
### List
A List is an ordered collection of elements that allows duplicate values. It is implemented by classes like ArrayList, LinkedList, and Vector. Elements in a List have an index, and you can access elements by their index. List supports various methods for adding, removing, and accessing elements by their index. Examples of usage include maintaining a collection of objects in a specific order or creating a sequence of elements.

### Map
A Map is a collection that stores key-value pairs, where each key is unique, and each key maps to exactly one value. It is implemented by classes like HashMap, TreeMap, and LinkedHashMap. Elements in a Map are not ordered by their insertion sequence; instead, they are ordered based on the key’s natural ordering (for TreeMap) or the insertion order (for LinkedHashMap). Map supports methods to put, get, and remove elements based on the keys. Examples of usage include creating a dictionary, mapping unique identifiers to corresponding objects, or counting occurrences of elements in a dataset.

## 8. OOP Principals
OOP (Object-Oriented Programming) principles, also known as OOP concepts, are fundamental concepts that guide the design and implementation of object-oriented software. These principles help developers create code that is easier to understand, maintain, and extend. The four main OOP principles are:

### 8.1 Encapsulation
Encapsulation involves bundling data and methods within a single unit (class) and hiding the internal details from the outside world. Example, let’s create a class Animal that represents different animals in the zoo. We'll encapsulate the animal's name and age within the class and provide methods to interact with this data.

```Java
public class Animal {
    private String name;
    private int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public void makeSound() {
        // Method to make the animal sound.
    }
}
```
In this example, we encapsulate the name and age attributes as private, preventing direct access from outside the class. The only way to access this data is through the getter methods `getName()` and `getAge()`. The `makeSound()` method is also encapsulated within the class.

### 8.2. Inheritance
Inheritance allows us to create a new class (subclass) based on an existing class (superclass), inheriting its attributes and methods. Let’s create two subclasses of `Animal`, `Lion` and `Elephant`.

```Java
public class Lion extends Animal {
    public Lion(String name, int age) {
        super(name, age);
    }

    @Override
    public void makeSound() {
        System.out.println("Roar!");
    }
}

public class Elephant extends Animal {
    public Elephant(String name, int age) {
        super(name, age);
    }

    @Override
    public void makeSound() {
        System.out.println("Trumpet!");
    }
}
```

Here, `Lion` and `Elephant` inherit the name and age attributes from the Animal class. Each `subclass` also overrides the `makeSound()` method to provide its specific implementation.

### 8.3. Polymorphism
Polymorphism allows objects of different classes to be treated as objects of a common superclass. We can use the Animal class to create objects of Lion and Elephant and call their respective methods.

```Java
public class Zoo {
    public static void main(String[] args) {
        Animal lion = new Lion("Leo", 5);
        Animal elephant = new Elephant("Ellie", 10);

        lion.makeSound();       // Output: Roar!
        elephant.makeSound();   // Output: Trumpet!
    }
}
```
In this example, we can treat both lion and elephant as Animal objects and call the makeSound() method. The actual implementation of makeSound() depends on the type of object (runtime polymorphism).

### 8.4. Abstraction
Abstraction allows us to define the common characteristics and behaviors of a class without specifying the complete implementation. In our Animal class, we define the `makeSound()` method as an abstraction of the animal's sound, but we don't specify the exact sound in the `Animal` class. It's left to the subclasses (`Lion` and `Elephant`) to provide their specific implementations.

## 9. hashcode() vs equals()
In Java, `hashcode` is a method defined in the Object class.

By default, the `hashcode` method returns an integer value based on the memory address of the object. However, many classes override this method to provide a more meaningful hash code based on the object's content or attributes.

The main purpose of a hash code is to support hash-based collections, such as `HashMap`, `HashSet`, and `Hashtable`. These collections use hash codes to quickly locate and organize objects for efficient retrieval.

The equals method is used to check if two objects are `equal` based on their content or state, not on their memory addresses.

## 10. Stream API in Java

![Stream API in Java](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ylxj99x6cvuc3bauzhq.png)

The Stream API in Java 8 is a strong addition that helps with functional-style actions on groups of things, like collections (e.g., List, Set, Map) and arrays. we can do different actions on these groups using functional programming ideas, which makes the code shorter, easier to understand, and possibly faster.

### Intermediate Operations
Intermediate operations are used to transform or filter the elements in the stream. Some common intermediate operations include `map`, `filter`, `sorted`, `distinct`, `limit`, and `skip`.

### Terminal Operations
Terminal operations are used to produce a final result or side effect. Once a terminal operation is called, the stream is consumed and cannot be reused. Common terminal operations include `forEach`, `collect`, `reduce`, `count`, `min`, `max`, `anyMatch`, `allMatch`, and `noneMatch`.

### Parallel Streams
The Stream API allows you to create parallel streams using the `parallelStream()` method. Parallel streams utilize multiple threads to process the elements concurrently, potentially speeding up processing for large datasets.

Thanks for reading!