람다란
메서들 전달 할 수 있는 함수를 단순화 한 것
그니까 코드를 더 짜기 귀찮아서 간결하게 표현하기 위해서 만들어 진 것이 람다식이다
람다를 알기 전에 도데체 어떤 코드를 간결하게 표현을 하는 것이고 덜 짜는 지에 대해 알기 위해서 가장 기초적인 자바 문법을 가지고 차례대로 개선을 해 나갈 것이다
예제에서 사용 될 모델
public class User {
private String name;
private int age;
private Role role;
}
public enum Role {
USER, ADMIN
}
private static List<User> users;
static {
users = Arrays.asList(
new User("userA", 10, Role.USER),
new User("userB", 10, Role.USER),
new User("userC", 15, Role.ADMIN),
new User("userB", 20, Role.ADMIN),
new User("userD", 20, Role.ADMIN),
new User("userB", 25, Role.USER),
new User("userE", 13, Role.USER)
);
}
요구사항
user에 대해서 이름 나이 역할에 대해서 필터링
하나의 조건에 대해서 할 수 도 있고 여러개의 조건을 합쳐서 할 수도 있음
Version1
자바를 배웠다면 다 알 수 있을 가장 기초적인 문법만을 사용해서 문제 해결
Main
private static List<User> filterByName() {
List<User> result = new ArrayList<>();
for (User user : users) {
if (user.getName().equals("userB")) {
result.add(user);
}
}
return result;
}
private static List<User> filterByAge() {
List<User> result = new ArrayList<>();
for (User user : users) {
if (user.getAge() >= 15) {
result.add(user);
}
}
return result;
}
private static List<User> filterByRole() {
List<User> result = new ArrayList<>();
for (User user : users) {
if (user.getRole().equals(Role.USER)) {
result.add(user);
}
}
return result;
}
private static List<User> filterByNameAndAge() {
List<User> result = new ArrayList<>();
for (User user : users) {
if (user.getAge() >= 15 && user.getName().equals("userB")) {
result.add(user);
}
}
return result;
}
해당 버전에서는 조건이 생길때 마다 추가적인 메서드를 계속해서 만들어 줘야한다
메서드들을 보게 되면 if문의 조건만 다를 뿐 나머지 문장들은 모두 동일함을 볼 수 있다
우리는 이런 중복적인 코드들을 싫어해야 하고 이렇게 반복되는 작업을 귀찮아 해야한다
이러한 반복적인 작업을 없애기 위해서 동작을 파라미터화 하는 방법으로 버전을 올려볼 것이다
Version2
이번 버전에서는 if문에 들어갈 조건(동작)을 파라미터화 할 것이다
동작 파라미터
동작을 파라미터화 하기 위해선 공통적인 인터페이스가 필요하다
여기서는 참과 거짓을 반환하는 boolean을 return 타입으로 가지는 인터페이스를 만들어 해당 인터페이스를 조건마다 구현 클래스를 만들 것이다
Interface
public interface UserPredicate {
boolean test(User user);
}
여기서 이름으로 predicate라는 용어를 사용했는데 람다를 조금이라도 공부를 해본 사람은 알겠지만 그렇지 않은 사람들은 이 이름을 잘 기억을 하고 있자 (음.. 하나의 복선이다)
구현 클래스
public class UserAgePredicateImpl implements UserPredicate {
@Override
public boolean test(User user) {
return user.getAge() >= 15;
}
}
public class UserNamePredicateImpl implements UserPredicate {
@Override
public boolean test(User user) {
return user.getName().equals("userB");
}
}
public class UserRolePredicateImpl implements UserPredicate {
@Override
public boolean test(User user) {
return user.getRole().equals(Role.USER);
}
}
Main
private static List<User> filterUser(UserPredicate predicate) {
List<User> result = new ArrayList<>();
for (User user : users) {
if (predicate.test(user)) {
result.add(user);
}
}
return result;
}
filterUser(new UserAgePredicateImpl())
filterUser(new UserNamePredicateImpl())
filterUser(new UserRolePredicateImpl())
해당 코드를 이해하기 위해서는 간단하게 override에 대해서 알고 있어야 한다
단순히 구현 객체에서 정의한 test메서드를 filterUser메서드에서 predicate.test(user)를 호출할 때 실행이 된다
이로써 우리는 여러개의 메서드를 하나로 통일하여 사용을 할 수 있게 되었다
하지만 이것에 동의하지 못하는 사람들이 있을 것이다 조건마다 구현 객체를 만들어야 하기 때문일 것이다
그것에 대한 것은 추후 개선을 할 것이므로 일단은 메서드를 하나로 만들었다는 것에서 기뻐해도 될 듯하다
Version3
이번 버전에서 사용할 메서드는 앞선 버전에서 사용한 filterUser(UserPredicate predicate)이다
이번엔 익명클래스를 사용하여 추가적으로 구현 객체를 만드는 번거로움을 줄여 보도록 할것이다
Main
filterUser(new UserPredicate() {
@Override
public boolean test(User user) {
return user.getName().equals("userB");
}
})
메서드를 호출 할때 위와 같이 익명객체를 통해서 추가적인 구현 객체를 생성하지 않아도 된다
하지만 이 방법도 코드가 너무 길다 많이 줄어들었다고는 하지만 아직까지는 가독성의 약간의 어려움이 있을 수도 있다
Version4
이제는 정말 우리가 그토록 원하던 람다를 사용할 것이다 람다를 어떻게 쓰는지 어디에다가 써야하는지는 일단 나중에 알아보도록 하고 찍먹한다 생각하고 코드를 봐보자
물론 여기서도 사용하는 메서드는 filterUser(UserPredicate predicate)이다
Main
filterUser(user -> user.getName().equals("userB"))
이 문장 하나가 끝이다
정말 간단해졌다 이걸로 다른 조건이 필요하게 된다면 우리는 추가적인 메서드를 만들 필요도 구현객체를 생성하지 않아도 되고
익명클래스와 같이 길게 적을 필요성 조차 없게 되었다
조건이 추가가 된다면 단순히
filterUser(user -> user.getName().equals("userB") && user.getRole().equals(Role.USER))
이렇게 작성을 해주면 된다
버전1의 코드를 보고 이번 코드를 보면 너무나도 짧아지고 편해진 상황이다 하지만 여기서 만족하지 말고 이번엔 자바가 제공하고 있는 것을 사용하여 코드를 더 간단하게 만들어보자
앞서 Predicate라는 이름을 기억하고 있을 것이다 자바에는 Predicate라는 인터페이스를 제공을 하고 있다 이는 우리가 직접 UserPredicate와 같이 인터페이스조차 만들어 줄 필요가 없다는 것이다
Predicate에 대해서는 추후 알아보도록 하고 일단 다음 버전을 보도록 하자
Version5
벌써 버전5이다 이제 정말 끝이 보인다
(아 힘들어..)
private static List<User> filterUserBySpecificPredicate(Predicate<User> predicate) {
List<User> result = new ArrayList<>();
for (User user : users) {
if (predicate.test(user)) {
result.add(user);
}
}
return result;
}
파라미터를 보게 되면 우리가 앞서 만든 UserPredicate가 아닌 것을 볼 수 있다
이렇게 우리는 자바가 제공하는 인터페이스를 사용하여 정말 그 많던 코드를 하나로 줄일 수 있게 되었다
Version6
여기서 마지막이라고 생각하면 안되고 정말로 하나만 더하고 끝을 내보도록 하겠다
여기서 우리는 오로지 User에 대해서만 filtering을 할 수 있다
하지만 다른 형식에 대해서 하고 싶다면? 바로 제네릭을 통해서 타입추상화를 해보자
private static <T> List<T> filterByPredicateUseGeneric(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T element : list) {
if (predicate.test(element)) {
result.add(element);
}
}
return result;
}
이렇게 제네릭을 쓰게 된다면 다른 타입에 대해서도 맘껏 해당 메서드를 사용할 수 있게 될 것이다
간단하게 예제를 보도록 하자
filterByPredicateUseGeneric(Arrays.asList(1, 2, 3, 4, 5), i -> i % 2 == 0)
이렇게 int형에 대한 것도 할 수 있게 되었다
이로써 해당 메서드 하나로 여러가지 타입을 filtering할 수 있게 되었다
정말 이것으로 끝이다!
다음에는 람다 작성법과 람다에 있는 predicate와 같은 것들을 알아보도록 하겠다
Git code
'공부기록 > Java' 카테고리의 다른 글
System.setIn() (0) | 2022.08.22 |
---|---|
람다 2 (0) | 2022.04.18 |
제네릭(2) (0) | 2021.08.09 |
제네릭(1) (0) | 2021.08.07 |
스레드(3) (0) | 2021.08.07 |