개요
Effective Java 7장에서 등장하는 개념을 공부하기 위한 포스팅이다.
1-1) 익명 클래스(Anonymous Class)
프로그램에서 일시적으로 한번만 사용되고 버려지는 객체다.
분명히 Java Spring을 공부할 때는 코드의 재사용성을 고려하며 프로그래밍을 해야 좋은 개발자라고 배웠다.
그렇다면 재사용이 불가능하고 확장성도 좋지 않은 익명클래스를 왜 사용하는 걸까?
- 프로그램 내에서 일시적으로 사용되는 객체 처리에 사용한다.
- 확장성을 활용하는 것이 유지보수에서 더 불리할 경우에 사용한다.
해당 블로그 에서 이를 이해하기 위한 아주 좋은 예시를 들고 있다.
일부를 발췌해서 이해해보자.
1-2) 확장성이 필요없는 극단적인 예시 (출처)
곤충이라는 부모클래를 상속받는 거미 구현 클래스가 있다.
거미는 전 세계에 약 3만 종이 알려져 있고, 이 3만종 거미들은 공통적인 특징도 없다. 심지어 프로그램 로직에서는 단 한번만 등장하고 사라진다고 하자.
이때 개발자가 할 수 있는 선택은 다음과 같다.
- 3만종의 거미클래스를 만들어 유지보수한다.
- 익명 클래스를 활용하여 한번 쓰고 버린다!
이러한 경우에 클래스를 별도로 만들 필요없이, 코드에서 익명 객체를 생성/정의하여 단 한번의 쓰임을 다 하고 소멸하게 하는 것이다.
단 하나의 예시로 바로 이해할 수 있도록 좋은 글을 남겨주신 Lim-Ky(출처)님 감사합니다😃
1-3) 익명 클래스 예제 (출처 )
보통 상속을 정의하고 사용하기 위해서는 부모 클래스 정의 > 자식 클래스 정의 > 상속 > 객체 인스턴스 초기화와 같은 과정을 거친다.
이를 코드로 나타내면 다음과 같다.
// Animal이라는 부모 클래스
class Animal {
public String bark() {
return "동물이 우는 소리";
}
}
// Dog라는 자식 클래스(Animal을 상속)
class Dog extends Animal {
@Override
public String bark() {
return "개가 짖는 소리";
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
System.out.println(dog.bark()); // 개가 짖는 소리
}
}
위 코드를 통해 일반적인 상속 예제에서는 부모 클래스와 그를 상속 받는 자식 클래스를 정의하고, 객체 인스턴스를 생성한 뒤에 Override된 메소드를 사용하는 것을 알 수 있다. 하지만 익명 클래스는, 클래스의 정의와 동시에 객체를 생성할 수 있다.
// Animal이라는 부모 클래스
class Animal {
public String bark() {
return "동물이 우는 소리";
}
}
public class Main {
public static void main(String[] args) {
// 객체 생성과 동시에 클래스 정의
Animal dog = new Animal() {
@Override
public String bark() {
return "개가 짖는 소리";
}
}; // 익명 클래스의 끝에 세미콜론을 반드시 붙여 주어야 한다.
dog.bark();
}
}
이렇게 bark를 Override한 메소드 활용이 단발성이라면, 위와 같이 익명 클래스를 선언해서 쓰는 것이 좋다.
익명 클래스라는 기법을 통해 긴 상속 과정을 압축하고, 익명으로 한방에 선언하고 날릴 수 있기 때문이다.
1-4) 익명 클래스와 람다
이러한 익명 클래스는 1997년 JDK 1.1이 등장하면서 함수 객체를 만드는 주요 수단으로 썼다.
Java 8에서는 함수형 인터페이스, 람다, 메서드 참조 등 개념이 등장하면서 함수 객체를 더 쉽게 만들 수 있게 되었다.
이펙티브 자바에서는 이러한 익명 클래스의 인스턴스를 함수 객체로 사용하는 대신, Lambda를 사용하는 것이 좋은 기법이라고 소개하고 있다.
2-1) 람다(Lambda)
람다식은 메소드를 하나의 식으로 표현한 것이다. 익명 클래스와 비슷한 개념이지만 코드는 훨씬 간결하다.
표현식은 다음과 같다.
(매개변수목록) -> {함수 몸체}
메소드의 매개변수를 전달할 수 있고, 메소드의 결과값으로 반환도 가능하다.
따라서 람다 표현식을 사용하면, 기존의 불필요한 코드를 줄여주고 작성된 코드의 가독성을 높여준다.
아래 예제를 통해 이해해보자.
첫번째 코드는 익명 클래스의 인스턴스를 함수 객체로 사용한 예제이다.
List<String> words = new ArrayList<>();
words.add("apple");
words.add("banana");
words.add("cherry");
// words를 sort하는 class method
Collections.sort(words, new Comparator<String>() {
// 익명 클래스 정의
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
for (String word : words) {
System.out.println(word);
}
위 익명 클래스를 다음과 같이 Lambda로 간결화할 수 있다.
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));
익명 클래스와 비교해서 코드가 매개변수와 반환값 관련해서 훨씬 간결해졌다. 이렇게 람다를 사용할 때는 매개변수의 타입을 생략해준다. 어차피 컴파일 과정에서 컴파일러가 타입 추론 원리로 타입을 추론해주기 때문이다. (100% 가능한 것은 아니고, 여기서 자세히 얘기하진 않는다. 이는 타입 추론 관련한 포스팅에서 알아본다.)
🍒 여기서 끝아님.
람다 표현식에 Java의 method reference를 쓰면 코드를 훨씬 더 간결하게 쓸 수 있다.
2-2) 메서드 참조(method reference)
❗️ 메서드 참조는 여기(포스팅 첨부) 서 더 자세히 설명한다.
위 람다 표현식은 다음과 같이 수정할 수 있다.
Collections.sort(words, comparingInt(String::length));
*문자열의 길이를 정렬하는 함수 length가 있고, String class를 정렬..
2-3) 람다(Lambda) 사용 시 주의할 점
1. 람다는 이름이 없고 문서화도 불가능하므로, 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아지면 쓰지 말아야 한다.
- 코드 줄 수가 많다는 기준 - 한 줄이 가장 좋고 길어야 세 줄!
2. 람다가 익명 클래스보다 훨씬 간결한 것은 맞지만, 대체 불가능한 경우가 있다.
- 람다는 함수형 인터페이스에서만 쓰이므로, 추상 클래스의 인스턴스를 만들 때는 익명 클래스를 써야 한다.
- 람다는 자기자신을 참조할 수 없다.(this X) 함수 객체가 자신을 참조해야 한다면 익명 클래스를 써야 한다.
- 람다를 직렬화하는 일은 극히 삼가야 한다.
'언어 > JAVA' 카테고리의 다른 글
[JAVA] Effective JAVA. 9장 일반적인 프로그래밍 원칙 1편(item 57, 58, 59, 60) (0) | 2024.03.24 |
---|---|
[JAVA] 날짜/시간 클래스 알아보기(Date, LocalDateTime, ZoneDateTime) (1) | 2024.03.08 |
[JAVA] Effective Java. 6장 열거 타입과 애너테이션 (2) | 2024.01.12 |
[JAVA] JAVA String | StringBuilder | 문자열 결합 연산 | 문자열 연산의 복잡도 (0) | 2023.05.19 |