Java Notes
Complete guide to Adapter pattern — class adapter vs object adapter, real-world integration scenarios, and bridging incompatible interfaces in Java.
Intent
Convert the interface of a class into another interface that clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
Think of it like a power adapter when traveling — your laptop plug doesn't fit the foreign socket, so you use an adapter that converts one interface to another.
UML Structure
| Client | ────────▶ | Target |
|---|---|---|
| (interface) | ||
| Adapter | ────────▶ | Adaptee |
| - adaptee | + specificReq() |
When to Use
- Integrating with third-party libraries that have incompatible interfaces
- Working with legacy code that can't be modified
- Reusing existing classes in a new system with different interfaces
- Creating a unified interface for multiple similar but different APIs
Real-World: Payment Gateway Adapter
// Your system's payment interface
interface PaymentProcessor {
boolean charge(String customerId, double amount, String currency);
boolean refund(String transactionId, double amount);
PaymentStatus getStatus(String transactionId);
}
enum PaymentStatus { SUCCESS, PENDING, FAILED, REFUNDED }
// Third-party: Stripe (different interface)
class StripeAPI {
public StripeCharge createCharge(int amountCents, String currency, String customerToken) {
System.out.println("Stripe: Charging " + amountCents + " cents");
return new StripeCharge("ch_" + System.currentTimeMillis(), "succeeded");
}
public StripeRefund createRefund(String chargeId, int amountCents) {
System.out.println("Stripe: Refunding " + amountCents + " cents");
return new StripeRefund("re_" + System.currentTimeMillis(), "succeeded");
}
}
// Third-party: PayPal (completely different interface)
class PayPalSDK {
public PayPalPayment executePayment(String payerId, PayPalAmount amount) {
System.out.println("PayPal: Payment of " + amount.value + " " + amount.currency);
return new PayPalPayment("PAY-" + System.currentTimeMillis(), "approved");
}
public PayPalRefund refundPayment(String paymentId, PayPalAmount amount) {
System.out.println("PayPal: Refund of " + amount.value);
return new PayPalRefund("REF-" + System.currentTimeMillis(), "completed");
}
}
// Adapter for Stripe
class StripeAdapter implements PaymentProcessor {
private StripeAPI stripe = new StripeAPI();
@Override
public boolean charge(String customerId, double amount, String currency) {
int amountCents = (int)(amount * 100); // Stripe uses cents
StripeCharge charge = stripe.createCharge(amountCents, currency, customerId);
return "succeeded".equals(charge.status);
}
@Override
public boolean refund(String transactionId, double amount) {
int amountCents = (int)(amount * 100);
StripeRefund refund = stripe.createRefund(transactionId, amountCents);
return "succeeded".equals(refund.status);
}
@Override
public PaymentStatus getStatus(String transactionId) {
return PaymentStatus.SUCCESS; // Simplified
}
}
// Adapter for PayPal
class PayPalAdapter implements PaymentProcessor {
private PayPalSDK paypal = new PayPalSDK();
@Override
public boolean charge(String customerId, double amount, String currency) {
PayPalAmount ppAmount = new PayPalAmount(String.valueOf(amount), currency);
PayPalPayment payment = paypal.executePayment(customerId, ppAmount);
return "approved".equals(payment.state);
}
@Override
public boolean refund(String transactionId, double amount) {
PayPalAmount ppAmount = new PayPalAmount(String.valueOf(amount), "USD");
PayPalRefund refund = paypal.refundPayment(transactionId, ppAmount);
return "completed".equals(refund.state);
}
@Override
public PaymentStatus getStatus(String transactionId) {
return PaymentStatus.SUCCESS; // Simplified
}
}
// Service layer — doesn't know about Stripe or PayPal
class CheckoutService {
private PaymentProcessor processor;
public CheckoutService(PaymentProcessor processor) {
this.processor = processor;
}
public boolean processOrder(String customerId, double total) {
return processor.charge(customerId, total, "USD");
}
}Stripe: Charging " + amountCents + " cents Stripe: Refunding " + amountCents + " cents
Adapter in Java Standard Library
// Arrays.asList() — adapts array to List interface
String[] array = {"a", "b", "c"};
List<String> list = Arrays.asList(array); // Array adapted to List
// InputStreamReader — adapts byte stream to character stream
InputStream bytes = new FileInputStream("file.txt");
Reader chars = new InputStreamReader(bytes, StandardCharsets.UTF_8);
// Collections.enumeration() — adapts Iterator to Enumeration
List<String> list = Arrays.asList("a", "b", "c");
Enumeration<String> enumeration = Collections.enumeration(list);
// java.util.concurrent.FutureTask — adapts Callable to Runnable
Callable<Integer> callable = () -> 42;
FutureTask<Integer> futureTask = new FutureTask<>(callable); // Runnable adapterInterview Questions
Q1: What is the Adapter pattern and give a real-world analogy?
Answer: Adapter converts one interface to another expected by the client. Like a power adapter (US→EU plug converter) or USB-C to HDMI adapter — the underlying functionality exists, the adapter bridges the interface gap.
Q2: What is the difference between Object Adapter and Class Adapter?
Answer: Object Adapter uses composition (holds reference to adaptee) — preferred in Java. Class Adapter uses multiple inheritance (extends adaptee, implements target) — not practical in Java since it doesn't support multiple class inheritance.
Q3: What is the difference between Adapter and Decorator?
Answer: Adapter changes the interface without changing behavior (makes incompatible things compatible). Decorator keeps the same interface but adds new behavior/responsibility. Adapter wraps differently-typed objects; Decorator wraps same-typed objects.
Q4: Where is Adapter used in Java?
Answer: Arrays.asList(), InputStreamReader/OutputStreamWriter, Collections.enumeration(), javax.swing.JTable model adapters, Spring MVC HandlerAdapter, and JDBC itself (adapts database protocols to standard Java interfaces).
Q5: How does Adapter support the Open/Closed principle?
Answer: New third-party integrations (new adaptees) are added by creating new Adapter classes — no existing code is modified. The client works with the target interface, completely unaware of which adapter or adaptee is behind it.
Summary
In this chapter, we learned about Adapter Design Pattern in detail. Here are the key points:
- Basic introduction to the concept and why it is needed
- Syntax and structure with complete examples
- Internal working and best practices
- Real-world applications and use cases
- Common mistakes and how to avoid them
- How this topic is asked in interviews
To practice these concepts, write and run the code in your IDE and verify the output. Modify each example and experiment so that you deeply understand the concept.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Adapter Design Pattern.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, design, patterns, adapter
Related Java Master Course Topics