{ }codelper

배우기 › Java

언어마다 다른 것 · 38

인터페이스와 제네릭

인터페이스로 "이런 메서드가 있다"는 약속을 정하고, 제네릭으로 한 함수를 여러 타입에 써요.

이 단원은 TypeScript · Java · C#에서 배워요.
Python·JavaScript는 덕 타이핑이라 area()만 있으면 어떤 객체든 쓸 수 있어서 인터페이스 키워드가 필요 없어요.
C++는 템플릿(template <typename T>)과 C++20 콘셉트(std::totally_ordered<T>)를 쓰고, C와 크레이콘에는 둘 다 없어요.

인터페이스는 "이 메서드들을 꼭 가진다"는 약속이고, 제네릭은 타입을 매개변수로 받는 거예요.
아래 예제는 Circle과 Rect를 Shape 목록에 함께 담아 넓이를 구하고, 제네릭 함수 max 하나로 가장 큰 넓이와 사전 순으로 가장 뒤인 이름을 찾아요.

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));    }}
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

핵심

  • 인터페이스 Shape는 area()와 name()만 정해요.
    각 클래스가 그 약속을 지키면 모두 Shape로 다룰 수 있어요.
  • s.area()를 부르면 어떤 클래스의 area()가 실행될지는 실행할 때 그 객체를 보고 정해져요.
  • 인터페이스의 메서드는 저절로 public이에요.
    구현하는 쪽에서 public을 빼면 컴파일 오류예요.
  • 제네릭 함수는 한 번만 써서 여러 타입에 써요.
    예제의 max는 실수 목록에도 문자열 목록에도 돼요.
  • 아무 타입이나 크기를 비교할 수는 없으니 제약을 걸어요: "T는 비교할 수 있는 타입".
  • 언어마다 제네릭 타입이 실행할 때도 남는지가 달라요.
    남지 않으면 실행 중에 T를 알 수 없어요.

언어별로 쓰는 법

TypeScriptinterface Shape { area(): number; }, class Circle implements Shape.
제약은 function max<T extends number | string>(items: T[]): T.
이름이 아니라 모양으로 봐서, implements가 없어도 메서드만 있으면 Shape로 통해요(implements는 "빠진 게 없는지 검사해 줘"에 가까워요).
타입은 실행할 때 모두 사라져요
Javainterface Shape { double area(); }, class Circle implements Shape.
제약은 static <T extends Comparable<T>> T max(List<T> items) — 인터페이스여도 extends라고 적어요.
public을 빼면 attempting to assign weaker access privileges.
제네릭 정보는 컴파일 뒤 지워져요(타입 지우기)
C#관례대로 이름 앞에 I: interface IShape, class Circle : IShape.
제약은 static T Max<T>(List<T> items) where T : IComparable<T>.
public을 빼면 CS0737.
제네릭이 실행할 때도 남아서 List<double>이 수를 포장 없이 담고, new T[n]·typeof(T)도 돼요

자주 하는 실수

제네릭에 int·double을 넣었어요Java

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

→ 둘 다 컴파일 오류. 타입 매개변수에는 객체 타입만 들어가고, 지워진 T로는 배열을 못 만들어요(generic array creation)

List<Double>·List<Integer>로 써요.
실행 중에 T가 무엇인지는 알아낼 수 없어요.

Integer 두 개를 ==로 비교했어요Java

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

→ false — 수가 객체로 포장(박싱)돼서 ==는 같은 객체인지를 봐요. 127까지는 true가 나올 수 있어 더 헷갈려요

포장된 수는 equals로 비교해요.

인터페이스로 실행 중에 검사했어요TypeScript

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

→ instanceof Shape는 'Shape' only refers to a type 오류예요. as Shape를 붙여도 정말 area()가 있는지는 아무도 확인하지 않아요

인터페이스는 실행 중에 없으니, 들어온 값은 typeof s.area === "function"처럼 직접 검사해요.

언어마다 다른 점
인터페이스와 제네릭은 컴파일할 때 실수를 잡는 도구예요.
TypeScript와 Java는 실행할 때 타입 정보를 지우니, 밖에서 들어온 데이터(입력, JSON)는 직접 값을 검사해야 안전해요.

연습 문제