Core Java Basics
Welcome to the Master Guide for Core Java Basics. Whether you are a fresher or have up to 5 years of experience, these curated questions will help you clear technical rounds at top MNCs like TCS, Infosys, and EPAM.
1. Java Program Structure & JVM
Q1: Write a simple Java program and explain 'public static void main'.
public class HelloWorld {
// public: Accessible from anywhere
// static: JVM can call it without creating an object
// void: Does not return any value
// main: Entry point of the program
public static void main(String[] args) {
System.out.println("Hello, Java!");
}
}
Q2: What is the difference between Heap and Stack memory?
- Stack: Used for method execution and local variables. (Fixed size, LIFO).
- Heap: Used for dynamic memory allocation of Objects. (Garbage collected).
2. Data Types & String Manipulation
Q3: How to reverse a String in Java without using built-in functions?
String str = "Indore";
String reversed = "";
for (int i = str.length() - 1; i >= 0; i--) {
reversed += str.charAt(i);
}
System.out.println(reversed); // Output: erodnI
Q4: What is the String Constant Pool (SCP)?
String s1 = "Java"; // Created in SCP String s2 = "Java"; // Points to the same object System.out.println(s1 == s2); // true
3. Keywords (Static, Final, This)
Q5: What is a 'static' variable and 'static' block?
class Test {
static int count = 0;
static {
System.out.println("Static Block Executed");
}
}
Q6: Difference between 'final', 'finally', and 'finalize'?
- final: Keyword to make a variable/method/class unchangeable.
- finally: Block used in try-catch to execute important code (like closing DB).
- finalize: Method called by Garbage Collector before destroying an object.
4. Control Statements & Logic
Q7: How to check if a number is Prime in Java?
int num = 29;
boolean isPrime = true;
for (int i = 2; i <= num / 2; i++) {
if (num % i == 0) {
isPrime = false;
break;
}
}
System.out.println(isPrime ? "Prime" : "Not Prime");
Q8: What is the difference between 'break' and 'continue'?
- break: Exits the loop entirely.
- continue: Skips the current iteration and moves to the next one.
5. Arrays & Wrapper Classes
Q9: How to find the largest element in an Array?
int[] arr = {10, 50, 20, 90, 30};
int max = arr[0];
for (int i : arr) {
if (i > max) max = i;
}
System.out.println("Largest: " + max);
Q10: What is Autoboxing and Unboxing?
- Autoboxing: Automatic conversion of primitive to Wrapper (int to Integer).
- Unboxing: Automatic conversion of Wrapper to primitive (Integer to int).
Integer a = 10; // Autoboxing int b = a; // Unboxing
6. Advance Levels
How does the Garbage Collector (GC) decide which Java objects to clean?
- Reference Counting (Old Method): Every object has a counter. If the count is zero, it's deleted. Note: This is rarely used now because it fails to handle "Circular References."
- Reachability Analysis (Modern Method): The GC starts from GC Roots (like local variables, active threads, and static fields). If an object cannot be reached by a chain of references from a GC Root, it is marked as unreachable and eligible for collection.
- Mark: GC traverses the object graph and marks all reachable objects.
- Sweep: Unmarked objects are removed from the heap.
- Compact: Remaining objects are moved together to prevent memory fragmentation.
// Example of an object becoming eligible for GC Student s1 = new Student(); // Reachable s1 = null; // Now the object is eligible for Garbage Collection
What is the difference between Minor GC and Major GC?
- Minor GC: This happens in the Young Generation (Eden and Survivor spaces). It is triggered when the Eden space is full. It is very fast and happens frequently because most objects die young.
- Major GC (or Full GC): This happens in the Old Generation (Tenured space). It is triggered when the Old Generation is full or when a Minor GC fails to move objects. It is much slower and can cause "Stop-the-World" pauses.
| Feature | Minor GC | Major GC |
|---|---|---|
| Area | Young Generation | Old Generation |
| Frequency | High | Low |
| Performance | Very Fast | Slow (Impacts App) |
Note: A Full GC cleans both the Young and Old generations (and sometimes the Metaspace/Permanent Generation).
Why is String immutable in Java, and what are its benefits in a multi-threaded environment?
1. Why is it Immutable?
- String Constant Pool (SCP): If Strings were mutable, changing one String would unintentionally change all other variables pointing to that same reference in the pool.
- Security: Strings are used for sensitive data like Database URLs, usernames, and passwords. Immutability ensures these values aren't tampered with by untrusted code.
- Caching HashCode: Since the value is fixed, the
hashCode()is calculated only once. This makes Strings very fast when used as keys in aHashMap.
2. Benefits in a Multi-threaded Environment
String is Thread-Safe by default. Because a String object cannot be modified, it provides the following advantages:
- No Synchronization Needed: You don't need to use
synchronizedblocks or locks when sharing Strings between threads. This significantly improves performance. - Side-Effect Free: Multiple threads can read the same String instance simultaneously without the risk of one thread changing the value while another is reading it.
- Prevents Data Corruption: In a mutable object, "Thread A" might update a value while "Thread B" is halfway through a process, leading to inconsistent states. With Strings, this is impossible.
// Thread Safety Example String shared = "Fixed"; // Even if 10 threads access 'shared', // none can change "Fixed" to something else. // They can only create a NEW string if they "modify" it.
How does the ClassLoader work in Java? Can we have multiple ClassLoaders in one JVM?
1. How it works (Delegation Principle):
When a request is made to load a class, the ClassLoader follows these steps:
- Check Cache: It first checks if the class is already loaded.
- Delegate Up: If not loaded, it delegates the request to its Parent ClassLoader.
- Load Down: If the parent cannot find the class, only then does the current ClassLoader attempt to load it from its own classpath.
2. The Hierarchy of ClassLoaders:
- Bootstrap ClassLoader: Loads core Java APIs (rt.jar). Written in native code (C/C++).
- Extension (Platform) ClassLoader: Loads classes from the
jre/lib/extdirectory. - Application (System) ClassLoader: Loads classes from the application's
CLASSPATH.
3. Can we have multiple ClassLoaders in one JVM?
Yes, a single JVM can (and usually does) have multiple ClassLoaders. This is useful for several reasons:
- Isolation: You can load two different versions of the same class (e.g., in a Web Server like Tomcat where different apps need different library versions).
- Security: It separates "trusted" core Java classes from "untrusted" user-defined classes.
- Custom Loading: You can create a Custom ClassLoader by extending the
java.lang.ClassLoaderclass to load classes from a database or a remote URL.
// Example of checking the ClassLoader of a class System.out.println(String.class.getClassLoader()); // Returns null (Bootstrap) System.out.println(MyClass.class.getClassLoader()); // Returns AppClassLoader
What is the difference between String and StringBuilder? Which one should be used where?
1. Main Differences:
- Immutability:
Stringis immutable (cannot be changed).StringBuilderis mutable (can be modified in-place). - Memory Usage: Every time you modify a
String, a new object is created in the String Constant Pool.StringBuildermodifies the existing object in the Heap, saving memory. - Performance:
StringBuilderis significantly faster when performing heavy string manipulations (like loops).
| Feature | String | StringBuilder |
|---|---|---|
| Storage | String Constant Pool | Heap Memory |
| Modifiable | No (Immutable) | Yes (Mutable) |
| Thread Safe | Yes | No |
2. Which one to use where?
Choosing the right class depends on the frequency of changes:
- Use String: If the data is not going to change frequently (e.g., a username, a fixed error message, or a configuration key). It is safe and memory-efficient for fixed values.
- Use StringBuilder: If you are performing a lot of modifications, such as concatenating strings inside a
fororwhileloop.
// BAD Practice (Creates 1000 String objects) String s = ""; for(int i=0; i<1000; i++) s += i; // GOOD Practice (Uses only 1 StringBuilder object) StringBuilder sb = new StringBuilder(); for(int i=0; i<1000; i++) sb.append(i);
Note: If you need a thread-safe mutable string, use StringBuffer instead of StringBuilder.
Which Java version have you worked on, and why is it preferred?
1. Why Java 17 is the Preferred Choice:
- LTS (Long Term Support): Companies prefer Java 17 because it receives security updates and support until at least 2029, making it stable for production.
- Performance: The G1 Garbage Collector is much more efficient in Java 17, leading to lower latency and better memory management for Microservices.
- Modern Syntax: It reduces "Boilerplate" code, making the application easier to maintain and read.
2. Key Java 17 Features I Use Daily:
- Records: Replaces heavy POJO classes. A single line replaces Private fields, Getters,
toString(), andhashCode(). - Sealed Classes: Provides better control over inheritance by specifying exactly which classes can extend a parent.
- Switch Expressions: Allows the
switchto return a value directly using the->arrow syntax, eliminating the need forbreakkeywords. - Text Blocks: Makes writing SQL queries or JSON strings inside Java code much cleaner.
// Traditional Switch vs Java 17 Switch Expression
String result = switch (day) {
case MONDAY, FRIDAY -> "Busy";
case SATURDAY, SUNDAY -> "Relax";
default -> "Normal";
};
// Text Blocks for SQL
String query = """
SELECT user_name, email
FROM users
WHERE status = 'ACTIVE'
""";
Pro-Tip for Interviews: Mentioning that you use Java 17 because of Spring Boot 3 compatibility shows that you understand the full technology stack, not just the language.
Will using Strings for millions of records create a memory problem in Java?
OutOfMemoryError (OOM) or high Garbage Collection (GC) overhead if not handled correctly. This is because Strings in Java are objects, and every object has a memory overhead.
1. Why it creates a problem:
- Object Overhead: Every String object has an overhead of ~24-32 bytes (Header + Fields) plus the actual character data. For 1 million empty Strings, you've already lost ~32MB just in headers!
- Duplicate Data: If 1 million records contain the same value (e.g., "India" repeated 500,000 times), creating a
new String()for each one wastes massive amounts of Heap memory. - GC Pressure: Millions of short-lived Strings fill up the Young Generation rapidly, causing frequent Minor GCs that slow down your application.
- String Interning: Use
string.intern()to store only one copy of repeated strings in the String Constant Pool. - Compact Strings (Java 9+): Java 17 automatically uses Compact Strings. If your data is just Latin-1 (standard English), Java uses 1 byte per character instead of 2 bytes, saving 50% memory.
- Use Enums or IDs: For fields like "Status" or "Country," use an
Enumor anInteger IDinstead of a String.
3. Performance Comparison:
If you have 1 million records of the word "Completed":
- Without Interning: ~40 MB of Heap used.
- With Interning: ~50 Bytes (only 1 object shared by all 1 million references).
// Optimization Example
List<String> records = new ArrayList<>();
for (String data : databaseResult) {
// Instead of: records.add(data);
records.add(data.intern()); // Saves massive memory if values repeat
}
Pro-Tip: In Java 17, the String Pool is part of the Heap, so it is safely garbage collected when no longer needed.
What are Default Methods in Interfaces, and why were they introduced?
default keyword. Prior to this, interfaces could only have abstract methods (no body).
1. Why were they introduced? (Backward Compatibility)
The main reason was to allow the Java Collection API to evolve. For example, the forEach() method was added to the Iterable interface. If it weren't a default method, every existing Java application in the world would have failed to compile because their custom collections didn't implement forEach().
2. Key Rules for Default Methods:
- Optional Override: Implementing classes can use the default logic or provide their own specific override.
- Multiple Inheritance (The Diamond Problem): If a class implements two interfaces that have the same default method signature, Java will throw a compilation error. You must manually resolve this by overriding the method in the class.
- Static vs Default: Unlike static methods, default methods are inherited and can be overridden.
3. The "Diamond Problem" Resolution:
interface InterfaceA {
default void sayHello() { System.out.println("Hello from A"); }
}
interface InterfaceB {
default void sayHello() { System.out.println("Hello from B"); }
}
public class MyClass implements InterfaceA, InterfaceB {
// Must override to resolve ambiguity
@Override
public void sayHello() {
InterfaceA.super.sayHello(); // Or provide custom logic
}
}
Pro-Tip for Java 17: In modern Java, we often use Private Methods inside interfaces to help break down complex logic used by multiple default methods, keeping the interface code clean.
Scenario: A Bank interface is used by multiple branches, but only the Mumbai branch needs a specific "Special Benefit" method. How can you achieve this using Interfaces?
1. The Solution:
We define the "Special Benefit" as a default method in the base BankService interface. By default, it returns a standard value or "No Benefit." Only the Mumbai branch will @Override this method to provide its specific logic.
2. Implementation Example:
interface BankService {
void deposit(double amount);
void withdraw(double amount);
// Default method: Other branches don't need to touch this code
default double getSpecialBenefit() {
return 0.0; // Standard benefit for most branches
}
}
class DelhiBranch implements BankService {
public void deposit(double a) { /* Logic */ }
public void withdraw(double a) { /* Logic */ }
// Uses default getSpecialBenefit() automatically
}
class MumbaiBranch implements BankService {
public void deposit(double a) { /* Logic */ }
public void withdraw(double a) { /* Logic */ }
@Override
public double getSpecialBenefit() {
return 500.0; // Specific benefit only for Mumbai
}
}
3. Why this is the best approach:
- Backward Compatibility: If you have 100 branches already using
BankService, they won't throw compilation errors when you add the benefit. - Clean Code: You avoid
instanceofchecks or type-casting in your main business logic. - Flexibility: If tomorrow the Bangalore branch also needs a benefit, they simply override the method as well.
Interview Tip: Mention that this follows the Interface Segregation Principle (part of SOLID) while utilizing the power of Java 8+ evolution.
Q: You need a thread-safe cache in a Java application. What would you use? Hard
1. Local (Single Instance) Solutions:
- ConcurrentHashMap (The Baseline): Best for simple key-value storage. It uses Lock Striping (segment-level locking), which allows multiple threads to read and write simultaneously without blocking the entire map.
- Caffeine / Guava Cache (The Professional Choice): These are high-performance caching libraries. Unlike a simple Map, they handle Eviction Policies (Least Recently Used), Expiration (TTL), and automatic loading. Caffeine is the standard for Spring Boot 3 applications.
2. Distributed (Multiple Instances) Solutions:
- Redis: Use this if you have multiple Microservices and they all need to share the same cache. It stores data in memory outside the JVM, so if your application restarts, the cache is still there.
- Hazelcast: An "In-Memory Data Grid" that distributes the cache across your application nodes. It is often used in high-frequency trading or large-scale banking systems.
| Feature | ConcurrentHashMap | Caffeine / Redis |
|---|---|---|
| Eviction (LRU) | No (Must code manually) | Yes (Automatic) |
| Data Expiry | No | Yes (Time-to-Live) |
| Complexity | Low | Medium to High |
// Using ConcurrentHashMap for simple thread-safe caching
Map<String, String> cache = new ConcurrentHashMap<>();
cache.putIfAbsent("key", "value");
// Using Caffeine (Recommended for Spring Boot)
Cache<String, Object> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10_000)
.build();
Pro-Tip for Interviews: Always clarify the Eviction Strategy. A cache that never deletes old data is just a Memory Leak.
Q: You need a map that is both Sorted and has Fast Lookups. Which one would you choose? Medium
1. LinkedHashMap (Fastest + Insertion Order):
- Performance:
O(1)for lookups (same as HashMap). - Sorting: It maintains the Insertion Order (the order in which keys were added).
- Use Case: Use this if you need a "Recent Items" list or a cache where the sequence of entry matters.
- Performance:
O(log n)for lookups (Logarithmic time). - Sorting: It maintains Natural Sorting Order (e.g., Alphabetical or Numerical) or a custom
Comparator. - Use Case: Use this if you need the map to always be sorted by the keys themselves (e.g., a Price List from lowest to highest).
| Feature | LinkedHashMap | TreeMap |
|---|---|---|
| Lookup Speed | O(1) - Constant | O(log n) - Logarithmic |
| Sorting Type | Insertion Order | Natural / Key Order |
| Internal Data Structure | Hash Table + Doubly Linked List | Red-Black Tree (Self-balancing) |
// Use LinkedHashMap for O(1) speed + Insertion Order Map<String, Integer> fastMap = new LinkedHashMap<>(); // Use TreeMap for Alphabetical Order (but O(log n) speed) Map<String, Integer> sortedMap = new TreeMap<>();
Interview Pro-Tip: If the interviewer asks for Thread-Safety as well, mention ConcurrentSkipListMap. It is the concurrent version of TreeMap and is highly efficient for sorted lookups in multi-threaded environments.
🚀 Ready to Crack Your Interview?
If you found these Java 17 questions helpful, don't miss our daily updates!
Finished studying this topic?
← Return to Home Page
Comments
Post a Comment
If you any doubts, please let me know Telegram Id: @Coding_Helperr
Instagram Id : codingsolution75