3장 인터페이스와 람다 표현식

Jasik·2021년 12월 7일
0

3.1 인터페이스

3.1.1 인터페이스 선언하기

예제 서비스 : 정수 시퀀스에서 처음 n개 앖의 평균을 보고하는 서비스

public static double average(IntSequence seq, int n)

예제 인터페이스

public interface IntSequence {
    boolean hasNext();
    int next(); 
}

// 인터페이스의 모든 메서드는 자동으로 public이 된다.

interface를 이용한 average 메서드 구현

public static double average(IntSequence seq, int n) {
    int count = 0;
    doulble sum = 0;
    while (seq.hasNext() && count < n) {
        count++;
        sum += seq.next();
    }
    return count == 0 ? 0 : sum / conut;
}

3.1.2 인터페이스 구현하기

예제 : 제곱수 수열

public class SquareSequence implements IntSequence {
    private int i;
    
    @Override
    public boolean hasNest() {
        return true;
    }
    
    @Override
    public int next() {
        i++;
        return i * i;
    }
}
  • @Override 어노테이션을 붙이면 상위 메서드와 다른 시그니처의 메서드를 구현하였을 경우 컴파일 에러가 발생하여 실수를 줄일 수 있다.

다음 코드로 1부터 100 까지 자연수들의 제곱수의 평균을 구할 수 있다.

IntSequence squares = new SquareSequence();
double avg = average(squares, 100);

3.1.3 인터페이스 타입으로 변환하기

IntSequence squares = new SquareSequence();
double avg = average(squares, 100);

위 코드의 IntSequence 타입 변수는 SquareSequence 클래스의 인스턴스를 참조하고 있다.
IntSequence 타입 변수는 IntSequence 인터페이스를 구현한 어떤 클래스의 객체라도 참조할 수 있다.

  • 서브타입의 모든 값을 변환 없이 슈퍼타입 변수에 할당할 수 있으면 타입 S는 타입 T(서브타입)의 슈퍼타입이다.

IntSequence는 SquareSequence의 슈퍼타입이다.

3.1.4 타입 변환과 instanceof 연산자

슈퍼타입 변수를 서브타입으로 변환할 때는 타입 변환(cast)이 필요하다.

IntSequence sequence = new SquareSequence();
SquareSequence squares = (SquareSequence) sequence;

잘못된 캐스팅을 할 경우 컴파일 오류 혹은 classCastException 이 발생한다.

instanceof 연산자로 실수를 방지할 수 있다.

if (sequence instanceof SquareSequence) {
    ...
}

위 코드의 if문 내 표현식은 SquareSequence 클래스가 sequence 변수의 타입의 슈퍼타입일 경우 true를 반환한다.

3.1.5 인터페이스 확장하기

public interface Closeable {
    void close();
}

위 Closeable 이라는 인터페이스를 아래 Channel 인터페이스 처럼 확장할 수 있다.

public interface Channel extends Closeable {
    boolean isOpen();
}

Channel 인터페이스를 구현하는 클래스는 반드시 Closeable에 있는 메서드들과 Channel에 있는 메서드들 모두를 구현해야 한다.

3.1.6 여러 인터페이스 구현하기

클래스는 인터페이스를 몇 개든 구현할 수 있다.

public class FileSequence implements IntSequence, Closeable {
    ...
}

위 경우 FileSequence 클래스는 IntSequece와 Closeable을 슈퍼타입으로 둔다.

3.1.7 상수

인터페이스에 정의된 변수는 자동으로 public static final 이다.
따라서 (interface name).(constant name) 으로 접근 가능하다.

하지만 상수 집합에는 enumeration을 사용하는 것이 훨씬 바람직하다.

3.2 정적 메서드와 기본 메서드

3.2.1 정적 메서드

일반적으로 인터페이스의 메서드들은 구현이 없는 추상 메서드이나, 구현을 포함한 정적 메서드도 선언이 가능하다.

public interface IntSequence {
    ...
    public static getSquareSequenceInstance() {
        return new SquareSequence();
    }
}

IntSequence squares = IntSequence.getSquareSequenceInstance();

3.2.2 기본 메서드

public interface IntSequence {
    default boolean hasNext() {
        return true;
    }
    int next();
}

위 코드의 hasNext() 메서드는 기본 메서드이며, 이 인터페이스를 구현하는 클래스는 hasNext 메서드를 오버라이드 하거나 기본 구현을 상속하는 방법 중 하나를 선택할 수 있다.

3.2.3 기본 메서드의 충돌 해결하기

Person interface와 Identified interface 두 인터페이스 모두 getId() 메서드를 default로 구현하여 가지고 있다고 하자.

그럼 아래와 같은 코드는 컴파일 에러를 발생시킨다.

public class Employee implements Person, Identified {
    ...
}

이러한 에러를 해결하기 위한 방법은
1. Employee 클래스가 getId() 메서드를 오버라이드해서 본인만의 구현을 가지면 된다.
2. 아래 코드처럼 슈퍼타입들의 default 구현중 하나를 선택하도록 한다.

public class Employee implements Person, Identified {
    public int getId() {
        return Identified.super.getId(); // super 키워드로 슈퍼타입 메서드를 호출 가능
    }
}

적어도 한 인터페이스에서 구현을 제공하면 컴파일러는 오류를 보고한다.

3.3 인터페이스의 예

Comparable interface (java.lang.Comparable)

public interface Comparable<T> {
    int compareTo(T other);
}

x.compareTo(y) 는 정수를 반환한다. 이 정수는 x와 y의 순서를 결정한다.
리턴값이 양수이면 x 다음 y가 온다.
음수이면 y 다음 x가 온다.
0이면 x와 y가 같다.

public class Employee implements Comparable<Employee> {
    public int compareTo(Employee other) {
        return Double.compare(salary, other.salary);
    }
}

// ? compare 메서드가 other.salary에 접근하는 것은 합법적이다. 자바의 메서드는 자신이 속한 클래스의 private 객체에 접근할 수 있다.

Comparator interface (java.util.Comparator)

public interface Comparator<T> {
    int compare(T first, T second);
}

문자열을 문자열의 길이로 비교

class LengthComparator implements Comparator<String> {
    public int compare(String first, String second) {
        return first.length() - second.length();
    }
}

test code

List<String> friends = new ArrayList<String>(Arrays.asList("Peter", "Paulson", "Marian"));
System.out.println("original : " + friends);
		
friends.sort(null);
System.out.println("dictionary order : " + friends);
		
friends.sort(new LengthComparator());
System.out.println("order by length : " + friends);

result

original : [Peter, Paulson, Marian]
dictionary order : [Marian, Paulson, Peter]
order by length : [Peter, Marian, Paulson]

3.4 람다 표현식

한 번 이상 나중에 실행할 수 있게 전달하는 코드 블록.

유용한 상황

  • Arrays.sort 에 비교 메서드 전달
  • 별도의 스레드에서 태스크 실행
  • 버튼을 클릭했을 때 일어나는 액션 지정

자바는 함수 타입이 없으며, 함수를 객체로 표현.
특정 인터페이스를 구현하는 클래스의 인스턴스로 표현.
람다 표현식은 이런 인스턴스를 생성하는 아주 편리한 문법을 제공.

예제

(String first, String second) -> first.length() - second.length()

표현식이 여러줄일 경우

(String first, String second) -> {
    int difference = first.length() - second.length();
    if (difference < 0) {
        return -1;
    } else if (difference > 0) {
        return 1;
    }
    
    return 0;
}

파라미터가 없는 경우 빈 소괄호 사용

Runnable task = () -> {
    for (int i=0; i < 1000; i++) {
        doWork();
    }
}

람다 표현식의 파라미터 타입을 추론할 수 있다면 파라미터 타입을 생략할 수 있다.

Comparator<String> comp = (first, second) -> first.length() - secound.length();

람다 표현식의 파라미터 타입을 추론할 수 있고, 파라미터가 한개라면 소괄호도 생략할 수 있다.

EventHandler<ActionEvent> listener = event -> System.out.println("Oh noes!");

람다 표현식의 결과 타입은 명시하지 않는다.
컴파일러는 구현부로부터 결과 타입을 추론해서 기대하는 타입과 일치하는지 검사한다.

3.4.2 함수형 인터페이스

추상 메서드가 한 개만 포함된 인터페이스를 함수형 인터페이스라고 한다.
함수형 인터페이스에 람다 표현식을 사용할 수 있다.

List.sort 메서드의 파라미터 변수는 Comparator 인터페이스를 구현하는 클래스의 객체를 받는다. 다음과 같이 파라미터로 람다 표현식을 전달할 수 있다.

List<String> friends = new ArrayList<String>(Arrays.asList("Peter", "Paulson", "Marian"));
friends.sort((first, second) -> first.length() - second.length());

위 표현식의 파라미터 변수는 Comparator 인터페이스를 구현하는 클래스의 객체이다. 이 객체의 compare 메서드를 호출하면 람다 표현식의 구현부를 실행한다.

3.5 메서드 참조와 생성자 참조

다른 코드에 전달하려는 액션을 수행하는 메서드가 이미 있을 때 사용하는 메서드 참조용 특수 문법.

3.5.1 메서드 참조

Arrays.sort(strings, (x, y) -> x.compareToIgnoreCase(y));

Arrays.sort(strings, String::compareToIgnoreCase);

세 가지 형태

1) 클래스::인스턴스메서드
첫 번째 파라미터가 메서드의 수신자, 나머지 파라미터는 해당 메서드로 전달된다.

String::compareToIgnoreCase
(x, y) -> x.compareToIgnoreCase(y)

2) 클래스::정적메서드
모든 파라미터가 정적 메서드로 전달된다.

Object::isNull
x -> Object.isNull(x)

3) 객체::인스턴스메서드
주어진 객체에서 메서드가 호출되며, 파라미터는 인스턴스 메서드로 전달된다.

System.out::println
x -> System.out.println(x)

3.5.2 생성자 참조

메서드 이름이 new라는 점만 제외하면 메서드 참조와 같다.

List<String> names = ...;
names.stream().map(Employee::new);
Employee[] buttons = stream.toArray(Employee[]::new);

를 통해 Obejct[] 가 아닌 특정 클래스 배열을 얻을 수 있다.

3.6 람다 표현식 처리하기

3.6.1 지연 실행 구현하기

지연 실행의 이유

  • 별도의 스레드에서 코드 실행
  • 코드를 여러 번 실행
  • 알고리즘의 올바른 지점에서 코드 실행
  • 어떤 일이 일어날 때 코드 실행
  • 필요할 때만 코드 실행
public static void repeat(int n, Runnable action) {
	for (int i=0; i < n; i++) {
		action.run();
	}
}

repeat(10, () -> System.out.println("Hello World"));

,

public interface IntConsumer {
	void accept(int value);
}

public static void repeat(int n, IntConsumer action) {
	for (int i=0; i < n; i++) {
    	action.accept();
    }
}

repeat(10, i -> System.out.println("Countdown: " + (9 - i)));
profile
가자~

0개의 댓글