Java 프로그래밍, 컬렉션(Collections)과 람다 표현식(Lambda Expression)
JDK 1.8부터 지원되기 시작한 람다 표현식(Lambda Expression)은 기존의 코드를 더욱 단순하고 가독성을 향상시키는 장점을 제공하고 있다. 람다 표현식(Lambda Expression)은 메소드를 한개 가진 인터페이스를 익명 클래스로 구현할 때 간결한 표현식을 사용할 수 있는 방법을 제공하는데, 람다 표현식이 갖는 간결함과 향상된 가독성은 컬렉션과 같은 다른 부분에도 영향을 주면서 컬렉션을 사용하는 방법도 좀더 간결한 코드로 표현할 수 있게 되었다.
예를 들어, ArrayList 의 forEach() 메소드는 리스트에 포함된 원소들을 한개씩 추출하여 forEach() 메소드의 아규먼트(Consumer)로 전달하는 기능을 갖고 있다. Java API 문서에서 forEach() 메소드를 참조하면 다음과 같은 선언문을 확인할 수 있다
public void forEach(Consumer<? super E> action)
그리고 froEach()의 파라미터로 선언된 Consumer 를 또 API 문서에서 찾아보면 다음과 같이 @FunctionalInterface 로 선언된 인터페이스인 것을 알 수 있다.
@FunctionalInterface
public interface Consumer<T>
{
void accept(T t);
}
그러므로 forEach() 를 호출할 때 Consumer 인스턴스를 생성하고 그 참조를 전달해주어야 하는데, 이 때 람다 표현식을 사용할 수가 있다. 왜냐면 람다 표현식은 추상 메소드가 1개 선언된 인터페이스를 구현하여 익명 클래스의 인스턴스를 생성하는 간략한 표현식을 제공하기 때문이다. 일단 forEach()에 Consumer 인스턴스를 전달하고 호출하면 forEach()는 List의 원소를 한개씩 추출하여 Consumer의 accept() 메소드 아규먼트로 전달하여 accept()가 실행되는 원리로 되어 있다.
따라서 개발자가 컬렉션의 forEach()를 사용할 때는 accept() 추상 메소드를 람다 표현식으로 구현하면 된다.
Consumer 인터페이스의 accept() 추상 메소드를 구현하는 예
- userList.forEach( (User u) -> System.out.print( u.getName() ) ;
- userList.forEach( (u) -> System.out.print( u.getName() ) ; // 자료형은 생략 가능
- userList.forEach( u -> System.out.print( u.getName() ) ; // 파라미터가 1개일 경우에는 괄호 생략 가능
- userList.forEach( System.out :: println ) ; // 메소드 참조(Method Reference, '::')를 이용하면 accept()의 아규먼트가 메소드에게도 전달됨
List 를 다룰 때 람다 표현식을 사용하는 예
import java.util.ArrayList; public class LambdaTest { public static void main(String[] args) { ArrayList<User> list = new ArrayList<>(); User u1 = new User(); u1.setNum(1); u1.setName("홍길동"); u1.setPhone("010-5479-8745"); list.add(u1); User u2 = new User(); u2.setNum(2); u2.setName("김인철"); u2.setPhone("010-2947-3949"); list.add(u2); // 컬렉션의 원소는 한개씩 추출되어 accept()의 아규먼트로 전달된다 : 이 과정을 마지막 원소까지 반복함 // List 뿐만 아니라 다른 컬렉션도 이러한 방법으로 다룰 수 있다 list.forEach(u->{ // Consumer.accept()를 구현한 람다 표현식 System.out.printf("Num:%d, Name:%s, Phone:%s %n", u.getNum(), u.getName(), u.getPhone()); }); // Method Reference 참조를 이용하면 accept()에 전달되는 리스트의 원소가 println()으로도 전달된다 // 그러므로 아래의 람다 표현식은 둘 다 동일한 표현이다 // ::기호를 이용하여 인스턴스 메소드나 클래스 메소드 호출이 가능함 list.forEach(System.out::println); list.forEach((u)->System.out.println(u)); list.forEach(u->System.out.println(u)); // User 클래스에 정의된 printUser() 메소드를 호출하는 예 // 인스턴스 참조를 이용하여 메소드를 호출하는 예 list.forEach(u->u.printUser()); // 메소드 참조(Method Reference)를 이용하여 호출하는 예 list.forEach(User::printUser); list.forEach(u->System.out.println(u.getName())); } } class User { private int num; private String name; private String phone; public void printUser(){ System.out.printf("Num:%d, Name:%s, Phone:%s %n", getNum(), getName(), getPhone()); } public int getNum() { return num; } public void setNum(int num) { this.num = num; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public void printName(){ System.out.println("Name:"+name); } }