Strings: Best Practices & Pitfalls
Common mistakes and best practices
1. Common String Pitfalls
Understanding and avoiding these common mistakes will make your string handling more robust and reliable.
Mistake 1: Reference vs Content Comparison
Wrong vs Right Comparison
// WRONG: Using == for content comparison
String userInput = getUserInput(); // Returns new String("admin")
if (userInput == "admin") { // FALSE! Different references
grantAccess();
}
// RIGHT: Using equals() for content comparison
if ("admin".equals(userInput)) { // TRUE! Same content
grantAccess();
}
// DEMONSTRATION: Why == fails
String str1 = new String("test");
String str2 = new String("test");
System.out.println(str1 == str2); // false (different objects)
System.out.println(str1.equals(str2)); // true (same content)
Mistake 2: Null Pointer Exceptions
Null Handling Problems
// DANGEROUS: Direct method call on potentially null string
String fileName = getFileName(); // Might return null
if (fileName.endsWith(".txt")) { // NullPointerException!
processTextFile(fileName);
}
// BETTER: Null check first
if (fileName != null && fileName.endsWith(".txt")) {
processTextFile(fileName);
}
// BEST: Literal-first pattern (defensive)
if (".txt".endsWith(getFileExtension(fileName))) {
processTextFile(fileName);
}
// ELEGANT: Using Objects.equals() for null safety
import java.util.Objects;
if (Objects.equals("expected", userInput)) {
// Safe even if userInput is null
}
Mistake 3: Inefficient String Building
Performance Killers
// TERRIBLE: String concatenation in loop
String result = "";
for (int i = 0; i < 1000; i++) {
result += "Item " + i + ", "; // Creates 3000+ temporary objects!
}
// BETTER: Collect and join
List<String> items = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
items.add("Item " + i);
}
String result = String.join(", ", items);
// BEST: StringBuilder (covered in Part 2)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append("Item ").append(i).append(", ");
}
String result = sb.toString();
Mistake 4: Case Sensitivity Issues
Case Handling Errors
// PROBLEM: Inconsistent case handling
String command = getUserCommand(); // User might type "EXIT", "exit", "Exit"
if (command.equals("exit")) { // Only matches lowercase
System.exit(0);
}
// SOLUTION 1: Case-insensitive comparison
if (command.equalsIgnoreCase("exit")) {
System.exit(0);
}
// SOLUTION 2: Normalize input
String normalizedCommand = command.toLowerCase().trim();
if ("exit".equals(normalizedCommand)) {
System.exit(0);
}
// REAL-WORLD EXAMPLE: File extension check
public static boolean isImageFile(String filename) {
if (filename == null) return false;
String lower = filename.toLowerCase();
return lower.endsWith(".jpg") || lower.endsWith(".jpeg") ||
lower.endsWith(".png") || lower.endsWith(".gif");
}
2. Performance Considerations
String operations can significantly impact application performance. Understanding the cost of different operations helps you make informed decisions.
String Concatenation Performance
Performance Comparison
// SCENARIO: Building a large string
int iterations = 10000;
// SLOW: String concatenation (O(n²) complexity)
long start = System.currentTimeMillis();
String slow = "";
for (int i = 0; i < iterations; i++) {
slow += "x"; // Each operation copies entire string
}
long slowTime = System.currentTimeMillis() - start;
// FAST: StringBuilder (O(n) complexity)
start = System.currentTimeMillis();
StringBuilder fast = new StringBuilder();
for (int i = 0; i < iterations; i++) {
fast.append("x"); // Efficient append operation
}
String result = fast.toString();
long fastTime = System.currentTimeMillis() - start;
System.out.println("String concat: " + slowTime + "ms");
System.out.println("StringBuilder: " + fastTime + "ms");
// StringBuilder is typically 100x+ faster
Memory Usage Patterns
Memory Efficiency Tips
// MEMORY EFFICIENT: Use string pool
String status1 = "ACTIVE"; // Shared in pool
String status2 = "ACTIVE"; // Same reference
// MEMORY WASTEFUL: Unnecessary objects
String status3 = new String("ACTIVE"); // Creates duplicate object
// SMART: Reuse common strings
public class StatusConstants {
public static final String ACTIVE = "ACTIVE";
public static final String INACTIVE = "INACTIVE";
public static final String PENDING = "PENDING";
}
// EFFICIENT: Pre-size StringBuilder when length known
int expectedLength = 1000;
StringBuilder sized = new StringBuilder(expectedLength);
3. Defensive Programming
Writing defensive code prevents failures and makes your applications more robust and maintainable.
Input Validation
Validating String Inputs
// COMPREHENSIVE INPUT VALIDATION
public class StringValidator {
public static boolean isValidEmail(String email) {
if (email == null || email.trim().isEmpty()) {
return false;
}
email = email.trim();
// Basic structure check
int atIndex = email.indexOf('@');
int lastDotIndex = email.lastIndexOf('.');
return atIndex > 0 && // @ not at start
lastDotIndex > atIndex + 1 && // . after @
lastDotIndex < email.length() - 1 && // . not at end
!email.contains("..") && // No consecutive dots
!email.startsWith(".") && // Not starting with dot
!email.endsWith("."); // Not ending with dot
}
public static boolean isValidUsername(String username) {
if (username == null) return false;
username = username.trim();
// Length check
if (username.length() < 3 || username.length() > 20) {
return false;
}
// Character validation
for (char c : username.toCharArray()) {
if (!Character.isLetterOrDigit(c) && c != '_' && c != '-') {
return false;
}
}
return true;
}
}
Safe String Operations
Null-Safe String Utilities
public class SafeStringUtils {
// Null-safe length check
public static int safeLength(String str) {
return str == null ? 0 : str.length();
}
// Null-safe substring
public static String safeSubstring(String str, int start, int end) {
if (str == null) return "";
int len = str.length();
if (start < 0) start = 0;
if (end > len) end = len;
if (start > end) return "";
return str.substring(start, end);
}
// Check if string is null, empty, or only whitespace
public static boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}
// Safe string comparison
public static boolean safeEquals(String str1, String str2) {
return Objects.equals(str1, str2);
}
}
4. Utility Methods & Patterns
These utility methods demonstrate best practices and provide reusable solutions for common string operations.
Text Processing Utilities
Practical String Utilities
public class TextUtils {
// Word count with proper delimiter handling
public static int countWords(String text) {
if (text == null || text.trim().isEmpty()) {
return 0;
}
String[] words = text.trim().split("\\s+");
return words.length;
}
// Truncate with ellipsis
public static String truncate(String text, int maxLength) {
if (text == null) return "";
if (text.length() <= maxLength) return text;
if (maxLength <= 3) return "...";
return text.substring(0, maxLength - 3) + "...";
}
// Convert to title case
public static String toTitleCase(String text) {
if (text == null || text.isEmpty()) return text;
String[] words = text.toLowerCase().split("\\s+");
StringBuilder result = new StringBuilder();
for (int i = 0; i < words.length; i++) {
if (i > 0) result.append(" ");
String word = words[i];
if (!word.isEmpty()) {
result.append(Character.toUpperCase(word.charAt(0)))
.append(word.substring(1));
}
}
return result.toString();
}
}
File and Path Utilities
File Handling Utilities
public class FileStringUtils {
// Extract file extension safely
public static String getFileExtension(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return "";
}
int lastDot = fileName.lastIndexOf('.');
int lastSeparator = Math.max(
fileName.lastIndexOf('/'),
fileName.lastIndexOf('\\')
);
// No extension or dot is part of directory name
if (lastDot == -1 || lastDot < lastSeparator) {
return "";
}
return fileName.substring(lastDot + 1).toLowerCase();
}
// Get file name without extension
public static String getBaseName(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return "";
}
// Remove path
int lastSeparator = Math.max(
fileName.lastIndexOf('/'),
fileName.lastIndexOf('\\')
);
String nameOnly = fileName.substring(lastSeparator + 1);
// Remove extension
int lastDot = nameOnly.lastIndexOf('.');
if (lastDot > 0) {
nameOnly = nameOnly.substring(0, lastDot);
}
return nameOnly;
}
}
Part 1C Summary
Part 1C completed the string fundamentals by covering critical pitfalls, performance considerations, and defensive programming techniques.
Key Takeaways
- Avoid Common Pitfalls: Use equals() for content, handle nulls safely, avoid inefficient concatenation
- Performance Awareness: Understand the cost of string operations, especially in loops
- Defensive Programming: Validate inputs, use null-safe methods, provide meaningful defaults
- Utility Patterns: Build reusable, well-tested utility methods for common operations