기본 콘텐츠로 건너뛰기

방송대 Java프로그래밍 6강: 제네릭 타입 안전성과 람다식

Java프로그래밍 6강 - 제네릭과 람다식

방송대 Java프로그래밍 6강: 제네릭 타입 안전성과 람다식

서로 다른 자료형을 다루겠다고 Object만 사용하면 잘못된 값이 들어가도 늦게 발견되고, 짧은 동작 하나를 전달하려고 익명 클래스를 매번 쓰면 핵심보다 틀이 길어진다. 제네릭은 허용할 자료형을 컴파일러에게 알려 오류 시점을 앞당기고, 람다식은 함수형 인터페이스가 요구하는 하나의 동작만 남긴다. 이 글은 두 문법이 각각 어떤 문제를 해결하고 어디까지 단순화할 수 있는지 코드 흐름으로 판정한다.

두 문제는 모두 “나중에 알게 되는 정보”에서 시작된다

첫 번째 문제는 자료형 정보가 늦게 드러나는 경우다. 여러 종류의 값을 담으려고 필드와 반환형을 Object로 선언하면 어떤 객체든 저장할 수 있지만, 꺼낼 때 개발자가 원래 자료형을 기억하고 형변환해야 한다. 저장한 값과 형변환이 다르면 컴파일은 통과해도 실행 중 오류가 발생한다.

두 번째 문제는 수행할 동작보다 객체 생성 문법이 더 길어지는 경우다. 인터페이스의 추상 메소드 하나를 구현하려고 익명 구현 클래스의 이름 없는 객체를 만들면 클래스 선언, 메소드 이름과 몸체를 모두 적어야 한다. 실제로 전달하려던 것은 메소드 몸체의 동작인데 그 주변 형식이 반복된다.

문제 상황잃어버린 정보해결 문법검사·결정 시점
Object로 값을 저장하고 나중에 형변환컨테이너가 허용할 구체적 자료형제네릭 타입·제네릭 메소드타입 인수를 바탕으로 컴파일 시 검사
추상 메소드 하나를 위해 익명 구현 클래스 작성문맥이 이미 알려 주는 메소드 이름과 자료형함수형 인터페이스와 람다식대입·인수 문맥의 타깃 타입으로 해석

공통 관점: 제네릭은 “어떤 자료형인가”를 선언 쪽에 먼저 기록하고, 람다식은 문맥으로 이미 확정된 형식을 생략한다. 하나는 정보를 추가하고 다른 하나는 중복 표기를 제거한다.

Object 컨테이너의 실패를 저장부터 꺼내기까지 추적한다

강의의 Data 예를 학습 흐름에 맞게 재구성해 보자. 일반 클래스는 값을 Object로 받기 때문에 문자열 전용으로 쓰려는 의도를 컴파일러가 알 수 없다.

class Data {
    private Object value;

    void set(Object value) {
        this.value = value;
    }

    Object get() {
        return value;
    }
}

Data data = new Data();
data.set(Integer.valueOf(42));       // 컴파일 통과
String text = (String) data.get();   // 실행 중 형변환 오류

오류가 만들어지는 과정은 세 단계다. 첫째, set(Object)가 정수를 받아들인다. 둘째, get()의 반환형이 Object이므로 문자열로 쓰려면 개발자가 캐스트한다. 셋째, 실제 객체가 Integer이므로 실행 시점에 String으로 바꿀 수 없다는 사실이 드러난다.

잘못된 생각은 “캐스트를 적었으니 문자열이 된다”는 것이다. 캐스트는 객체의 실제 종류를 바꾸는 변환이 아니라, 해당 참조를 그 자료형으로 취급할 수 있다고 개발자가 주장하는 검사다. 실제 객체가 맞지 않으면 주장은 실행 중 실패한다.

타입 매개변수는 한 객체 안의 입구와 출구를 같은 형으로 묶는다

제네릭은 클래스, 인터페이스 또는 메소드를 정의할 때 자료형을 타입 매개변수로 두고 사용할 때 구체적인 타입 인수를 지정하는 방법이다. 클래스 이름 오른쪽의 꺾쇠괄호 안에 T 같은 타입 매개변수를 선언한다. 이름은 관례적으로 T, E, K, V 등을 사용하지만 핵심은 같은 기호가 같은 자료형 자리를 연결한다는 점이다.

class Data<T> {
    private T value;

    void set(T value) {
        this.value = value;
    }

    T get() {
        return value;
    }
}

Data<String> data = new Data<>();
data.set("generic");
String text = data.get();
// data.set(Integer.valueOf(42));  // 컴파일 오류

Data<String>이라고 정하면 이 객체의 T가 쓰인 필드, set()의 매개변수와 get()의 반환형이 모두 String으로 연결된다. 따라서 잘못된 정수 저장을 컴파일러가 입구에서 막고, 출구에서는 별도 캐스트 없이 문자열을 얻는다.

강의의 ArrayList<E>도 같은 구조다. List<String>에 문자열을 넣으면 get()의 결과도 문자열로 검사된다. 제네릭의 재사용성은 자료형을 없애는 데서 생기는 것이 아니라, 정의할 때는 자료형 자리를 비워 두고 사용할 때 일관된 자료형으로 채우는 데서 생긴다.

new Data<>()의 빈 다이아몬드 연산자는 타입이 없는 raw 타입을 뜻하지 않는다. 왼쪽의 Data<String>과 생성자 문맥에서 String을 추론한다. 반면 new Data()처럼 꺾쇠괄호 자체를 생략하면 raw 타입이 된다.

타입 매개변수가 둘이면 각 자리를 독립적으로 따라간다

제네릭 인터페이스도 여러 타입 매개변수를 가질 수 있다. 강의의 Pair<K, V>에서 K는 키의 자료형, V는 값의 자료형을 나타낸다. 이를 구현하는 제네릭 클래스는 두 자리를 그대로 전달할 수 있다.

interface Pair<K, V> {
    K getKey();
    V getValue();
}

class OrderedPair<K, V> implements Pair<K, V> {
    private K key;
    private V value;

    OrderedPair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }
}

Pair<String, Integer> item = new OrderedPair<>("chapter", 6);

마지막 문장에서 KString, VInteger가 된다. 따라서 item.getKey()는 문자열, item.getValue()는 정수를 반환한다. 두 자리를 순서 없이 “모두 객체형”으로만 기억하면 키와 값의 반환형을 뒤바꾸기 쉽다.

일반 클래스가 제네릭 인터페이스를 구현하면서 타입 인수를 고정할 수도 있다. 예를 들어 class TextCount implements Pair<String, Integer>라면 클래스 자체에는 타입 매개변수가 없지만 구현 메소드의 반환형은 각각 StringInteger로 확정된다.

raw 타입은 호환을 남기지만 타입 안전성은 되돌린다

raw 타입은 제네릭 타입을 타입 인수 없이 사용하는 형태다. 예를 들어 Data<T>가 정의되어 있어도 Data raw = new Data("hello");처럼 쓸 수 있다. 이때 타입 매개변수로 연결했던 입구와 출구의 구체적 자료형 정보가 사라져 Object 중심의 사용으로 돌아간다.

raw 타입이 존재한다고 해서 새 코드에서 일부러 선택할 이유가 생기는 것은 아니다. 컴파일러의 경고를 무시한 채 서로 다른 자료형을 섞을 수 있고, 꺼낼 때 다시 캐스트와 실행 오류 위험이 생긴다. 기존의 비제네릭 코드와 함께 쓰기 위한 호환 경계로 이해하고, 새 선언에서는 타입 인수를 명시하는 편이 타입 안전성을 유지한다.

raw 타입 판정: 클래스 이름 뒤에 타입 인수도 다이아몬드도 없고 제네릭 클래스 이름만 단독으로 보이면 raw 타입을 의심한다. “컴파일된다”와 “타입 안전하다”는 같은 판단이 아니다.

제네릭 메소드는 클래스 전체가 아니라 한 호출의 자료형만 일반화한다

제네릭 클래스는 객체를 만드는 시점에 타입 인수를 정하고 그 객체의 여러 멤버가 공유한다. 반면 제네릭 메소드는 메소드 선언의 반환형 앞에 타입 매개변수를 두며, 호출마다 인수에서 자료형을 추론할 수 있다. 클래스가 일반 클래스여도 인스턴스 메소드와 static 메소드 모두 제네릭 메소드가 될 수 있다.

다음은 강의의 배열 원소 교환을 다른 값으로 추적한 학습용 예다.

class ArrayTools {
    static <T> void swap(T[] array, int left, int right) {
        T temp = array[left];
        array[left] = array[right];
        array[right] = temp;
    }
}

String[] colors = { "red", "green", "blue" };
ArrayTools.swap(colors, 0, 2);

호출 인수 colorsString[]이므로 이 호출의 TString으로 추론된다. 실행은 tempred 저장 → 0번 원소에 blue 저장 → 2번 원소에 red 저장의 순서이며 결과는 { "blue", "green", "red" }다. 다음 호출에서 Integer[]를 전달하면 같은 메소드의 T는 다시 Integer가 된다.

Util.<Integer, String>compare(p1, p2)처럼 타입 인수를 명시할 수도 있지만, 전달된 Pair<Integer, String>에서 충분히 알 수 있으면 보통 Util.compare(p1, p2)로 추론시킨다. 메소드 이름 앞의 타입 인수는 객체 생성의 다이아몬드와 위치가 다르다는 점에 주의한다.

상한 제한과 불공변성은 허용 범위를 서로 다르게 조절한다

<T extends Number>TNumber 또는 그 하위 자료형으로 제한하는 상한 제한이다. 따라서 Data<Integer>Data<Double>은 만들 수 있지만 Data<String>은 제한을 만족하지 않아 컴파일 오류다. 여러 수치형을 받되 문자열 같은 범위 밖의 타입은 거부하고 싶을 때 사용한다.

그러나 IntegerNumber의 하위 타입이라는 사실이 Data<Integer>Data<Number>의 하위 타입이라는 뜻은 아니다. 일반적인 제네릭 타입은 타입 인수 사이의 상속 관계를 그대로 전달하지 않는 불공변 관계다.

Data<Number> numbers = new Data<>();
numbers.set(Integer.valueOf(10));
numbers.set(Double.valueOf(2.5));

// Data<Number> wrong = new Data<Integer>(); // 컴파일 오류

class FormattedData<T> extends Data<T> { }
Data<Integer> ok = new FormattedData<Integer>();

만약 Data<Integer>Data<Number> 변수에 넣을 수 있다고 가정하면, 그 변수를 통해 Double을 저장할 수 있어 정수 전용 객체의 약속이 깨진다. 반면 FormattedData<Integer>는 클래스 선언 자체가 같은 T를 유지한 채 Data<Integer>를 상속하므로 대입할 수 있다.

제네릭의 제한은 타입이 객체마다 정해진다는 사실로 설명한다

시도판정이유와 올바른 방향
Data<int>컴파일 오류타입 인수에는 참조형을 사용하므로 Integer 같은 래퍼 클래스를 쓴다.
new T()컴파일 오류컴파일 시 T의 실제 생성자를 확정할 수 없으므로 객체를 외부에서 전달받는 방식 등을 사용한다.
static T shared;컴파일 오류static 필드는 클래스에 하나지만 T는 제네릭 객체마다 다르게 정해질 수 있다.
new Data<Integer>[5]컴파일 오류매개변수화된 타입의 배열을 직접 생성할 수 없다.

배열 제한에서는 선언과 생성을 구분해야 한다. Data<Integer>[] items;처럼 참조 변수의 자료형을 적는 선언 자체는 가능하지만, new Data<Integer>[5]처럼 매개변수화된 타입의 배열 객체를 직접 생성할 수는 없다. 오류 위치를 꺾쇠괄호가 보이는 모든 배열 선언으로 확대하면 안 된다.

제한을 외울 때는 “제네릭은 아무 자료형이나 무엇이든 할 수 있다”는 생각을 버려야 한다. 타입 매개변수는 참조형 자리의 일관성을 검사하는 도구이며, 구체적 타입을 모르는 상태에서 생성자·정적 저장 공간·배열 생성까지 자동으로 해결해 주는 문법은 아니다.

람다식은 익명 구현 클래스에서 하나의 동작만 남긴다

람다식은 함수형 인터페이스의 추상 메소드를 구현하는 객체 생성 부분을 (매개변수 목록) -> { 실행문 목록 }으로 표현한다. 여기서 함수형 인터페이스는 구현해야 할 추상 메소드가 하나인 인터페이스다. 람다식은 독립적으로 자료형을 갖는 이름 없는 함수가 아니라, 대입이나 메소드 인수 문맥이 제공하는 함수형 인터페이스를 구현하는 표현이다.

직접 구성한 알림 동작 예에서 익명 구현 클래스와 람다식을 단계별로 줄여 보자.

interface Notifier {
    void send(String message);
}

Notifier first = new Notifier() {
    public void send(String message) {
        System.out.println("알림: " + message);
    }
};

Notifier second = (String message) -> {
    System.out.println("알림: " + message);
};

Notifier third = message -> System.out.println("알림: " + message);

Notifier라는 타깃 타입이 send(String)을 알려 주므로 람다식에서는 메소드 이름을 쓰지 않는다. 매개변수 하나의 자료형을 문맥에서 추론하면 괄호와 자료형을 생략할 수 있고, 실행문도 하나이므로 중괄호를 생략할 수 있다. 세 표현은 모두 같은 계약에 맞는 동작 객체를 만든다.

람다식이 익명 구현 클래스의 모든 기능을 대신하는 것은 아니다. 강의 범위에서 람다는 인터페이스의 추상 메소드가 하나일 때 그 메소드의 구현을 간단히 표현한다. 일반 클래스를 상속하는 익명 서브클래스 생성이나 추상 메소드가 둘 이상인 인터페이스 구현을 람다식으로 바꿀 수 없다.

람다 문법은 입력 개수와 몸체 형태를 차례로 판정한다

판정 지점생략 가능한 조건
매개변수 자료형타깃 타입에서 추론할 수 있음(a, b) -> a + b
매개변수 괄호매개변수가 정확히 하나이고 자료형을 생략함value -> value * 2
매개변수 목록입력이 없음() -> 7
몸체 중괄호몸체가 하나의 문장 또는 수식(a, b) -> a + b
return과 세미콜론값을 내는 몸체가 하나의 수식(a, b) -> a + b

중괄호를 사용하면서 값을 반환한다면 (a, b) -> { return a + b; }처럼 return과 세미콜론이 필요하다. 반대로 하나의 수식 몸체에서는 return, 중괄호와 세미콜론을 함께 생략한다. (a, b) -> return a + b처럼 일부만 제거하면 문법이 성립하지 않는다.

가장 중요한 검사는 생략 개수가 아니라 타깃 인터페이스의 메소드와 람다식의 입력·출력이 맞는지 확인하는 것이다. 매개변수 개수와 자료형이 맞고, 반환값을 요구하는 메소드라면 몸체도 호환되는 값을 만들어야 한다.

표준 함수형 인터페이스는 입력과 출력 모양으로 선택한다

직접 함수형 인터페이스를 선언할 수 있지만, java.util.function 패키지는 자주 쓰는 입력·출력 모양을 제네릭 인터페이스로 제공한다. 강의에서는 Consumer, Supplier, Function을 비교한다. 이름부터 외우기보다 값을 받는지와 결과를 돌려주는지를 먼저 표시하면 선택이 쉬워진다.

필요한 동작인터페이스핵심 메소드람다 모양
값 하나를 받아 소비하고 반환하지 않음Consumer<T>void accept(T t)value -> System.out.println(value)
입력 없이 값을 공급함Supplier<T>T get()() -> 7
값을 받아 다른 결과로 변환함Function<T, R>R apply(T t)text -> text.length()
import java.util.function.Function;
import java.util.function.Supplier;

Supplier<Integer> chapter = () -> 6;
Function<String, Integer> length = text -> text.length();

int first = chapter.get();
int second = length.apply("lambda");
System.out.println(first + second); // 12

Supplier<Integer>는 입력 없이 6을 공급하고, Function<String, Integer>는 문자열 lambda를 받아 길이 6을 반환한다. 마지막 출력은 6 + 6 = 12다. 강의의 Thread 예도 Runnablerun() 동작을 람다식으로 전달한다는 같은 원리를 사용한다.

컴파일 오류를 만났을 때 타입 자리와 동작 자리를 분리한다

제네릭과 람다식이 한 코드에 함께 등장하면 꺾쇠괄호와 화살표를 한꺼번에 읽지 않는다. 먼저 제네릭이 정한 자료형 계약을 확정하고, 그다음 함수형 인터페이스의 추상 메소드 모양을 람다식과 대조한다.

  1. 타입 선언 찾기: 클래스·인터페이스·메소드 중 어디에 타입 매개변수가 선언되었는지 찾는다.
  2. 타입 인수 대입:T, K, V, R 자리에 실제 참조형을 넣어 필드·매개변수·반환형을 다시 읽는다.
  3. 제한과 대입 검사: 상한 제한을 만족하는지, 서로 다른 제네릭 타입 사이의 대입을 상속 관계로 오해하지 않았는지 본다.
  4. 타깃 타입 찾기: 람다식이 대입되거나 전달되는 함수형 인터페이스를 찾고 추상 메소드 하나의 입력과 출력을 적는다.
  5. 람다 몸체 대조: 매개변수 개수, 반환 필요 여부와 중괄호·return 생략 규칙을 확인한다.

예를 들어 Function<String, Integer> f = text -> text.length();에서는 먼저 T=String, R=Integerapply(String)이 정수를 반환해야 한다고 확정한다. 그다음 람다식이 문자열 매개변수 하나를 받고 length()의 정수 결과를 내므로 계약과 일치한다고 판정한다.

핵심 개념 정리

  • 제네릭은 타입 매개변수를 필드·매개변수·반환형에 연결하여 잘못된 자료형 사용을 컴파일 단계에서 찾게 한다.
  • 제네릭 클래스의 타입은 객체 단위로, 제네릭 메소드의 타입은 호출 단위로 정해지며 문맥에서 추론할 수 있다.
  • raw 타입은 구체적 타입 정보를 잃고, 상한 제한은 허용할 타입 인수의 범위를 정하며, 제네릭 타입은 타입 인수의 상속 관계를 그대로 전달하지 않는다.
  • 기본형 타입 인수, new T(), 타입 매개변수형 정적 필드와 매개변수화된 타입의 배열 생성에는 제한이 있다.
  • 람다식은 함수형 인터페이스의 하나뿐인 추상 메소드 구현을 표현하며, 대입·호출 문맥의 타깃 타입이 입력과 출력을 결정한다.
  • Consumer, Supplier, Function은 각각 소비, 공급, 변환이라는 입력·출력 모양으로 선택한다.

새 코드를 판정할 때는 타입 매개변수의 선언 위치를 찾고 → 실제 타입 인수를 모든 사용 자리에 대입하고 → 제한과 제네릭 대입 가능성을 검사한 뒤 → 람다식의 타깃 인터페이스에서 추상 메소드의 입력·출력을 복원하고 → 람다 몸체가 그 계약을 만족하는지 확인한다. 제네릭은 값의 이동 경로를 안전하게 만들고, 람다식은 그 경로에서 수행할 동작을 간결하게 전달한다.

예상문제 10선

1. 제네릭을 사용하는 가장 직접적인 효과는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②

  • ① 오답: 타입 인수에는 참조형을 사용하며 기본형과 참조형의 구분은 유지된다.
  • ② 정답: 저장·전달·반환에 같은 타입 계약을 적용해 잘못된 값을 더 이른 시점에 찾는다.
  • ③ 오답: 제네릭은 타입 자리의 재사용 문법이지 클래스 상속 관계를 생성하는 문법이 아니다.
  • ④ 오답: 타입 검사와 객체의 실제 종류를 변경하는 것은 서로 다른 문제다.

2. Data<String> words = new Data<>(); 다음에 컴파일 오류가 발생하는 문장은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: 이 객체의 TString이므로 문자열 저장은 계약과 맞는다.
  • ② 오답: get()의 반환형도 String으로 연결되어 캐스트 없이 대입할 수 있다.
  • ③ 오답: 양쪽의 타입 인수가 같으므로 같은 제네릭 타입 사이의 참조 대입이다.
  • ④ 정답: set()이 요구하는 문자열 자리에 정수를 전달해 컴파일 단계에서 거부된다.

3. Data<String> typed = new Data<>();Data raw = new Data();의 차이로 옳은 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③

  • ① 오답: 둘째 선언에는 String을 추론할 타입 인수나 다이아몬드 문맥이 없다.
  • ② 오답: raw 타입은 타입 인수를 생략한 둘째 선언이며 첫 선언의 다이아몬드는 왼쪽에서 추론된다.
  • ③ 정답: 첫 선언은 입구와 출구를 문자열로 검사하고 raw 타입은 Object 중심 사용으로 돌아간다.
  • ④ 오답: raw 타입 사용은 가능하지만 경고와 타입 안전성 저하를 감수한다.

4. 제네릭 메소드 swap(T[] array, ...)Integer[]를 전달했을 때 가장 먼저 확정되는 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①

  • ① 정답: 메소드 타입 매개변수는 전달된 배열 자료형에서 호출 단위로 추론된다.
  • ② 오답: 제네릭 메소드의 T는 클래스 전체의 필드 자료형을 바꾸지 않는다.
  • ③ 오답: 다음 호출은 다른 배열 자료형에서 새로운 T를 추론할 수 있다.
  • ④ 오답: 제네릭 추론은 원소 값을 다른 실제 자료형으로 변환하지 않는다.

5. IntegerNumber의 하위 타입인데도 Data<Number> d = new Data<Integer>();가 컴파일되지 않는 이유는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②

  • ① 오답: IntegerNumber는 모두 참조형이므로 타입 인수로 사용할 수 있다.
  • ② 정답: 일반적인 제네릭 타입은 불공변이므로 두 구체화 타입을 별도의 타입으로 판정한다.
  • ③ 오답: Number는 숫자 클래스들의 부모 역할을 하는 참조형 클래스다.
  • ④ 오답: 명시한 Integer를 다이아몬드로 바꾸면 왼쪽 문맥에서 Number가 추론되어 서로 다른 코드가 된다.

6. 다음 중 제네릭 타입 사용 제한을 정확히 적용한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③

  • ① 오답: 타입 인수에는 참조형이 필요하므로 기본형 int를 직접 넣을 수 없다.
  • ② 오답: 클래스에 하나인 정적 필드는 객체마다 달라질 수 있는 타입 매개변수에 의존할 수 없다.
  • ③ 정답: 기본형 값이 필요한 경우 해당 래퍼 클래스인 Integer를 사용한다.
  • ④ 오답: 타입 매개변수만으로 실제 생성자를 알 수 없어 new T()는 허용되지 않는다.

7. 람다식의 타깃 타입이 될 수 있는 인터페이스의 핵심 조건은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①

  • ① 정답: 추상 메소드가 하나여야 람다 몸체가 구현할 메소드를 하나로 확정할 수 있다.
  • ② 오답: 제네릭 타입 매개변수의 개수는 함수형 인터페이스 여부를 결정하지 않는다.
  • ③ 오답: 람다식은 정적 메소드가 아니라 함수형 인터페이스의 추상 인스턴스 메소드 계약을 구현한다.
  • ④ 오답: Runnable은 한 예일 뿐이며 다른 함수형 인터페이스도 타깃 타입이 될 수 있다.

8. Function<String, Integer> 변수에 대입할 수 있는 람다식은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: Function<String, Integer>apply는 문자열 입력 하나를 요구한다.
  • ② 오답: 타깃 메소드는 매개변수가 하나이므로 두 매개변수 람다와 맞지 않는다.
  • ③ 오답: 출력문은 값을 반환하지 않아 Integer 결과 계약을 만족하지 않는다.
  • ④ 정답: 문자열 하나를 받아 length()의 정수 결과를 반환하므로 입력과 출력이 모두 맞는다.

9. 다음 코드의 출력은? Supplier<Integer> a = () -> 8;, Function<String, Integer> b = s -> s.length();, System.out.println(a.get() + b.apply("type"));

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②

  • ① 오답: 문자열 길이만 계산하고 공급자가 반환한 8을 더하지 않았다.
  • ② 정답: a.get()은 8이고 "type"의 길이는 4이므로 정수 덧셈 결과는 12다.
  • ③ 오답: 두 결과는 정수이므로 문자열 이어 붙이기가 아니라 산술 덧셈을 수행한다.
  • ④ 오답: 두 람다 모두 각 표준 함수형 인터페이스의 입력·출력 계약과 일치한다.

10. 제네릭 타입과 람다식이 함께 있는 선언을 검사하는 순서로 가장 적절한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③

  • ① 오답: 타입 정보를 버리면 제네릭의 검사 이점을 잃고 람다 계약도 판정할 수 없다.
  • ② 오답: raw 타입은 오류 해결책이 아니라 구체적 타입 정보를 잃게 하는 호환 형태다.
  • ③ 정답: 제네릭의 자료형 계약을 먼저 복원한 뒤 함수형 인터페이스와 람다의 입출력을 대조하는 순서다.
  • ④ 오답: 변수 이름과 타입 인수 개수만으로 실제 매개변수·반환형의 호환 여부를 알 수 없다.

참고 자료와 작성 기준

이 글은 해당 차시 강의자료를 바탕으로 학습 목적에 맞게 재구성한 비공식 학습자료입니다. 개념 관계, 예제와 문제 해설은 학습자의 이해를 돕도록 구성하고 검토했습니다.

  • 작성·편집: 올에이클래스 학습연구팀
  • 주요 근거: 한국방송통신대학교 컴퓨터과학과 Java프로그래밍 6강 「제네릭과 람다식」 강의자료(교재 5장, PDF 메타데이터 기준 2024년)
  • 보충 자료: 외부 보충 자료를 사용하지 않음
  • 편집 원칙: 올에이클래스 편집 정책
  • 최종 내용 검토: 2026-09-01

댓글