Core API Concepts
Object & String Fundamentals
By default, the toString() method in the Object class returns the memory address (hash) of the object, which is often not useful. It is standrad practice to override this method to return a string representation of the object's internal state.
The == operator comapres primitive types by value, but for reference types, it compares memory addresses.
The String class is immutable; once created, its value cannot be changed. Reassigning a String variable points the reference to a new object in the string pool rather than modifying the original data.
// String comparison nuances
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true (same pool reference)
System.out.println(s1 == s3); // false (different objects)
System.out.println(s1.equals(s3)); // true (content is same)
Essential String Methods
char[] toCharArray(): Converts the string into a character array for iteration.String toLowerCase()/toUpperCase(): Case conversion.boolean startsWith(String prefix)/endsWith(String suffix): Checks prefixes and suffixes.boolean contains(CharSequence seq): Checks for substring existence.String substring(int begin, int end): Extracts a portion of the string (inclusive start, exclusive end).
StringBuilder: Mutable Strings
StringBuilder acts as a mutable container for characters, improving performance when concatenating many strings.
StringBuilder builder = new StringBuilder("init");
builder.append("_data"); // Adds to the internal buffer
builder.delete(0, 4); // Removes characters directly in the buffer
System.out.println(builder.toString()); // Output: _data
Capacity: If the appended content exceeds the default capacity (usually 16), the internal array expands (typically to 2 * oldCapacity + 2).
StringBuilder vs StringBuffer
Date and Time API
Legacy Date
The Date class (old API) uses getTime() to fetch milliseconds since the Unix epoch (1970-01-01).
long start = new Date().getTime();
// Simulate work
long end = new Date().getTime();
System.out.println("Elapsed: " + (end - start) + "ms");
Modern Date API (java.time)
LocalDate handles dates without time zones. LocalDateTime includes time.
LocalDate today = LocalDate.now();
System.out.println(today.getYear()); // e.g., 2024
// Manipulating dates
LocalDate futureDate = today.plusYears(3);
boolean isLeap = futureDate.isLeapYear();
Formatting and Parsing
Convert between Strings and Date objects using DateTimeFormatter.
LocalDateTime current = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = current.format(formatter);
System.out.println(formatted);
// Parsing string back to date
LocalDate parsedDate = LocalDate.parse("2012-10-12");
Regular Expressions
Regex provides a powerful way to match string patterns.
// Validate phone number pattern
String phoneRegex = "1[3-9]\\d{9}";
System.out.println("13837808450".matches(phoneRegex)); // true
// Validate email pattern
String emailRegex = "\\w+@\\w+\\.\\w+";
System.out.println("test@example.com".matches(emailRegex)); // true
Quantifiers: X? (0 or 1), X* (0 or more), X+ (1 or more), X{n,m} (between n and m).
Splitting and Replacing
String data = "one two three";
String[] parts = data.split("\\s+"); // Split by one or more spaces
String result = "apple123banana".replaceAll("\\d+", "-"); // "apple-banana"
Auto-boxing and Caching
Java automatically converts primitives to wrappers (boxing) and vice versa (unboxing).
Integer num = 100; // Auto-boxing: Integer.valueOf(100)
int val = num; // Auto-unboxing: num.intValue()
Caching: Integer objects for values between -128 and 127 are cached. Thus, == returns true for these ranges.
Random and Generators
Generate pseudo-random numbers using the Random class.
Random rand = new Random();
int randomNum = rand.nextInt(10); // 0 to 9
// Simple verification code generator
char[] alphabet = new char[26];
for (int i = 0; i < 26; i++) alphabet[i] = (char) ('a' + i);
StringBuilder code = new StringBuilder();
for (int i = 0; i < 4; i++) code.append(alphabet[rand.nextInt(26)]);
System.out.println(code);
Collection Framework
Introduction to Collections
Collections solve the limitation of fixed-size arrays, allowing dynamic storage of elements.
Iteration Methods
Collections are typically traversed using Iterators or Enhanced For loops.
Collection<String> items = new ArrayList<>();
items.add("A");
items.add("B");
// 1. Iterator
Iterator<String> it = items.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
// 2. Enhanced For
for (String item : items) {
System.out.println(item);
}
Note: Do not modify a collection structurally (add/remove) while iterating using the collection's methods; use the iterator's remove method instead to avoid ConcurrentModificationException.
Generics
Generics enforce type safety at compile time, eliminating the need for casting.
// Generic Class
class Container<T> {
private T value;
public void set(T val) { this.value = val; }
public T get() { return value; }
}
// Usage
Container<Integer> intBox = new Container<>();
intBox.set(10);
// intBox.set("text"); // Compile-time error
List Implementations
ArrayList (Fast Read, Slow Write)
Backed by a dynamic array. Default capacity is 10. When full, it grows by roughly 1.5x the old size.
ArrayList<Person> roster = new ArrayList<>();
roster.add(new Person("Alice", 30));
// Access by index
Person p = roster.get(0);
LinkedList (Slow Read, Fast Write)
Backed by a doubly-linked list. Efficient for inserting/removing at the ends.
LinkedList<String> queue = new LinkedList<>();
queue.addFirst("First");
queue.addLast("Last");
System.out.println(queue.getFirst()); // First
Set Interface
Sets do not allow duplicates and have no specific order (unless using LinkedHashSet).
HashSet and Hashing
Uniqueness is determined by hashCode() and equals().
- If hash codes differ, the element is added.
- If hash codes are the same,
equals()is checked. - If
equals()is true, the element is considered a duplicate and not added.
// Overriding equals and hashCode in a model class
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
LinkedHashSet
Similar to HashSet but maintains insersion order.
Map Interface
Maps store key-value pairs (e.g., ID to Name).
HashMap Operations
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "John");
map.put(2, "Jane");
String name = map.get(1); // Returns "John"
boolean exists = map.containsKey(2);
Map Traversal
Maps do not have a direct iterator, so they must be converted to a Set.
// 1. Key Set approach
for (Integer key : map.keySet()) {
System.out.println(key + ": " + map.get(key));
}
// 2. Entry Set approach (more efficient)
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
LinkedHashMap
Maintains the order of keys based on insertion.
Advanced Utilities
Nested Collections
Collections can hold other collections.
HashMap<String, HashMap<String, String>> school = new HashMap<>();
HashMap<String, String> classA = new HashMap<>();
classA.put("001", "Student A");
school.put("Class A", classA);
Varargs
Allows passing a variable number of arguments to a method.
public void printAll(String... messages) {
for (String msg : messages) {
System.out.println(msg);
}
}
// Call: printAll("Hello", "World");
Arrays and Collections Tools
Arrays.asList(T... a): Converts an array to a fixed-size list.Collections.addAll(Collection, T...): Adds elements to a collection.Collections.shuffle(List): Randomizes the order of a list.
Wildcards
Used to restrict the types that can be used in generics.
? extends T: Upper bound (T or subclasses).? super T: Lower bound (T or superclasses).