Inheritance & Interfaces
An interface declares method signatures without implementations.
13.1 Interfaces
An interface declares method signatures without implementations. A class can implement multiple interfaces.
Important platform interfaces: Comparable, System.Comparator<T>, Database.Batchable<T>, Database.Stateful, Database.AllowsCallouts, Queueable, Schedulable, Finalizer, HttpCalloutMock, System.StubProvider, Messaging.InboundEmailHandler, Iterable<T>, Iterator<T>, Callable, Process.Plugin (legacy), Auth.RegistrationHandler.
public interface DiscountStrategy {
Decimal apply(Decimal amount);
}
public class PercentageDiscount implements DiscountStrategy {
private Decimal pct;
public PercentageDiscount(Decimal pct) { this.pct = pct; }
public Decimal apply(Decimal amount) { return amount * (1 - pct / 100); }
}
public class FlatDiscount implements DiscountStrategy {
private Decimal off;
public FlatDiscount(Decimal off) { this.off = off; }
public Decimal apply(Decimal amount) { return Math.max(0, amount - off); }
}
// polymorphism
List<DiscountStrategy> strategies = new List<DiscountStrategy>{
new PercentageDiscount(10), new FlatDiscount(15)
};
for (DiscountStrategy s : strategies) {
System.debug(s.apply(100)); // 90.0, 85
}13.2 Virtual and abstract classes
Apex classes are final by default — they cannot be extended unless declared virtual or abstract. Methods also cannot be overridden unless marked virtual or abstract.
virtual (class) Can be extended and instantiated abstract (class)
Can be extended but not instantiated; may contain abstract methods virtual (method)
Has an implementation that subclasses may override abstract (method) No body; subclasses must implement it
Required on the subclass method that overrides a virtual/abstract method
Calls the parent's constructor or method Refers to the current instance
public abstract class Notification {
protected String recipient;
public Notification(String recipient) { this.recipient = recipient; }
public abstract String channel(); // must be implemented
public virtual String format(String msg) { // may be overridden
return '[' + channel() + '] ' + msg;
}
public void send(String msg) { // template method — not overridable
System.debug('To ' + recipient + ': ' + format(msg));
}
}
public class EmailNotification extends Notification {
public EmailNotification(String email) { super(email); }
public override String channel() { return 'EMAIL'; }
}
public class SmsNotification extends Notification {
public SmsNotification(String phone) { super(phone); }
public override String channel() { return 'SMS'; }
public override String format(String msg) {
return super.format(msg).abbreviate(160); // reuse parent behavior
}
}
Notification n = new SmsNotification('+15551234567');
n.send('Your order shipped');13.3 Type checks, casts, and Type
Object o = new EmailNotification('a@b.com');
if (o instanceof Notification) {
Notification n = (Notification) o;
}
// create objects dynamically by name (foundation for plug-ins and DI, §36)
Type t = Type.forName('EmailNotification');
Object instance = t.newInstance(); // needs a public no-arg constructor
System.debug(t.getName());13.4 The Callable interface
System.Callable provides a loosely coupled, string-dispatched entry point — useful across packages:
public class MathExtension implements Callable {
public Object call(String action, Map<String, Object> args) {
switch on action {
when 'add' { return (Decimal) args.get('a') + (Decimal) args.get('b'); }
when else { throw new IllegalArgumentException('Unknown action ' + action); }
}
}
}
Callable ext = (Callable) Type.forName('MathExtension').newInstance();
Decimal sum = (Decimal) ext.call('add', new Map<String, Object>{ 'a' => 2, 'b' => 3 });