Introduction
Imagine you're organizing a team workflow where you have public rules everyone follows, but also internal procedures that only the team uses internally. That's exactly what private methods in interfaces do in Java! They allow interfaces to have internal helper methods that can't be accessed from outside, making interfaces more powerful and self-contained.
Private methods in interfaces (introduced in Java 9) are like secret recipes that help the default methods work efficiently without exposing internal implementation details to implementing classes.
What are Private Methods in Interfaces?
Private methods in interfaces are methods that can only be used within the interface itself. They help reduce code duplication in default methods and keep helper logic encapsulated within the interface.
Key Characteristics:
- π Private access: Only accessible within the interface
- π οΈ Code reuse: Reduce duplication in default methods
- π¦ Encapsulation: Hide implementation details
- π― Helper functions: Support default method implementations
- β‘ Java 9+ feature: Not available in earlier versions
Code Explanation with Examples
Example 1: Basic Private Methods in Interfaces
public class BasicPrivateMethods {
public static void main(String[] args) {
System.out.println("=== BASIC PRIVATE METHODS IN INTERFACES ===");
// Creating instances of classes implementing the interface
Calculator basicCalc = new BasicCalculator();
Calculator scientificCalc = new ScientificCalculator();
// Using default methods that internally use private methods
System.out.println("Basic Calculator:");
System.out.println("5 + 3 = " + basicCalc.add(5, 3));
System.out.println("10 - 4 = " + basicCalc.subtract(10, 4));
basicCalc.displayResult("Addition", 8);
System.out.println("\nScientific Calculator:");
System.out.println("7 * 6 = " + scientificCalc.multiply(7, 6));
System.out.println("15 / 3 = " + scientificCalc.divide(15, 3));
scientificCalc.displayResult("Multiplication", 42);
// Testing validation
try {
scientificCalc.divide(10, 0);
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
interface Calculator {
// Abstract methods
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
double divide(double a, double b);
// Default method using private methods
default void displayResult(String operation, double result) {
String formattedResult = formatResult(result);
String message = createMessage(operation, formattedResult);
printMessage(message);
}
// Private method - helper for formatting
private String formatResult(double result) {
if (result == (long) result) {
return String.format("%d", (long) result);
} else {
return String.format("%.2f", result);
}
}
// Private method - helper for creating message
private String createMessage(String operation, String formattedResult) {
return String.format("Operation: %s, Result: %s", operation, formattedResult);
}
// Private method - helper for printing
private void printMessage(String message) {
System.out.println("=== CALCULATOR RESULT ===");
System.out.println(message);
System.out.println("=========================");
}
// Private static method
private static void validateDivisor(double divisor) {
if (divisor == 0) {
throw new IllegalArgumentException("Division by zero is not allowed");
}
}
// Default method using private static method
default double safeDivide(double dividend, double divisor) {
validateDivisor(divisor); // Using private static method
return dividend / divisor;
}
}
class BasicCalculator implements Calculator {
@Override
public double add(double a, double b) {
return a + b;
}
@Override
public double subtract(double a, double b) {
return a - b;
}
@Override
public double multiply(double a, double b) {
return a * b;
}
@Override
public double divide(double a, double b) {
// Using the safeDivide default method
return safeDivide(a, b);
}
}
class ScientificCalculator implements Calculator {
@Override
public double add(double a, double b) {
return a + b;
}
@Override
public double subtract(double a, double b) {
return a - b;
}
@Override
public double multiply(double a, double b) {
return a * b;
}
@Override
public double divide(double a, double b) {
return safeDivide(a, b);
}
// Additional scientific methods
public double power(double base, double exponent) {
return Math.pow(base, exponent);
}
public double squareRoot(double number) {
return Math.sqrt(number);
}
}
Output:
=== BASIC PRIVATE METHODS IN INTERFACES === Basic Calculator: 5 + 3 = 8.0 10 - 4 = 6.0 === CALCULATOR RESULT === Operation: Addition, Result: 8 ========================= Scientific Calculator: 7 * 6 = 42.0 15 / 3 = 5.0 === CALCULATOR RESULT === Operation: Multiplication, Result: 42 ========================= Error: Division by zero is not allowed
Example 2: Real-World Use Cases
public class RealWorldPrivateMethods {
public static void main(String[] args) {
System.out.println("=== REAL-WORLD PRIVATE METHODS IN INTERFACES ===");
// Logger implementations
Logger consoleLogger = new ConsoleLogger();
Logger fileLogger = new FileLogger();
consoleLogger.logInfo("Application started successfully");
consoleLogger.logError("Database connection failed", new RuntimeException("Connection timeout"));
consoleLogger.logDebug("User authentication completed");
fileLogger.logInfo("File processing started");
fileLogger.logWarning("Low disk space detected");
// Data validator usage
System.out.println("\n=== DATA VALIDATION ===");
DataValidator userValidator = new UserValidator();
DataValidator emailValidator = new EmailValidator();
System.out.println("Username 'john_doe': " + userValidator.validate("john_doe"));
System.out.println("Username 'ab': " + userValidator.validate("ab"));
System.out.println("Email '[email protected]': " + emailValidator.validate("[email protected]"));
System.out.println("Email 'invalid': " + emailValidator.validate("invalid"));
// Payment processor
System.out.println("\n=== PAYMENT PROCESSING ===");
PaymentProcessor stripe = new StripeProcessor();
PaymentProcessor paypal = new PayPalProcessor();
stripe.processPayment(100.0, "USD");
paypal.processPayment(150.0, "EUR");
try {
stripe.processPayment(-50.0, "USD");
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
// Logger interface with private methods for common functionality
interface Logger {
// Abstract methods
void log(String message, LogLevel level);
// Default methods using private methods
default void logInfo(String message) {
log(formattedMessage(message, LogLevel.INFO), LogLevel.INFO);
}
default void logWarning(String message) {
log(formattedMessage(message, LogLevel.WARNING), LogLevel.WARNING);
}
default void logError(String message, Throwable error) {
String errorMessage = message + " - " + formatError(error);
log(formattedMessage(errorMessage, LogLevel.ERROR), LogLevel.ERROR);
}
default void logDebug(String message) {
log(formattedMessage(message, LogLevel.DEBUG), LogLevel.DEBUG);
}
// Private method for formatting messages
private String formattedMessage(String message, LogLevel level) {
String timestamp = getCurrentTimestamp();
return String.format("[%s] [%s] %s", timestamp, level, message);
}
// Private method for error formatting
private String formatError(Throwable error) {
return error.getClass().getSimpleName() + ": " + error.getMessage();
}
// Private static method for timestamp
private static String getCurrentTimestamp() {
return java.time.LocalDateTime.now().format(
java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
);
}
}
enum LogLevel {
INFO, WARNING, ERROR, DEBUG
}
class ConsoleLogger implements Logger {
@Override
public void log(String message, LogLevel level) {
String color = getColor(level);
System.out.println(color + message + "\u001B[0m"); // Reset color
}
private String getColor(LogLevel level) {
switch (level) {
case INFO: return "\u001B[32m"; // Green
case WARNING: return "\u001B[33m"; // Yellow
case ERROR: return "\u001B[31m"; // Red
case DEBUG: return "\u001B[36m"; // Cyan
default: return "\u001B[0m"; // Reset
}
}
}
class FileLogger implements Logger {
@Override
public void log(String message, LogLevel level) {
// Simulate file logging
System.out.println("FILE LOG: " + message);
}
}
// Data validation interface
interface DataValidator {
boolean validate(String data);
// Default methods with private helpers
default boolean validateWithRules(String data, String fieldName) {
if (isNullOrEmpty(data)) {
logValidationError(fieldName, "cannot be null or empty");
return false;
}
if (!meetsLengthRequirements(data)) {
logValidationError(fieldName, "does not meet length requirements");
return false;
}
logValidationSuccess(fieldName);
return true;
}
// Private methods for validation helpers
private boolean isNullOrEmpty(String data) {
return data == null || data.trim().isEmpty();
}
private boolean meetsLengthRequirements(String data) {
return data.length() >= 3 && data.length() <= 50;
}
private void logValidationError(String fieldName, String error) {
System.out.println("β Validation failed: " + fieldName + " " + error);
}
private void logValidationSuccess(String fieldName) {
System.out.println("β
Validation passed: " + fieldName);
}
}
class UserValidator implements DataValidator {
@Override
public boolean validate(String username) {
return validateWithRules(username, "Username") &&
username.matches("[a-zA-Z0-9_]+");
}
}
class EmailValidator implements DataValidator {
@Override
public boolean validate(String email) {
return validateWithRules(email, "Email") &&
email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$");
}
}
// Payment processor interface
interface PaymentProcessor {
void processPayment(double amount, String currency);
// Default methods with private validation
default void validatePayment(double amount, String currency) {
validateAmount(amount);
validateCurrency(currency);
validateBusinessRules(amount, currency);
}
default String formatReceipt(double amount, String currency, String processor) {
String formattedAmount = formatCurrency(amount, currency);
return createReceipt(formattedAmount, processor);
}
// Private validation methods
private void validateAmount(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Payment amount must be positive: " + amount);
}
if (amount > 100000) {
throw new IllegalArgumentException("Payment amount exceeds maximum limit: " + amount);
}
}
private void validateCurrency(String currency) {
if (currency == null || currency.length() != 3) {
throw new IllegalArgumentException("Invalid currency code: " + currency);
}
if (!getSupportedCurrencies().contains(currency.toUpperCase())) {
throw new IllegalArgumentException("Unsupported currency: " + currency);
}
}
private void validateBusinessRules(double amount, String currency) {
// Additional business rules
if ("USD".equals(currency) && amount < 1.0) {
throw new IllegalArgumentException("Minimum payment for USD is 1.0");
}
}
// Private helper methods
private String formatCurrency(double amount, String currency) {
return String.format("%s %.2f", currency, amount);
}
private String createReceipt(String formattedAmount, String processor) {
String timestamp = java.time.LocalDateTime.now().toString();
return String.format(
"=== PAYMENT RECEIPT ===\n" +
"Processor: %s\n" +
"Amount: %s\n" +
"Time: %s\n" +
"Status: COMPLETED\n" +
"======================",
processor, formattedAmount, timestamp
);
}
// Private static method
private static java.util.Set<String> getSupportedCurrencies() {
return java.util.Set.of("USD", "EUR", "GBP", "JPY", "CAD");
}
}
class StripeProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount, String currency) {
validatePayment(amount, currency); // Using default validation
System.out.println("Processing payment via Stripe...");
System.out.println("Amount: " + amount + " " + currency);
// Simulate payment processing
String receipt = formatReceipt(amount, currency, "Stripe");
System.out.println(receipt);
System.out.println("Stripe payment completed successfully!\n");
}
}
class PayPalProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount, String currency) {
validatePayment(amount, currency); // Using default validation
System.out.println("Processing payment via PayPal...");
System.out.println("Amount: " + amount + " " + currency);
// Simulate PayPal specific processing
String receipt = formatReceipt(amount, currency, "PayPal");
System.out.println(receipt);
System.out.println("PayPal payment completed successfully!\n");
}
}
Output:
=== REAL-WORLD PRIVATE METHODS IN INTERFACES === [2024-01-15 10:30:45] [INFO] Application started successfully [2024-01-15 10:30:45] [ERROR] Database connection failed - RuntimeException: Connection timeout [2024-01-15 10:30:45] [DEBUG] User authentication completed FILE LOG: [2024-01-15 10:30:45] [INFO] File processing started FILE LOG: [2024-01-15 10:30:45] [WARNING] Low disk space detected === DATA VALIDATION === β Validation passed: Username β Validation failed: Username does not meet length requirements Username 'john_doe': true Username 'ab': false β Validation passed: Email β Validation failed: Email does not meet length requirements Email '[email protected]': true Email 'invalid': false === PAYMENT PROCESSING === Processing payment via Stripe... Amount: 100.0 USD === PAYMENT RECEIPT === Processor: Stripe Amount: USD 100.00 Time: 2024-01-15T10:30:45.123 Status: COMPLETED ====================== Stripe payment completed successfully! Processing payment via PayPal... Amount: 150.0 EUR === PAYMENT RECEIPT === Processor: PayPal Amount: EUR 150.00 Time: 2024-01-15T10:30:45.124 Status: COMPLETED ====================== PayPal payment completed successfully! Error: Payment amount must be positive: -50.0
Example 3: Advanced Private Method Patterns
import java.util.*;
import java.util.stream.Collectors;
public class AdvancedPrivateMethods {
public static void main(String[] args) {
System.out.println("=== ADVANCED PRIVATE METHOD PATTERNS ===");
// Cache manager usage
System.out.println("=== CACHE MANAGEMENT ===");
CacheManager memoryCache = new MemoryCacheManager();
CacheManager redisCache = new RedisCacheManager();
memoryCache.put("user:1", "John Doe");
memoryCache.put("user:2", "Jane Smith");
memoryCache.put("config:timeout", "30");
System.out.println("user:1 = " + memoryCache.get("user:1"));
System.out.println("All keys: " + memoryCache.getAllKeys());
memoryCache.displayStats();
redisCache.put("session:abc", "user_data");
System.out.println("session:abc = " + redisCache.get("session:abc"));
// API client usage
System.out.println("\n=== API CLIENT ===");
ApiClient httpClient = new HttpClient();
ApiClient rpcClient = new RpcClient();
String response1 = httpClient.get("/api/users");
System.out.println("HTTP Response: " + response1);
String response2 = rpcClient.post("/api/data", "{\"key\":\"value\"}");
System.out.println("RPC Response: " + response2);
// Testing error cases
try {
httpClient.get("invalid_url");
} catch (IllegalArgumentException e) {
System.out.println("Expected error: " + e.getMessage());
}
// Notification service
System.out.println("\n=== NOTIFICATION SERVICE ===");
NotificationService emailService = new EmailNotificationService();
NotificationService pushService = new PushNotificationService();
emailService.sendNotification("[email protected]", "Welcome!", "Hello and welcome to our service!");
pushService.sendNotification("device_token", "Update", "New version available!");
emailService.sendBulkNotification(
Arrays.asList("[email protected]", "[email protected]"),
"Newsletter",
"Check out our latest updates!"
);
}
}
// Cache manager with private methods for common operations
interface CacheManager {
void put(String key, String value);
String get(String key);
void remove(String key);
boolean contains(String key);
// Default methods with private helpers
default void putWithExpiry(String key, String value, long expirySeconds) {
validateKey(key);
validateValue(value);
validateExpiry(expirySeconds);
put(key, value);
scheduleExpiry(key, expirySeconds);
logOperation("PUT", key, value);
}
default Map<String, String> getMultiple(Set<String> keys) {
validateKeys(keys);
return keys.stream()
.filter(this::contains)
.collect(Collectors.toMap(
key -> key,
this::get
));
}
default Set<String> getAllKeys() {
return getKeysMatching(".*"); // Get all keys
}
default Set<String> getKeysMatching(String pattern) {
validatePattern(pattern);
return internalGetKeysMatching(pattern);
}
default void displayStats() {
System.out.println("=== CACHE STATISTICS ===");
System.out.println("Total Keys: " + getTotalKeys());
System.out.println("Memory Usage: " + getMemoryUsage());
System.out.println("Hit Ratio: " + getHitRatio() + "%");
System.out.println("========================");
}
// Private validation methods
private void validateKey(String key) {
if (key == null || key.trim().isEmpty()) {
throw new IllegalArgumentException("Cache key cannot be null or empty");
}
if (key.length() > 256) {
throw new IllegalArgumentException("Cache key too long: " + key);
}
}
private void validateValue(String value) {
if (value == null) {
throw new IllegalArgumentException("Cache value cannot be null");
}
}
private void validateExpiry(long expirySeconds) {
if (expirySeconds <= 0) {
throw new IllegalArgumentException("Expiry must be positive: " + expirySeconds);
}
}
private void validateKeys(Set<String> keys) {
if (keys == null || keys.isEmpty()) {
throw new IllegalArgumentException("Keys set cannot be null or empty");
}
keys.forEach(this::validateKey);
}
private void validatePattern(String pattern) {
if (pattern == null) {
throw new IllegalArgumentException("Pattern cannot be null");
}
}
// Private helper methods
private void scheduleExpiry(String key, long expirySeconds) {
// Simulate expiry scheduling
System.out.println("Scheduled expiry for key '" + key + "' in " + expirySeconds + " seconds");
}
private void logOperation(String operation, String key, String value) {
String timestamp = java.time.LocalDateTime.now().toString();
System.out.printf("[%s] %s: key='%s', value='%s'%n",
timestamp, operation, key,
value.length() > 20 ? value.substring(0, 20) + "..." : value);
}
// Private methods to be implemented by concrete classes
Set<String> internalGetKeysMatching(String pattern);
int getTotalKeys();
String getMemoryUsage();
double getHitRatio();
}
class MemoryCacheManager implements CacheManager {
private final Map<String, String> cache = new HashMap<>();
private int hits = 0;
private int misses = 0;
@Override
public void put(String key, String value) {
cache.put(key, value);
}
@Override
public String get(String key) {
if (cache.containsKey(key)) {
hits++;
return cache.get(key);
} else {
misses++;
return null;
}
}
@Override
public void remove(String key) {
cache.remove(key);
}
@Override
public boolean contains(String key) {
return cache.containsKey(key);
}
@Override
public Set<String> internalGetKeysMatching(String pattern) {
return cache.keySet().stream()
.filter(key -> key.matches(pattern))
.collect(Collectors.toSet());
}
@Override
public int getTotalKeys() {
return cache.size();
}
@Override
public String getMemoryUsage() {
return "~" + (cache.size() * 100) + " bytes"; // Simplified calculation
}
@Override
public double getHitRatio() {
int total = hits + misses;
return total > 0 ? (double) hits / total * 100 : 0.0;
}
}
class RedisCacheManager implements CacheManager {
// Simulating Redis cache
private final Map<String, String> redis = new HashMap<>();
@Override
public void put(String key, String value) {
redis.put(key, value);
System.out.println("Redis: SET " + key);
}
@Override
public String get(String key) {
System.out.println("Redis: GET " + key);
return redis.get(key);
}
@Override
public void remove(String key) {
redis.remove(key);
System.out.println("Redis: DEL " + key);
}
@Override
public boolean contains(String key) {
return redis.containsKey(key);
}
@Override
public Set<String> internalGetKeysMatching(String pattern) {
return redis.keySet().stream()
.filter(key -> key.matches(pattern))
.collect(Collectors.toSet());
}
@Override
public int getTotalKeys() {
return redis.size();
}
@Override
public String getMemoryUsage() {
return "Redis memory info unavailable in simulation";
}
@Override
public double getHitRatio() {
return 95.5; // Simulated high hit ratio for Redis
}
}
// API Client interface with private methods for request handling
interface ApiClient {
String get(String url);
String post(String url, String data);
String put(String url, String data);
String delete(String url);
// Default methods with private request handling
default String executeRequest(String method, String url, String data) {
validateUrl(url);
validateMethod(method);
String requestId = generateRequestId();
logRequestStart(method, url, requestId);
long startTime = System.currentTimeMillis();
String response = internalExecute(method, url, data);
long duration = System.currentTimeMillis() - startTime;
logRequestComplete(method, url, requestId, duration, response);
return response;
}
default Map<String, String> executeWithHeaders(String method, String url,
String data, Map<String, String> headers) {
validateHeaders(headers);
String requestWithHeaders = addHeadersToRequest(data, headers);
return parseResponse(executeRequest(method, url, requestWithHeaders));
}
// Private validation methods
private void validateUrl(String url) {
if (url == null || url.trim().isEmpty()) {
throw new IllegalArgumentException("URL cannot be null or empty");
}
if (!url.startsWith("/")) {
throw new IllegalArgumentException("URL must start with '/': " + url);
}
}
private void validateMethod(String method) {
List<String> validMethods = Arrays.asList("GET", "POST", "PUT", "DELETE");
if (!validMethods.contains(method.toUpperCase())) {
throw new IllegalArgumentException("Invalid HTTP method: " + method);
}
}
private void validateHeaders(Map<String, String> headers) {
if (headers == null) {
throw new IllegalArgumentException("Headers cannot be null");
}
headers.forEach((key, value) -> {
if (key == null || value == null) {
throw new IllegalArgumentException("Header key/value cannot be null");
}
});
}
// Private helper methods
private String generateRequestId() {
return UUID.randomUUID().toString().substring(0, 8);
}
private void logRequestStart(String method, String url, String requestId) {
System.out.printf("[%s] START %s %s (ID: %s)%n",
getTimestamp(), method, url, requestId);
}
private void logRequestComplete(String method, String url, String requestId,
long duration, String response) {
System.out.printf("[%s] COMPLETE %s %s (ID: %s) - %dms - Response: %s%n",
getTimestamp(), method, url, requestId, duration,
response.length() > 30 ? response.substring(0, 30) + "..." : response);
}
private String addHeadersToRequest(String data, Map<String, String> headers) {
StringBuilder sb = new StringBuilder();
sb.append("HEADERS: ");
headers.forEach((key, value) -> sb.append(key).append("=").append(value).append("; "));
sb.append("\nDATA: ").append(data);
return sb.toString();
}
private Map<String, String> parseResponse(String response) {
Map<String, String> result = new HashMap<>();
result.put("status", "200");
result.put("body", response);
result.put("content-type", "application/json");
return result;
}
private String getTimestamp() {
return java.time.LocalDateTime.now().format(
java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss.SSS")
);
}
// Abstract method for internal execution
String internalExecute(String method, String url, String data);
}
class HttpClient implements ApiClient {
@Override
public String get(String url) {
return executeRequest("GET", url, null);
}
@Override
public String post(String url, String data) {
return executeRequest("POST", url, data);
}
@Override
public String put(String url, String data) {
return executeRequest("PUT", url, data);
}
@Override
public String delete(String url) {
return executeRequest("DELETE", url, null);
}
@Override
public String internalExecute(String method, String url, String data) {
// Simulate HTTP request
return String.format("{\"method\":\"%s\",\"url\":\"%s\",\"status\":\"success\"}", method, url);
}
}
class RpcClient implements ApiClient {
@Override
public String get(String url) {
return executeRequest("GET", url, null);
}
@Override
public String post(String url, String data) {
return executeRequest("POST", url, data);
}
@Override
public String put(String url, String data) {
return executeRequest("PUT", url, data);
}
@Override
public String delete(String url) {
return executeRequest("DELETE", url, null);
}
@Override
public String internalExecute(String method, String url, String data) {
// Simulate RPC call
return String.format("RPC Response for %s %s: OK", method, url);
}
}
// Notification service with private methods
interface NotificationService {
void sendNotification(String recipient, String title, String message);
// Default methods with private helpers
default void sendBulkNotification(List<String> recipients, String title, String message) {
validateRecipients(recipients);
validateMessage(title, message);
System.out.println("Sending bulk notification to " + recipients.size() + " recipients...");
recipients.forEach(recipient -> {
try {
sendNotification(recipient, title, message);
logSuccess(recipient);
} catch (Exception e) {
logFailure(recipient, e.getMessage());
}
});
printBulkSummary(recipients.size());
}
default String formatNotification(String title, String message) {
return String.format(
"Title: %s\nMessage: %s\nTime: %s\n",
title, message, getCurrentTime()
);
}
// Private helper methods
private void validateRecipients(List<String> recipients) {
if (recipients == null || recipients.isEmpty()) {
throw new IllegalArgumentException("Recipients list cannot be null or empty");
}
}
private void validateMessage(String title, String message) {
if (title == null || title.trim().isEmpty()) {
throw new IllegalArgumentException("Title cannot be null or empty");
}
if (message == null || message.trim().isEmpty()) {
throw new IllegalArgumentException("Message cannot be null or empty");
}
}
private void logSuccess(String recipient) {
System.out.println("β
Sent to: " + recipient);
}
private void logFailure(String recipient, String error) {
System.out.println("β Failed to send to " + recipient + ": " + error);
}
private void printBulkSummary(int totalRecipients) {
System.out.println("π Bulk notification completed for " + totalRecipients + " recipients");
}
private String getCurrentTime() {
return java.time.LocalDateTime.now().format(
java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
);
}
}
class EmailNotificationService implements NotificationService {
@Override
public void sendNotification(String recipient, String title, String message) {
String formattedMessage = formatNotification(title, message);
System.out.println("π§ Sending email to: " + recipient);
System.out.println(formattedMessage);
System.out.println("Email sent successfully!\n");
}
}
class PushNotificationService implements NotificationService {
@Override
public void sendNotification(String recipient, String title, String message) {
String formattedMessage = formatNotification(title, message);
System.out.println("π± Sending push to device: " + recipient);
System.out.println(formattedMessage);
System.out.println("Push notification sent successfully!\n");
}
}
Output:
=== ADVANCED PRIVATE METHOD PATTERNS ===
=== CACHE MANAGEMENT ===
Scheduled expiry for key 'user:1' in 3600 seconds
[2024-01-15T10:30:45.123] PUT: key='user:1', value='John Doe'
Scheduled expiry for key 'user:2' in 3600 seconds
[2024-01-15T10:30:45.124] PUT: key='user:2', value='Jane Smith'
Scheduled expiry for key 'config:timeout' in 3600 seconds
[2024-01-15T10:30:45.125] PUT: key='config:timeout', value='30'
user:1 = John Doe
All keys: [user:1, user:2, config:timeout]
=== CACHE STATISTICS ===
Total Keys: 3
Memory Usage: ~300 bytes
Hit Ratio: 25.0%
========================
Redis: SET session:abc
Scheduled expiry for key 'session:abc' in 3600 seconds
[2024-01-15T10:30:45.126] PUT: key='session:abc', value='user_data'
Redis: GET session:abc
session:abc = user_data
=== API CLIENT ===
[10:30:45.127] START GET /api/users (ID: a1b2c3d4)
[10:30:45.128] COMPLETE GET /api/users (ID: a1b2c3d4) - 1ms - Response: {"method":"GET","url":"/api/use...
HTTP Response: {"method":"GET","url":"/api/users","status":"success"}
[10:30:45.129] START POST /api/data (ID: e5f6g7h8)
[10:30:45.130] COMPLETE POST /api/data (ID: e5f6g7h8) - 1ms - Response: RPC Response for POST /api/data:...
RPC Response: RPC Response for POST /api/data: OK
Expected error: URL must start with '/': invalid_url
=== NOTIFICATION SERVICE ===
π§ Sending email to: [email protected]
Title: Welcome!
Message: Hello and welcome to our service!
Time: 2024-01-15 10:30:45
Email sent successfully!
π± Sending push to device: device_token
Title: Update
Message: New version available!
Time: 2024-01-15 10:30:45
Push notification sent successfully!
Sending bulk notification to 2 recipients...
π§ Sending email to: [email protected]
Title: Newsletter
Message: Check out our latest updates!
Time: 2024-01-15 10:30:45
Email sent successfully!
β
Sent to: [email protected]
π§ Sending email to: [email protected]
Title: Newsletter
Message: Check out our latest updates!
Time: 2024-01-15 10:30:45
Email sent successfully!
β
Sent to: [email protected]
π Bulk notification completed for 2 recipients
Example 4: Best Practices and Common Pitfalls
public class BestPracticesPitfalls {
public static void main(String[] args) {
System.out.println("=== BEST PRACTICES AND COMMON PITFALLS ===");
// Good practices demonstration
System.out.println("=== GOOD PRACTICES ===");
GoodInterfaceExample goodImpl = new GoodInterfaceImpl();
goodImpl.publicMethod();
goodImpl.complexOperation();
// Pitfall demonstration
System.out.println("\n=== COMMON PITFALLS ===");
try {
ProblematicInterface problematic = new ProblematicImpl();
problematic.processData(null);
} catch (Exception e) {
System.out.println("Caught exception: " + e.getMessage());
}
// Utility interface pattern
System.out.println("\n=== UTILITY INTERFACE PATTERN ===");
StringValidator validator = new StringValidator() {};
System.out.println("Is 'hello' alphabetic? " + validator.isAlphabetic("hello"));
System.out.println("Is '123' numeric? " + validator.isNumeric("123"));
System.out.println("Is '[email protected]' email? " + validator.isEmail("[email protected]"));
// Factory pattern with private methods
System.out.println("\n=== FACTORY PATTERN ===");
ShapeFactory factory = new ShapeFactory() {};
Shape circle = factory.createShape("circle", 5.0);
Shape rectangle = factory.createShape("rectangle", 4.0, 6.0);
System.out.println("Circle area: " + circle.calculateArea());
System.out.println("Rectangle area: " + rectangle.calculateArea());
}
}
// Good practices for private methods in interfaces
interface GoodInterfaceExample {
// Public abstract method
void publicMethod();
// Default method using private helpers
default void complexOperation() {
validateState();
String data = prepareData();
processData(data);
cleanup();
logSuccess("Complex operation completed");
}
// Private helper methods - well named and focused
private void validateState() {
if (!isReady()) {
throw new IllegalStateException("Interface not ready for operation");
}
}
private String prepareData() {
String rawData = fetchRawData();
return transformData(rawData);
}
private void processData(String data) {
System.out.println("Processing data: " + data);
// Complex processing logic
}
private void cleanup() {
System.out.println("Cleaning up resources...");
}
private void logSuccess(String message) {
String timestamp = java.time.LocalDateTime.now().toString();
System.out.printf("[SUCCESS] %s - %s%n", timestamp, message);
}
// Private methods can call other private methods
private String transformData(String rawData) {
validateData(rawData);
return rawData.toUpperCase().trim();
}
private void validateData(String data) {
if (data == null || data.trim().isEmpty()) {
throw new IllegalArgumentException("Data cannot be null or empty");
}
}
// Abstract methods for implementation-specific logic
boolean isReady();
String fetchRawData();
}
class GoodInterfaceImpl implements GoodInterfaceExample {
@Override
public void publicMethod() {
System.out.println("Public method implementation");
}
@Override
public boolean isReady() {
return true;
}
@Override
public String fetchRawData() {
return " some raw data ";
}
}
// Common pitfalls with private methods
interface ProblematicInterface {
void processData(String data);
// β PITFALL: Private method doing too much
private void problematicPrivateMethod(String data) {
// Too many responsibilities
if (data == null) {
throw new IllegalArgumentException("Data is null");
}
if (data.length() > 100) {
throw new IllegalArgumentException("Data too long");
}
if (!data.matches("[a-zA-Z0-9]+")) {
throw new IllegalArgumentException("Invalid characters");
}
// ... and more validation
System.out.println("Processing: " + data);
// ... and more processing
}
// β PITFALL: Private method with poor name
private void doStuff(String input) { // What does this do?
// Unclear purpose
String result = input + " processed";
System.out.println(result);
}
// β
BETTER: Split into focused private methods
private void validateInput(String data) {
if (data == null) {
throw new IllegalArgumentException("Data cannot be null");
}
}
private void checkLength(String data) {
if (data.length() > 100) {
throw new IllegalArgumentException("Data exceeds maximum length");
}
}
private void validateCharacters(String data) {
if (!data.matches("[a-zA-Z0-9]+")) {
throw new IllegalArgumentException("Data contains invalid characters");
}
}
private void processValidatedData(String data) {
System.out.println("Processing validated data: " + data);
}
}
class ProblematicImpl implements ProblematicInterface {
@Override
public void processData(String data) {
// Using the problematic method
// problematicPrivateMethod(data); // This would be bad
// Using the better approach
validateInput(data);
checkLength(data);
validateCharacters(data);
processValidatedData(data);
}
}
// Utility interface pattern with private methods
interface StringValidator {
// Default validation methods
default boolean isAlphabetic(String text) {
return isValidText(text) && text.matches("[a-zA-Z]+");
}
default boolean isNumeric(String text) {
return isValidText(text) && text.matches("\\d+");
}
default boolean isAlphanumeric(String text) {
return isValidText(text) && text.matches("[a-zA-Z0-9]+");
}
default boolean isEmail(String text) {
return isValidText(text) && text.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$");
}
default boolean isStrongPassword(String password) {
return isValidText(password) &&
password.length() >= 8 &&
password.matches(".*[A-Z].*") &&
password.matches(".*[a-z].*") &&
password.matches(".*\\d.*") &&
password.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?].*");
}
// Private helper method
private boolean isValidText(String text) {
return text != null && !text.trim().isEmpty();
}
// Default method with complex validation
default ValidationResult validateWithDetails(String text, ValidationType type) {
if (!isValidText(text)) {
return new ValidationResult(false, "Text cannot be null or empty");
}
return switch (type) {
case ALPHABETIC -> validateAlphabetic(text);
case NUMERIC -> validateNumeric(text);
case ALPHANUMERIC -> validateAlphanumeric(text);
case EMAIL -> validateEmail(text);
case PASSWORD -> validatePassword(text);
};
}
// Private methods for detailed validation
private ValidationResult validateAlphabetic(String text) {
boolean valid = text.matches("[a-zA-Z]+");
return new ValidationResult(valid, valid ? "Valid alphabetic text" : "Must contain only letters");
}
private ValidationResult validateNumeric(String text) {
boolean valid = text.matches("\\d+");
return new ValidationResult(valid, valid ? "Valid numeric text" : "Must contain only digits");
}
private ValidationResult validateAlphanumeric(String text) {
boolean valid = text.matches("[a-zA-Z0-9]+");
return new ValidationResult(valid, valid ? "Valid alphanumeric text" : "Must contain only letters and digits");
}
private ValidationResult validateEmail(String text) {
boolean valid = text.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$");
return new ValidationResult(valid, valid ? "Valid email format" : "Invalid email format");
}
private ValidationResult validatePassword(String text) {
if (text.length() < 8) {
return new ValidationResult(false, "Password must be at least 8 characters long");
}
if (!text.matches(".*[A-Z].*")) {
return new ValidationResult(false, "Password must contain at least one uppercase letter");
}
if (!text.matches(".*[a-z].*")) {
return new ValidationResult(false, "Password must contain at least one lowercase letter");
}
if (!text.matches(".*\\d.*")) {
return new ValidationResult(false, "Password must contain at least one digit");
}
if (!text.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?].*")) {
return new ValidationResult(false, "Password must contain at least one special character");
}
return new ValidationResult(true, "Strong password");
}
}
enum ValidationType {
ALPHABETIC, NUMERIC, ALPHANUMERIC, EMAIL, PASSWORD
}
class ValidationResult {
private final boolean valid;
private final String message;
public ValidationResult(boolean valid, String message) {
this.valid = valid;
this.message = message;
}
public boolean isValid() { return valid; }
public String getMessage() { return message; }
@Override
public String toString() {
return String.format("ValidationResult{valid=%s, message='%s'}", valid, message);
}
}
// Factory pattern with private methods
interface ShapeFactory {
// Factory method
default Shape createShape(String type, double... dimensions) {
validateType(type);
validateDimensions(type, dimensions);
return switch (type.toLowerCase()) {
case "circle" -> createCircle(dimensions);
case "rectangle" -> createRectangle(dimensions);
case "triangle" -> createTriangle(dimensions);
case "square" -> createSquare(dimensions);
default -> throw new IllegalArgumentException("Unknown shape type: " + type);
};
}
// Private creation methods
private Shape createCircle(double[] dimensions) {
if (dimensions.length != 1) {
throw new IllegalArgumentException("Circle requires exactly 1 dimension (radius)");
}
return new Circle(dimensions[0]);
}
private Shape createRectangle(double[] dimensions) {
if (dimensions.length != 2) {
throw new IllegalArgumentException("Rectangle requires exactly 2 dimensions (width, height)");
}
return new Rectangle(dimensions[0], dimensions[1]);
}
private Shape createTriangle(double[] dimensions) {
if (dimensions.length != 3) {
throw new IllegalArgumentException("Triangle requires exactly 3 dimensions (side1, side2, side3)");
}
return new Triangle(dimensions[0], dimensions[1], dimensions[2]);
}
private Shape createSquare(double[] dimensions) {
if (dimensions.length != 1) {
throw new IllegalArgumentException("Square requires exactly 1 dimension (side)");
}
return new Square(dimensions[0]);
}
// Private validation methods
private void validateType(String type) {
if (type == null || type.trim().isEmpty()) {
throw new IllegalArgumentException("Shape type cannot be null or empty");
}
}
private void validateDimensions(String type, double[] dimensions) {
if (dimensions == null) {
throw new IllegalArgumentException("Dimensions cannot be null");
}
for (double dim : dimensions) {
if (dim <= 0) {
throw new IllegalArgumentException("All dimensions must be positive");
}
}
}
}
interface Shape {
double calculateArea();
double calculatePerimeter();
}
class Circle implements Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
@Override
public double calculatePerimeter() {
return 2 * Math.PI * radius;
}
}
class Rectangle implements Shape {
private final double width;
private final double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double calculateArea() {
return width * height;
}
@Override
public double calculatePerimeter() {
return 2 * (width + height);
}
}
class Triangle implements Shape {
private final double side1, side2, side3;
public Triangle(double side1, double side2, double side3) {
this.side1 = side1;
this.side2 = side2;
this.side3 = side3;
}
@Override
public double calculateArea() {
// Heron's formula
double s = (side1 + side2 + side3) / 2;
return Math.sqrt(s * (s - side1) * (s - side2) * (s - side3));
}
@Override
public double calculatePerimeter() {
return side1 + side2 + side3;
}
}
class Square implements Shape {
private final double side;
public Square(double side) {
this.side = side;
}
@Override
public double calculateArea() {
return side * side;
}
@Override
public double calculatePerimeter() {
return 4 * side;
}
}
Output:
=== BEST PRACTICES AND COMMON PITFALLS === === GOOD PRACTICES === Public method implementation Processing data: SOME RAW DATA Cleaning up resources... [SUCCESS] 2024-01-15T10:30:45.131 - Complex operation completed === COMMON PITFALLS === Caught exception: Data cannot be null === UTILITY INTERFACE PATTERN === Is 'hello' alphabetic? true Is '123' numeric? true Is '[email protected]' email? true === FACTORY PATTERN === Circle area: 78.53981633974483 Rectangle area: 24.0
Private Methods in Interfaces Rules Summary
| Rule | Description | Example |
|---|---|---|
| Java 9+ Only | Available from Java 9 onwards | Not in Java 8 |
| Private Access | Only accessible within the interface | private void helper() |
| No Abstract | Cannot be abstract | private abstract void m(); // ERROR |
| Static Allowed | Can be static or instance | private static void util() |
| Default Method Use | Can only be used in default/private methods | Cannot be called from abstract methods |
Best Practices
β Do:
- Use for code reuse in default methods
- Keep methods focused and single-responsibility
- Use descriptive names for private methods
- Extract common validation logic into private methods
- Use private static methods for pure utilities
- Document complex private methods
β Don't:
- Expose private methods accidentally
- Create overly complex private methods
- Use for business logic that should be in implementations
- Ignore error handling in private methods
- Create circular dependencies between private methods
Common Use Cases
- Validation Logic: Input validation in default methods
- Logging Utilities: Consistent logging across default methods
- Data Transformation: Common data processing patterns
- Resource Management: Cleanup and initialization
- Algorithm Steps: Breaking down complex operations
- Factory Methods: Object creation with validation
Benefits
- Code Reuse: Eliminate duplication in default methods
- Encapsulation: Hide implementation details
- Maintainability: Centralize common logic
- Readability: Break down complex default methods
- Testability: Easier to test focused private methods
Limitations
- Java 9+ Only: Not available in older versions
- Interface Only: Cannot be used in abstract classes
- Private Scope: Cannot be accessed by implementing classes
- No Overriding: Cannot be overridden by implementing classes
Conclusion
Private methods in interfaces are Java's way of bringing encapsulation and code organization to interfaces:
- π True encapsulation: Internal implementation details hidden
- π οΈ Code reuse: Eliminate duplication in default methods
- π¦ Better organization: Break down complex default methods
- π― Focused methods: Single responsibility principle
- β‘ Java 9+ feature: Modern Java capability
Key Takeaways:
- Use private methods to reduce duplication in default methods
- Keep private methods focused and single-purpose
- Use for validation, logging, and utilities
- Cannot be accessed by implementing classes
- Available only in Java 9 and later versions
Mastering private methods in interfaces helps you create more maintainable, organized, and encapsulated interface designs while leveraging the full power of modern Java!