{ }codelper

Learn › TypeScript

Language-specific · 38

Interfaces and generics

An interface promises "these methods exist"; generics let one function work for many types.

This lesson is for TypeScript · Java · C#.
Python and JavaScript are duck-typed: any object with an area() will do, so no interface keyword is needed.
C++ uses templates (template <typename T>) and C++20 concepts (std::totally_ordered<T>), and C and Craykon have neither.

An interface is a promise that a type has certain methods, and generics let a function take a type as a parameter.
The example below puts Circle and Rect in one Shape list to get their areas, then uses a single generic max to find the largest area and the name that comes last alphabetically.

import { readFileSync } from "node:fs"; interface Shape {  area(): number;  name(): string;} class Circle implements Shape {  r: number;  constructor(r: number) { this.r = r; }  area(): number { return Math.PI * this.r * this.r; }  name(): string { return "circle"; }} class Rect implements Shape {  w: number;  h: number;  constructor(w: number, h: number) { this.w = w; this.h = h; }  area(): number { return this.w * this.h; }  name(): string { return "rect"; }} function max<T extends number | string>(items: T[]): T {  let best = items[0];  for (const x of items) if (x > best) best = x;  return best;} const lines: string[] = readFileSync(0, "utf8").trim().split("\n");const shapes: Shape[] = [];for (const line of lines.slice(1)) {  const [kind, ...nums] = line.trim().split(" ");  const v = nums.map(Number);  shapes.push(kind === "circle" ? new Circle(v[0]) : new Rect(v[0], v[1]));} for (const s of shapes) console.log(`${s.name()} ${s.area().toFixed(2)}`);console.log(`largest area: ${max(shapes.map((s) => s.area())).toFixed(2)}`);console.log(`last name: ${max(shapes.map((s) => s.name()))}`);
stdin
3
circle 1
rect 3 4
circle 2
stdout
circle 3.14
rect 12.00
circle 12.57
largest area: 12.57
last name: rect

Key points

  • The interface Shape only names area() and name().
    Any class that keeps that promise can be used as a Shape.
  • When you call s.area(), which class's area() runs is decided at run time by looking at the object.
  • Interface methods are automatically public.
    Leaving out public in the implementing class is a compile error.
  • A generic function is written once and used with many types.
    The example's max works on a list of doubles and on a list of strings.
  • Not every type can be compared by size, so you add a constraint: "T must be comparable".
  • Languages differ on whether generic types survive to run time.
    If they do not, the program cannot tell what T is while it runs.

How each language writes it

TypeScriptinterface Shape { area(): number; }, class Circle implements Shape.
Constraint: function max<T extends number | string>(items: T[]): T.
Types are matched by shape, not name: an object with the right methods counts as a Shape even without implements (which is more like "please check nothing is missing").
All types vanish at run time
Javainterface Shape { double area(); }, class Circle implements Shape.
Constraint: static <T extends Comparable<T>> T max(List<T> items) — you write extends even for an interface.
Dropping public gives attempting to assign weaker access privileges.
Generic information is removed after compiling (type erasure)
C#By convention names start with I: interface IShape, class Circle : IShape.
Constraint: static T Max<T>(List<T> items) where T : IComparable<T>.
Dropping public gives CS0737.
Generics survive to run time, so List<double> holds numbers without boxing, and new T[n] and typeof(T) work

Common mistakes

Checking against an interface at run timeTypeScript

const s = JSON.parse(text) as Shape;
if (s instanceof Shape) { }

→ instanceof Shape is the error 'Shape' only refers to a type. And as Shape checks nothing: no one makes sure area() really exists

Interfaces do not exist at run time, so check incoming values yourself, for example typeof s.area === "function".

Putting int or double in a genericJava

List<double> areas = new ArrayList<>();
T[] arr = new T[n];

→ Both are compile errors. Type parameters take only object types, and an erased T cannot make an array (generic array creation)

Write List<Double> and List<Integer>.
What T is cannot be found out at run time.

Comparing two Integers with ==Java

Integer a = 128, b = 128;
System.out.println(a == b);

→ false — the numbers are wrapped in objects (boxing), so == asks whether they are the same object. Up to 127 it may say true, which makes it more confusing

Compare boxed numbers with equals.

How languages differ
Interfaces and generics are tools for catching mistakes at compile time.
TypeScript and Java erase type information at run time, so data from outside (input, JSON) is safe only after you check the values yourself.

Practice