10 Common Questions about Java Fundamentals (Part 2)

Jacky - Oct 11 '23 - - Dev Community

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:

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
Enter fullscreen mode Exit fullscreen mode

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.

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.
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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!");
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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!
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .