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()))}`);import java.util.*; interface Shape { double area(); String name();} class Circle implements Shape { private final double r; Circle(double r) { this.r = r; } public double area() { return Math.PI * r * r; } public String name() { return "circle"; }} class Rect implements Shape { private final double w, h; Rect(double w, double h) { this.w = w; this.h = h; } public double area() { return w * h; } public String name() { return "rect"; }} public class Main { static <T extends Comparable<T>> T max(List<T> items) { T best = items.get(0); for (T x : items) if (x.compareTo(best) > 0) best = x; return best; } public static void main(String[] args) { Scanner sc = new Scanner(System.in); int n = sc.nextInt(); List<Shape> shapes = new ArrayList<>(); for (int i = 0; i < n; i++) { String kind = sc.next(); if (kind.equals("circle")) shapes.add(new Circle(sc.nextDouble())); else shapes.add(new Rect(sc.nextDouble(), sc.nextDouble())); } List<Double> areas = new ArrayList<>(); // List<double>은 안 돼요 List<String> names = new ArrayList<>(); for (Shape s : shapes) { System.out.println(s.name() + " " + String.format("%.2f", s.area())); areas.add(s.area()); names.add(s.name()); } System.out.println("largest area: " + String.format("%.2f", max(areas))); System.out.println("last name: " + max(names)); }}using System;using System.Collections.Generic; interface IShape{ double Area(); string Name();} class Circle : IShape{ private readonly double r; public Circle(double r) { this.r = r; } public double Area() => Math.PI * r * r; public string Name() => "circle";} class Rect : IShape{ private readonly double w, h; public Rect(double w, double h) { this.w = w; this.h = h; } public double Area() => w * h; public string Name() => "rect";} class Program{ static T Max<T>(List<T> items) where T : IComparable<T> { T best = items[0]; foreach (T x in items) if (x.CompareTo(best) > 0) best = x; return best; } static void Main() { int n = int.Parse(Console.ReadLine()); var shapes = new List<IShape>(); for (int i = 0; i < n; i++) { string[] p = Console.ReadLine().Split(' '); if (p[0] == "circle") shapes.Add(new Circle(double.Parse(p[1]))); else shapes.Add(new Rect(double.Parse(p[1]), double.Parse(p[2]))); } var areas = new List<double>(); var names = new List<string>(); foreach (var s in shapes) { Console.WriteLine($"{s.Name()} {s.Area():F2}"); areas.Add(s.Area()); names.Add(s.Name()); } Console.WriteLine($"largest area: {Max(areas):F2}"); Console.WriteLine($"last name: {Max(names)}"); }}3 circle 1 rect 3 4 circle 2
circle 3.14 rect 12.00 circle 12.57 largest area: 12.57 last name: rect
Key points
- The interface
Shapeonly namesarea()andname().
Any class that keeps that promise can be used as aShape. - When you call
s.area(), which class'sarea()runs is decided at run time by looking at the object. - Interface methods are automatically
public.
Leaving outpublicin the implementing class is a compile error. - A generic function is written once and used with many types.
The example'smaxworks on a list of doubles and on a list of strings. - Not every type can be compared by size, so you add a constraint: "
Tmust be comparable". - Languages differ on whether generic types survive to run time.
If they do not, the program cannot tell whatTis while it runs.
How each language writes it
| TypeScript | interface 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 |
|---|---|
| Java | interface 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.
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.