Java Fundamentals: Common Pitfalls and Best Practices
Recommended Utility Libraries
Guava
- Guava library maps.filterkeys - CSDN blog
- https://github.com/google/guava
- Overview (Guava: Google Core Libraries for Java HEAD-jre-SNAPSHOT API)
Hutool
- Hutool reference documentation
Core Concepts
Q: What is the memory structure of an object?
Original: https://www.cnblogs.com/jiangyang/p/11422732.html

Q: What is the method call stack?
Vertical version:

Horizontal version:

Q: How to check the number of bytes an object occupies?
In JDK 8: long size = ObjectSizeCalculator.getObjectSize(obj);
- 8 bits = 1 Byte
- 1024 Bytes = 1 KB
- 1024 KB = 1 MB
- 1024 MB = 1 GB
Q: What are the pitfalls in declaring primitive variables?
Example 1: float f = 3.4; is incorrect. This is a narrowing conversion from double to float. You need explicit casting: float f = (float) 3.4; or use the F suffix: float f = 3.4F;.
Example 2: short s1 = 1; s1 = s1 + 1; is incorrect for the same reason. However, short s1 = 1; s1 += 1; is correct because s1 += 1; implicitly casts the result to short.
Example 3: long w = 12345678901; is incorrect because the literal exceeds the int range. Use the L suffix: long w = 12345678901L;.
Q: Which conversion and instantiation methods are recommended?
It is recommended to use the valueOf method of wrapper classes for instantiation over direct constructors. For example: Integer.valueOf(), BigDecimal.valueOf(), String.valueOf().
Q: What is the output of System.out.println(x + y + "" + (x + y) + y); if int x = 20, y = 5;?
Answer: 25255.
Q: What is the output of System.out.println(x++ + ++x + x); if int x = 20;?
Answer: 20 + 22 + 22 = 64. x++ uses the value before increment, while ++x increments before use.
Q: Can char store Chinese characters?
Yes, a char occupies 2 bytes and can store a single Chinese character. Example: char c = '我';. Note the use of single quotes, not double quotes.
Q: What is the output of the following code?
public class Test {
static {
int x = 5;
}
static int x, y;
public static void main(String[] args) {
x--;
myMethod();
System.out.println(x + y + ++x);
}
public static void myMethod() {
y = x++ + ++x;
}
}
Answer: 3. The key is that the x in the static block is a local variable, not the static field.
Q: What are the outputs of these three statements?
System.out.println("is " + 100 + 5);
System.out.println(100 + 5 + " is");
System.out.println("is " + (100 + 5));
Output:
is 1005105 isis 105
Q: What is the output?
public class Inc {
public static void main(String[] args) {
Inc inc = new Inc();
int i = 0;
inc.fermin(i);
i = i++;
System.out.println(i);
}
void fermin(int i) {
i++;
}
}
Answer: 0. This tests value passing. The tricky part is i = i++;, which uses the value before incrementing.
Q: What is the output of the following code?
public class SendValue {
public String str = "6";
public static void main(String[] args) {
SendValue sv = new SendValue();
sv.change(sv.str);
System.out.println(sv.str);
}
public void change(String str) {
str = "10";
}
}
Answer: "6". Java passes object references by value.
Q: What is the output?
public class StringDemo {
private static final String MESSAGE = "taobao";
public static void main(String[] args) {
String a = "tao" + "bao";
String b = "tao";
String c = "bao";
System.out.println(a == MESSAGE);
System.out.println((b + c) == MESSAGE);
}
}
Answer: true, false. At compile time, "tao" + "bao" is optimized to "taobao". However, b + c is not optimized because the compiler cannot guarantee that b and c won't change later. It uses StringBuilder under the hood.
Q: The hashCode() and equals() contract
- If two objects are equal according to
equals(), they must have the samehashCodevalue. The reverse is not necessarily true. - If two objects are not equal according to
equals(), they can still have the samehashCodevalue. This is why collisions can occur in aHashMap. - The
get()method first checks if thehashCodeis the same. If it is, it then checksequals().
Q: Modulo-based hashing v.s. consistent hashing
Both algorithms use the hash of data to distribute data across shards, enabling divide-and-conquer, segmentation, and sharding. However, consistent hashing has lower expansion costs and requires less data migration. With modulo-based hashing, resharding affects all data shards.
// Simplest sharding using modulo
int index = hash(shardingField) % numberOfShards;
select xx from 'busy_' + index where shardingField = xxx;
Q: Which method definitions are invalid?
public class Demo {
float func0() {
byte i = 1;
return i;
}
float func1() {
int i = 1;
return; // Invalid: no return value
}
float func2() {
short i = 2;
return i;
}
float func3() {
long i = 3;
return i;
}
float func4() {
double i = 4;
return i; // Invalid: narrowing conversion
}
}
Answer: func1 and func4. The concept is about type conversion rules.
Q: Bitwise operations are more efficient
<< nis equivalent to multiplying by 2 to the power of n.>> nis equivalent to dividing by 2 to the power of n.
Q: Java reference types
- Strong references: The default. An object with a strong reference will not be garbage collected as long as the reference exists.
- Soft references: The object may be reclaimed if the JVM is about to throw an
OutOfMemoryError. - Weak references: The object will be reclaimed during the next garbage collection cycle.
- Phantom references: The object has been finalized, and its memory is about to be reclaimed.


Q: The final keyword
- A
finalclass cannot be subclassed. - A
finalmethod cannot be overridden. - A
finalvariable can only be assigned once. For primitive types, the value is immutable. For reference types, the reference cannot be changed, but the state of the object it points to can be modified. finalvariables are inherently thread-safe.- A
finalvariable must be initialized, at the latest, in the constructor. Remember to initializefinalvariables even in the no-argument constructor.
public class SimLockInfo {
private final Long sn;
private final AtomicInteger retry;
public SimLockInfo() {
retry = null;
sn = null;
}
public SimLockInfo(Integer dhhm, Long sn, String id, Boolean get, Long begintime) {
this.sn = sn;
this.retry = new AtomicInteger(0);
}
}
Q: Hidden dangers of exception.printStackTrace() and System.out.println()
- During high concurrency with deep stack traces, printing the full stack trace can consume significant memory from the string constant pool, potentially leading to GC overhead, application hangs, or
OutOfMemoryError(OOM). It's better to log a portion of the exception to a file. - The
System.out.println()method holds a lock internally, which can negatively impact performance at high concurrency.
Q: Handling URL encoding sequences like %22, %20, etc.
%20represents a space.%22represents a double quote (").%26represents an ampersand (&).%28represents an opening parenthesis (().%29represents a closing parenthesis ()).
Q: How to check if two files are the same?
When storing uploaded files, save the original filename, server-assigned name, path, URL, MD5 checksum, SHA1 checksum, and size.
- Compare files using MD5 and SHA1. There is a very small probability of false positives with MD5 alone, especially for large files. It is better to combine MD5, SHA1, and file size.
- Use a centralized system to manage the URLs that reference files.
Preventing HTTP request data from being tampered with
- Use HTTPS.
- Add a signature: The sender calculates a signature by combining important parameters. The server calculates the same signature and compares it with the received one.
Graceful shutdown using ShutdownHook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Shutting down application, closing thread pool, releasing resources");
}));
Design Patterns, Structures, and Principles
Providing APIs for dynamic creation and modification of JVM objects
In the JVM, everything is an object. External input needs to be converted into a JVM object model. The Object serves as the central intermediary for this conversion.
Database data (ORM: 1 Row == 1 Object)
Configuration information <-----> Open API (input/read) <----> Create/Modify JVM [Object] ----> Store in Reference/Container
External request parameters
Example: Using a Spring MVC RESTful API to dynamically modify system configuration:
@PostMapping(value = "/updateconfig")
@ResponseBody
public Boolean updateConfig(@RequestParam("id") String id, @RequestBody Info info) {
Config config = new Config(info);
config.setId(id);
configMap.put(id, config);
configMapper.update(id, config);
return true;
}
Performance optimization principles
- Convert synchronous to asynchronous.
- Convert serial to parallel.
- Trade space for time.
- Divide and conquer.
- Horizontally scale to eliminate single points of contention.
Divide and conquer & asynchrony
These are fundamental concepts in algorithms, data structures, and system architecture. The idea is to break a large task into smaller units and decompose synchronous serial execution into asynchronous parallel execution. Examples include: Fork/Join framework, quick sort, merge sort, tree structures, HashMap, segmented locks, database sharding, message queues (MQ), clustering, and distributed systems.
Leverage memory
To improve response time, reduce I/O operations. Use local and distributed (e.g., Redis) caches.
Singleton and Flyweight patterns
Use the Singleton pattern for functional classes to save memory, and combine it with the Flyweight pattern for sharing. Be mindful of thread safety. For example, Spring beans are singletons by default.
Implementing chain calls
Method 1: Use arrays, linked lists, or stacks with loops or recursion. All elements on the chain implement the same interface and method. Examples include HandlerExecutionChain in Spring MVC, BeanPostProcessor in Spring, and ProtocolFilterWrapper#buildInvokerChain in Dubbo.
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
i++; // Pre-action
chain.next(request, response, chain); // Execute next filter
i--; // Post-action
}
Example 1: Filter chain implementation
public class FilterChain implements Filter {
List<Filter> filters = new ArrayList<>();
int index = 0;
public FilterChain addFilter(Filter f) {
filters.add(f);
return this;
}
@Override
public void doFilter(Request request, Response response, FilterChain chain) {
if (index == filters.size()) return;
Filter nextFilter = filters.get(index);
index++;
nextFilter.doFilter(request, response, chain);
}
public static void main(String[] args) {
String msg = "Test, <script>, sensitive";
FilterChain fc = new FilterChain();
fc.addFilter(new HTMLFilter()).addFilter(new SensitiveFilter());
Request request = new Request();
Response response = new Response();
// ...
fc.doFilter(request, response, fc);
}
}
Example 2: Building an invoker chain in Dubbo by iterating filters in reverse order and using closures.
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
Invoker<T> last = invoker;
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class)
.getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
last = new Invoker<T>() {
@Override
public Result invoke(Invocation invocation) throws RpcException {
return filter.invoke(next, invocation);
}
};
}
}
return last;
}
Method 2: Add a wrapper to the target method and rely on the call stack to invoke the actual method. Examples include AOP, Decorator pattern, and Proxy pattern.
Achieving pluggable, extensible design
Use filter chains, interceptors, proxies, wrappers, or template methods to isolate sub-functionality. The main flow focuses on core business logic, making it easy to extend, maintain, and reuse components. For example, implement reusable filters for authentication, logging, statistics, and rate limiting.
Method 1: Define a common interface and register different implementations in an array or list. Use the Template Method pattern to iterate over the list at specific points in the workflow.
Method 2: Use Aspect-Oriented Programming (AOP).
Lazy loading / lazy evaluation
Avoid dependencies on timed tasks to save resource consumption, though it may increase complexity and response time.
Examples:
- Redis TTL: Check expiration during
getoperations. - Token bucket rate limiter: Add tokens to the bucket when a token is requested.
- Guava wrappers: Wrap a container and check element validity when getting an element.
- Lazy singleton instantiation.
Wrapper/Proxy pattern
A common approach to enhance the functionality of an object. For example, wrap cached objects in a custom Node class to add features like expiration checks.
class Node<T> {
private T value;
private String key;
private DateTime createTime;
// Add properties and methods to enhance value
}
Reuse, caching, and the Prototype pattern
Besides using HashMap or Redis for data caching, frameworks often cache complex objects that are expensive to assemble. For example, a filter chain may be cached after its first assembly and reused later.
Method extraction
Break a complex method into smaller, well-named sub-methods to improve readability and reusability.
Adapter pattern for third-party components
Wrap a third-party component within an adapter class. Other classes in the project should only depend on this adapter. When the underlying component needs to be replaced, only the adapter needs to be modified or rewritten.
String
Q: Why is the String class declared final?
- Security: It cannot be subclassed and overridden.
- Thread safety:
Stringis immutable. Any thread can access its value without worrying about it changing. - Support for the string constant pool: This enables sharing and saves memory, improving efficiency.
- Efficient caching of
hashCode: Since the value cannot change, thehashCodecan be cached, making it an excellent key forHashMap.
Q: How is String immutability implemented?
Methods that appear to modify a String (e.g., concat()) actually return a new String instance. They do not modify the internal value[] array.
public final class String implements Serializable, Comparable<String>, CharSequence {
private final char value[];
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
}
Q: A test of String immutability
@Test
public void testStringImmutability() {
getString("s", 1);
}
private void getString(String s, int i) {
if (i > 2) return;
System.out.println(i + " recursive call before: " + s);
getString(s + "s", i + 1);
System.out.println("Return to level " + i + ": " + s);
}
Output:
1 recursive call before: s
2 recursive call before: ss
Return to level 2: ss
Return to level 1: s
Q: What is the purpose of the string constant pool?
It reduces the number of string objects created, saving memory. Without it, each method call would instantiate new strings.
Q: How does String.intern() work?
It returns the reference to a string from the constant pool that is equal to the current string (as determined by equals()). If the string is not already in the pool, it is added.
String s1 = new StringBuilder("12").append("ab").toString();
System.out.println(s1.intern() == s1); // true
String s2 = new StringBuilder().append("12ab").toString();
System.out.println(s2.intern() == s1); // true
System.out.println(s2.intern() == s2); // false
Data Types
Q: Evaluating Integer comparisons
int h = 9;
double j = 9.0;
Integer a = new Integer(9);
Integer b = new Integer(9);
Integer c = 9;
Integer d = 9;
Double i = 9.0;
Integer e = Integer.valueOf(9);
Integer k = Integer.valueOf("9");
int l = Integer.valueOf(9).intValue();
Integer f = 128;
Integer g = 128;
Results:
h == j→truea == h→true(auto-unboxing)a == b→falsea == c→falsec == d→true(cached in range [-128, 127])c == e→truef == g→false(outside cache range)i.equals(h)→falsei.equals(j)→true
Q: Types of a and b after these assignments?
a = Integer.parseInt("1024");
b = Integer.valueOf("1024").intValue();
Answer: Both a and b are int types with the same value.
Q: Evaluate the comparisons
Integer i01 = 59;
int i02 = 59;
Integer i03 = Integer.valueOf(59);
Integer i04 = new Integer(59);
System.out.println(i01 == i02); // true
System.out.println(i01 == i03); // true
System.out.println(i03 == i04); // false
System.out.println(i02 == i04); // true
Q: Why avoid double and float for sensitive decimals?
They can cause precision loss. For example, 1.0 - 0.9 = 0.09999999....
Solution 1: Store monetary amounts in cents using long.
Solution 2: Use BigDecimal for decimal calculations. Always initialize BigDecimal from a String.
System.out.println(new BigDecimal(1.22)); // 1.2199999999999999733546474089962430298328399658203125
System.out.println(new BigDecimal(String.valueOf(1.22))); // 1.22
When comparing BigDecimal with other types like Double, convert the Double to a BigDecimal and use compareTo(). Note that BigDecimal values 0.1 and 0.10 are not equal according to equals(), but compareTo() considers them equal.
BigDecimal best practices
- Use for commercial calculations.
- Use the
Stringconstructor orBigDecimal.valueOf(), which internally converts the argument to aString. 2.0is not equal to2.00according toequals(), but they are equal according tocompareTo(). UsecompareTo()for comparison.BigDecimalis immutable. Each operation returns a new object.- All arithmetic operations return a new
BigDecimal; they do not modify the original instance.
BigDecimal a = new BigDecimal("1.22");
a.divide(new BigDecimal(2));
System.out.println(a); // 1.22
a = a.divide(new BigDecimal(2));
System.out.println(a); // 0.61
Class Initialization
Q: What is wrong with the following code?
public class Test {
public static int i = 1;
static {
i = 2;
j = 2;
System.out.println(i);
System.out.println(j); // Compilation error
}
public static int j = 1;
}
Answer: System.out.println(j); will cause a compilation error. In a static block, a varible (like j) declared later in the class can be assigned but cannot be referenced before its declaration.
Q: What is the output?
public class B {
public static B t1 = new B();
public static B t2 = new B();
{
System.out.println("Instance block");
}
static {
System.out.println("Static block");
}
public static void main(String[] args) {
B t = new B();
}
}
Answer:
Instance block
Instance block
Static block
Instance block
The static block executes only once, when the class is first loaded.
Q: What is the output?
class A {
public A() { System.out.println("class A"); }
{ System.out.println("I'm A class"); }
static { System.out.println("class A static"); }
}
public class B extends A {
public B() { System.out.println("class B"); }
static { System.out.println("class B static"); }
{ System.out.println("I'm B class"); }
public static void main(String[] args) {
new B();
}
}
Answer (initialization order): Parent static -> Child static -> Parent instance -> Parent constructor -> Child instance -> Child constructor.
class A static
class B static
I'm A class
class A
I'm B class
class B
Q: What is the output?
public class Cshsx {
private int i = 1;
public static int s = 2;
static {
System.out.println("Static code block");
System.out.println(s);
}
{
System.out.println("Code block");
System.out.println(i);
System.out.println(s);
}
public static void main(String[] args) {
new Cshsx();
}
}
Answer:
Static code block
2
Code block
1
2
Q: What is the output?
class X {
Y y = new Y();
public X() { System.out.print("X"); }
}
class Y {
public Y() { System.out.print("Y"); }
}
public class Z extends X {
Y y = new Y();
public Z() { System.out.print("Z"); }
public static void main(String[] args) {
new Z();
}
}
Answer: YXYZ
Inheritance
Q: What is the output?
public class Demo {
class Super {
int flag = 1;
Super() { test(); }
void test() { System.out.println("Super.test() flag=" + flag); }
}
class Sub extends Super {
Sub(int i) {
flag = i;
System.out.println("Sub.Sub() flag=" + flag);
}
void test() { System.out.println("Sub.test() flag=" + flag); }
}
public static void main(String[] args) {
new Demo().new Sub(5);
}
}
Answer:
Sub.test() flag=1
Sub.Sub() flag=5
Q: What is the output?
public class Son extends Father {
public int a = 1;
private int i = 1;
@Override
public String getMsg() { return "son"; }
public int getSuperA() { return super.a; }
public static void main(String[] args) {
Son son = new Son();
Father sonRef = son;
System.out.println(sonRef.getMsg());
System.out.println(son.getSuperA());
System.out.println(son.getA());
System.out.println(son.getI());
son.setI(100);
System.out.println(son.getI());
}
}
public class Father {
public int a = 0;
private int i = 0;
public String getMsg() { return "father"; }
public int getI() { return this.i; }
public void setI(int i) { this.i = i; }
public int getA() { return this.a; }
public void setA(int a) { this.a = a; }
}
Answer:
son
0
0
0
100
Q: What is the output?
public class Main {
private String baseName = "base";
public Main() { callName(); }
public void callName() { System.out.println(baseName); }
static class Sub extends Main {
private String baseName = "sub";
public void callName() { System.out.println(baseName); }
}
public static void main(String[] args) {
Main b = new Sub();
}
}
Answer: null. The child class constructor calls super() implicitly. Due to dynamic binding and overriding, super() calls the child's overridden callName(), which tries to access the child's baseName variable. However, at this point, the child's baseName has only been initialized to its default value (null) and has not yet been assigned "sub".
Q: True or false: In Java polymorphism, the method of the class that is instantiated with new is always called.
Answer: False. This depends on dynamic vs. static binding.
final,static,private, and constructor methods use static binding and cannot be overridden.- Non-static methods that are visible to the subclass can be overridden. The method called is determined by the actual runtime type of the object (dynamic binding).
- Static methods are not overridden; they are hidden. The method called is determined by the reference type (static dispatch).
Q: What is the output?
public class Demo {
public static void main(String[] args) {
Collection<?>[] collections = {
new HashSet<String>(),
new ArrayList<String>(),
new HashMap<String, String>().values()
};
Super subToSuper = new Sub();
for (Collection<?> collection : collections) {
System.out.println(subToSuper.getType(collection));
}
}
abstract static class Super {
public static String getType(Collection<?> collection) { return "Super:collection"; }
public static String getType(List<?> list) { return "Super:list"; }
public String getType(ArrayList<?> list) { return "Super:arrayList"; }
public static String getType(Set<?> set) { return "Super:set"; }
public String getType(HashSet<?> set) { return "Super:hashSet"; }
}
static class Sub extends Super {
public static String getType(Collection<?> collection) { return "Sub"; }
}
}
Answer:
Super:collection
Super:collection
Super:collection
Q: What is true about this code?
class Person {
String name = "No name";
public Person(String nm) { name = nm; }
}
class Employee extends Person {
String empID = "0000";
public Employee(String id) { empID = id; }
}
public class Test {
public static void main(String[] args) {
Employee e = new Employee("123");
System.out.println(e.empID);
}
}
Answer: It will not compile because the Employee constructor implicitly calls super(), but the Person class does not have a no-argument constructor.
Q: What is the output of this code?
public class Father {
protected void doSomething() {
System.out.println("Father doSomething");
this.doSomething();
}
public static void main(String[] args) {
Father father = new Son();
father.doSomething();
}
}
class Son extends Father {
@Override
public void doSomething() {
System.out.println("Son's doSomething");
super.doSomething();
}
}
Answer: This will cause a StackOverflowError due to infinite recursion.
Try-Catch-Finally
Q: Where is the finally block executed if there is a return in the try block?
Answer: The finally block is executed right before the return statement executes.
Q: What is the output?
public class Test {
public int add(int a, int b) {
try {
return a + b;
} catch (Exception e) {
System.out.println("catch block");
} finally {
System.out.println("finally block");
}
return 0;
}
public static void main(String[] args) {
Test test = new Test();
System.out.println("Sum is: " + test.add(9, 34));
}
}
Answer:
finally block
Sum is: 43
Q: What is the final output?
public class Main {
public static int add() {
try {
return 5;
} finally {
return 10;
}
}
public static void main(String[] args) {
System.out.println(add());
}
}
Answer: 10
Q: What is the final output?
public class Main {
public static int add() {
int a = 1;
try {
return a;
} finally {
a = 10;
}
}
public static void main(String[] args) {
System.out.println(add());
}
}
Answer: 1. The finally block cannot change the value of a primitive that has already been returned.
Q: What is the final output?
public class Main {
public static String add() {
String a = "123";
try {
return a;
} finally {
a = "456";
}
}
public static void main(String[] args) {
System.out.println(add());
}
}
Answer: 123. The finally block cannot change the reference that has already been returned.
Q: Using a flag variable to track execution progress
public static int process() {
int progress = 0;
int value = 1;
try {
value = value * 2;
progress++;
value = value * 2;
progress++;
return value;
} catch (Exception e) {
if (progress == 1) {
value = value / 2;
}
if (progress == 2) {
value = value / 4;
}
}
return 0;
}
Exceptions
Q: The difference between RuntimeException and Exception
- Non-
RuntimeException(checked exceptions) must be handled by acatchblock or declared in the method signature withthrows. If declared withthrows, it must eventually be caught somewhere up the call stack. RuntimeException(unchecked exceptions) do not need to be caught or declared. They will cause the current thread to stop if unhandled. Custom exceptions that represent programming errors often extendRuntimeException.
public class BaseException extends RuntimeException {
private int status = 200;
public BaseException(String message, int status) {
super(message);
this.status = status;
}
// getters and setters
}
Exception hierarchy

- If a block of code might throw an
Error, catchThrowable.
Console exception print order
Exceptions are printed in reverse order of the cause (Caused by). The bottom Caused by is the root cause. Within each Caused by, the stack trace is printed in the order method calls were made.
Collections
Q: What collection types are available in Java, their characteristics, and use cases?
- List: Ordered, allows duplicates, indexed by position. (
ArrayList,LinkedList). - Set: Unordered, no duplicates. (
HashSet,LinkedHashSet,TreeSet). - Queue/Deque: Typically FIFO, but can be LIFO or priority-based. (
ArrayDeque,LinkedList,PriorityQueue). - Map: Key-value pairs. (
HashMap,TreeMap,LinkedHashMap).
Q: Common APIs of the Collections utility class
// Ordering
static void sort(List<T> list, Comparator<? super T> c);
static T max(Collection<? extends T> coll, Comparator<? super T> comp);
static T min(Collection<? extends T> coll, Comparator<? super T> comp);
static void reverse(List<?> list);
static void shuffle(List<?> list);
// Searching
static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c);
static int frequency(Collection<?> c, Object o);
static int indexOfSubList(List<?> source, List<?> target);
static int lastIndexOfSubList(List<?> source, List<?> target);
// Copying, Replacing
static <T> void copy(List<? super T> dest, List<? extends T> src);
static <T> boolean replaceAll(List<T> list, T oldVal, T newVal);
// Miscellaneous
static boolean disjoint(Collection<?> c1, Collection<?> c2);
static <T> void fill(List<? super T> list, T obj);
// Wrappers
static <T> Collection<T> synchronizedCollection(Collection<T> c);
static <E> List<E> checkedList(List<E> list, Class<E> type);
static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c);
Q: How to improve performance when creating a collection that will hold many objects?
Answer: Specify the initial capacity to avoid repeated resizing. This applies to all array-backed data structures.
Q: Efficiently using array indices
Arrays and ArrayList allow fast access using indices: [index-n] for elements to the left, [index+n] for elements to the right. Similarly for linked lists: node.previous.previous, node.next.
Q: HashMap initialization and resizing
- The constructor accepts initial capacity and load factor.
HashMapsets the actual capacity to the smallest power of 2 greater than or equal to the given value. - The table is not created in the constructor. It is created during the first
put()call, which triggersresize(). - Example:
new HashMap(1000)will have an initial table capacity of 1024, and the threshold will be1024 * 0.75 = 768.
Q: The HashMap load factor
- The load factor determines when to resize. The default is 0.75.
- Threshold = capacity * load factor.
- A higher load factor uses more space, but may increase collision rates. A lower load factor uses less space but leads to faster lookups.
Q: Differences between HashMap and TreeMap
HashMap: Suitable for general-purpose storage and caching. (Thread-safe version:ConcurrentHashMap).TreeMap: Uses a Red-Black tree. Suitable for sorted or range-based operations (e.g., statistics, range queries). (Thread-safe version:ConcurrentSkipListMap).TreeMap.get()relies on theComparatororComparableinterface, nothashCode()andequals().- Useful methods:
subMap(),headMap(),tailMap(),firstKey(),lastKey(),firstEntry(),lastEntry(). values()returns a list in ascending key order.
Q: subList is a view, not a copy
subList returns a view of a portion of the original list. Modifying either the original list's structure (e.g., adding or removing elements) or the sublist's structure will affect the other. Structural modifications to the original list while iterating over the sublist will throw a ConcurrentModificationException.
Q: Arrays.asList() caveats
- Returns a fixed-size list backed by the original array.
- Does not support
add,remove, orclearoperations. Attempting these will throwUnsupportedOperationException. - Modifications to the original array affect the returned list.
To get a mutable ArrayList, use new ArrayList<>(Arrays.asList(array)).
Q: List to array conversion
Avoid using toArray() without arguments, as it returns Object[]. The recommended approach is:
String[] array = new String[list.size()];
array = list.toArray(array);
This is most efficient when the array size matches the list size.
Q: Modifying a collection during iteration
Do not use add or remove directly on the collection within a for or foreach loop. This can cause a ConcurrentModificationException. Use the Iterator's remove() method instead.
Q: Fail-fast vs. fail-safe iterators
- Fail-fast: The iterator checks for structural modifications to the underlying collection. If a modification is detected, it throws a
ConcurrentModificationException. Used by most non-concurrent collections. - Fail-safe: The iterator works on a snapshot of the collection. Modifications to the original collection do not affect the iteration. However, this can lead to weak consistency. Used by concurrent collections (e.g.,
ConcurrentHashMap,CopyOnWriteArrayList).
Q: CopyOnWriteArrayList
- Suitable for read-heavy, write-rarely scenarios.
- Write operations lock the list, copy the underlying array, make the modification on the copy, and then replace the reference to the array. This means writes are expensive.
- To improve write performance, consider using batch operations (
addAll,removeAll).
Q: Efficiently traversing a List
if (list instanceof RandomAccess) {
// Use traditional for loop.
} else {
// Use Iterator or foreach.
}
Q: Custom objects as HashMap keys
Override hashCode() and equals() for any custom class used as a key. If not overridden, HashMap will use the default Object implementations, which are based on memory address.
Q: Efficiently traversing a Map
Use entrySet() with an Iterator or the built-in Map.forEach() method (available since Java 8). entrySet() provides access to both keys and values.
Q: Map views: keySet(), values(), entrySet()
These views support remove(), clear(), and modifications to the state of the elements, but not structural additions (like add).
Q: HashMap vs. TreeMap summary
HashMapuseshashCode()andequals()for deduplication.TreeMapusesComparableorComparator.HashMapgenerally has better insertion and deletion performance.TreeMapis better for sorted data, range queries, and finding the first/last elements.
Q: Fast deduplication of a List
Set<T> set = new LinkedHashSet<>(arrayList);
List<T> deduplicatedList = new ArrayList<>(set);
Q: Atomic operations in ConcurrentMap
putIfAbsent()compute(),computeIfAbsent(),computeIfPresent()remove(key, oldValue)replace(key, oldValue, newValue)
Q: The ConcurrentHashMap.computeIfAbsent() infinite loop bug
Avoid modifying the Map from within a lambda passed to computeIfAbsent(). This can cause an infinite loop and high CPU usage.
// BUG: This might cause an infinite loop
map.computeIfAbsent("a", key -> {
map.put("a", "v2");
return "v1";
});
A workaround:
V value = map.get(key);
if (value == null) {
V newValue = computeValue(key);
V existingValue = map.putIfAbsent(key, newValue);
if (existingValue == null) {
return newValue;
}
return existingValue;
}
return value;
Q: Efficiently handling large data structures (e.g., Top-K, merging sorted lists)
- For Top-K problems, use a heap (e.g.,
PriorityQueuein Java). A min-heap of size K can find the top K largest elements efficiently without sorting the entire dataset. PriorityQueueis a min-heap by default but can be made into a max-heap by providing a customComparator.
Q: Third-party libraries for collections
- Google Guava: Provides many efficient utilities (
Lists,Sets,Maps) and new data structures. - Apache Commons Collections: Another popular library.
Q: Implementing an LRU cache using LinkedHashMap
Map<Integer, String> lruCache = new LinkedHashMap<>(10, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, String> eldest) {
return size() > 10;
}
};
Q: Implementing a cache for a list of trending news
Use a TreeMap (or ConcurrentSkipListMap for thread safety) with the news ID (a Long) as the key to sort by ID (assuming newer news has higher IDs). Use lowerEntry(), higherEntry(), and subMap() for pagination.
// Get 10 news items with a higher ID
for (int i = 0; i < 10; i++) {
Map.Entry<Long, News> entry = newsMap.higherEntry(lastNewsId);
if (entry == null) break;
lastNewsId = entry.getKey();
newsList.add(entry.getValue());
}
Q: ConcurrentSkipListMap
- A thread-safe, scalable version of
TreeMap. get()relies onComparableorComparatorfor ordering, nothashCode()andequals().- Its internal structure is a skip list, which is like a multi-level linked list for efficient searching.

Generics
Q: Important notes about generics in collections
- Assigning a raw type
Listto a typedList<T>will generate a warning and the type check is lost at runtime. List<T>andList<E>are not related even ifTis a parenet ofE.- Wildcard
?:List<?>can accept any type, but you cannot add elements (exceptnull) to it. You can only read and remove elements. - Upper-bounded wildcard
? extends T:List<? extends T>can hold elements of typeTor its subtypes. It allows reading (asT), but not adding. - Lower-bounded wildcard
? super T:List<? super T>can hold elements of typeTor its supertypes. It allows adding elements of typeTor its subtypes, but reading returnsObject.
Q: Generic methods
Generic methods can infer their type parameters from their arguments, so you don't need to specify the type explicitly when calling them.
private <T, E> T getObject(E name) {
// ...
}
Q: The bridge method and type erasure
Type erasure removes generic type information at compile time. A bridge method is a synthetic method generated by the compiler in some cases (e.g., when a subclass overrides a generic method) to maintain polymorphism.
I/O
Q: How to close multiple I/O streams safely?
If you close streams in a finally block and the first close() fails, the second may not be closed. Since Java 7, you can use the try-with-resources statement, which automatically closes all resources that implement Closeable.
try (OutputStream out1 = new FileOutputStream("");
OutputStream out2 = new FileOutputStream("")) {
// Use streams
} catch (Exception e) {
// Handle exception
}
Transactions
Q: Which statement about PROPAGATION_REQUIRES_NEW is true?
Answer: If the inner transaction rolls back, the outer transaction can still commit. PROPAGATION_REQUIRES_NEW suspends the outer transaction and starts a new, independent inner transaction.
Q: Can @Transactional with synchronized cause issues?
Yes. The correct order is to enter the synchronized block first, then start the transaction. If the transaction starts before entering the synchronized block, database isolation levels can cause thread-safety issues.
// Incorrect: Transactions start before lock is acquired
@Transactional
public synchronized void update() throws Exception {
// ...
}
Inner Classes
Q: What are the key points about Java inner classes?
- An anonymous inner class can access local variables of the enclosing method if they are effectively final.
- To make a local variable mutable inside an anonymous class, you can wrap it in an array or an object.
- A static inner class does not have an implicit reference to the outer class object.
- Anonymous inner classes are compiled to a
.classfile at compile time.
void put() {
final int i = 0; // effectively final
new Runnable() {
@Override
public void run() {
System.out.println(i);
}
};
}
Multithreading
Q: The three elements of thread safety
- Atomicity: Operations are indivisible.
- Visibility: Changes made by one thread are visible to others.
- Ordering: The compiler and CPU may reorder instructions for optimization. The
happens-beforeprinciple guarantees ordering in certain scenarios.
Q: Which of the following is an atomic operation?
j = 1 is atomic. j++, j = j, and j = j+1 are compound operations that are not thread-safe.
Q: Thread interference and reordering
int x = 1;
int y = 1;
// Thread 1
public void a() {
x = 5;
y = 6;
}
// Thread 2
public void b() {
if (y == 6 && x != 5) {
System.out.println("haha");
}
}
It is possible for Thread 2 to print "haha" due to instruction reordering in Thread 1.
Q: Deadlocks
To avoid deadlocks:
- Always acquire locks in a fixed, global order.
- Use
tryLock()with a timeout, which will returnfalseif the lock cannot be acquired within a specified time.
Q: Interrupting a thread with interrupt()
- Calling
interrupt()on a thread sets its interrupt flag. It does not stop the thread unless the thread is blocked on an interruptible operation (e.g.,sleep(),wait(),join()). In that case, it throws anInterruptedException. - If the thread is not blocked, it continues running with its interrupt flag set. The program must check the flag using
Thread.interrupted()orThread.currentThread().isInterrupted()and handle it appropriately.
Q: synchronized keyword and lock optimization
- A
synchronizedblock guarantees atomicity, visibility, and ordering. - Locks in Java can be biased (for single-thread access), lightweight (using CAS spinning), or heavyweight (requiring OS system calls). The JVM automatically upgrades the lock as contention increases.
- Each object has a monitor. The
synchronizedkeyword uses this monitor.
Q: synchronized vs. ReentrantLock
Both are reentrant locks. ReentrantLock provides more flexibility (e.g., tryLock(), lockInterruptibly(), Condition objects) but requires explicit unlock (usually in a finally block).synchronized is more concise.
Q: The volatile keyword
volatile ensures visibility and ordering but not atomicity. volatile + CAS (Compare-And-Swap) can achieve thread safety. ConcurrentLinkedQueue is a classic example of a lock-free concurrent queue using volatile + CAS.
Q: ThreadLocal<T>
ThreadLocal provides a per-thread copy of a variable, achieving thread closure. It is useful for thread-level global variables.
Q: Can ReentrantReadWriteLock be upgraded?
No, a read lock cannot be upgraded to a write lock. If any thread holds a read lock, no thread can acquire the write lock.
Q: Controlling the order of thread execution
CountDownLatchor other AQS components.join().- A
volatileorAtomicflag variable. wait()andnotify()/Condition.
Q: Thread states and transitions
- New: Created but not started.
- Runnable: Ready to run or running.
- Blocked: Waiting for a monitor lock to enter a
synchronizedblock/method. - Waiting: Waiting indefinitely for another thread to perform a particular action (e.g.,
wait(),join()without timeout). - Timed Waiting: Waiting with a timeout (e.g.,
sleep(),wait()with timeout,join()with timeout). - Terminated: Completed execution.

Q: Suspending and waking a thread
A thread cannot wake itself up. It must be awakened by another thread. This is typically done using methods like wait()/notify(), await()/signal() on a Condition, or AQS components.
Q: The wait() and notify()/notifyAll() methods
Must be called within a synchronized block on the same object. This is a requirement enforced by the JDK to avoid race conditions like lost wake-ups.
Q: Choosing between notify() and notifyAll()
notify() only wakes up one thread. notifyAll() wakes up all waiting threads. If you are not sure, use notifyAll() to avoid potential thread hang-ups.
Q: Avoiding spurious wake-ups in Condition
Always check the waiting condition in a loop. This ensures that the thread rechecks the condition after being woken up.
lock.lock();
try {
while (conditionIsNotMet) {
condition.await();
}
// Proceed
} finally {
lock.unlock();
}
Q: Releasing resources in finally block
Any unlock or wake-up operations (e.g., countDown(), signal(), unlock()) should be performed in a finally block to ensure they are executed, even if an exception occurs.
Q: Asynchronous execution and Future
Using a Future allows one thread to submit a task to another thread and later retrieve the result. This can improve overall throughput.
Future<Integer> future = executorService.submit(() -> {
Thread.sleep(1000);
return 1 + 1;
});
// Do other work...
int result = future.get(); // Will wait (block) if not yet completed
Q: Preventing cache stampede (dog-piling)
Several approaches to prevent multiple threads from simultaneously executing an expensive cache update:
- Lock + Double-check: Simple but forces other threads to wait for the lock even after the cache is populated.
- CAS + Double-check: Uses
Atomicvariables for a non-blocking lock. computeIfAbsent(): Uses internal locking.- Future-based: The first thread puts a
Futurein the cache. Other threads see theFutureand block onFuture.get().
// Future-based approach (pseudo-code)
Map<String, Future<List<Data>>> cache = new ConcurrentHashMap<>();
List<Data> getData(String key) throws Exception {
Future<List<Data>> future = cache.get(key);
if (future == null) {
Callable<List<Data>> task = () -> fetchFromDatabase(key);
FutureTask<List<Data>> newFuture = new FutureTask<>(task);
Future<List<Data>> oldFuture = cache.putIfAbsent(key, newFuture);
if (oldFuture == null) {
// This means this thread was the first to put the future
newFuture.run();
return newFuture.get();
} else {
future = oldFuture;
}
}
return future.get();
}
Q: Lock vs. CAS + Spin Lock
Lockblocks the thread, causing it to be suspended and wait to be woken up. This reduces CPU consumption but has overhead from thread context switching.CAS + Spinuses a busy-wait loop, which keeps the thread active. This can be more efficient for short critical sections but wastes CPU cycles on contention.
Q: Business-level segmented locking
ConcurrentHashMap<String, Object> lockMap = new ConcurrentHashMap<>();
public void updateInfo(String id, String info) {
Object lock = lockMap.computeIfAbsent(id, k -> new Object());
synchronized (lock) {
// Update resource for this ID
}
}
Q: Which behavior does NOT throw InterruptedException if interrupted?
Thread.suspend(). The suspend() and resume() methods are deprecated and can lead to deadlocks.
Q: Is SimpleDateFormat thread-safe?
No. Use ThreadLocal<DateFormat> or DateTimeFormatter (from Java 8's java.time package).
Q: Is Math.random() thread-safe?
Math.random() will cause a performance bottleneck under high concurrency due to contention on a single AtomicLong seed. Use ThreadLocalRandom instead.
Q: Thread context propagation
When a task is submitted to a thread pool, the thread executing the task is different from the submitting thread. ThreadLocal values from the submitting thread will not be automatically passed to the worker thread. You must explicitly pass the context (e.g., as a parameter, using a custom wrapper, or via an InheritableThreadLocal).
Q: How to detect if a thread holds a monitor lock on an object?
Thread.holdsLock(Object obj) returns true if the current thread holds the monitor lock on the specified object.
Q: When is an exception from a thread pool task caught?
An exception thrown from a task submitted to a thread pool is not automatically caught by the submitting thread. It is only caught when calling Future.get(), which will throw an ExecutionException wrapping the actual exception.
Q: Performance of System.currentTimeMillis()
This call can be expensive under high concurrency. Strategies to mitigate this include:
- Caching the timestamp in a separate thread (at the cost of precision).
- Using
System.nanoTime()(better resolution but not related to wall-clock time).
Q: Asynchronous processing using queues
A common pattern is to use a queue (e.g., BlockingQueue, MQ) to switch between threads. A producer thread puts tasks into the queue, and a consumer thread takes tasks from the queue. This is the basis of thread pools and many distributed systems.
Q: Implementing simple rate limiting and circuit breaking
Use a single-threaded executor or a separate thread pool for isolated business logic. The main thread can wait for the result with a timeout and fall back to a default action if the task times out.
Reflection
Q: What is the output?
package test;
import java.util.Date;
public class SuperTest extends Date {
private static final long serialVersionUID = 1L;
private void test() {
System.out.println(super.getClass().getName());
}
public static void main(String[] args) {
new SuperTest().test();
}
}
Answer: test.SuperTest. The getClass() method returns the runtime class of the current object, which is SuperTest.
Q: Difference between Class.forName() and ClassLoader.loadClass()
Class.forName() loads the class, initializes it (runs static blocks), and returns the Class object. ClassLoader.loadClass() (or ClassLoader.loadClass()) loads the class but does not initialize it; the class is initialized when it is first actively used (e.g., newInstance()).
JVM
Q: Stack, heap, and constant pool
Primitive types and references are often stored on the stack (as local variables) or in the heap (as instance fields). The string constant pool is a special part of the heap that stores string literals.
Q: Common causes of memory leaks
- Static fields holding references to objects unnecessarily.
- Unbounded collections (e.g., a
HashMapused as a cache that never removes entries). - Improperly used
ThreadLocal(thread is not reused, or theThreadLocalvalue is not removed). - Unclosed resources (e.g., streams, connections).
- Non-static inner classes holding an implicit reference to the outer class, preventing the outer class from being garbage collected.
SPI
Q: Example of SPI
The MySQL JDBC driver implements the java.sql.Driver interface. The driver JAR contains a service provider file (META-INF/services/java.sql.Driver) that lists the implementation class (com.mysql.cj.jdbc.Driver). The JVM uses SPI to load this implementation when connecting to a MySQL database.