implements & abstract classes
Make a TypeScript class promise a shape with implements, and write abstract base classes that subclasses must finish, using payment classes you can break.
- 01Make a class keep a promiseUse
implementsso TypeScript checks a class against an interface, and know what it doesn't change. - 02Write a base class to finishWrite an abstract class with shared code and abstract members that every subclass must fill in.
- 03Pick the right toolChoose between an interface and an abstract class, and know what each leaves in the JavaScript.
A class that promises a shape
A checkout page lets the shopper pay by card or with a gift card. Each way to pay is a class, and the checkout calls the same method on each one: pay. Here it is in plain JavaScript (Class basics covers classes). One class got its method name wrong:
class CardPayment {
pay(amount) {
return `Paid $${amount} by card`;
}
}
class GiftCardPayment {
charge(amount) {
return `Paid $${amount} with a gift card`;
}
}
const methods = [new CardPayment(), new GiftCardPayment()];
for (const method of methods) {
console.log(method.pay(7));
}Lines 1 to 5 make CardPayment, with a pay method. Lines 7 to 11 make GiftCardPayment, but its method on line 8 is called charge. Line 13 puts one of each in a list, and line 15 calls pay(7) on each. The card prints Paid $7 by card. The gift card has no pay, so the code stops with TypeError: method.pay is not a function. The first person to find out is a shopper with a gift card.
In TypeScript, you can write down the shape every payment method must have, and make each class promise to have it. The shape is an interface: a named list of the properties and methods an object must have (the Type aliases & interfaces lesson introduced them). The promise is the word implements:
interface PaymentMethod { pay(amount: number): string;} class CardPayment implements PaymentMethod { pay(amount: number) { return `Paid $${amount} by card`; }} class GiftCardPayment implements PaymentMethod { charge(amount: number) { return `Paid $${amount} with a gift card`; }}Lines 1 to 3 are the interface. PaymentMethod says: anything that is a payment method has a pay method that takes a number and gives back a string. Line 5 says CardPayment implements PaymentMethod, and it does. Line 11 makes the same promise for GiftCardPayment, and TypeScript marks it, on purpose, because the class breaks its promise:
Class 'GiftCardPayment' incorrectly implements interface 'PaymentMethod'means "this class said it has the shape ofPaymentMethod, and it doesn't".- The second line of the message says what's wrong:
Property 'pay' is missing in type 'GiftCardPayment' but required in type 'PaymentMethod'.
Nobody has used the class yet, and the mistake is already marked, on the class itself. Try it: type a different name for the gift card's method and watch the list under the code.
interface PaymentMethod { pay(amount: number): string;} class CardPayment implements PaymentMethod { pay(amount: number) { return `Paid $${amount} by card`; }} class GiftCardPayment implements PaymentMethod { charge(amount: number) { return `Paid $${amount} with a gift card`; }}chargecharge, no pay1 error: line 11The gift card has a charge method and no pay. TypeScript marks line 11: Class 'GiftCardPayment' incorrectly implements interface 'PaymentMethod'. The second line of the message says why: Property 'pay' is missing in type 'GiftCardPayment'.
With pay, the class keeps its promise and the error disappears. Pay with a capital P and payNow keep the error: the checkout calls pay, so only that exact name counts.
implements PaymentMethod is checked against that list. GiftCardPayment has no pay, so TypeScript marks line 11.A delivery company posts a job advert: "You need a driving licence." An applicant ticks a box that says "I meet every point". Before the interview, the recruiter checks the claim against the advert and finds the licence missing. Ticking the box was a promise; it didn't give the applicant a licence.
- In real life: The job advert lists what the job needs: a driving licence
- In TypeScript: The interface lists what a payment method needs:
pay(amount: number) - In real life: An applicant writes "I meet every point" on the form
- In TypeScript: A class writes
implements PaymentMethod - In real life: The recruiter checks each point before the interview
- In TypeScript: TypeScript checks each member before the code runs
- In real life: Writing the claim doesn't give anyone a licence
- In TypeScript:
implementsdoesn't addpayto the class; you write it
Interfaces are one way to share a shape. The second half of this lesson covers the other: an abstract class, a base class with real code and some blanks that every class built on it must fill in.
implements makes a class promise to have an interface's shape, and TypeScript checks the promise on the class itself. An abstract class is a base class you can't make objects from: it shares its code, and its abstract members are blanks that each subclass must fill in. Both checks happen before the code runs.
What implements checks, and what it doesn't
Without implements, TypeScript still catches the wrong method name, but later: only where the class is used as a PaymentMethod. Here the checkout function on lines 11 to 13 takes a PaymentMethod, and line 15 passes it a gift card. Press each button and watch where the errors appear:
interface PaymentMethod { pay(amount: number): string;} class GiftCardPayment { charge(amount: number) { return `Paid $${amount} with a gift card`; }} function checkout(method: PaymentMethod, total: number) { console.log(method.pay(total));} checkout(new GiftCardPayment(), 7);TypeError: method.pay is not a functionclass GiftCardPayment {1 error: line 15The class makes no promise, so TypeScript has nothing to check it against. The mistake only shows up on line 15, where the class is used: Argument of type 'GiftCardPayment' is not assignable to parameter of type 'PaymentMethod'. In a real project, line 15 could be in another file, written by someone else.
Without implements, only line 15 gets an error: Argument of type 'GiftCardPayment' is not assignable to parameter of type 'PaymentMethod'. An argument is a value you pass in a call. The message is right, but it points at the place the class is used, not the place the mistake is. With implements, line 5 gets its own error, right on the class. Both errors go when line 6 says pay.
A class can promise more than one shape. List the interfaces after implements, with commas:
interface PaymentMethod { pay(amount: number): string;} interface Refundable { refund(amount: number): string;} class CardPayment implements PaymentMethod, Refundable { lastFour = "4242"; pay(amount: number) { return `Paid $${amount} by card`; } refund(amount: number) { return `Refunded $${amount} to the card`; }} const card = new CardPayment();console.log(card.pay(7));console.log(card.refund(3), card.lastFour);- Lines 1 to 3 and 5 to 7 are two interfaces: one for paying, one for giving money back.
- Line 9 promises both. TypeScript checks for
pay(lines 11 to 13) andrefund(lines 14 to 16). - Line 10 adds
lastFour, which neither interface asks for. That's fine: a class may have more than its interfaces list. - Line 20 prints
Paid $7 by card, and line 21 printsRefunded $3 to the card 4242.
Now the part that surprises most people. implements is only a check. It doesn't change the class in any way, so it doesn't type the class's methods for you:
interface PaymentMethod { pay(amount: number): string;} class CashPayment implements PaymentMethod { pay(amount) { return `Pay $${amount.toFixed(2)} at the door`; }} console.log(new CashPayment().pay(7));The interface says pay takes a number, so you might expect amount on line 6 to be a number too. It isn't. TypeScript types the class first, on its own, and only then compares it with the interface. Line 6 gets Parameter 'amount' implicitly has an 'any' type: amount has no type, so TypeScript uses any, which means "anything, and don't check it", and strict mode won't do that silently. Point at amount to see (parameter) amount: any. Line 11 still prints Pay $7.00 at the door. Write pay(amount: number) and the error goes.
For the same reason, an optional property in the interface doesn't appear on the class:
interface Profile { name: string; avatarUrl?: string;} class GuestProfile implements Profile { name = "Guest";} const guest = new GuestProfile();guest.avatarUrl = "/images/guest.png";Line 3 says a Profile may have an avatarUrl; the ? makes it optional. Lines 6 to 8 make a guest profile with only a name, and that keeps the promise. Line 11 sets guest.avatarUrl, and TypeScript says Property 'avatarUrl' does not exist on type 'GuestProfile'. The class is exactly what you wrote in it. To have an avatar, declare avatarUrl?: string; in the class.
The table sums it up. Read each row as: something you might expect from implements, does it do it, and why.
| Does implements… | Answer | Why |
|---|---|---|
| Check that the class has every member the interface lists | Yes. | A missing member, or one with the wrong type, gets an error on the class line. |
| Add the interface's members to the class | No. | You write every member yourself. implements only checks them. |
| Give the method's parameters their types | No. | pay(amount) still needs amount: number, or strict mode reports it. |
| Add the interface's optional properties | No. | GuestProfile has no avatarUrl until the class declares one. |
| Stop the class from having extra members | No. | lastFour is fine. A class may have more than the interface asks for. |
| Leave anything in the JavaScript | No. | implements is removed with the other types. |
Abstract classes: a base with a blank to fill in
A shop has discount codes. Every discount works the same way: work out how much to take off, then take it off the total. Only the first step differs: one code takes 10% off, another takes $5 off. In JavaScript, you'd write the shared part once in a parent class and let child classes build on it with extends (Class inheritance covers extends). TypeScript lets you mark the parent class abstract and leave the different part blank:
abstract class Discount { constructor(public code: string) {} abstract amountOff(total: number): number; apply(total: number) { return total - this.amountOff(total); }} class PercentOff extends Discount { amountOff(total: number) { return total * 0.1; }} const spring = new PercentOff("SPRING10");console.log(spring.code, spring.apply(20));- Line 1 makes
Discountan abstract class. It's meant to be built on, not used on its own. - Line 2 stores the discount's code, like
"SPRING10", with a parameter property. - Line 4 is an abstract method: a name, its parameters, and its return type, with no body. It says "every discount has an
amountOffthat takes a total and gives back a number, and each subclass writes its own". - Lines 6 to 8 are a normal method that every discount shares.
applycallsthis.amountOff(total)without knowing which discount it is. - Line 11 makes
PercentOff, a subclass ofDiscount: a class that builds on it. Lines 12 to 14 fill in the blank: take off 10% of the total. - Line 17 makes a
PercentOff. Line 18 printsSPRING10 18: the code, then 20 minus 2.
Discount writes apply once and leaves amountOff blank. PercentOff fills in the blank, so you can make a PercentOff with new, but not a Discount.What you can't do is make a Discount itself. It has a blank, so there would be nothing to call:
abstract class Discount { constructor(public code: string) {} abstract amountOff(total: number): number; apply(total: number) { return total - this.amountOff(total); }} const mystery = new Discount("MYSTERY");console.log(mystery.apply(20));Line 11 tries new Discount("MYSTERY"), and TypeScript says Cannot create an instance of an abstract class. An instance is an object made from a class with new. When the code runs anyway, line 12 calls apply, which calls this.amountOff, which doesn't exist: TypeError: this.amountOff is not a function.
A café prints one recipe card for its muffins. The steps are the same for every batch, but one line is blank: "Flavour: ______". Each baker fills in a flavour on their own copy. Nobody bakes from a card with the blank still empty, because the steps would say "add the flavour" and there wouldn't be one.
- In real life: A printed recipe card for flavoured muffins
- In TypeScript: The abstract class
Discount - In real life: The steps everyone follows: mix, bake for 20 minutes
- In TypeScript: The shared method
apply - In real life: The blank line: "Flavour: ______"
- In TypeScript: The abstract method
amountOff - In real life: A copy with "blueberry" written in
- In TypeScript: A subclass,
PercentOff, that writesamountOff - In real life: Nobody bakes from the card with the blank still empty
- In TypeScript:
new Discount()is an error
The rules for abstract members
A class that extends an abstract class must write every abstract member, unless it's abstract too. Try four ways of writing amountOff in a HalfOff discount:
abstract class Discount { constructor(public code: string) {} abstract amountOff(total: number): number; apply(total: number) { return total - this.amountOff(total); }} class HalfOff extends Discount { amountOff(total: number) { return total / 2; }} const half = new HalfOff("HALF");console.log(half.apply(20));10No errorsHalfOff writes amountOff: it takes a number and gives back a number, as the abstract method says. No errors, and apply(20) prints 10.
Left out gets Non-abstract class 'HalfOff' does not implement inherited abstract member amountOff from class 'Discount'. A non-abstract class is one you can make objects from, so it can't have blanks. Returns text breaks the abstract method's promise to give back a number. No parameter is fine: a method may ignore values it's given, so it can take fewer parameters than the abstract method lists.
Two more rules, each broken here on purpose:
class Banner { abstract render(): string;} abstract class Popup { abstract render(): string { return "Sale!"; }}- Line 1 makes
Bannerwithoutabstract, and line 2 gives it an abstract method. TypeScript saysAbstract methods can only appear within an abstract class: a class you can make objects from can't have a blank. - Line 6 gives an abstract method a body. TypeScript says
Method 'render' cannot have an implementation because it is marked abstract. An implementation is the code inside the braces. For a method with code that subclasses may replace, leave outabstract.
A property can be abstract too. The base class uses it, and each subclass gives it a value:
abstract class Notice { abstract title: string; show() { return `[${this.title}]`; }} class SaleNotice extends Notice { title = "Summer sale";} console.log(new SaleNotice().show());Line 2 says every Notice has a title that is a string, without giving one. Lines 4 to 6 use it in show. Line 10 gives SaleNotice its title, and line 13 prints [Summer sale]. Leave out line 10 in Edit & run and line 9 gets the same "does not implement inherited abstract member" error.
Interfaces or abstract classes?
Both describe a shape that classes must match. The big difference is what's left when the types are removed. Here is an interface and an abstract class together:
interface PaymentMethod { pay(amount: number): string;} abstract class Payment implements PaymentMethod { abstract pay(amount: number): string; receipt(amount: number) { return `Receipt: ${this.pay(amount)}`; }}Lines 1 to 3 are the interface. Line 5 makes an abstract class that promises to keep it. An abstract class can leave an interface's method blank, as line 6 does with pay; its subclasses will write it. Lines 8 to 10 are real code that every payment shares: a receipt. Here is what TypeScript gives the browser:
implements, and the abstract method are only types, so they're removed. The class and its receipt method are real code, so the browser gets them.class Payment {
receipt(amount) {
return `Receipt: ${this.pay(amount)}`;
}
}The interface is gone. So are implements, the word abstract, and the abstract method on line 6: they were only types. Line 1 is a normal JavaScript class, and lines 2 to 4 are the receipt method, word for word.
Because the abstract class is still there when the code runs, you can test for it with instanceof, which asks "was this object made by this class, or a subclass of it?" (Checking types covers instanceof). You can't test for an interface:
interface PaymentMethod { pay(amount: number): string;} abstract class Payment implements PaymentMethod { abstract pay(amount: number): string;} class CardPayment extends Payment { pay(amount: number) { return `Paid $${amount} by card`; }} const card = new CardPayment();console.log(card instanceof Payment);console.log(card instanceof PaymentMethod);Line 16 prints true: the card was made by CardPayment, a subclass of Payment. Line 17 gets 'PaymentMethod' only refers to a type, but is being used as a value here. In plain words: that name is only a type, and instanceof needs something that exists while the code runs. If you run it anyway, line 17 stops with ReferenceError: PaymentMethod is not defined.
The table compares the two. Read each row as a question, then the answer for an interface and for an abstract class.
| Question | An interface | An abstract class |
|---|---|---|
| What does it hold? | Only a list of members and their types. | Real code, like receipt, plus abstract members for subclasses to fill in. |
| What is left in the JavaScript? | Nothing. It's removed with the types. | A normal class. Only the abstract members and the word abstract are removed. |
| Which word does a class use? | implements, as in class CardPayment implements PaymentMethod. | extends, as in class CardPayment extends Payment. |
| How many can one class use? | Many, with commas: implements PaymentMethod, Refundable. | One. A class can extend only one class. |
Can you test for it with instanceof? | No. It doesn't exist when the code runs. | Yes. card instanceof Payment is true. |
| Can a plain object match it? | Yes, like the voucher object at checkout. | Its type, yes. But only a subclass gets its code, like receipt. |
A simple way to choose:
- Use an interface to say what something can do, when the classes or objects that match it share no code. Many unrelated classes, and plain objects, can match it.
- Use an abstract class when the classes share real code, like
applyorreceipt, and each must fill in its own part. - Use both, as above: the interface for the shape the rest of your code uses, and the abstract class for the shared code.
Payment methods on a checkout page
Here is a checkout that uses both. The rest of the page only knows about PaymentMethod. An abstract class shares the code that works out a total with a fee, and a voucher is a plain object:
interface PaymentMethod { label: string; pay(amount: number): string;} abstract class Payment implements PaymentMethod { abstract label: string; abstract fee(amount: number): number; pay(amount: number) { const total = amount + this.fee(amount); return `${this.label}: $${total}`; }} class CardPayment extends Payment { label = "Card"; fee() { return 0; }} class CashOnDelivery extends Payment { label = "Cash on delivery"; fee(amount: number) { return amount < 20 ? 2 : 0; }} const voucher: PaymentMethod = { label: "Voucher", pay: (amount) => `Voucher: $${amount} covered`,}; const methods: PaymentMethod[] = [new CardPayment(), new CashOnDelivery(), voucher];for (const method of methods) { console.log(method.pay(12));}- Lines 1 to 4 are the interface: every payment method has a
labelto show on the page and apaymethod. - Lines 6 to 14 are the abstract class. Lines 7 and 8 are blanks: each payment has its own label and its own fee. Lines 10 to 13 add the fee to the amount and build the text the page shows.
- Lines 16 to 21 fill in the blanks for cards: no fee. Lines 23 to 28 do it for cash on delivery: $2 for orders under $20.
- Lines 30 to 33 make a voucher as a plain object, with no class at all. The
: PaymentMethodon line 30 is enough: TypeScript checks the object against the interface, and it even givesamounton line 32 its type, because the object is written right where aPaymentMethodis expected. - Line 35 puts all three in one list of
PaymentMethod. Line 37 printsCard: $12,Cash on delivery: $14, andVoucher: $12 covered.
Now sort each card. Will TypeScript report an error, or is it fine?
interface Item { id: number } class Row implements Item { id = 1; }interface Item { id: number } class Row implements Item { id = "1"; }interface Item { id: number } class Row implements Item { id = 1; name = "Tea"; }interface Item { id: number } class Row implements Item {}abstract class Widget { abstract render(): string } new Widget();abstract class Widget { abstract render(): string } class Tip extends Widget {}abstract class Widget { abstract render(): string } class Tip extends Widget { render() { return "Tip"; } }abstract class Widget { render() { return "Widget"; } }
Each card is checked on its own with this lesson's settings. Place it in the group TypeScript would put it in.
Common mix-ups
- "
implementsadds the interface's methods to my class." No. An interface has no code to add. You write every member, andimplementschecks them. - "
implementsgives my method's parameters their types." No. The class is typed on its own first. Writepay(amount: number), oramountisany. - "I can check
instanceof PaymentMethod." No. An interface is removed with the types, so there's nothing to check against while the code runs. Use an abstract class, or check for the method. - "An abstract class can't have any code." No. It can have fields, a constructor, and normal methods like
apply. Only itsabstractmembers are blank. - "
abstractstops the class from being made while the page runs." No. The word is removed with the types. It's TypeScript that reportsnew Discount(), before the code runs. - "A class can extend two abstract classes." No. It can extend one class, and implement as many interfaces as it likes.
Practice exercises
Each exercise is TypeScript. Press Edit & run on the starter code: the errors update as you type, and the console shows what it prints. Try a guess first, then use the hints.
A theme class that promises a shape. Which line will TypeScript mark?
interface Theme {
name: string;
apply(): string;
}
class DarkTheme implements Theme {
name = "dark";
}interface Theme { name: string; apply(): string;} class DarkTheme implements Theme { name = "dark"; apply() { return `Theme set to ${this.name}`; }} console.log(new DarkTheme().apply());Theme asks for name and apply, and DarkTheme only has name. TypeScript marks line 6, the class line: Class 'DarkTheme' incorrectly implements interface 'Theme'. Adding apply keeps the promise; it prints Theme set to dark.
What does this code print? Guess first, then press Edit & run.
abstract class Greeting { abstract name(): string; text() { return `Hello, ${this.name()}!`; }} class GuestGreeting extends Greeting { name() { return "guest"; }} console.log(new GuestGreeting().text());abstract class Greeting { abstract name(): string; text() { return `Hello, ${this.name()}!`; }} class GuestGreeting extends Greeting { name() { return "guest"; }} // text() comes from Greeting, and it calls GuestGreeting's name().console.log(new GuestGreeting().text());text comes from the abstract class. It calls this.name(), and the object is a GuestGreeting, so it gets "guest". The code prints Hello, guest!.
Free shipping takes $5 off any order. Make FreeShipping fill in its blank. What does the fixed code print?
abstract class Discount { constructor(public code: string) {} abstract amountOff(total: number): number; apply(total: number) { return total - this.amountOff(total); }} class FreeShipping extends Discount {} const shipping = new FreeShipping("FREESHIP");console.log(shipping.code, shipping.apply(20));abstract class Discount { constructor(public code: string) {} abstract amountOff(total: number): number; apply(total: number) { return total - this.amountOff(total); }} class FreeShipping extends Discount { amountOff() { return 5; }} const shipping = new FreeShipping("FREESHIP");console.log(shipping.code, shipping.apply(20));FreeShipping writes amountOff, so it has no blanks left and the error is gone. apply(20) takes 5 off, and the code prints FREESHIP 15.
The header's search box promises to be a SearchBox, and TypeScript marks two lines. Fix the class. What should the method on line 8 be called?
interface SearchBox { query: string; clear(): void;} class HeaderSearch implements SearchBox { query = "green tea"; reset() { this.query = ""; }} const box = new HeaderSearch();box.clear();console.log(`[${box.query}]`);interface SearchBox { query: string; clear(): void;} class HeaderSearch implements SearchBox { query = "green tea"; clear() { this.query = ""; }} const box = new HeaderSearch();box.clear();console.log(`[${box.query}]`);SearchBox asks for clear, and the class called it reset. With the right name, the class keeps its promise, line 14's call works, and the code prints []: an empty query.
TypeScript marks line 6, even though the interface says what price is. Fix it. What does the code print?
interface PriceFormatter { format(price: number): string;} class DollarFormatter implements PriceFormatter { format(price) { return `$${price.toFixed(2)}`; }} console.log(new DollarFormatter().format(4.5));interface PriceFormatter { format(price: number): string;} class DollarFormatter implements PriceFormatter { format(price: number) { return `$${price.toFixed(2)}`; }} console.log(new DollarFormatter().format(4.5));implements doesn't type price, so it was any. With price: number, TypeScript knows toFixed exists, the error is gone, and the code prints $4.50.
Two toast messages repeat the same show method. Move it into an abstract base class, so each toast only says its message. Which word goes in front of message() in the base class?
class SavedToast { message() { return "Saved"; } show() { return `Toast: ${this.message()}`; }} class ErrorToast { message() { return "Could not save"; } show() { return `Toast: ${this.message()}`; }} console.log(new SavedToast().show());console.log(new ErrorToast().show());abstract class Toast { abstract message(): string; show() { return `Toast: ${this.message()}`; }} class SavedToast extends Toast { message() { return "Saved"; }} class ErrorToast extends Toast { message() { return "Could not save"; }} console.log(new SavedToast().show());console.log(new ErrorToast().show());show is written once in Toast, and each toast fills in message. abstract in front of message() makes it a blank that every subclass must write. The output is the same two lines as before.
Check your understanding
For each question, think about two moments: what TypeScript says before the code runs, and what the code does when it runs. The quiz shows its code without the checker, so the error list doesn't give the answers away.
Question 1 of 7What does
implements PaymentMethoddo to a class?Choose an answer to see the explanation.
Question 2 of 7Which line does TypeScript flag in this cart item class?
Read the code, then predictinterface CartItem { name: string; price: number; } class TeaItem implements CartItem { name = "Tea"; }Choose an answer to see the explanation.
Question 3 of 7This makes an abstract class with
new. What does it print when it runs anyway?Read the code, then predictabstract class Banner { text = "Summer sale"; } const banner = new Banner(); console.log(banner.text);Choose an answer to see the explanation.
Question 4 of 7What type does TypeScript give
widthon line 6 of this image resizer?Read the code, then predictinterface Resizer { resize(width: number): string; } class ImageResizer implements Resizer { resize(width) { return `${width}px`; } }Choose an answer to see the explanation.
Question 5 of 7Which line does TypeScript flag in this widget subclass?
Read the code, then predictabstract class Widget { abstract render(): string; mount() { return `<div>${this.render()}</div>`; } } class TipWidget extends Widget { mount() { return "<div>Tip</div>"; } }Choose an answer to see the explanation.
Question 6 of 7A class needs shared code from a base class and must match two interfaces. What can it do?
Choose an answer to see the explanation.
Question 7 of 7What's left of an interface in the JavaScript the browser runs?
Choose an answer to see the explanation.
Key takeaways
class CardPayment implements PaymentMethodpromises the class has the interface's shape, and TypeScript marks the class line if it doesn't.- A class can implement several interfaces, and may have more members than they list.
implementsonly checks. It doesn't add members, type parameters, or leave anything in the JavaScript.- An abstract class shares real code and leaves
abstractmembers blank. You can't make one withnew, and every non-abstract subclass must fill in every blank. - An interface disappears from the JavaScript; an abstract class stays as a normal class, so
instanceofworks with it.
Remember the one-liner.
An interface is a shape to promise; an abstract class is shared code with blanks to fill in.
Coming next: Static members & class types. It shows what a class name means as a type, the type of the class itself, and members that belong to the class instead of its objects.