Object-Oriented and Procedural Programming
Object-oriented programming decomposes problems into abstract objects, then combines and calls these objects to solve problems. It has relatively higher resource usage and slower execution speed.
Procedural programming follows a top-down design, breaking problems into individual steps implemented as functions, which are called sequentially. It uses fewer resources and runs faster.
Object-Oriented Programming Features
- Inheritance: Allows subclasses to extend parent class functionality without rewriting code, with two implementation forms: implementation inheritance and interface inheritance.
- Polymorphism: Refers to the same method of a class instance behaving differently in different scenarios, where the same operation produces different results for different objects. Strict polymorphism requires runtime dynamic binding, meeting three conditions: inheritance or interface implementation, subclass overriding parent class methods, and parent class reference pointing to subclass objects. Static polymorphism (compile-time binding) refers to method overloading.
- Encapsulation: Wraps objective things into abstract classes, providing different levels of protection for internal data. Classes can expose data and methods only to trusted objects, hiding information from untrusted ones.
Overload vs Override
- Overload: Resolved at compile time, occurs when multiple methods share the same name but have different parameter lists (return types, access modifiers, and thrown exceptions can vary). Thrown exceptions can be broader than parent class methods.
- Override: Resolved at runtime, requires subclass methods to have the same name, parameter list, and return type as parent class methods. Access modifier restrictions must be weaker or equal to parent class methods, and thrown exceptions must be narrower or equal to parent class methods.
Java Inheritance vs Implementation
- Inheritance: Use
extendskeyword to inherit a parent class, used for code reuse. Parent classes can define properties, methods, variables, and constants. Ideal when multiple classes share common functionality. - Implementation: Use
implementskeyword to implement interfaces. Interfaces define global constants (static final) and abstract methods (default methods were added in Java 8). Ideal when multiple classes share the same target but different implementation logic.
Java Inheritance vs Composition
- Inheritance: Follows the "is-a" relationship, resolved at compile time. Advantages: subclasses automatically inherit parent class interfaces. Disadvantages: tight coupling between subclass and parent class, reduced independence.
- Composition: Follows the "has-a" relationship, resolved at runtime. Advantages: low coupling and good extensibility. Disadvantages: composite classes cannot automatically obtain the same interfaces as component classes.
Composition is generally preferred over inheritance.
Java Variables
Java defines three types of variables: class variables, member variables, and local variables, stored in the JVM's method area, heap memory, and stack memory respectively.
Access Modifier Scope
The following table compares the access permissions of public, protected, private, and default (package-private) modifiers:
| Modifier | Visibility Scope |
|---|---|
public |
Accessible by all classes and objects |
private |
Only accessible within the current class; subclasses and other classes cannot access |
protected |
Accessible within the current class and same-package classes; accessible by subclasses even in different packages |
default |
Accessible only within the current class and same-package classes; not accessible by subclasses in different packages |
Platform Independence Implementation
Java's platform independence is implemented across the entire Java ecosystem, with core components including:
- Front-end Compilation: The
javaccompiler converts.javasource files into platform-neutral.classbytecode files. - Back-end Compilation: The Java Virtual Machine (JVM) translates bytecode into native machine code for the target operating system and hardware.
Key supporting components:
- Java Language Specification: Defines the value ranges and behaviors of basic data types to ensure consistent language behavior across platforms.
- Class Files: All Java code is compiled into a unified class file format, eliminating platform-specific source code differences.
- JVM: Each platform has its own JVM implementation, which shields underlying hardware and operating system differences by translating class files into native code.
Java also supports multiple JVM languages like Kotlin, Groovy, JRuby, Jython, and Scala, as all can be compiled into JVM bytecode regardless of the source language.
Pass by Value and Pass by Reference
- Pass by Value: Copies the actual parameter and passes the copy to the function; modifications to the parameter inside the function do not affect the original actual parameter.
- Pass by Reference: Passes the direct memory address of the actual parameter to the function; modifications to the parameter inside the function affect the original actual parameter.
Java uses only pass by value: when passing object references, the reference itself is copied and passed to the function, so modifications to the referenced object inside the function will affect the original object, but reassigning the reference inside the function will not change the original reference.
Primitive and Wrapper Classes
All wrapper classes are located in the java.lang package. The following table lists the corresponding wrapper classes for each primitive type:
| Primitive Type | Wrapper Class |
|---|---|
byte |
Byte |
boolean |
Boolean |
short |
Short |
char |
Character |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
- Primitive Types: Stored in stack memory, offering high execution efficiency.
- Wrapper Classes: Are objects stored in heap memory, accessed via stack references. They are more resource-intensive than primitive types but provide object-oriented features like methods and properties, and are required for collection frameworks that only accept object types.
Auto-Boxing and Unboxing
Auto-boxing automatically converts primitive types to wrapper classes, e.g., Integer i = 10; is equivalent to Integer i = Integer.valueOf(10);. Auto-unboxing automatically converts wrapper classes to primitive types, e.g., int b = i; is equivalent to int b = i.intValue();.
Common Auto-Boxing/Unboxing Scenarios
- Storing primitive types in collection classes
- Comparing wrapper types with primitive types
- Performing arithmetic operations on wrapper types
- Using ternary operators
- Function parameters and return values
Auto-Boxing Cache Mechanism
Most integer wrapper classes cache instances for values between -128 and 127:
Byte,Short,Long: Cache values from -128 to 127, range cannot be modified.Character: Caches values from 0 to 127, range cannot be modified.Integer: Caches values from -128 to 127 by default; the upper limit can be adjusted via the JVM parameterjava.lang.Integer.IntegerCache.highin Java 6+.
Issues with Auto-Boxing
- Numeric comparison of wrapper objects should use
equals()instead of==, except for values within the cached range. - Auto-unboxing a
nullwrapper object will throw aNullPointerException. - Mass auto-boxing/unboxing in loops can waste significant memory resources.
Correct Boolean Naming in POJOs
When defining boolean fields in POJOs, RPC parameters, or return values:
- Use wrapper type
Booleaninstead of primitivebooleanto avoid serialization erors. - Do not use field names starting with
is(e.g.,isSuccess), usesuccessinstead, as primitive type getters use theisXXX()naming convention which can cause serialization issues. - Use primitive
booleanfor local variables for better performance.
String Handling
String Immutability
Once a String object is created in heap memory, its value cannot be modified. All String class methods return new string objects instead of modifying the original string. For mutable string operations, use StringBuffer or StringBuilder to avoid excessive garbage collection from frequent string object creation.
substring() Method Differences Between JDK 6 and JDK 7
- JDK 6: The
substring()method shared the original character array with the parent string, which could cause memory leaks if only a small substring was extracted from a large string. A common workaround wasx = x.substring(x, y) + ""to create a new string. - JDK 7+: The
substring()method creates a new character array for the extracted substring, eliminating the memory leak issue.
replaceFirst(), replaceAll(), and replace() Differences
The three string replacemant methods have the following distinctions:
replace(CharSequence target, CharSequence replacement): Replaces all exact matches of the target string with the replacement string. Both parameters are plain text strings.replaceAll(String regex, String replacement): Replaces all matches of the given regular expression with the replacement string.replaceFirst(String regex, String replacement): Same asreplaceAll(), but only replaces the first matching result.
String Concatenation
Java supports multiple string concatenation methods, with performance rankings (from fastest to slowest): StringBuilder < StringBuffer < concat < + < StringUtils.join. StringUtils.join is ideal for concatenating string arrays or lists.
+: Compile-time optimization usesStringBuilder.append()for non-constant concatenation, but creates unnecessary objects in loops.concat(): Creates a new character array by combining the lengths of the two strings, copies both strings, then returns a newStringobject.StringBuilder: Mutable character array, non-thread-safe, fastest for single-threaded scenarios.StringBuffer: Thread-safe viasynchronizedon theappend()method, slower thanStringBuilder.StringUtils.join: UsesStringBuilderinternally, optimized for array/list concatenation.
Best practices:
- Use
+for simple concatenation outside loops. - Use
StringBufferfor concurrent scenarios. - Use
StringBuilderfor loop-based concatenation to avoid performance and memory issues.
String.valueOf() vs Integer.toString()
Three common ways to convert an int to String:
int num = 5;
String str1 = "" + num; // Equivalent to new StringBuilder().append(num).toString()
String str2 = String.valueOf(num); // Internally calls Integer.toString(num)
String str3 = Integer.toString(num);
String.valueOf(num) and Integer.toString(num) are functionally identical, while "" + num creates additional StringBuilder and String objects.
Class Constant Pool and Runtime Constant Pool
Class Constant Pool
The class constant pool is a resource repository in the .class file. In addition to class version, fields, methods, and interface descriptions, the class file contains a constant pool table storing compiler-generated literals and symbolic references.
- Literals: Fixed value representations in source code, such as integers, floating-point numbers, strings, booleans, and characters.
- Symbolic References: References relative to direct references, including fully qualified class/interface names, field names and descriptors, and method names and descriptors.
During JVM loading, symbolic references are resolved to direct memory addresses during class creation or runtime.
Runtime Constant Pool
The runtime constant pool is the runtime representation of the class constant pool for each class or interface. It stores both compile-time literals and runtime-resolved method/field references, allocated to the JVM's method area when the class is loaded.
Differences and Connections Between Class Constant Pool, Runtime Constant Pool, and String Constant Pool
- The class constant pool is part of the
.classfile, loaded into the runtime constant pool during JVM class loading. - The string constant pool is a subset of the runtime constant pool, storing string literal values. String literals from the class constant pool are moved to the string constant pool during class loading.
String.intern() Method
The intern() method adds a string to the string constant pool if it does not already exist, then returns a reference to the pooled string. For example, String s = new String("abc").intern() will return a reference to the "abc" string in the constant pool.
String Length Limits
- Compile Time: String constants in the class constant pool have a maximum length of 65534 characters, enforced by the
javaccompiler. - Runtime: The maximum length of a
Stringobject is limited by the range of theinttype (2^31 -1), as the internal character array length is stored as anint. Exceeding this limit will throw anOutOfMemoryErroror other runtime exceptions.
Java Keywords
transient Keyword
The transient modifier marks instance variables that should not be serialized. When an object is serialized via Java's serialization mechanism, transient fields are excluded from the serialized data, and their values are set to default values (0 for primitives, null for objects) after deserialization. A common use case is the elementData array in ArrayList, which uses transient to avoid serializing the unused underlying array capacity.
instanceof Keyword
instanceof is a binary operator that tests whether an object is an instance of a specified class, interface, or subclass, returning a boolean value.
volatile Keyword
The volatile keyword solves concurrency issues related to atomicity, visibility, and order:
- Visibility: When a volatile variable is modified, the JVM sends a lock prefix instruction to flush the variable from the processor cache to main memory. All other processors sniff the bus to detect cached value changes, invalidate their local caches, and reload the latest value from main memory.
- Ordering: Prevents instruction reordering by inserting memory barriers.
volatile guarantees visibility and order but not atomicity. For example, volatile int count ensures visibility of count changes, but count++ is not atomic as it involves read-modify-write operations.
final Keyword
The final keyword indicates "unmodifiable" and can be used to define variables, methods, and classes:
- Final Variables: Cannot be reassigned after initialization. Instance final variables must be initialized in the constructor or at declaration.
- Final Methods: Cannot be overridden by subclasses.
- Final Classes: Cannot be inherited.
static Keyword
The static keyword belongs to the class rather than instances:
- Static Variables: Shared across all class instances, not thread-safe. Often used with
finalfor shared global resources, accessible viaClassName.variableNameif not privatized. - Static Methods: Belong to the class, can only call other static methods and access static variables. Common examples include utility classes like
java.util.Collections. - Static Code Blocks: Execute once when the class is loaded by the class loader, used to initialize static variables or resources. Multiple static blocks can exist in a class, executed in declaration order.
- Static Nested Classes: Nested classes marked
static, can be accessed without an instance of the outer class, used for organizational purposes.
const Keyword
const is a reserved keyword in Java that is not currently used, intended for future language extansions. Its usage is similar to final, but it is rarely employed in practice.